開發與維運

針對多狀態訂單詳情的前端mock方案

作者:閒魚技術——樹城

背景

閒魚作為國內最大的二手交易電商平臺,有著驗貨寶/省心賣/優品等有著閒魚特色的交易鏈路,而作為交易鏈路的閉環,一旦形成有效訂單,就會有對應的訂單詳情頁,訂單詳情頁往往承載著複雜的交易狀態的變化。以驗貨寶為例, 驗貨寶是閒魚推出針對二手商品存在的質量/真偽的不確定性,提出的先驗貨後交易模式。
作為交易訂單節點: 就有買家拍下->付款->賣家發貨->鑑定方收貨->鑑定為真->發貨給買家->買家收貨 等多個交易節點,一個訂單詳情頁在不同流轉狀態下可能存在數十種細分狀態,每種狀態之間存在著有著文案/操作按鈕/進度條等視覺交互上的差別, 如下圖所示:

存在問題

訂單詳情頁的不同的狀態視圖流轉,依賴於接口返回的字段的改變,存在和服務端的溝通成本;
前端的直接mock的方式,像集團內的山海關,dummy更多是通用的mock數據映射,本質還是要手動修改mock數據的status值;在真機調試時,依賴的Charles的pc代理,單次修改成本高;
除了開發同學外,測試同學測試迴歸的成本高. UI同學在視覺走查中難以覆蓋全部,容易帶問題到線上,引起線上輿情。
總結來說,面對此類訂單的訂單詳情頁的前端開發測試,我們現有的mock存在著以下問題:

  • 開發聯調週期長,自測難以充分;
  • 測試/視覺迴歸成本高;
  • 線上樣式問題定位還原週期長;

需求分析

針對以上的問題,能否讓訂單狀態頁面的mock方式更為易用,我們通過真實的開發體驗,提出了針對此類頁面的mock方式要達到好用,應該具備以下特點:

  1. 調試方便

mock方式應該在本地pc端調試和真機調試都能夠方便使用;

  1. 業務語義

不同狀態的切換交互應該帶有業務語義,能夠方便開發和測試快速找到想要的訂單狀態,而不只是簡單的修改接口的某個字段值;

  1. 代碼解耦

mock的方式本身應該和業務代碼儘量解耦,即不會將mock的邏輯引入線上環境;

  1. mock數據精簡

訂單頁面狀態雖多,但是接口往往統一,接口字段,對mock數據的維護不應該每一種狀態都是單獨一份mock數據而是應該在一份主的mock數據上針對產生變化的字段進行單獨mock;

技術方案

方案總攬

基於以上需求,我們嘗試設計開發更為貼近此類業務的mock方案。先別看廣告,看療效, 此方案的真實效果如下圖所示:

從效果圖可以看出我們通過按鈕點擊實現了狀態視圖的變更,也就實現了目標一、二,而這個按鈕本身不會在線上環境所帶入也就不實現了代碼解耦;那麼這套方案是如何實現的呢?如下圖所示,我們將整體方案設計為三大模塊:

膠水層:是整個方案的編譯層基座,負責在編譯層將視圖層插件和mock層插件按照生產環境狀態加載進入業務層,實現在本地和預發環境下具有mock能力,並負責在視圖層進行狀態切換時按照狀態碼重新生成對應mock.json,利用膠水層的打包邏輯做到了與業務解耦的目標;
mock層:負責將前端發起的mtop請求攔截,根據路由映射到本地webserver下的mock.json並模擬返回對應結果,利用mock層方式實現了mock數據精簡,調試方便的目標
視圖層: 負責頁面處的整體mock狀態切換的交互邏輯,我們將交互入口設計為類似於eruda調試工具的喚起方式,在需要的訂單頁面側植入, 點擊會喚起彈層,彈層展示所有可枚舉的訂單狀態文案以及其他可枚舉會影響訂單視圖的變量條件,點擊對應狀態進行頁面重載展示對應狀態的視覺; 視圖層負責實現了業務語義的目標。

膠水層實現

