上一篇文章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-static
的koa-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