mockm
Version:
Analog interface server, painless parallel development of front and back ends.
577 lines (500 loc) • 15.9 kB
JavaScript
;
var _interopRequireDefault = require("@babel/runtime-corejs3/helpers/interopRequireDefault");
require("core-js/modules/es.symbol.description.js");
var _url = _interopRequireDefault(require("@babel/runtime-corejs3/core-js-stable/url"));
var _find = _interopRequireDefault(require("@babel/runtime-corejs3/core-js-stable/instance/find"));
var _sort2 = _interopRequireDefault(require("@babel/runtime-corejs3/core-js-stable/instance/sort"));
var _keys = _interopRequireDefault(require("@babel/runtime-corejs3/core-js-stable/object/keys"));
var _setInterval2 = _interopRequireDefault(require("@babel/runtime-corejs3/core-js-stable/set-interval"));
var _stringify = _interopRequireDefault(require("@babel/runtime-corejs3/core-js-stable/json/stringify"));
var _includes = _interopRequireDefault(require("@babel/runtime-corejs3/core-js-stable/instance/includes"));
var _forEach = _interopRequireDefault(require("@babel/runtime-corejs3/core-js-stable/instance/for-each"));
var _entries = _interopRequireDefault(require("@babel/runtime-corejs3/core-js-stable/object/entries"));
var _findIndex = _interopRequireDefault(require("@babel/runtime-corejs3/core-js-stable/instance/find-index"));
var _splice = _interopRequireDefault(require("@babel/runtime-corejs3/core-js-stable/instance/splice"));
const util = require(`./util/index.js`);
const {
print
} = require(`./util/log.js`);
function serverTest() {
const {
tool,
business
} = util;
const {
httpClient,
file: {
getBackUrl
}
} = tool;
const {
url: {
parseRegPath
},
middleware,
saveLog,
initHandle,
reqHandle,
clientInjection,
historyHandle,
customApi,
reStartServer,
listToData
} = business;
const {
getOpenApi
} = initHandle();
const {
allowCors
} = clientInjection();
const {
getHistory,
getHistoryList
} = historyHandle();
const {
sendReq
} = reqHandle();
const {
middlewaresObj
} = middleware.getJsonServerMiddlewares();
const apiWebStore = tool.file.fileStore(global.config.apiWeb);
const disableApiList = apiWebStore.get(`disable`);
const jsonServer = require(`@wll8/json-server`);
const serverTest = jsonServer.create();
business.getHttpServer({
app: serverTest,
name: `testPort`
});
serverTest.use(middlewaresObj.corsMiddleware);
serverTest.use(middlewaresObj.jsonParser);
serverTest.use(middleware.compression());
serverTest.get(`*`, (req, res, next) => {
let {
path
} = httpClient.getClientUrlAndPath(req.originalUrl);
if (path.match(/^\/api\//)) {
// 为 /api/ 则视为 api, 否则为静态文件
next();
} else {
path = path === `/` ? `/index.html` : path; // 访问 / 时默认返回 index.html
const filePath = require(`path`).resolve(__dirname, `./page/${path}`);
if (tool.file.hasFile(filePath)) {
return res.sendFile(filePath);
} else {
return res.status(404).send({
msg: `文件未找到: ${path}`
});
}
}
});
serverTest.get(`/api/:actionRaw/:api0(*)`, async (req, res, next) => {
// 给后端查询前端请求的接口
let {
actionRaw,
api0
} = parseRegPath(req.route.path, req.url);
const [action, ...actionArg] = actionRaw.split(`,`);
api0 = `/${api0}`;
const [, method, api] = api0.match(/\/(\w+)(.*)/) || [];
const actionArg0 = actionArg[0];
const fullApi = api ? `${method} ${api}` : undefined;
function getFilePath({
reqOrRes,
id
}) {
try {
const httpData = getHistory({
fullApi,
id
}).data[reqOrRes];
if (reqOrRes === `res`) {
// 模仿 res 中的响应头, 但是开启跨域
const headers = httpData.lineHeaders.headers || require(require(`path`).resolve(httpData.lineHeaders.headPath));
res.set(headers);
allowCors({
res,
req
});
}
if (tool.file.hasFile(httpData.bodyPath)) {
res.sendFile(require(`path`).resolve(httpData.bodyPath));
} else {
throw new Error(`不存在文件 ${httpData.bodyPath}`);
}
} catch (err) {
console.log(`err`, {
api,
err
});
res.status(404).json({
msg: err.message
});
}
}
const actionFnObj = {
getApiList() {
const list = getHistoryList({});
let {
_sort = ``,
_order = ``,
_page = 1,
_limit = 10
} = req.query;
_sort = _sort.split(`,`);
_order = _order.split(`,`);
if (_sort[0] === `id`) {
// 把 id 转换为数字, 这样 orderBy 才能进行比较
_sort[0] = item => Number(tool.hex.string62to10(item.id));
}
if (_sort[0] === `date`) {
_sort[0] = item => new Date(item.date).getTime();
}
const page = _page;
const limit = _limit;
const orderBy = require(`lodash.orderby`);
const drop = require(`lodash.drop`);
const take = require(`lodash.take`);
const results = take(drop(orderBy(list, _sort, _order), (page - 1) * limit), limit);
const sendData = {
count: list.length,
results
};
res.send(sendData);
},
getApiHistry(apiId) {
const list = getHistoryList({
method,
api
});
res.send(list);
},
getOpenApi() {
const api = req.query.api;
let openApiPrefix = `/`; // openApi 的前缀
const openApi = {
string: () => global.config.openApi,
// 字符串时, 直接返回
array: () => {
// 数组时, 返回 pathname 匹配度最高的项
const pathname = new _url.default(`http://127.0.0.1${api}`).pathname;
return tool.url.findLikeUrl({
urlList: global.config.openApi,
pathname
});
},
object: () => {
var _context, _context2;
// 对象时, 以 `new RegExp(key, 'i').test(pathname)` 的形式匹配
const pathname = new _url.default(`http://127.0.0.1${api}`).pathname;
let firstKey = ``;
const key = (0, _find.default)(_context = (0, _sort2.default)(_context2 = (0, _keys.default)(global.config.openApi)).call(_context2, (a, b) => {
// 优先从 url 目录层级较多的开始比较
return b.split(`/`).length - a.split(`/`).length;
})).call(_context, key => {
if (firstKey === ``) {
// 把第一个 key 保存起来, 当没有找到对应的 key 时则把它作为默认的 key
firstKey = key;
}
const re = new RegExp(key, `i`);
return re.test(pathname);
});
openApiPrefix = key || firstKey;
return global.config.openApi[openApiPrefix];
}
}[tool.type.isType(global.config.openApi)]();
openApiPrefix = openApiPrefix.replace(/\/$/, ``); // 最后面不需要 `/`, 否则会出现两个 `//`, 因为它是拼接在 `/` 开头的 api 前面的
openApi && getOpenApi({
openApi
}).then((openApiData = {}) => {
openApiData.info = { ...openApiData.info,
_openApiPrefix: openApiPrefix
};
res.send(openApiData);
}).catch(async err => {
// 当 openApi 获取失败时, 尝试从历史记录中获取, 以期望总是可以查看接口文档
// header 中的 x-mockm-msg 值为 from history 表示是来自本地历史
const file = await getBackUrl(global.config._openApiHistoryDir, openApi);
if (file) {
res.setHeader(`x-mockm-msg`, `from history`);
const openApiData = require(file);
openApiData.info = { ...openApiData.info,
_openApiPrefix: openApiPrefix
};
res.send(openApiData);
} else {
res.status(500);
res.send({
msg: `openApi 读取错误`,
err
});
}
console.log(`err`, err);
});
if (!openApi) {
res.status(404);
res.send({
msg: `openApi 未找到`
});
}
},
getApiListSse() {
res.writeHead(200, {
"Content-Type": `text/event-stream`,
"Cache-Control": `no-cache`,
"Connection": `keep-alive`
});
res.write(`retry: 10000\n`);
res.write(`event: message\n`);
let oldSize = -1;
const interval = (0, _setInterval2.default)(() => {
const fs = require(`fs`);
fs.stat(global.config._httpHistory, (err, stats) => {
// 不用全部读取文件即可读取文件大小信息, 减少内存占用
if (err) {
return console.error(err);
}
if (stats.size !== oldSize) {
const str = (0, _stringify.default)(getHistoryList({}));
res.write(`data:${str}\n\n`);
res.flush();
oldSize = stats.size;
}
});
}, 500);
req.connection.addListener(`close`, () => {
clearInterval(interval);
}, false);
},
replay() {
sendReq({
getHistory,
api: fullApi,
res,
apiId: actionArg0
});
},
getBodyFileReq() {
getFilePath({
reqOrRes: `req`,
id: actionArg0
});
},
getBodyFileRes() {
getFilePath({
reqOrRes: `res`,
id: actionArg0
});
},
getHttpData() {
const historyRes = JSON.parse((0, _stringify.default)(getHistory({
fullApi,
id: actionArg0
})));
if (historyRes.data) {
const {
method,
path
} = historyRes.data.req.lineHeaders.line;
historyRes.data.req.lineHeaders.headers = historyRes.data.req.lineHeaders.headers || require(require(`path`).resolve(historyRes.data.req.lineHeaders.headPath));
historyRes.data.req.lineHeaders.headPath = undefined;
historyRes.data.res.lineHeaders.headers = historyRes.data.res.lineHeaders.headers || require(require(`path`).resolve(historyRes.data.res.lineHeaders.headPath));
historyRes.data.res.lineHeaders.headPath = undefined;
const webApi = (apiWebStore.get([`paths`, path]) || {})[method];
if (webApi) {
var _context3;
webApi.disable = (0, _includes.default)(_context3 = apiWebStore.get(`disable`)).call(_context3, historyRes.fullApi);
}
res.send({
webApi,
historyRes
});
} else {
res.status(404).send({
msg: `记录不存在`
});
}
},
getApiResponseById() {
middleware.replayHistoryMiddleware({
id: actionArg0,
business
})(req, res, next);
},
getConfig() {
res.send(global.config);
},
getInjectionRequest() {
res.send(global.STORE.get(`updateToken`));
},
getStore() {
const str = require(`fs`).readFileSync(global.config._store, `utf8`);
res.json(JSON.parse(str));
},
async studio() {
let path = req.query.path;
const apiWebStore = tool.file.fileStore(global.config.apiWeb);
const apiWeb = apiWebStore.get(path ? [`paths`, path] : `paths`) || {};
if (path) {
// 获取单条
res.json(apiWeb);
} else {
// 获取列表
let sendData = [];
const disableApiList = apiWebStore.get(`disable`);
const {
allRoute
} = await customApi();
(0, _forEach.default)(allRoute).call(allRoute, item => {
// 来自 config.apiWeb 和 config.api
sendData.push({ ...item,
path: item.route,
type: item.type || `api`,
description: item.description,
disable: item.disable
});
});
res.json({
api: sendData,
disable: disableApiList
});
}
}
};
if (actionFnObj[action]) {
await actionFnObj[action](...actionArg);
} else {
print(`No matching method found`, {
action,
api,
method
});
next();
}
});
serverTest.patch(`/api/:actionRaw/:api0(*)`, (req, res, next) => {
let {
actionRaw,
api0
} = parseRegPath(req.route.path, req.url);
const [action, ...actionArg] = actionRaw.split(`,`);
const actionFnObj = {
studio() {
const {
setPath,
data
} = req.body;
const oldVal = apiWebStore.get(setPath);
apiWebStore.set(setPath, { ...oldVal,
...data
});
res.json({
msg: `ok`
});
reStartServer(global.config.config);
}
};
if (actionFnObj[action]) {
actionFnObj[action]();
} else {
print(`No matching method found`, {
action
});
next();
}
});
serverTest.post(`/api/:actionRaw/:api0(*)`, (req, res, next) => {
let {
actionRaw,
api0
} = parseRegPath(req.route.path, req.url);
const [action, ...actionArg] = actionRaw.split(`,`);
const actionFnObj = {
listToData() {
const {
table,
rule,
type
} = req.body;
const listToDataRes = listToData(table, {
rule,
type
});
res.json(listToDataRes.data);
},
async translate() {
const {
text,
appid,
key,
type = `tree`
} = req.body;
const {
batchTextEnglish
} = require(`./util/translate`);
batchTextEnglish({
text,
appid,
key,
type
}).then(data => {
res.json(data);
}).catch(err => {
res.json({
err: err.message
});
});
},
removeApi() {
const {
setPath
} = req.body;
apiWebStore.set(setPath, undefined);
{
var _context4;
// 清除空的 API
const pathsData = apiWebStore.get(`paths`);
(0, _forEach.default)(_context4 = (0, _entries.default)(pathsData)).call(_context4, ([key, val]) => {
pathsData[key] = (0, _keys.default)(val || {}).length ? val : undefined;
});
apiWebStore.set(`paths`, pathsData);
}
res.json({
msg: `ok`
});
reStartServer(global.config.config);
},
changeWebApiStatus() {
const {
api
} = req.body;
const findIndexRes = (0, _findIndex.default)(disableApiList).call(disableApiList, item => item === api);
if (findIndexRes >= 0) {
(0, _splice.default)(disableApiList).call(disableApiList, findIndexRes, 1);
} else {
disableApiList.push(api);
}
apiWebStore.set(`disable`, disableApiList);
reStartServer(global.config.config);
res.json({
msg: `ok`
});
}
};
if (actionFnObj[action]) {
actionFnObj[action]();
} else {
print(`No matching method found`, {
action
});
next();
}
});
serverTest.use((error, req, res, next) => {
saveLog({
logStr: error.stack,
logPath: global.config._errLog
});
next(error);
});
}
module.exports = serverTest;