在具體實現過程中,由於閒魚前端開發使用集團提供的rax的前端方案,rax-app也提供了在編譯層可以定製的插件機制, rax-app基於工程構建工具 build-scripts 封裝,因此在插件能力上也完整繼承了build-scripts。除了通過插件定製工程能力以外,rax-app 還為插件擴展了運行時定製的能力, 我們定製了@ali/build-plugin-rax-mock和本地的selfBuild兩個編譯插件,分別對應mock層和膠水層的設計需求,以如下方式在項目的build.json裡進行引入,很好地實現與業務層代碼的解耦。

{
  "plugins": [
    [
      "build-plugin-rax-app",
      {
        "targets": [
          "web"
        ],
        "type": "mpa"
      }
    ],
    "@ali/build-plugin-rax-mock",
    "./selfBuild"
  ]
}

​rax-app的插件機制提供的針對webpack打包方案所提供的onGetWebpackConfig api將視圖層組件植入業務頁面,
如下方代碼所示,會在打包過程中根據根據指定頁面文件路徑選擇性地注入,並且判斷編譯環境在真實生產環境中不做任何mock模塊的打包。

if (api.context.command === 'build') return;
    api.onGetWebpackConfig('web', (config) => {
      config.entryPoints.values().forEach(entry => {
        const entrys = entry.values();
        const entryName = entrys[1];
        // 只對訂單頁面注入
        if (!/pages\/Order\/index$/.test(entryName)) {
          return;
        }
 
        const prefixLoader = __filename;
        const debugOrderPath = path.resolve(__dirname, 'src/components/DebugOrder');
        const newOrderPage = `${prefixLoader}?debugOrderPath=${debugOrderPath}!${entryName}`;
     
        entry.clear();
        // 視圖層組件 
        entry.add(entrys[0]);
        entry.add(newOrderPage);
      });
    });
  }

因此通過膠水層,我們可以快速將mock模塊和視圖層模塊快速引入到工程方案中,並能按需引入,不對業務造成明顯侵入痕跡。

mock層實現

首先我們會根據狀態合集所需訂單詳情接口生成一份mock.json數據的合集,這樣就能涵蓋所有狀態下所需消費字段,一般接口格式如下所示:

{
    "api": "mtop.a.order.info",
    "data": {
      "status": 0,
      "orderStatus": 1001,
        ...
      "trade": {
        "actions": [],
          ....
        "amount": "2189.00",
        "attributes": {
          "consis": "10",
            ...
          "ultronPP": "a_3_0@c",
         }
       }
     },
     "ret": [
      "SUCCESS:成功"
        ],
        "v": "1.0"
}

如下圖所示,mock層既要更新視圖層切換狀態而組成新的mock.json, 也要攔截頁面側發起的mtop網絡請求定向到對應的mock.json文件;


為了真實能夠把接口請求到本地,需要對h5頁面側發起的mtop請求進行攔截,這裡利用了淘寶mtop庫的運行機制會將mtop對象加載到頁面全局的window.lib對象上,利用Proxy的代理機制監聽window.lib對象的掛載mtop時機set進行hook, 並根據所處的生產環境判斷是否使用發送請求到本地的自定義request請求, 實現代碼如下:

lib = window.lib;
// 攔截Mtop對象的request方法掛載
const getMtop = (originValue) => 
new Proxy(originValue, {
  set(target, p, v, r) {
    if (p === 'request' 
    || p === 'H5Request') {
      Reflect.set(target, p, getRequest(v), r);
    } else {
      Reflect.set(target, p, v, r);
    }

    return true;
  }
});

// 攔截window.lib對象掛載mtop
if (!lib) {
    lib = new Proxy({}, {
    set(target, p, v, r) {
      if (p.toLowerCase() === 'mtop') {
        Reflect.set(target, p, v,r);
      } else {
        Reflect.set(target, p, v, r);
      }
      return true;
    }
  });
} else if (!lib.mtop) {
lib.mtop = getMtop({});
} else {
lib.mtop.request = getRequest(lib.mtop.request);
lib.mtop.H5Request = getRequest(lib.mtop.H5Request);
}

