手動(dòng)編寫(xiě)mock服務(wù)(ma-mock)

上一篇文章json-server的實(shí)踐與自定義配置化提到過(guò)赤赊,json-server在我看來(lái)不太適用劝萤;之前有贊開(kāi)源的zan-proxy我也嘗試用過(guò)渊涝,其痛點(diǎn)在于mock數(shù)據(jù)保存在第三方,這個(gè)特性使得公司項(xiàng)目不適合使用zan-proxy稳其,所以嘗試自己搭建一個(gè)mock服務(wù)——ma-mock驶赏。

背景

項(xiàng)目中需要對(duì)兩個(gè)開(kāi)發(fā)地址進(jìn)行代理,部分?jǐn)?shù)據(jù)也需要使用mock數(shù)據(jù)既鞠,所以可以參照zan-proxy做代理和mock的切換按鈕煤傍,鑒于之前是使用koa編寫(xiě)后端服務(wù)的,所以這次使用koa編寫(xiě)可用于mock和proxy的可視化服務(wù)嘱蛋。

構(gòu)思

雖說(shuō)是參照zan-proxy蚯姆,但是我還是保留著自己的想法;首先洒敏,與zan-proxy不同龄恋,zan-proxy使用瀏覽器插件進(jìn)行地址代理,其主要目的是用于調(diào)試線上頁(yè)面凶伙,但我們只在dev環(huán)境使用郭毕,使用webpack的proxyTable將后端接口都代理到mock服務(wù),由mock服務(wù)統(tǒng)一分發(fā)代理還是返回mock數(shù)據(jù)即可函荣;其次显押,數(shù)據(jù)保存在本地,構(gòu)建一個(gè)本地文件增刪查改的操作傻挂,mock服務(wù)只在dev開(kāi)發(fā)中使用乘碑,io的損耗其實(shí)沒(méi)有太大的區(qū)別;

后端

主要有三個(gè)功能金拒,分發(fā)mock和proxy兽肤、提供可視化界面的后端接口部署前端資源绪抛,因?yàn)橹饕墙o前端人員使用资铡,所以維護(hù)一份全局變量(lib/Global.js)替代redis

三個(gè)功能的執(zhí)行順序?yàn)?分發(fā)mock和proxy -> 返回單頁(yè)面資源 -> 可視化界面的后端接口

分發(fā)功能可以利用koa中間件特性:

'use strict';

const { Logger, fsHandler, Global } = require('../lib/index');
const axios = require('axios');
const pathToRegexp = require('path-to-regexp');
/**
 *
 * @param  {object} options 配置項(xiàng)
 *         {object} options.prefix mock數(shù)據(jù)的url前綴
 * @return {function}
 *
 */
module.exports = options => {
  // 進(jìn)行中間件參數(shù)的配置幢码,最終返回一個(gè)中間件函數(shù)
  let prefix = options.prefix;

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

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

    let pathArr = curPath.split(prefix);

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

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

    // 規(guī)則是mock優(yōu)先級(jí)大于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 = '未開(kāi)啟proxy';
    }
    // 此處沒(méi)有next()笤休,直接返回?cái)?shù)據(jù)
  };

  // 用mock數(shù)據(jù)
  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__',
  })
);

可視化界面的后端接口

常規(guī)后端restful接口,此處略過(guò)不講蛤育。

前端靜態(tài)資源部署

因?yàn)槭情_(kāi)發(fā)環(huán)境使用宛官,所以不必部署到nginx上,自己編寫(xiě)了基于koa-statickoa-spa-static瓦糕。

使用方法底洗,配置采用vue打包出來(lái)的目錄,react可能需要自行按情況修改:

const spaStatic = required('koa-spa-static');
// 掛在靜態(tài)資源
app.use(
  spaStatic({
    matchReg: /^(?!\/api)/, // 不以"/api"開(kāi)頭的接口地址會(huì)返回靜態(tài)資源
    root: path.join(__dirname, './dist'), // 靜態(tài)資源目錄
    staticReg: /^\/static/, // 前端static資源返回文件咕娄,其他返回index.html
  })
);

前端

使用element-ui2的組件構(gòu)造亥揖,屬于簡(jiǎn)單的組裝,基于自己編寫(xiě)的vue cli模板圣勒,使用命令

vue init masongzhi/vue-template-webpack

其他

ma-mock服務(wù)的安裝和使用

安裝

npm install -D ma-mock

在根目錄編寫(xiě).mamockrc.js配置文件

const path = require('path');
 
// 默認(rèn)配置
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: {
      // 填寫(xiě) .mamockrc.js的prefix费变,默認(rèn)為'/__DEV__'
      '/__DEV__': {
        target: 'http://localhost:3001', // 接口的域名
        // secure: false,  // 如果是https接口,需要配置這個(gè)參數(shù)
        changeOrigin: true, // 如果接口跨域圣贸,需要進(jìn)行這個(gè)參數(shù)配置
      }
    },
  },
}

package.json添加script命令

mamock [--port 3001]

.mamockrc.js的讀取

我們?cè)陧?xiàng)目跟目錄編寫(xiě)了.mamockrc.js文件挚歧,那是怎么在mock服務(wù)中讀取到這個(gè)文件的信息呢,或者說(shuō)怎樣才能將npm包的配置參數(shù)寫(xiě)在根目錄的文件內(nèi)吁峻。

我們可以使用rc-config-loader滑负,其他類(lèi)似包也行,像prettier就使用editorconfig和自己編寫(xiě)的editorconfig-to-prettier用含。

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

// 會(huì)向上遍歷.mamockrc or .mamockrc.json or .mamockrc.js or.<product>rc.yml, .mamockrc.yaml
// 我們需要用到基于跟目錄的data文件矮慕,所以需要用到__dirname,所以使用js文件
const data = rcfile('mamock');

