手動編寫mock服務(ma-mock)

天驅發表於2018-07-22

上一篇文章json-server的實踐與自定義配置化提到過,json-server在我看來不太適用;之前有贊開源的zan-proxy我也嘗試用過,其痛點在於mock資料儲存在第三方,這個特性使得公司專案不適合使用zan-proxy,所以嘗試自己搭建一個mock服務——ma-mock

背景

專案中需要對兩個開發地址進行代理,部分資料也需要使用mock資料,所以可以參照zan-proxy做代理和mock的切換按鈕,鑑於之前是使用koa編寫後端服務的,所以這次使用koa編寫可用於mock和proxy的視覺化服務

構思

雖說是參照zan-proxy,但是我還是保留著自己的想法;首先,與zan-proxy不同,zan-proxy使用瀏覽器外掛進行地址代理,其主要目的是用於除錯線上頁面,但我們只在dev環境使用,使用webpack的proxyTable將後端介面都代理到mock服務,由mock服務統一分發代理還是返回mock資料即可;其次,資料儲存在本地,構建一個本地檔案增刪查改的操作,mock服務只在dev開發中使用,io的損耗其實沒有太大的區別;

後端

主要有三個功能,分發mock和proxy提供視覺化介面的後端介面部署前端資源,因為主要是給前端人員使用,所以維護一份全域性變數(lib/Global.js)替代redis

三個功能的執行順序為 分發mock和proxy -> 返回單頁面資源 -> 視覺化介面的後端介面

分發功能可以利用koa中介軟體特性:

'use strict';

const { Logger, fsHandler, Global } = require('../lib/index');
const axios = require('axios');
const pathToRegexp = require('path-to-regexp');
/**
 *
 * @param  {object} options 配置項
 *         {object} options.prefix mock資料的url字首
 * @return {function}
 *
 */
module.exports = options => {
  // 進行中介軟體引數的配置,最終返回一箇中介軟體函式
  let prefix = options.prefix;

  // 相容prefix格式的寫法 "/__DEV__/xxx" 或者 "/__DEV__/xxx/"
  if (prefix.lastIndexOf('/') + 1 !== prefix.length) {
    prefix = prefix + '/';
  }

  return async function(ctx, next) {
    let curPath = ctx.path;
    // 不符合prefix的介面地址直接跳過
    if (curPath.indexOf(prefix) !== 0) return await next();

    let pathArr = curPath.split(prefix);

    // 如果prefix之後不再有path則請求不合法
    // 例如:prefix為 __DEV__/pay/但請求路徑為http://*/__DEV__/pay/
    if (pathArr.length < 2) {
      ctx.body = '請求路徑不合法';
    }

    // 判斷是否使用MOCK資料
    const find = Global.mockList.find(it => {
      const re = pathToRegexp(it.url);
      return re.test(`/${pathArr[1]}`);
    });

    // 規則是mock優先順序大於proxy
    if (find && find.enable) {
      ctx.body = handlerMock(find.url.slice(1));
    } else if (Global.enableProxy) {
      ctx.body = await handlerProxySync(`/${pathArr[1]}`, ctx);
    } else {
      ctx.body = '未開啟proxy';
    }
    // 此處沒有next(),直接返回資料
  };

  // 用mock資料
  function handlerMock(filePath) {
    let result = '';

    try {
      result = fsHandler.getMockFile(filePath);
    } catch (e) {
      result = e;
    }
    Logger.debug(result);

    return { ...result, type: 'MOCK' };
  }

  // 後端代理
  async function handlerProxySync(api, ctx) {
    const options = {
      ...ctx.request,
      url: `${Global.currentProxyUrl}/${api}`,
      params: ctx.query,
    };

    try {
      const res = await axios(options);
      return { ...res, type: 'PROXY' };
    } catch (e) {
      return {
        message: e.message,
        type: 'PROXY',
      };
    }
  }
};

複製程式碼

中介軟體使用

// 配置mock
app.use(
  mockProxy({
    prefix: '__DEV__',
  })
);
複製程式碼

