UNPKG

mockm

Version:

Analog interface server, painless parallel development of front and back ends.

577 lines (500 loc) 15.9 kB
"use strict"; 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;