// 根據運行環境選擇加載對應的request請求
function getRequest(originRequest) {
  return async function () {
    if (getMockSwitch()) {
      //
    }

    return originRequest();
  }
}

當掛載完成即可實現接口的mock功能:

  1. 當本地頁面發起 mtop 請求,如:mtop.com.test.one;
  2. 請求被注入的插件代碼 hold 住,當判斷是在本地開發環境或者鏈接帶上mock query時, 用本地的mtopRquest替換,請求以 http://127.0.0.1/_mtop_mock_/com.test.one 格式重新發起請求;
  3. 請求打到本地 webpack-dev-server 上,server 再去本地 mock 目錄上找 com.test.one.js
  4. 如果找到就執行 com.test.one.js 文件,將執行結果返回
  5. 如果未找到,則走原有的 mtop 請求;

視圖層實現

視圖層UI由頁面側直接可見的切換icon和彈層構成,icon設置為dragable方便用戶隨時拖動, 彈層的渲染考慮到擴展性由樹狀節點組成。目前根據真實的業務需要,將節點層級分為2層(可擴展),一級節點代表:已下單,已驗貨等核心節點, 二級節點代表在一級節點下可能存在的正負向交易細節節點, 如:在已下單環節下存在取消訂單的副狀態;
單點數據結構為如下代碼所示,包含有每個枚舉狀態的語義文案,每種狀態對應接口字段的主副狀態碼,狀態枚舉值,子節點。

// 狀態數據結構
export interface ClassifyDataItem {
  [key: string]: any;
  /**
   * 節點名稱
   */
  nodeName: string;
  /**
   * 節點枚舉值
   */
  node?: StatusEnum;
  /**
   * 對應的協議主狀態
   */
  status?: string;
  /**
   * 對應的協議副狀態
   */
  subStatus?: string;
  childNode?: ClassifyDataItem[];
}

// 真實的狀態數據枚舉
export const classifyData: ClassifyDataItem[] = [
  {
    nodeName: '已下單,等待順豐上門取件',
    status: '1',
    subStatus: '10',
    node: StatusEnum.BUYER_CREATE,
    childNode: [
     ...classifyData
    ]
  }
  ...
];

每個節點在渲染過程中採用樹狀遞歸渲染,視覺層展示nodeName, 同時綁定點擊回調onItemChange。當彈窗的某一狀態結點發生點擊行為時,設置節點選中態樣式,同時通過Modal組件通過props傳入的函數方法向外層傳遞綁定在結點上的status和subStatus值,從而實現了點擊單個選項能夠修改對應的mock.json文件, 同時會觸發頁面的window.reload()功能重新加載頁面發起mtop請求,因此此時接口返回的數據已經是切換後的狀態數據,因此頁面也會呈現對應狀態的視圖。

業務應用

目前該方案已經在閒魚驗貨寶、奢侈品寄賣等交易鏈路場景下接入, 從真實使用過程中,能夠有效提升開發聯調的效率,單次狀態的切換時間成本從分鐘級別下降到秒級, 並能幫助測試和視覺同學快速回歸業務場景,定位線上問題,簡單的視覺問題不再依賴於後端接口的狀態切換,約能節省30%以上的聯調溝通成本。在項目開發人員出現流動時,新人能夠通過這套方案方式快速地理解業務,上手開發。


總結與展望

本章節主要介紹了閒魚在前端開發典型交易場景-訂單詳情頁時,面對多狀態模式切換頁面的一種開發體驗提升的一種嘗試,通過上述的方案設計基本實現了開篇所定義的業務mock的4個目標:調試方便、業務語義、代碼解耦、mock數據精簡,不僅提升了在開發/測試同學的體驗,通過語義化的方式也能幫助項目更好地進行維護升級。
伴隨著業務系統的迭代,目前方案只針對單頁面mock, 如何去覆蓋整個交易鏈路的mock,比如一個完整訂單鏈路的狀態一致性,也是值得探索的方向。

Leave a Reply

Your email address will not be published. Required fields are marked *