bin命令的使用

我們?cè)趐ackage.json的scripts編寫(xiě)了mamock --port 3001命令啄骇,是怎么實(shí)現(xiàn)的呢痴鳄。

在package.json添加

"bin": {
    "ma-mock": "./bin/mock-proxy.js",
    "mamock": "./bin/mock-proxy.js"
}

在bin目錄添加mock-proxy.js

#!/usr/bin/env node

'use strict';

// 定義用到的參數(shù)
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;
});
// 執(zhí)行koa的index.js
require('../server/index.js');

自動(dòng)打開(kāi)瀏覽器

啟動(dòng)ma-mock服務(wù)時(shí)自動(dòng)打開(kāi)瀏覽器

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

app.listen(PORT);

opn(`http://localhost:${PORT}`, {app: 'google chrome'});

總結(jié)

目前還是比較粗糙,后端只做了簡(jiǎn)單的joi參數(shù)校驗(yàn)缸夹,沒(méi)對(duì)mock地址進(jìn)行url校驗(yàn)痪寻;mock數(shù)據(jù)也只支持基本的application/json類(lèi)型,且不支持mock.js的配置明未;最重要的槽华,單元測(cè)試沒(méi)補(bǔ)全,后續(xù)會(huì)慢慢填趟妥。

如果有什么建議或者想貢獻(xiàn)代碼猫态,可以發(fā)issue和pr,項(xiàng)目地址: https://github.com/masongzhi/ma-mock

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末披摄,一起剝皮案震驚了整個(gè)濱河市亲雪,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌疚膊,老刑警劉巖义辕,帶你破解...
    沈念sama閱讀 211,194評(píng)論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異寓盗,居然都是意外死亡灌砖,警方通過(guò)查閱死者的電腦和手機(jī)璧函,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,058評(píng)論 2 385
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)基显,“玉大人蘸吓,你說(shuō)我怎么就攤上這事×糜模” “怎么了库继?”我有些...
    開(kāi)封第一講書(shū)人閱讀 156,780評(píng)論 0 346
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)窜醉。 經(jīng)常有香客問(wèn)我宪萄,道長(zhǎng),這世上最難降的妖魔是什么榨惰? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 56,388評(píng)論 1 283
  • 正文 為了忘掉前任拜英,我火速辦了婚禮,結(jié)果婚禮上琅催,老公的妹妹穿的比我還像新娘聊记。我一直安慰自己,他們只是感情好恢暖,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,430評(píng)論 5 384
  • 文/花漫 我一把揭開(kāi)白布排监。 她就那樣靜靜地躺著,像睡著了一般杰捂。 火紅的嫁衣襯著肌膚如雪舆床。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 49,764評(píng)論 1 290
  • 那天嫁佳,我揣著相機(jī)與錄音挨队,去河邊找鬼。 笑死蒿往,一個(gè)胖子當(dāng)著我的面吹牛盛垦,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播瓤漏,決...
    沈念sama閱讀 38,907評(píng)論 3 406
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼腾夯,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了蔬充?” 一聲冷哼從身側(cè)響起蝶俱,我...
    開(kāi)封第一講書(shū)人閱讀 37,679評(píng)論 0 266
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎饥漫,沒(méi)想到半個(gè)月后榨呆,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,122評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡庸队,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,459評(píng)論 2 325
  • 正文 我和宋清朗相戀三年积蜻,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了闯割。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,605評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡竿拆,死狀恐怖纽谒,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情如输,我是刑警寧澤,帶...
    沈念sama閱讀 34,270評(píng)論 4 329
  • 正文 年R本政府宣布央勒,位于F島的核電站不见,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏崔步。R本人自食惡果不足惜稳吮,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,867評(píng)論 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望井濒。 院中可真熱鬧灶似,春花似錦、人聲如沸瑞你。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,734評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)者甲。三九已至春感,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間虏缸,已是汗流浹背鲫懒。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,961評(píng)論 1 265
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留刽辙,地道東北人窥岩。 一個(gè)月前我還...
    沈念sama閱讀 46,297評(píng)論 2 360
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像宰缤,于是被迫代替她去往敵國(guó)和親颂翼。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,472評(píng)論 2 348

推薦閱讀更多精彩內(nèi)容

  • Spring Cloud為開(kāi)發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見(jiàn)模式的工具(例如配置管理慨灭,服務(wù)發(fā)現(xiàn)疚鲤,斷路器,智...
    卡卡羅2017閱讀 134,628評(píng)論 18 139
  • 前言 最近一直在搗鼓畢設(shè)缘挑,準(zhǔn)備做的是一個(gè)基于前后端開(kāi)發(fā)的Mock平臺(tái)集歇,前期花了很多時(shí)間完成了功能模塊的交互。現(xiàn)在進(jìn)...
    臨水照影233閱讀 9,562評(píng)論 0 8
  • 更多干貨就在我的個(gè)人博客 http://blackblog.tech 歡迎關(guān)注语淘! 決策樹(shù):構(gòu)建一個(gè)基于屬性的樹(shù)形分...
    BlackBlog__閱讀 1,690評(píng)論 0 2
  • 1诲宇、取消了一些過(guò)時(shí)的 HTML4的標(biāo)簽 其中包括純粹顯示效果的標(biāo)記际歼,如 和<centwer>,它們已經(jīng)被 CSS完...
    Pitfalls閱讀 4,231評(píng)論 1 0
  • 她總是扎著馬尾辮姑蓝,柔順的頭發(fā)在風(fēng)中像楊柳飄蕩鹅心,瘦小的身材總喜歡穿著一件黑色外衣。臉色雖然偏黑纺荧,卻不影響她精致...
    淺綠或玄米閱讀 846評(píng)論 3 1