視覺化介面的後端介面

常規後端restful介面,此處略過不講。

前端靜態資源部署

因為是開發環境使用,所以不必部署到nginx上,自己編寫了基於koa-statickoa-spa-static

使用方法,配置採用vue打包出來的目錄,react可能需要自行按情況修改:

const spaStatic = required('koa-spa-static');
// 掛在靜態資源
app.use(
  spaStatic({
    matchReg: /^(?!\/api)/, // 不以"/api"開頭的介面地址會返回靜態資源
    root: path.join(__dirname, './dist'), // 靜態資源目錄
    staticReg: /^\/static/, // 前端static資源返回檔案,其他返回index.html
  })
);
複製程式碼

前端

使用element-ui2的元件構造,屬於簡單的組裝,基於自己編寫的vue cli模板,使用命令

vue init masongzhi/vue-template-webpack

其他

ma-mock服務的安裝和使用

安裝

npm install -D ma-mock

在根目錄編寫.mamockrc.js配置檔案

const path = require('path');
 
// 預設配置
module.exports = {
  prefix: '/__DEV__',
  rootPath: path.resolve(__dirname, './data/mock'),
  proxyPath: path.resolve(__dirname, './data/proxy'),
  proxyFilename: 'config.json',
};
複製程式碼

配置webpack proxyTable

// ...省略
module.exports = {
  // ...省略
  dev: {
    // ...省略
    proxyTable: {
      // 填寫 .mamockrc.js的prefix,預設為'/__DEV__'
      '/__DEV__': {
        target: 'http://localhost:3001', // 介面的域名
        // secure: false,  // 如果是https介面,需要配置這個引數
        changeOrigin: true, // 如果介面跨域,需要進行這個引數配置
      }
    },
  },
}
複製程式碼

package.json新增script命令

mamock [--port 3001]

.mamockrc.js的讀取

我們在專案跟目錄編寫了.mamockrc.js檔案,那是怎麼在mock服務中讀取到這個檔案的資訊呢,或者說怎樣才能將npm包的配置引數寫在根目錄的檔案內。

我們可以使用rc-config-loader,其他類似包也行,像prettier就使用editorconfig和自己編寫的editorconfig-to-prettier

const rcfile = require("rc-config-loader");

// 會向上遍歷.mamockrc or .mamockrc.json or .mamockrc.js or.<product>rc.yml, .mamockrc.yaml
// 我們需要用到基於跟目錄的data檔案,所以需要用到__dirname,所以使用js檔案
const data = rcfile('mamock');
複製程式碼

bin命令的使用

我們在package.json的scripts編寫了mamock --port 3001命令,是怎麼實現的呢。

在package.json新增

"bin": {
    "ma-mock": "./bin/mock-proxy.js",
    "mamock": "./bin/mock-proxy.js"
}
複製程式碼

在bin目錄新增mock-proxy.js

#!/usr/bin/env node

'use strict';

// 定義用到的引數
const keys = ['port', 'prefix', 'rootPath', 'proxyPath', 'proxyFilename'];
const argvs = process.argv.slice(2);
function getArgv(key) {
  const index = argvs.findIndex(it => it === `--${key}`);
  return index >= 0 && argvs[index + 1];
}
keys.forEach(key => {
  const value = getArgv(key);
  if (value) process.env[key] = value;
});
// 執行koa的index.js
require('../server/index.js');

複製程式碼

自動開啟瀏覽器

啟動ma-mock服務時自動開啟瀏覽器

// index.js
const opn = require('opn');

app.listen(PORT);

opn(`http://localhost:${PORT}`, {app: 'google chrome'});
複製程式碼

總結

目前還是比較粗糙,後端只做了簡單的joi引數校驗,沒對mock地址進行url校驗;mock資料也只支援基本的application/json型別,且不支援mock.js的配置;最重要的,單元測試沒補全,後續會慢慢填。

如果有什麼建議或者想貢獻程式碼,可以發issue和pr,專案地址: github.com/masongzhi/m…

相關文章