mockm
Version:
Analog interface server, painless parallel development of front and back ends.
1,754 lines (1,487 loc) • 91 kB
JavaScript
"use strict";
var _interopRequireDefault = require("@babel/runtime-corejs3/helpers/interopRequireDefault");
require("core-js/modules/es.symbol.description.js");
var _assign = _interopRequireDefault(require("@babel/runtime-corejs3/core-js-stable/object/assign"));
var _includes = _interopRequireDefault(require("@babel/runtime-corejs3/core-js-stable/instance/includes"));
var _map = _interopRequireDefault(require("@babel/runtime-corejs3/core-js-stable/instance/map"));
var _keys = _interopRequireDefault(require("@babel/runtime-corejs3/core-js-stable/object/keys"));
var _url = _interopRequireDefault(require("@babel/runtime-corejs3/core-js-stable/url"));
var _reduce = _interopRequireDefault(require("@babel/runtime-corejs3/core-js-stable/instance/reduce"));
var _flat = _interopRequireDefault(require("@babel/runtime-corejs3/core-js-stable/instance/flat"));
var _forEach = _interopRequireDefault(require("@babel/runtime-corejs3/core-js-stable/instance/for-each"));
var _sort = _interopRequireDefault(require("@babel/runtime-corejs3/core-js-stable/instance/sort"));
var _find = _interopRequireDefault(require("@babel/runtime-corejs3/core-js-stable/instance/find"));
var _splice = _interopRequireDefault(require("@babel/runtime-corejs3/core-js-stable/instance/splice"));
var _trim = _interopRequireDefault(require("@babel/runtime-corejs3/core-js-stable/instance/trim"));
var _some = _interopRequireDefault(require("@babel/runtime-corejs3/core-js-stable/instance/some"));
var _isArray = _interopRequireDefault(require("@babel/runtime-corejs3/core-js-stable/array/is-array"));
var _promise = _interopRequireDefault(require("@babel/runtime-corejs3/core-js-stable/promise"));
var _entries = _interopRequireDefault(require("@babel/runtime-corejs3/core-js-stable/object/entries"));
var _stringify = _interopRequireDefault(require("@babel/runtime-corejs3/core-js-stable/json/stringify"));
var _startsWith = _interopRequireDefault(require("@babel/runtime-corejs3/core-js-stable/instance/starts-with"));
var _values = _interopRequireDefault(require("@babel/runtime-corejs3/core-js-stable/object/values"));
var _setInterval2 = _interopRequireDefault(require("@babel/runtime-corejs3/core-js-stable/set-interval"));
var _concat = _interopRequireDefault(require("@babel/runtime-corejs3/core-js-stable/instance/concat"));
var _filter = _interopRequireDefault(require("@babel/runtime-corejs3/core-js-stable/instance/filter"));
var _reverse = _interopRequireDefault(require("@babel/runtime-corejs3/core-js-stable/instance/reverse"));
var _indexOf = _interopRequireDefault(require("@babel/runtime-corejs3/core-js-stable/instance/index-of"));
var _slice = _interopRequireDefault(require("@babel/runtime-corejs3/core-js-stable/instance/slice"));
var _now = _interopRequireDefault(require("@babel/runtime-corejs3/core-js-stable/date/now"));
var _findIndex = _interopRequireDefault(require("@babel/runtime-corejs3/core-js-stable/instance/find-index"));
const fs = require(`fs`);
const lib = require(`./lib.js`);
const {
print
} = require(`./log.js`);
const tool = require(`./tool.js`);
const http = require(`./http.js`);
const fileList = require(`../lib/file-list.js`);
const jsonServer = require(`@wll8/json-server`);
const {
lib: {
express
}
} = jsonServer;
function business() {
// 与业务相关性的函数
function Side(arg) {
return new Side.prototype.init(arg);
}
Side.prototype = {
init: function (arg) {
const res = (0, _assign.default)(this, arg);
return res;
}
};
Side.prototype.init.prototype = Side.prototype;
/**
* 运行每个插件的监听函数
* @param {*} key 插件的函数
* @param {...any} arg 函数的参数
*/
async function pluginRun(key, ...arg) {
const plugin = global.config.plugin;
for (let index = 0; index < plugin.length; index++) {
const [item] = plugin[index];
item._mainReturn[key] && (await item._mainReturn[key](...arg));
}
}
/**
* 根据一个 app 来创建以及 https 配置来生成 httpServer 并添加端口监听功能
* @param {*} param0
* @returns {httpServer, onlyHttpServer} 当额外配置 https 端口时才会存在 onlyHttpServer
*/
function getHttpServer({
app,
name
}) {
let httpServer;
let onlyHttpServer;
const https = global.config.https;
const httpProt = global.config[name];
let httpsProt = https[name];
httpsProt = Number(httpsProt) === Number(httpProt) ? undefined : httpsProt;
/**
* 如果没有配置 https 的端口时, 使用 httpolyglot 实现同一个端口支持 http 和 https
*/
if (https.key === undefined) {
httpServer = require(`http`).createServer(app);
}
/**
* 如果没有配置 https 的端口时, 使用 httpolyglot 实现同一个端口支持 http 和 https
*/
if (https.key && httpsProt === undefined) {
app.use((req, res, next) => {
const protocol = tool.httpClient.getProtocol(req);
if (protocol === `http` && https.redirect) {
const hostName = require(`url`).parse(`ws://${req.headers.host}`).hostname;
res.redirect(301, `https://${hostName}:${httpProt}${req.url}`);
} else {
next();
}
});
httpServer = require(`@httptoolkit/httpolyglot`).createServer({
key: fs.readFileSync(https.key),
cert: fs.readFileSync(https.cert)
}, app);
}
/**
* 如果配置了 https 的端口时, 那么不使用 httpolyglot 而使用原生 http/https, 实现 http => 301 => https
*/
if (https.key && httpsProt) {
onlyHttpServer = require(`http`).createServer(https.redirect ? (req, res) => {
const hostName = require(`url`).parse(`ws://${req.headers.host}`).hostname;
res.writeHead(301, {
Location: `https://${hostName}:${httpsProt}${req.url}`
});
res.end();
} : undefined, app).listen(httpProt, `0.0.0.0`, async () => {
await pluginRun(`serverCreated`, {
onlyHttpServer,
httpProt
});
});
httpServer = require(`https`).createServer({
key: fs.readFileSync(https.key),
cert: fs.readFileSync(https.cert)
}, app);
}
httpServer.listen(httpsProt || httpProt, `0.0.0.0`, async () => {
await pluginRun(`serverCreated`, {
httpServer,
httpsProt,
httpProt
});
});
const server = {
httpServer,
onlyHttpServer
};
app._server = server;
return server;
}
function midResJson({
res,
proxyRes,
key,
val,
cb = body => body
}) {
const modifyResponse = require(`node-http-proxy-json`);
modifyResponse(res, proxyRes, body => {
var _context;
if ((0, _includes.default)(_context = [`array`, `object`]).call(_context, tool.type.isType(body))) {
key && tool.obj.deepSet(body, key, val);
}
return cb(body);
});
}
function url() {
// url 处理程序
function prepareProxy(proxy = {}) {
var _context2;
// 解析 proxy 参数, proxy: string, object
const pathToRegexp = require(`path-to-regexp`);
const isType = tool.type.isType;
const proxyType = isType(proxy);
let resProxy = [];
if (proxyType === `string`) {
// 任何路径都转发到 proxy
proxy = {
'/': proxy
};
}
function setIndexOfEnd(proxy) {
// 需要排序 key:/ 到最后, 否则它生成的拦截器会被其他 key 覆盖
const indexVal = proxy[`/`];
delete proxy[`/`];
proxy[`/`] = indexVal;
return proxy;
}
proxy = setIndexOfEnd(proxy);
resProxy = (0, _map.default)(_context2 = (0, _keys.default)(proxy)).call(_context2, context => {
let options = proxy[context];
const optionsType = isType(options);
if (optionsType === `string`) {
// 转换字符串的 value 为对象
const rootOptions = proxy[`/`];
options = {
pathRewrite: {
[`^${context}`]: options
},
// 原样代理 /a 到 /a
target: (0, _includes.default)(options).call(options, `://`) // 如果要代理的目录地址已有主域
? new _url.default(options).origin // 那么就代理到该主域上
: {
// 否则就代理到 / 设定的域上
string: rootOptions,
object: rootOptions.target // 当主域是对象时则取其 target
}[isType(rootOptions)]
};
}
if (optionsType === `array`) {
// 是数组时, 视为设计 res body 的值, 语法为: [k, v]
const [item1, item2] = options;
const item1Type = isType(item1);
const item2Type = isType(item2);
const deepMergeObject = tool.obj.deepMergeObject;
if (item1Type !== `function` && options.length <= 1) {
// 只有0个或一个项, 直接替换 res
options = {
onProxyRes(proxyRes, req, res) {
midResJson({
proxyRes,
res,
cb: () => item1
});
}
};
}
if (item1Type === `function` && options.length <= 1) {
options = {
onProxyRes(proxyRes, req, res) {
midResJson({
proxyRes,
res,
cb: json => item1({
req,
json
})
});
}
};
}
if (item1Type === `function` && item2Type === `function`) {
options = {
onProxyRes(proxyRes, req, res) {
midResJson({
proxyRes,
res,
cb: json => {
return item2({
req,
json: item1({
req,
json
})
});
}
});
}
};
}
if (item1Type === `string` && item2Type === `function`) {
options = {
onProxyRes(proxyRes, req, res) {
midResJson({
proxyRes,
res,
cb: json => {
return item2({
req,
json: tool.obj.deepGet(json, item1)
});
}
});
}
};
}
if (item1Type === `function` && item2Type === `string`) {
options = {
onProxyRes(proxyRes, req, res) {
midResJson({
proxyRes,
res,
cb: json => {
return tool.obj.deepGet(item1({
req,
json
}), item2);
}
});
}
};
}
if (item1Type === `string` && options.length === 2) {
// 以 item1 作为 key, item2 作为 val, 修改原 res
options = {
onProxyRes(proxyRes, req, res) {
midResJson({
proxyRes,
res,
key: item1,
val: item2
});
}
};
}
if (item1Type === `object` && options.length === 2) {
// 根据 item2 的类型, 合并 item1
options = {
onProxyRes(proxyRes, req, res) {
midResJson({
proxyRes,
res,
cb: body => {
return {
'deep': deepMergeObject(body, item1),
// 父级【不会】被替换
'...': { ...body,
...item1
} // 父级【会】被替换, 类似于js扩展运行符
}[item2 || `deep`];
}
});
}
};
}
}
const re = pathToRegexp(context);
return {
re,
context,
options
};
});
return resProxy;
}
function parseProxyTarget(proxy) {
// 解析 proxy 为 {pathname, origin}
let origin = ``;
try {
if (typeof proxy === `string`) {
origin = proxy;
}
if (typeof proxy === `object`) {
origin = proxy[`/`].target || proxy[`/`];
}
const parentUrl = new _url.default(origin);
const res = {
host: parentUrl.host,
port: parentUrl.port || 80,
hostname: parentUrl.hostname,
pathname: parentUrl.pathname.replace(/\/$/, ``) + `/`,
origin: parentUrl.origin,
isIp: (parentUrl.host.match(/\./g) || []).length === 3
};
return res;
} catch (error) {
console.error(`请正确填写 proxy 参数`, error);
process.exit();
}
}
function parseRegPath(rePath, url) {
// 使用 path-to-regexp 转换 express 的 router, 并解析参数为对象
// 注: path-to-regexp 1.x 自带 match 方法可处理此方法, 但是当前的 json-server 依赖的 express 的路由语法仅支持 path-to-regexp@0.1.7
// 所以只能手动转换, 参考: https://github.com/ForbesLindesay/express-route-tester/blob/f39c57fa660490e74b387ed67bf8f2b50ee3c27f/index.js#L96
const pathToRegexp = require(`path-to-regexp`);
const keys = [];
const re = pathToRegexp(rePath, keys); // 去除 url 中的 origin
const pathUrl = url.match(/^\//) ? url : url.replace(new _url.default(url).origin, ``);
const result = re.exec(pathUrl);
const obj = (0, _reduce.default)(keys).call(keys, (acc, cur, index) => {
acc[cur.name] = result[index + 1];
return acc;
}, {});
return obj;
}
return {
prepareProxy,
parseProxyTarget,
parseRegPath
};
}
function middleware() {
// express 中间件
const compression = require(`compression`); // 压缩 http 响应
function httpLog() {
// 设置 http 请求日志中间件
const morgan = require(`morgan`);
const colors = tool.cli.colors;
return morgan((tokens, req, res) => {
const testApi = res.getHeader(global.config.apiInHeader); // 当保存了历史记录时, 才在控制台的 http log 显示它
if (Boolean(testApi) === false) {
return undefined;
}
const colorTable = {
1: `gray`,
2: `green`,
3: `yellowBright`,
4: `yellow`,
5: `red`
};
const statusCode = String(res.statusCode);
const len = res.getHeader(`Content-Length`);
const str = [tool.time.dateFormat(`YYYY-MM-DD hh:mm:ss`, new Date()), tool.httpClient.getClientIp(req), testApi, `${statusCode} ${res.statusMessage}`, `${tokens[`response-time`](req, res)} ms`, len ? `${len} byte` : ``].join(` `); // 使用原生 nodejs 打印日志
print(colors[colorTable[statusCode[0]]](str)); // 根据状态码的第一位获取颜色函数
return []; // return list.join(' ')
});
}
function getJsonServerMiddlewares() {
var _context3;
// 获取 jsonServer 中的中间件
// 利用 jsonServer 已有的中间件, 而不用额外的安装
// 注意: 可能根据 jsonServer 版本的不同, 存在的中间件不同
const jsonServer = require(`@wll8/json-server`);
const middlewares = jsonServer.defaults({
bodyParser: global.config._bodyParserMid,
logger: false
}); // 可以直接使用的所有中间件数组
middlewares.push(httpLog());
const middlewaresObj = (0, _reduce.default)(_context3 = (0, _flat.default)(middlewares).call(middlewares)).call(_context3, (res, item) => {
// 使用 jsonServer 里面的中间件, 以保持一致:
// compression, corsMiddleware, serveStatic, logger, jsonParser, urlencodedParser
return { ...res,
[item.name]: item
};
}, {});
return {
middlewares,
middlewaresObj
};
}
function reWriteRouter({
app,
routes = {}
}) {
var _context4;
// 根据 routes 对象, 重写路由
const rewrite = require(`express-urlrewrite`);
(0, _forEach.default)(_context4 = (0, _keys.default)(routes)).call(_context4, key => {
app.use(rewrite(key, routes[key]));
});
}
function replayHistoryMiddleware({
id,
business
} = {}) {
const {
clientInjection,
historyHandle
} = business;
const {
allowCors,
setHeader,
reSetApiInHeader
} = clientInjection();
const {
getHistory
} = historyHandle();
return (req, res, next) => {
// 修改分页参数, 符合项目中的参数
const method = req.method.toLowerCase();
const fullApi = id ? undefined : `${method} ${req.originalUrl}`;
const history = getHistory({
id,
fullApi,
find: list => {
const getSize = path => {
const bodyPathCwd = require(`path`).join(process.cwd(), path);
return require(`fs`).readFileSync(bodyPathCwd).length;
};
const getStatus = item => {
try {
return item.data.res.lineHeaders.line.statusCode;
} catch (err) {
console.log(`err`, err);
}
};
(0, _sort.default)(list).call(list, (item, item1) => {
try {
const size = getSize(item.data.res.bodyPath);
const size1 = getSize(item1.data.res.bodyPath);
return size1 - size;
} catch (error) {
console.log(`error`, error);
return 0;
}
});
const getStatusCodeItem = list => (0, _find.default)(list).call(list, item => getStatus(item) === 200); // 查找 http 状态码为 200 的条目
let getItemRes = undefined;
if (global.config.replayProxyFind) {
// 先使用配置的 replayProxyFind 函数, 如果没有打到则使用普通状态码
try {
getItemRes = (0, _find.default)(list).call(list, (...arg) => global.config.replayProxyFind(...arg));
} catch (error) {
console.log(error);
}
}
getItemRes = getItemRes || getStatusCodeItem(list) || list[0] || {}; // 如果也没有找到状态码为 200 的, 则直接取第一条
return getItemRes;
}
}).data;
try {
const lineHeaders = history.res.lineHeaders;
const headers = lineHeaders.headers || require(require(`path`).resolve(lineHeaders.headPath));
setHeader(res, { ...headers,
// 还原 headers
...reSetApiInHeader({
headers
}) // 更新 testApi
});
allowCors({
res,
req
});
const bodyPath = history.res.bodyPath;
if (bodyPath) {
const path = require(`path`);
const newPath = path.resolve(bodyPath); // 发送 body
// 由于 newPath 是由程序生成的, 而不是用户输入的, 所以不用担心路径遍历
res.sendFile(newPath);
} else {
const {
statusCode,
statusMessage
} = lineHeaders.line;
res.statusCode = statusCode;
res.statusMessage = statusMessage;
res.send();
}
} catch (err) {
console.log(`err`, err);
res.json(global.config.resHandleReplay({
req,
res
}));
}
};
}
return {
reWriteRouter,
compression,
getJsonServerMiddlewares,
replayHistoryMiddleware
};
}
/**
* 保存日志
*/
function saveLog({
logStr = ``,
logPath,
code = `-`
}) {
var _context5;
const fs = require(`fs`);
const os = require(`os`);
const packageJson = require(`../package.json`);
fs.existsSync(logPath) && fs.writeFileSync(logPath, [[tool.time.dateFormat(`YYYY-MM-DD hh:mm:ss`, new Date()), // 当前时间
`mockm:${packageJson.version}`, // mockm 版本号
`node:${process.version}`, // node 版本号
`os:${os.type()} ${os.release()}`, // 操作系统和版本号
`code:${code}`, // 退出码
`arg:${(0, _splice.default)(_context5 = process.argv).call(_context5, 2)}`, // 命令行参数
`lang:${process.env.LANG}` // 终端语言环境
].join(`, `), // 附件信息
`\n`, (0, _trim.default)(logStr).call(logStr), // 调用栈
`\n\n`, fs.readFileSync(logPath, `utf8`) // 旧 log
].join(``));
}
/**
* 通过重新保存文件的方式触发 nodemon 的文件监听, 然后让服务重启
*/
function reStartServer(filePath) {
const fs = require(`fs`);
const str = fs.readFileSync(filePath, `utf8`);
fs.writeFileSync(filePath, str);
}
function wrapApiData({
data,
code = 200
}) {
// 包裹 api 的返回值
code = String(code);
return {
code,
success: Boolean(code.match(/^[2]/)),
// 如果状态码以2开头则为 true
data
};
}
/**
* 把类似 schema 的列表转换为数据
* @param {array} list 列表
* @param {object} options 规则
*/
function listToData(list, options = {}) {
var _context6;
const Mock = require(`@wll8/better-mock`);
const mockMethods = (0, _map.default)(_context6 = (0, _keys.default)(Mock.Random)).call(_context6, key => `@${key}`);
function listToDataRef(list) {
// 注: 通过此函数转换出的结果并不是可以生成随机值的模板, 而是已生成固定值模板, 因为需要使用值转换为对应的类型
let res = {};
(0, _forEach.default)(list).call(list, item => {
var _context8;
let example = item.example ? String(item.example) : ``;
if (item.type === `eval`) {
// 使用代码执行结果
try {
const {
NodeVM
} = require(`vm2`);
const vm = new NodeVM({
sandbox: {
// 给 vm 使用的变量
Mock
}
});
example = vm.run(`module.exports = ${example}`, `vm.js`) || ``;
} catch (err) {
console.log(`err`, err);
} // 处理含有 @mock 方法或为正则的 example
} else if ((0, _some.default)(mockMethods).call(mockMethods, item => (0, _includes.default)(example).call(example, item)) === false) {
// 如果不包含 @mock 方法则进行类型转换
// todo 不应该直接使用 includes 判断, 例如可以是 `@inc` 或 `@inc c` 但不能是 `@incc`
// 根据 type 转换 example 值的类型
if (strReMatch(example)) {
// 猜测为正则
example = strReMatch(example);
} else if (item.type === `number`) {
// 数字
example = Number(example);
} else if (item.type === `boolean`) {
var _context7;
// 布尔
example = (0, _includes.default)(_context7 = [`false`, `0`, `假`, `T`, `t`]).call(_context7, example) ? false : Boolean(example);
}
} // 如果是对象或数里进行递归调用
if ((0, _includes.default)(_context8 = [`object`, `array`]).call(_context8, item.type) && (0, _isArray.default)(item.children)) {
switch (item.type) {
case `object`:
res[item.name] = listToDataRef(item.children);
break;
case `array`:
res[item.name] = res[item.name] || [];
res[item.name].push(listToDataRef(item.children));
break;
default:
console.log(`no type`, item.type);
}
} else {
// 如果不是引用类型, 则应用最后转换后的值 example
res[`${item.name}#${item.type || `string`}`] = example;
}
});
return res;
}
let res = listToDataRef(list);
res = {
[`data${options.rule ? `|${options.rule}` : ``}`]: {
object: res,
array: [res]
}[options.type]
};
const data = Mock.mock(res);
return data;
}
/**
* 如果字符串是正则就返回正则, 否则返回 false
* @param {string} str 前后有 / 号的字符串
*/
function strReMatch(str) {
let reStr = (str.match(/^\/(.*)\/$/) || [])[1];
let re = undefined;
if (reStr) {
try {
re = new RegExp(reStr);
} catch (error) {
// 正则转换失败
return false;
}
} else {
return false;
}
return re;
}
async function customApi() {
// 所有 api, 例如 db 解析后的中间件信息
const serverRouterItem = {
alias: [],
// String[], 路由别名
route: undefined,
// String, 路由地址
re: undefined,
// RegExp, 由 route 转换的正则
method: undefined,
// String, 请求方式, 例如 use get post
type: undefined,
// Enum, 指定接口来源, 例如 db api
description: undefined,
// String, 接口描述
disable: undefined,
// Boolean, 是否禁用
action: undefined,
// Function, 指定接口要运行的方法
occupied: {
// Object, 被谁占用, 如果是被占用的状态, 则不会被使用
type: undefined,
// Enum
route: undefined // String
},
info: {} // Object, 根据 type 可能不同结构的附加信息
};
const pathToRegexp = require(`path-to-regexp`);
/**
* 自定义 api 处理程序, 包括配置中的用户自定义路由(config.api), 以及mock数据生成的路由(config.db)
*/
async function parseApi() {
var _context10, _context12;
// 解析自定义 api
const {
setHeader,
allowCors
} = clientInjection();
const run = {
async curl({
req,
res,
cmd
}) {
// cmd: curl/bash
const options = await tool.cli.getOptions(cmd);
return new _promise.default(async (resolve, reject) => {
const request = await tool.generate.initPackge(`request`);
request(options, (err, curlRes = {}, body) => {
setHeader(res, curlRes.headers); // 复制远程的 header
allowCors({
req,
res
}); // 设置 header 为允许跨域模式
const mergeRes = curlRes;
err ? reject(err) : resolve(mergeRes);
});
});
},
fetch({
req,
res,
fetchRes
}) {
// node-fetch
return new _promise.default((resolve, reject) => {
fetchRes.then(fetchThenRes => {
var _context9;
const headers = (0, _reduce.default)(_context9 = [...fetchThenRes.headers]).call(_context9, (acc, cur) => ({ ...acc,
[cur[0]]: cur[1]
}), {});
const contentEncoding = headers[`content-encoding`];
if (contentEncoding && (0, _includes.default)(contentEncoding).call(contentEncoding, `gzip`)) {
// 由于返回的内容其实已经被解码过了, 所以不能再告诉客户端 content-encoding 是压缩的 `gzip`, 否则会导致客户端无法解压缩
// - 例如导致浏览器无法解码: net::ERR_CONTENT_DECODING_FAILED 200 (OK)
delete headers[`content-encoding`];
}
setHeader(res, headers);
allowCors({
req,
res
});
const mergeRes = fetchThenRes;
resolve(mergeRes);
}).catch(err => {
console.log(`err`, err);
reject(err);
});
});
}
};
const apiUtil = {
// 向 config.api 暴露一些工具库
run
};
let api = global.config.api(apiUtil);
await pluginRun(`apiParsed`, api, apiUtil);
const side = {}; // 从 side 函数中扩展 api
api = (0, _reduce.default)(_context10 = (0, _entries.default)(api)).call(_context10, (acc, [key, val]) => {
if (val instanceof Side) {
var _context11;
const sideObj = {
alias: [] // 路由别名
};
val = { ...sideObj,
...val
};
(0, _forEach.default)(_context11 = val.alias).call(_context11, alias => {
acc[alias] = val.action;
});
side[key] = val;
val = val.action;
}
acc[key] = val;
return acc;
}, {});
const serverRouterList = []; // server 可使用的路由列表
(0, _forEach.default)(_context12 = (0, _keys.default)(api)).call(_context12, key => {
var _context14;
let {
method,
url
} = tool.url.fullApi2Obj(key);
method = method.toLowerCase();
let val = api[key];
if (method === `use`) {
var _context13;
// 自定义中间件时不使用自动返回 json 的规则
if ((0, _includes.default)(_context13 = [`function`, `array`, `asyncfunction`]).call(_context13, tool.type.isType(val)) === false) {
// use 支持单个和多个(数组)中间件
print(tool.cli.colors.red(`Data other than function|array type is not allowed in the use mode in config.api: ${val}`));
val = (req, res, next) => next();
}
} else if ((0, _includes.default)(_context14 = [`string`, `number`, `object`, `array`, `boolean`, `null`]).call(_context14, tool.type.isType(val))) {
// 如果配置的值是 json 支持的数据类型, 则直接作为返回值, 注意, 读取一个文件也是对象
const backVal = val;
if (method === `ws`) {
val = (ws, req) => {
const strData = (0, _stringify.default)(backVal);
ws.send(strData);
ws.on(`message`, msg => ws.send(strData));
};
} else {
val = (req, res, next) => res.json(backVal);
}
}
serverRouterList.push({ ...serverRouterItem,
...side[key],
route: url,
re: pathToRegexp(url),
method,
type: `api`,
action: val,
occupied: {}
});
});
await pluginRun(`apiListParsed`, serverRouterList);
return serverRouterList;
}
/**
* 测试给定的 method 和 pathname 是否匹配当前存在的 route
* @param {*} param0
* @returns
*/
function allRouteTest({
allRoute,
upgrade,
method,
pathname
}) {
// return true 时不走真实服务器, 而是走自定义 api
return (0, _find.default)(allRoute).call(allRoute, item => {
if (item.method === `ws` && method === `get`) {
// ws 连接时, 实际上得到的 method 是 get, 并且 pathname + .websocket
return item.re.exec(pathname) && upgrade.match(/websocket/i) && Boolean(item.disable) === false;
}
if (item.method === `use`) {
// 当为中间件模式时, 匹配其后的任意路径
if (item.type === `db`) {
var _context15;
// 如果是 db 模式下, 需要匹配 db 下的存在的接口
return (0, _find.default)(_context15 = item.info.apiList).call(_context15, dbItem => dbItem.re.exec(pathname) && dbItem.method === method && Boolean(dbItem.disable) === false);
} else {
return (0, _startsWith.default)(pathname).call(pathname, item.route) && Boolean(item.disable) === false;
}
} // 当方法相同时才去匹配 url
if (item.method === `all` || item.method === method) {
return item.re.exec(pathname) // 如果匹配到自定义的 api 则走自定义 api
&& Boolean(item.disable) === false // 如果自定义 api 为禁用状态, 则走真实服务器
;
} else {
return false;
}
});
}
/**
* 展开 db 中的 api
*/
function unzipDbApi() {
const db = JSON.parse(require(`fs`).readFileSync(global.config.dbJsonPath));
const keys = (0, _keys.default)(db);
const isType = tool.type.isType;
let apiList = [];
(0, _forEach.default)(keys).call(keys, key => {
const val = db[key];
if (isType(val, `object`)) {
var _context16;
(0, _forEach.default)(_context16 = `get post put patch`.split(` `)).call(_context16, method => {
apiList.push({
method,
route: `/${key}`
});
});
}
if (isType(val, `array`)) {
var _context17, _context18;
(0, _forEach.default)(_context17 = `get post`.split(` `)).call(_context17, method => {
apiList.push({
method,
route: `/${key}`
});
});
(0, _forEach.default)(_context18 = `get put patch delete`.split(` `)).call(_context18, method => {
apiList.push({
method,
route: `/${key}/:id`
});
});
}
});
apiList = (0, _map.default)(apiList).call(apiList, item => {
return {
route: item.route,
re: pathToRegexp(item.route),
method: item.method,
type: `db`,
action: undefined,
occupied: {}
};
});
return {
keys,
apiList
};
}
function parseDbApi() {
const db = tool.file.fileStore(global.config.dbJsonPath).get();
if ((0, _keys.default)(db).length === 0) {
return [];
}
const router = jsonServer.router(global.config.dbJsonPath, {
_preciseNeste: true,
_noRemoveDependents: true,
_noDataNext: true,
_noDbRoute: true
});
global.config._set(`_db`, router.db);
router.render = async (req, res) => {
// 修改输出的数据, 符合项目格式
// 在 render 方法中, req.query 会被删除
// https://github.com/typicode/json-server/issues/311
// https://github.com/typicode/json-server/issues/314
const querystring = require(`querystring`);
if (req._parsedUrl) {
const query = querystring.parse(req._parsedUrl.query);
req.query = query;
}
let returnData = res.locals.data; // 前面的数据返回的 data 结构
const xTotalCount = res.get(`X-Total-Count`);
if (xTotalCount) {
returnData = {
count: xTotalCount,
results: res.locals.data
};
}
const data = await res.mm.resHandleJsonApi({
req,
res,
data: returnData,
resHandleJsonApi: global.config.resHandleJsonApi
});
res.json(data);
};
const {
keys,
apiList
} = unzipDbApi();
const list = [{
route: `/`,
re: pathToRegexp(`/`),
method: `use`,
type: `db`,
action: router,
info: {
keys,
apiList
},
occupied: {}
}];
return list;
}
function apiWebHandle() {
var _context19, _context20;
// 处理 webApi 为 api
const apiWebStore = tool.file.fileStore(global.config.apiWeb);
const paths = apiWebStore.get(`paths`);
const disableApiList = apiWebStore.get(`disable`);
const pathList = (0, _flat.default)(_context19 = (0, _map.default)(_context20 = (0, _keys.default)(paths)).call(_context20, path => {
var _context21;
return (0, _map.default)(_context21 = (0, _keys.default)(paths[path])).call(_context21, method => ({
key: `${method} ${path}`,
path,
method,
...paths[path][method]
}));
})).call(_context19);
const apiList = (0, _reduce.default)(pathList).call(pathList, (acc, cur, curIndex) => {
let fn = (req, res, next) => {
const {
example = {},
table = []
} = cur.responses[`200`];
const {
headers = {},
useDataType = `table`,
custom,
history,
rule,
type
} = example;
if (useDataType === `table`) {
// 使用表格中的数据
try {
const {
setHeader
} = clientInjection();
setHeader(res, headers); // 设置自定义 header
let data;
const listToDataRes = listToData(table, {
rule,
type
});
data = listToDataRes.data; // 根据 apiWebWrap 处理数据
if (global.config.apiWebWrap === true) {
data = wrapApiData({
data,
code: 200
});
} else if (typeof global.config.apiWebWrap === `function`) {
data = global.config.apiWebWrap({
data,
code: 200
});
}
res.json(data);
} catch (err) {
print(err);
res.status(500).json({
msg: String(err)
});
}
}
if (useDataType === `custom`) {
// 自定义逻辑
try {
var _context22;
const {
NodeVM
} = require(`vm2`);
const vm = new NodeVM({
sandbox: {
// 给 vm 使用的变量
tool: {
libObj: lib,
wrapApiData,
listToData,
cur
}
}
});
const code = vm.run(`module.exports = ${custom}`, `vm.js`) || ``;
const codeType = typeof code;
if ((0, _includes.default)(_context22 = [`function`]).call(_context22, codeType)) {
// 如果是函数则运行函数
code(req, res, next);
} else {
// 函数之外的类型则当作 json 返回
res.json(code);
}
} catch (err) {
console.log(`err`, err); // 处理客户端代码出现问题, 代码错误或出现不允许的权限
res.status(403).json({
msg: err.message
});
}
}
if (useDataType === `history`) {
// 使用历史记录
middleware().replayHistoryMiddleware({
id: history,
business: business()
})(req, res, next);
}
};
if (cur.method === `ws`) {
// 如果是 websocket 方法则更换函数模版
fn = (ws, req) => {
const sendData = data => {
const strData = (0, _stringify.default)(data);
ws.send(strData);
ws.on(`message`, msg => {
ws.send(strData);
});
};
const {
example = {},
table = []
} = cur.responses[`200`];
const {
headers = {},
useDataType = `table`,
custom,
history,
rule,
type
} = example;
if (useDataType === `table`) {
// 使用表格中的数据
try {
let data;
const listToDataRes = listToData(table, {
rule,
type
});
data = listToDataRes.data;
sendData(data);
} catch (err) {
print(err);
sendData({
msg: String(err)
});
}
}
if (useDataType === `custom`) {
// 自定义逻辑
try {
var _context23;
const {
NodeVM
} = require(`vm2`);
const vm = new NodeVM({
sandbox: {
// 给 vm 使用的变量
tool: {
libObj: lib,
wrapApiData,
listToData,
cur
}
}
});
const code = vm.run(`module.exports = ${custom}`, `vm.js`) || ``;
const codeType = typeof code;
if ((0, _includes.default)(_context23 = [`function`]).call(_context23, codeType)) {
// 如果是函数则运行函数
code(ws, req);
} else {
// 函数之外的类型则当作 json 返回
sendData(code);
}
} catch (err) {
console.log(`err`, err); // 处理客户端代码出现问题, 代码错误或出现不允许的权限
sendData({
msg: err.message
});
}
}
};
}
const disable = (0, _includes.default)(disableApiList).call(disableApiList, `/`) // 如果含有根结点, 则表示全部禁用
? true : (0, _includes.default)(disableApiList).call(disableApiList, cur.key);
acc.push({
route: cur.path,
re: pathToRegexp(cur.path),
method: cur.method,
type: `apiWeb`,
description: cur.description,
disable,
action: fn,
occupied: {}
});
return acc;
}, []);
return apiList;
}
function prepareProxy(proxy = {}) {
var _context24;
// 解析 proxy 参数, proxy: string, object
const isType = tool.type.isType;
const proxyType = isType(proxy);
let resProxy = [];
if (proxyType === `string`) {
// 任何路径都转发到 proxy
proxy = {
'/': proxy
};
}
function setIndexOfEnd(proxy) {
// 需要排序 key:/ 到最后, 否则它生成的拦截器会被其他 key 覆盖
const indexVal = proxy[`/`];
delete proxy[`/`];
proxy[`/`] = indexVal;
return proxy;
}
proxy = setIndexOfEnd(proxy);
resProxy = (0, _map.default)(_context24 = (0, _keys.default)(proxy)).call(_context24, context => {
let options = proxy[context];
const optionsType = isType(options);
if (optionsType === `string`) {
// 转换字符串的 value 为对象
const rootOptions = proxy[`/`];
options = {
pathRewrite: {
[`^${context}`]: options
},
// 原样代理 /a 到 /a
target: (0, _includes.default)(options).call(options, `://`) // 如果要代理的目录地址已有主域
? new _url.default(options).origin // 那么就代理到该主域上
: {
// 否则就代理到 / 设定的域上
string: rootOptions,
object: rootOptions.target // 当主域是对象时则取其 target
}[isType(rootOptions)]
};
}
if (optionsType === `array`) {
// 是数组时, 视为设计 res body 的值, 语法为: [k, v]
const [item1, item2] = options;
const item1Type = isType(item1);
const item2Type = isType(item2);
const deepMergeObject = tool.obj.deepMergeObject;
if (item1Type !== `function` && options.length <= 1) {
// 只有0个或一个项, 直接替换 res
options = {
onProxyRes(proxyRes, req, res) {
midResJson({
proxyRes,
res,
cb: () => item1
});
}
};
}
if (item1Type === `function` && options.length <= 1) {
options = {
onProxyRes(proxyRes, req, res) {
midResJson({
proxyRes,
res,
cb: json => item1({
req,
json
})
});
}
};
}
if (item1Type === `function` && item2Type === `function`) {
options = {
onProxyRes(proxyRes, req, res) {
midResJson({
proxyRes,
res,
cb: json => {
return item2({
req,
json: item1({
req,
json
})
});
}
});
}
};
}
if (item1Type === `string` && item2Type === `function`) {
options = {
onProxyRes(proxyRes, req, res) {
midResJson({
proxyRes,
res,
cb: json => {
return item2({
req,
json: tool.obj.deepGet(json, item1)
});
}
});
}
};
}
if (item1Type === `function` && item2Type === `string`) {
options = {
onProxyRes(proxyRes, req, res) {
midResJson({
proxyRes,
res,
cb: json => {
return tool.obj.deepGet(item1({
req,
json
}), item2);
}
});
}
};
}
if (item1Type === `string` && options.length === 2) {
// 以 item1 作为 key, item2 作为 val, 修改原 res
options = {
onProxyRes(proxyRes, req, res) {
midResJson({
proxyRes,
res,
key: item1,
val: item2
});
}
};
}
if (item1Type === `object` && options.length === 2) {
// 根据 item2 的类型, 合并 item1
options = {
onProxyRes(proxyRes, req, res) {
midResJson({
proxyRes,
res,
cb: body => {
return {
'deep': deepMergeObject(body, item1),
// 父级【不会】被替换
'...': { ...body,
...item1
} // 父级【会】被替换, 类似于js扩展运行符
}[item2 || `deep`];
}
});
}
};
}
}
return {
route: context,
re: pathToRegexp(context),
method: undefined,
type: `proxy`,
action: undefined,
occupied: {},
info: options
};
});
return resProxy;
}
function staticHandle() {
var _context25;
// 处理 static 为 api, 实际上是转换为 use 中间件的形式
let list = [];
(0, _map.default)(_context25 = global.config.static).call(_context25, item => {
list.push({
route: item.path,
re: pathToRegexp(item.path),
method: `use`,
type: `static`,
action: [async (req, res, next) => {
// 开启列表显示时
if (item.list && item.mode !== `history`) {
fileList(item)(req, res, next);
} else {
next();
}
}, async (req, res, next) => {
// mode history
if (item.mode === `history`) {
;
(await tool.generate.initPackge(`connect-history-api-fallback`))(item.option)(req, res, next);
} else {
next();
}
}, express.static(item.fileDir, item.option)],
occupied: {}
});
});
return list;
}
function resetUrl() {
// 重置 req.url
/**
某些中间件例如 json-server 会改变 req.url, 导致后续的 app.use 逻辑不符合预期
@see https://github.com/typicode/json-server/blob/5df482bdd6e864258d6e7180342a30bf7b923cbc/src/server/router/nested.js#L13
*/
return [{
route: `/`,
re: pathToRegexp(`/`),
method: `use`,
type: `resetUrl`,
action: (req, res, next) => {
req.url = req.originalUrl;
next();
},
info: {},
occupied: {}
}];
} // 处理为统一的列表, 注意列表中的顺序对应 use 注册的顺序
const obj = {
api: await parseApi(),
db: parseDbApi(),
resetUrl: resetUrl(),
static: staticHandle(),
apiWeb: apiWebHandle(),
proxy: prepareProxy(global.config.proxy)
};
const allRoute = [...obj.api, ...obj.db, ...obj.resetUrl, ...obj.static, ...obj.apiWeb, ...obj.proxy];
allRoute.obj = obj;
return {
allRouteTest,
allRoute
};
}
/**
* 过时的 API 提示
* @param {*} param0
* @param {*} param0.type api 类型: cli 命令行 option 选项
* @param {*} param0.no 旧API
* @param {*} param0.yew 新API
* @param {*} param0.v 被删除的版本
*/
function oldAPI({
type,
no,
yes,
v
}) {
type === `cli` && print(tool.cli.colors.red(`API更新: 请将命令行参数 ${no} 替换为 ${yes}, 在 v${v} 版本之后 ${no} 将停止使用!`));
}
function initHandle() {
// 初始化处理程序
/**
* 检查运行环境是否兼容
*/
function checkEnv() {
return lib.compareVersions.compare(process.version, `10.12.0`, `>=`);
}
function templateFn({
cliArg,
version
}) {
if (cliArg[`--template`]) {
const path = require(`path`);
const cwd = process.cwd();
if (tool.file.hasFile(cwd) === false) {
require(`fs`).mkdirSync(cwd, {
recursive: true
});
}
const copyPath = path.normalize(`${cwd}/mm/`);
tool.file.copyFolderSync(path.normalize(`${__dirname}/../example/template/`), copyPath);
{
// 创建 package.json 中的 scripts 和 devDependencies
const fs = require(`fs`);
const jsonPath = `${process.cwd()}/package.json`;
const hasJson = tool.file.hasFile(jsonPath);
const scripts = `npx mockm --cwd=mm`;
const devDependencies = version;
if (hasJson && (require(jsonPath).scripts || {}).mm && (require(jsonPath).devDependencies || {}).mockm) {// console.log(`无需修改`)
} else {
var _context26;
hasJson === false && fs.writeFileSync(jsonPath, (0, _trim.default)(_context26 = tool.string.removeLeft(`
{
"scripts": {
"mm": "${scripts}"
},
"devDependencies": {
"mockm": "${devDependencies}"
}
}
`)).call(_context26));
let packageText = fs.readFileSync(jsonPath, `utf8`);
const packageJson = JSON.parse(packageText);
packageJson.scripts = packageJson.scripts || {};
packageJson.devDependencies = packageJson.devDependencies || {};
packageJson.scripts.mm = packageJson.scripts.mm || scripts;
packageJson.devDependencies.mockm = packageJson.devDependencies.mockm || devDependencies;
const split = (packageText.match(/[\t ]+/) || [` `])[0]; // 获取缩进风格
packageText = (0, _stringify.default)(packageJson, null, split);
fs.writeFileSync(jsonPath, packageText);
}
}
process.chdir(copyPath);
print(`模板 ${copyPath} 已创建成功!`);
print(`使用命令 npm run mm`);
}
}
function configFileFn({
cliArg
}) {
const path = require(`path`);
const fs = require(`fs`);
const packagePath = `${process.cwd()}/package.json`;
const packageType = fs.existsSync(packagePath) && require(packagePath).type || `commonjs`; // 要在当前位置创建什么配置文件
const cwdConfigPath = packageType === `commonjs` ?