├── .DS_Store ├── .gitignore ├── Learn.md ├── Plan.md ├── README.md ├── dist ├── base │ └── base.js ├── file │ ├── file.js │ └── urlTpl.js ├── html │ ├── desc.css │ └── temp.html ├── main.js ├── model │ ├── dataModel.js │ ├── log.js │ └── swagger │ │ ├── model.js │ │ └── swagger.js ├── server │ ├── describe.js │ ├── server.js │ └── valid.js └── utils │ ├── dict.js │ ├── mock.js │ └── utils.js ├── example ├── fetch.js ├── index.js ├── test.js └── urlsReal.js ├── gulpfile.js ├── jest.config.json ├── package-lock.json ├── package.json ├── src ├── base │ └── base.ts ├── file │ ├── file.ts │ └── urlTpl.ts ├── html │ ├── desc.scss │ └── temp.html ├── main.ts ├── model │ ├── dataModel.ts │ ├── log.ts │ └── swagger │ │ ├── model.ts │ │ └── swagger.ts ├── server │ ├── describe.ts │ ├── server.ts │ └── valid.ts └── utils │ ├── dict.ts │ ├── mock.ts │ └── utils.ts ├── test └── utils.test.js └── tsconfig.json /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jdf2e/SMock/71a199c25b6ff555adb3a6e45e9c24199086a284/.DS_Store -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | *mock/ 3 | SMock.json 4 | .DS_Store -------------------------------------------------------------------------------- /Learn.md: -------------------------------------------------------------------------------- 1 | 8 | .vscode 使用vscode调试的时候配置的内容用于执行node index.js 9 | 怎么实现不编译出来的,直接在Ts中调试代码? 10 | 现在是在两个地方,一个是打包的dist文件夹一个是在默认的ts位置 11 | 应该是 12 | 13 | smock的学习: 14 | 打开node基础语言,调试方式 15 | 关于Smock的研究和编写代码是和node相互关联的 16 | 主线和函数,达到可以用的程度 17 | 和阿里的pont进行对比,提升产品的能力 18 | 19 | smock的核心 20 | 入参: 21 | 出参: 22 | 打包方式:gulp 23 | 整个的数据流动,在外面调用传入到里面 24 | 25 | vscode调试的方法: 26 | 在vscode中去除掉插件需要重新开启下vscode -------------------------------------------------------------------------------- /Plan.md: -------------------------------------------------------------------------------- 1 | ### 后续计划 2 | 3 | - 导出的URL支持es6 module引入方式 4 | - smock服务启动后,修改json文件,服务器热更新 5 | 6 | ### 项目技术选型 7 | 8 | - TypeScript架构开发 9 | - Gulp项目构建。 10 | - Jest进行单元测试。 11 | - ESLint进行代码标准化。 12 | - 全程使用async异步编程进行流程控制。 13 | 14 | ### 项目涉及模块计划 15 | 16 | | 模块名 | 开发人员| 描述 | 17 | | --- | --- | --- | 18 | | 项目构建 | - | 负责gulp环境维护搭建,主要负责针对ts文件编译后进行压缩合并,以及文件移动整理 | 19 | | 主文件入口 | - | 负责调用每个环节的功能 | 20 | | 文件操作流 | - | 负责根据数据生成对应的JSON假数据,以及url文件 | 21 | | 服务操作流 | - | 负责启动本地服务、把所有mock接口插入到SMock中 | 22 | | 校验流程 | - | 负责在启动服务前对文件进行整理、以及调用接口是对入参进行验证、调用接口后可以提供参数描述功能 | 23 | 24 | ### 功能模块以及完成情况 25 | | 功能名 | 完成情况 | 完成人 | 单元测试覆盖 | 26 | | --- | --- | --- | --- | 27 | | TS框架搭建 | 100% | 杨磊 | - | 28 | | Gulp环境搭建 | 100% | 杨磊 | - | 29 | | Jest环境搭建 | - | 张誉、印凤 | - | 30 | | 入口逻辑 | 100% | 杨磊 | - | 31 | | swagger数据解析| 100% | 杨磊 | - | 32 | | 创建文件功能 | 100% | 杨磊 | - | 33 | | 创建URL功能 | 100% | 孙印凤 | - | 34 | | 注入API功能 | 100% | 王悦、杨磊 | - | 35 | | 服务启动 | 100% | 杨磊 | - | 36 | | 入参校验 | 100% | 杨磊、廖艳丽、杨进军 | - | 37 | | 接口调用描述 | 100% | 杨磊 | - | 38 | | VS Code插件 | - | 王悦 | - | 39 | | 创建配置文件的html页 | - | - | - | 40 | | 网站增加更新日志页面功能展示 | - | - | - | 41 | | 增加测试用例覆盖度 | - | - | - | 42 | | 为SMock增加生命周期功能 | - | all | - | 43 | | 增加SMock支持plugins功能 | - | 王悦 | - | 44 | 45 | ### 整个项目流程 46 | 47 | 1. 开始 -> 48 | 2. 根据文档类型进入不同功能分支 -> 49 | 3. 获取对应的假数据文件(接口或者json文件性质) -> 50 | 4. 把所有数据全部转换成对应的SMockData类型 -> 51 | 5. 基于SMockData生成本地服务的数据文件(可以合并为一个文件也可以单独存在) -> 52 | 6. 基于SMockData生成URL的Map -> 53 | 7. 基于URLMap创建服务接口 -> 54 | 8. 启动服务以供用户访问 -> 55 | 9. 访问接口是校验入参是否合法 -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 |

smock-core

5 |

SMock的核心代码,smock run的主要逻辑

6 |
7 | 8 | ### 版本命名规范 9 | 采用银河系九大行星的顺序来命名。 10 | - 1.0->mercury 11 | - 2.0->venus 12 | 13 | ### 安装 14 | 15 | ```bash 16 | npm install smock-core --save-dev 17 | ``` 18 | 19 | ### 调用 20 | 21 | ```bash 22 | let Core = require('smock-core').Core; 23 | new Core({ 24 | docPath:'xxx.xxx.xx.xx', 25 | docPort:'80', 26 | path:'' 27 | }) 28 | ``` 29 | 30 | ### 参数说明 31 | 32 | |Attributes|forma|describe|default| 33 | |---|---|---|---| 34 | |type|String|文档数据源类型,暂只支持swagger|swagger| 35 | |docPath|String|type为swagger时,swagger文档访问路径|-| 36 | |docPort|Number|type为swagger时,swagger的文档端口号|80| 37 | |path|String|type为swagger时,swagger模式接口路径|/v2/api-docs| 38 | |method|String|type为swagger时,文档数据请求方式|GET| 39 | |realHostName|String|项目上线后访问的真实域名|-| 40 | |mockPort|Number|启动服务的端口号|3000| 41 | |customProtocol|String|type为swagger时,具体文档服务器协议http或https|http| 42 | |headers|Object|创建本地服务器时接口header附加参数|-| 43 | |jsPath|String|创建服务器时抽取Url路径文件的存储路径|-| 44 | |descInclude|Array|调用接口时展示接口文档的白名单|-| 45 | |override|Boolean|重启服务时是否重新刷新数据|false| 46 | 47 | ## 代码架构 48 | 49 | 代码采用 typescript。 50 | 代码校验:ESLint 51 | 52 | ## 项目命令 53 | 54 | npm run dev: 执行Demo,可热更新,使用VSCode来调试开发即可 55 | npm run build: 打包编译 56 | npm run test: 运行单元测试js 57 | -------------------------------------------------------------------------------- /dist/base/base.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | class Base { 4 | constructor(opts, data) { 5 | this.option = opts; 6 | this.data = data; 7 | } 8 | } 9 | exports.Base = Base; 10 | -------------------------------------------------------------------------------- /dist/file/file.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { 3 | return new (P || (P = Promise))(function (resolve, reject) { 4 | function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } 5 | function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } 6 | function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); } 7 | step((generator = generator.apply(thisArg, _arguments || [])).next()); 8 | }); 9 | }; 10 | Object.defineProperty(exports, "__esModule", { value: true }); 11 | const base_1 = require("./../base/base"); 12 | const dict_1 = require("./../utils/dict"); 13 | const utils_1 = require("./../utils/utils"); 14 | const log_1 = require("./../model/log"); 15 | class File extends base_1.Base { 16 | constructor(opts, data) { 17 | super(opts, data); 18 | } 19 | //创建JSON文件 20 | createJSONFile() { 21 | return __awaiter(this, void 0, void 0, function* () { 22 | let filePromise = []; 23 | let customFileDir = utils_1.join(process.cwd(), dict_1.mockDirName); //默认mock相关文件目录名 24 | utils_1.createDir(customFileDir); 25 | for (let fileIndex = 0; fileIndex < this.data.length; fileIndex++) { 26 | let item = this.data[fileIndex]; 27 | let data = JSON.stringify(item.data); 28 | let fileUrl = utils_1.join2(process.cwd(), dict_1.mockDirName, item.id + ".json"); 29 | filePromise.push(utils_1.makeFileSync(fileUrl, data)); 30 | } 31 | Promise.all(filePromise).then((data) => { 32 | return; 33 | }); 34 | }); 35 | } 36 | // 创建URL文件 37 | createUrlFile() { 38 | return __awaiter(this, void 0, void 0, function* () { 39 | return yield new Promise((resolve, reject) => { 40 | let option = this.option, urlDatas = this.data; 41 | let jsContent = this.customJsTpl(urlDatas); 42 | let customFileDir = utils_1.join(process.cwd(), dict_1.mockDirName); //默认mock相关文件目录名 43 | let jsFilePath = utils_1.join2(process.cwd(), dict_1.mockDirName, `${dict_1.urlsRealName}.js`); //默认生成位置,如果用户配置则生成至用户配置的位置 44 | if (option.jsPath) { 45 | //用户如果有自定义文件目录,则需要生成至用户自定义目录 46 | customFileDir = utils_1.join(process.cwd(), option.jsPath); 47 | if (!utils_1.existsSync(customFileDir)) 48 | return utils_1.error(log_1.jsPathError); 49 | jsFilePath = utils_1.join(customFileDir, `${dict_1.urlsRealName}.js`); 50 | } 51 | utils_1.createDir(customFileDir); 52 | utils_1.makeFileSync(jsFilePath, jsContent); //生成一个js文件 53 | let jsonFilePath = utils_1.join2(process.cwd(), dict_1.mockDirName, `${dict_1.urlsRealName}.json`); //生成一个json文件,只有全部url 54 | let jsonContent = utils_1.toStr(this.jsonDeal(urlDatas)); //将要写入文件的内容串 55 | utils_1.makeFileSync(jsonFilePath, jsonContent); //生成一个json文件只有url 56 | resolve(); 57 | }); 58 | }); 59 | } 60 | //根据用户定义的参数,生成指定格式的url聚合文件 61 | customJsTpl(data) { 62 | let tpl = require('./urlTpl'), option = this.option, host = option.realHostName; 63 | return tpl.getTpl(data, option.mockPort, host); 64 | } 65 | //处理json数据 66 | jsonDeal(urls) { 67 | let pathKey = '', obj = {}; 68 | for (let key in urls) { 69 | let item = urls[key]; 70 | pathKey = item.id; 71 | obj[pathKey] = { 72 | url: item.url, 73 | type: item.type 74 | }; 75 | } 76 | return obj; 77 | } 78 | } 79 | exports.File = File; 80 | -------------------------------------------------------------------------------- /dist/file/urlTpl.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | let urls, port, hostname; 4 | function getTpl(_urls, _port, _hostname) { 5 | urls = _urls; 6 | port = _port; 7 | hostname = _hostname; 8 | let tpl = ` 9 | (function (global, factory) { 10 | typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) : 11 | typeof define === 'function' && define.amd ? define(['exports'], factory) : 12 | (global = global || self, factory(global.SMOCK = {})); 13 | }(this, (function(exports) { 'use strict'; 14 | ${hostTpl()} 15 | ${restfulTpl()} 16 | var url = { 17 | ${urlTpl()} 18 | } 19 | ${moduleTpl()} 20 | })))`; 21 | return tpl; 22 | } 23 | exports.getTpl = getTpl; 24 | //创建host部分 25 | function hostTpl() { 26 | let tpl = `var isDebug = (window.location.href).indexOf('debug') > -1; 27 | var host = isDebug?'//127.0.0.1:${port}':'//${hostname}';`; 28 | return tpl; 29 | } 30 | //创建RESTful函数 31 | function restfulTpl() { 32 | let tpl = `var restfulURL = function(url, param) { 33 | let result = url; 34 | for(var prop in param) { 35 | result = result.replace('{'+prop+'}', param[prop]); 36 | } 37 | return result; 38 | }`; 39 | return tpl; 40 | } 41 | //创建url对象 42 | function urlTpl() { 43 | let urlTpl = ''; 44 | for (let key in urls) { 45 | urlTpl += `'${urls[key].id}': { 46 | url: host + '${urls[key].url}', 47 | type: '${urls[key].type}' 48 | },`; 49 | } 50 | return urlTpl.substr(0, urlTpl.length - 1); 51 | } 52 | //创建模块依赖部分 53 | function moduleTpl() { 54 | let moduleTpl = ` 55 | exports.isDebug = isDebug; 56 | exports.host = host; 57 | exports.url = url; 58 | exports.restfulURL = restfulURL; 59 | 60 | Object.defineProperty(exports, '__esModule', { value: true }); 61 | `; 62 | return moduleTpl; 63 | } 64 | -------------------------------------------------------------------------------- /dist/html/desc.css: -------------------------------------------------------------------------------- 1 | .w { 2 | width: 990px; 3 | margin: 0 auto; } 4 | 5 | .search input { 6 | height: 40px; 7 | width: 300px; 8 | text-indent: 10px; 9 | outline: none; } 10 | 11 | .tb { 12 | width: 100%; 13 | text-align: center; 14 | border-collapse: collapse; } 15 | .tb th:nth-child(1) { 16 | width: 200px; } 17 | .tb th, .tb tr { 18 | height: 40px; } 19 | .tb th { 20 | background: #f8f8f8; } 21 | .tb td, .tb th { 22 | border-bottom: 1px solid #ccc; } 23 | -------------------------------------------------------------------------------- /dist/html/temp.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 文档参数描述 9 | 10 | 11 | 12 |
13 |
14 |

接口名称:{{API}}

15 |

接口描述:{{DESC}}

16 |
17 | {{CODE}} 18 |
19 |
20 |
21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /dist/main.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | const swagger_1 = require("./model/swagger/swagger"); 4 | const file_1 = require("./file/file"); 5 | const server_1 = require("./server/server"); 6 | const utils_1 = require("./utils/utils"); 7 | const log_1 = require("./model/log"); 8 | class Core { 9 | constructor(opts) { 10 | // let config = Config; 11 | this.options = Object.assign({ 12 | type: 'swagger', 13 | docPath: '', 14 | docPort: 80, 15 | path: '/v2/api-docs', 16 | method: 'GET', 17 | realHostName: '', 18 | mockPort: 3000, 19 | customProtocol: 'http', 20 | headers: {}, 21 | jsPath: '', 22 | descInclude: [], 23 | override: false 24 | }, opts); 25 | //确保地址存在 26 | if (this.options.docPath == "") { 27 | utils_1.log(log_1.docPathError); 28 | return; 29 | } 30 | //不同类型跳转到不同数据层 31 | switch (this.options.type) { 32 | case 'swagger': 33 | let swagger = new swagger_1.Swagger(this.options); 34 | console.log(swagger, swagger.getData(), 'hello'); 35 | // 这里为什么一直是pending状态 36 | this.dataPromise = swagger.getData(); 37 | break; 38 | } 39 | this.workflow(); 40 | process.on('unhandledRejection', (error) => { 41 | console.error('unhandledRejection', error); 42 | process.exit(1); // To exit with a 'failure' code 43 | }); 44 | } 45 | // SMock主流程 46 | workflow() { 47 | 48 | this.dataPromise.then((data) => { 49 | console.log(data, 'hello'); 50 | 51 | let process = []; 52 | this.data = data; 53 | //声明变量 54 | var server = new server_1.Server(this.options, this.data); 55 | if (!utils_1.isNew() && !this.options.override) { 56 | } 57 | else { 58 | let file = new file_1.File(this.options, this.data); 59 | //创建文件 60 | let filePromise = file.createJSONFile(); 61 | process.push(filePromise); 62 | //创建URL 63 | let urlPromise = file.createUrlFile(); 64 | process.push(urlPromise); 65 | } 66 | //插入接口 67 | let apiPromise = server.addAPI(); 68 | process.push(apiPromise); 69 | //全部过程执行完毕,执行启动服务 70 | Promise.all(process).then(() => { 71 | server.startServer(() => { 72 | utils_1.log(`【${new Date()}】服务器启动!`); 73 | // log(`http://127.0.0.1:${this.options.mockPort}`); 74 | }); 75 | }); 76 | }); 77 | } 78 | } 79 | exports.Core = Core; 80 | -------------------------------------------------------------------------------- /dist/model/dataModel.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | -------------------------------------------------------------------------------- /dist/model/log.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | let startServerError = '服务器启动错误'; 4 | exports.startServerError = startServerError; 5 | let jsPathError = 'jsPath配置有误'; 6 | exports.jsPathError = jsPathError; 7 | let docPathError = '文档路径配置错误,请检查docPath是否配置'; 8 | exports.docPathError = docPathError; 9 | -------------------------------------------------------------------------------- /dist/model/swagger/model.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { 3 | return new (P || (P = Promise))(function (resolve, reject) { 4 | function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } 5 | function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } 6 | function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); } 7 | step((generator = generator.apply(thisArg, _arguments || [])).next()); 8 | }); 9 | }; 10 | Object.defineProperty(exports, "__esModule", { value: true }); 11 | const mock_1 = require("./../../utils/mock"); 12 | //数据查找路径,针对swagger返回数据的特殊处理 13 | function queryData(hash) { 14 | let result = ''; 15 | if (hash) { 16 | result = (hash.substring(2, hash.length)).split('/'); 17 | } 18 | return result[1] || ''; 19 | } 20 | //递归替换 21 | function dealData(def) { 22 | return __awaiter(this, void 0, void 0, function* () { 23 | let result = {}; 24 | //保存接口返回参数的描述 25 | let desc = []; 26 | let definition = def.definition; 27 | let type = definition && definition.type ? definition.type : ''; 28 | if (type) { 29 | if (type == 'string') { 30 | result = mock_1.setString(); 31 | let varDesc = {}; 32 | varDesc.key = def.prevKey; 33 | varDesc.desc = definition.description || '暂无定义'; 34 | varDesc.type = definition.type; 35 | desc.push(varDesc); 36 | } 37 | if (type == 'integer') { 38 | result = mock_1.setInteger(); 39 | let varDesc = {}; 40 | varDesc.key = def.prevKey; 41 | varDesc.desc = definition.description || '暂无定义'; 42 | varDesc.type = definition.type; 43 | desc.push(varDesc); 44 | } 45 | if (type == 'boolean') { 46 | result = mock_1.setBoolean(); 47 | let varDesc = {}; 48 | varDesc.key = def.prevKey; 49 | varDesc.desc = definition.description || '暂无定义'; 50 | varDesc.type = definition.type; 51 | desc.push(varDesc); 52 | } 53 | if (type == 'object') { 54 | if (definition.properties) { 55 | result = definition.properties; 56 | for (let key in result) { 57 | //防止递归数据造成死循环 58 | if (result[key].type && result[key].type == 'array' && result[key]['$ref'] && (queryData(result[key]['$ref']) == def.prevKey)) { 59 | result[key] = {}; 60 | } 61 | else { 62 | dealData({ 63 | prevKey: key, 64 | definition: result[key], 65 | definitionMap: def.definitionMap 66 | }).then((data) => { 67 | result[key] = data.data; 68 | desc = desc.concat(data.desc); 69 | }); 70 | } 71 | } 72 | } 73 | else { 74 | result = {}; 75 | } 76 | } 77 | if (type == 'array') { 78 | let items = def.definition.items; 79 | if (items.type) { 80 | } 81 | else { 82 | let objKey = queryData(items['$ref']); 83 | //防止递归数据造成死循环 84 | if (objKey != def.prevKey) { 85 | dealData({ 86 | prevKey: objKey, 87 | definition: def.definitionMap[objKey], 88 | definitionMap: def.definitionMap 89 | }).then((data) => { 90 | result = data.data; 91 | desc = desc.concat(data.desc); 92 | }); 93 | } 94 | else { 95 | result = {}; 96 | } 97 | } 98 | } 99 | } 100 | else { 101 | let goObject = def.definition && def.definition['$ref'] ? def.definition['$ref'] : ''; 102 | if (goObject) { 103 | let objKey = queryData(goObject); 104 | dealData({ 105 | prevKey: objKey, 106 | definition: def.definitionMap[objKey], 107 | definitionMap: def.definitionMap 108 | }).then((data) => { 109 | result = Object.assign(result, data.data); 110 | desc = desc.concat(data.desc); 111 | }); 112 | } 113 | else { 114 | result = mock_1.setString(); 115 | } 116 | } 117 | return { 118 | data: result, 119 | desc: [] 120 | }; 121 | }); 122 | } 123 | exports.dealData = dealData; 124 | //解析API参数 125 | function getParamData(params, definitions) { 126 | let result = []; 127 | for (let i = 0; i < params.length; i++) { 128 | let param = params[i]; 129 | let p = {}; 130 | if (param.schema) { 131 | p.type = 'object'; 132 | p.value = param.name; 133 | if (param.schema['$ref']) { 134 | p.child = getParamSchemaData(param.schema['$ref'], definitions); 135 | } 136 | else { 137 | p.type = param.schema.type; 138 | } 139 | p.required = param.required; 140 | } 141 | else { 142 | p.type = param.type ? param.type : 'string'; 143 | p.value = param.name; 144 | p.required = param.required; 145 | } 146 | p.in = param.in; 147 | p.required = param.required; 148 | p.desc = param.description; 149 | result.push(p); 150 | } 151 | return result; 152 | } 153 | exports.getParamData = getParamData; 154 | function getParamSchemaData(key, definitionMap) { 155 | let validResult = []; 156 | let defineKey = queryData(key); 157 | let props = definitionMap[defineKey].properties; 158 | for (let key in props) { 159 | let prop = props[key]; 160 | let validItem = {}; 161 | if (prop.$ref) { 162 | validItem.type = 'object'; 163 | validItem.desc = prop.description; 164 | validItem.param = getParamSchemaData(prop.$ref, definitionMap); 165 | } 166 | else { 167 | validItem.name = key; 168 | validItem.type = prop.type; 169 | validItem.desc = prop.description; 170 | } 171 | validResult.push(validItem); 172 | } 173 | return validResult; 174 | // definitionMap[key]; 175 | } 176 | //获取json数据的key 177 | function getJsonDataKey(responses) { 178 | let schema = responses['200'].schema; 179 | let mockJsonKey = schema ? schema['$ref'] : ''; 180 | return queryData(mockJsonKey); 181 | } 182 | exports.getJsonDataKey = getJsonDataKey; 183 | -------------------------------------------------------------------------------- /dist/model/swagger/swagger.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | /** 3 | * 4 | * 使用Swagger时,处理数据的文件 5 | */ 6 | var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { 7 | return new (P || (P = Promise))(function (resolve, reject) { 8 | function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } 9 | function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } 10 | function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); } 11 | step((generator = generator.apply(thisArg, _arguments || [])).next()); 12 | }); 13 | }; 14 | Object.defineProperty(exports, "__esModule", { value: true }); 15 | const server_1 = require("../../server/server"); 16 | const utils_1 = require("../../utils/utils"); 17 | const model_1 = require("./model");//Swagger处理数据模型 18 | const mock_1 = require("./../../utils/mock"); 19 | let clone = require('clone'); 20 | class Swagger { 21 | constructor(opts) { 22 | this.config = utils_1.dealConfig(opts); 23 | } 24 | getData() { 25 | return __awaiter(this, void 0, void 0, function* () { 26 | let self = this; 27 | let d; 28 | yield self.getDataFromServer() 29 | .then((data) => { 30 | d = self.convertData(clone(data)); 31 | }); 32 | return d; 33 | }); 34 | } 35 | getDataFromServer() { 36 | return __awaiter(this, void 0, void 0, function* () { 37 | let server = new server_1.Server(this.config); 38 | let result; 39 | let url = `${this.config.customProtocol}://${this.config.docPath}:${this.config.docPort}${this.config.path}`; 40 | console.log(url, 'swagger'); 41 | 42 | yield server.fetchData({ 43 | url: url 44 | }) 45 | .then((data) => { 46 | console.log(data, 'swagger1'); 47 | result = data; 48 | }); 49 | return result; 50 | }); 51 | } 52 | convertData(data) { 53 | console.log(data, 'swagger'); 54 | 55 | return __awaiter(this, void 0, void 0, function* () { 56 | return yield new Promise((resolve, reject) => { 57 | let allDealDataPromises = []; 58 | // 格式规划 59 | let apis = data.paths; 60 | let definitions = data.definitions; 61 | let result = []; 62 | for (let prop in apis) { 63 | let d = {}; 64 | let item = apis[prop]; 65 | d.url = prop; 66 | // if(d.url != '/api/service/{serviceId}') continue; 67 | let dataModelFlag = ''; 68 | for (let type in item) { 69 | // let descs:any = []; 70 | d.type = type; 71 | d.id = item[type].operationId; 72 | d.desc = item[type].summary; 73 | 74 | dataModelFlag = model_1.getJsonDataKey(item[type].responses); 75 | if (dataModelFlag) { 76 | let promise = model_1.dealData({ 77 | prevKey: dataModelFlag, 78 | definition: definitions[dataModelFlag], 79 | definitionMap: definitions 80 | }).then((data) => { 81 | d.data = data.data; 82 | d.responseDesc = data.desc; 83 | if (!utils_1.keyInData(item[type].operationId, result)) { 84 | result.push(d); 85 | } 86 | }).then((err) => { 87 | console.log(err, 'swagger-err'); 88 | 89 | }).catch((err) => { 90 | console.log(err, 'swagger-err'); 91 | }); 92 | console.log(promise, 'promise'); 93 | 94 | allDealDataPromises.push(promise); 95 | } 96 | else { 97 | d.data = mock_1.setString(); 98 | if (!utils_1.keyInData(item[type].operationId, result)) { 99 | result.push(d); 100 | } 101 | } 102 | if (item[type].parameters) { 103 | d.params = model_1.getParamData(item[type].parameters, definitions); 104 | } 105 | else { 106 | d.params = null; 107 | } 108 | } 109 | } 110 | Promise.all(allDealDataPromises).then(() => { 111 | resolve(result); 112 | }); 113 | }); 114 | }); 115 | } 116 | } 117 | exports.Swagger = Swagger; 118 | -------------------------------------------------------------------------------- /dist/server/describe.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | const base_1 = require("../base/base"); 4 | const utils_1 = require("./../utils/utils"); 5 | const fs_1 = require("fs"); 6 | const child_process_1 = require("child_process"); 7 | class Describe extends base_1.Base { 8 | constructor(opts, data) { 9 | super(opts, data); 10 | } 11 | getDescribeHtmlUrl(id) { 12 | this.createHtml(id); 13 | return `http://127.0.0.1:${this.option.mockPort}/desc/${id}`; 14 | } 15 | createHtml(id) { 16 | let result; 17 | let data = utils_1.getDataFromArrayById(this.data, id); 18 | let html = ''; 19 | if (data.params) { 20 | html += ` 21 |

入参

22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | `; 31 | for (let inIndex = 0; inIndex < data.params.length; inIndex++) { 32 | let param = data.params[inIndex]; 33 | html += ` 34 | 35 | 36 | 37 | 38 | 39 | `; 40 | if (param.child) { 41 | for (let childIndex = 0; childIndex < param.child.length; childIndex++) { 42 | let childParam = param.child[childIndex]; 43 | html += ` 44 | 45 | 46 | 47 | 48 | 49 | `; 50 | } 51 | } 52 | } 53 | html += ` 54 | 55 |
参数名类型位置是否必填描述
${param.value}${param.type}${param.in}${param.required}${param.desc}
${param.value}.${childParam.name}${childParam.type}${param.in}${param.required}${param.desc}
`; 56 | } 57 | if (data.responseDesc && data.responseDesc.length > 0) { 58 | html += ` 59 |

出参

60 | 61 | 62 | 63 | 64 | 65 | 66 | `; 67 | for (let resIndex = 0; resIndex < data.responseDesc.length; resIndex++) { 68 | let desc = data.responseDesc[resIndex]; 69 | html += ` 70 | 71 | 72 | 73 | 74 | `; 75 | } 76 | html += ` 77 | 78 |
参数名类型描述
${desc.key}${desc.type}${desc.desc}
79 | `; 80 | } 81 | let file = fs_1.readFileSync(utils_1.join(__dirname, './../html/temp.html'), 'utf8'); 82 | result = file.replace('{{CODE}}', html); 83 | result = result.replace('{{API}}', data.url); 84 | result = result.replace('{{DESC}}', data.desc); 85 | return result; 86 | } 87 | openAPIDesc(descUrl) { 88 | let cmd = ''; 89 | if (process.platform == 'win32') { 90 | cmd = 'start'; 91 | } 92 | else if (process.platform == 'linux') { 93 | cmd = 'xdg-open'; 94 | } 95 | else if (process.platform == 'darwin') { 96 | cmd = 'open'; 97 | } 98 | child_process_1.exec(`${cmd} ${descUrl}`); 99 | } 100 | } 101 | exports.Describe = Describe; 102 | -------------------------------------------------------------------------------- /dist/server/server.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { 3 | return new (P || (P = Promise))(function (resolve, reject) { 4 | function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } 5 | function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } 6 | function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); } 7 | step((generator = generator.apply(thisArg, _arguments || [])).next()); 8 | }); 9 | }; 10 | Object.defineProperty(exports, "__esModule", { value: true }); 11 | const base_1 = require("./../base/base"); 12 | const utils_1 = require("./../utils/utils"); 13 | const dict_1 = require("./../utils/dict"); 14 | const describe_1 = require("./describe"); 15 | const valid_1 = require("./valid"); 16 | let express = require('express'); 17 | var bodyParser = require("body-parser"); 18 | let axios = require('axios'); 19 | let app = express(); 20 | app.use(bodyParser.urlencoded({ extended: false })); 21 | class Server extends base_1.Base { 22 | constructor(opts, data) { 23 | super(opts, data); 24 | } 25 | //启动服务 26 | startServer(cal) { 27 | app.listen(this.option.mockPort, () => { 28 | if (cal) 29 | cal(); //启动成功 30 | this.addStatic(); 31 | }); 32 | } 33 | // 托管静态页面,如接口描述页等 34 | addStatic() { 35 | app.use(express.static(utils_1.join(__dirname, './../html'))); 36 | } 37 | // 注入接口 38 | addAPI() { 39 | return __awaiter(this, void 0, void 0, function* () { 40 | let self = this; 41 | let desc = new describe_1.Describe(this.option, this.data); 42 | let data = this.data; 43 | return yield new Promise((resolve, reject) => { 44 | app.get('/desc/:id', function (req, res) { 45 | let id = req.params.id; 46 | res.send(desc.createHtml(id)); 47 | }); 48 | for (let index = 0; index < data.length; index++) { 49 | const element = data[index]; 50 | let realUrl = utils_1.dealUrl(element.url); 51 | app[element.type](realUrl, function (req, res) { 52 | res.header("Access-Control-Allow-Origin", req.headers.origin); 53 | res.header('Access-Control-Allow-Credentials', true); //告诉客户端可以在HTTP请求中带上Cookie 54 | res.header("Access-Control-Allow-Headers", "Origin, No-Cache, X-Requested-With, If-Modified-Since, Pragma, " + 55 | "Last-Modified, Cache-Control, Expires, Content-Type, Content-Language, Cache-Control, X-E4M-With,X_FILENAME"); 56 | res.header("Access-Control-Allow-Methods", "PUT,POST,GET,DELETE,OPTIONS"); 57 | res.header("X-Powered-By", ' 3.2.1'); 58 | res.header("Content-Type", "application/json;charset=utf-8"); 59 | //创建接口描述页面 60 | let descUrl = desc.getDescribeHtmlUrl(element.id); 61 | utils_1.log('调用接口的文档链接:' + descUrl); 62 | if (self.isInclude(element.id, self.option.descInclude)) { 63 | desc.openAPIDesc(descUrl); 64 | } 65 | let params = self.getParamByType(req); 66 | let errMsg = valid_1.validParam(params, element); 67 | if (errMsg === '') { 68 | let fileUrl = utils_1.join2(process.cwd(), dict_1.mockDirName, element.id + ".json"); 69 | res.send(require(fileUrl)); 70 | } 71 | else { 72 | res.send(` 73 | ${errMsg} 74 | 接口描述参考:${descUrl} 75 | `); 76 | } 77 | }); 78 | } 79 | resolve(); 80 | }); 81 | // return 1; 82 | //TODO: 注入SMock的接口服务 83 | }); 84 | } 85 | getParamByType(req) { 86 | let params = {}; 87 | if (req.params) { 88 | params = Object.assign(params, req.params); 89 | } 90 | if (req.query) { 91 | params = Object.assign(params, req.query); 92 | } 93 | if (req.body) { 94 | params = Object.assign(params, req.body); 95 | } 96 | return params; 97 | } 98 | isInclude(urlId, includes) { 99 | let exist = false; 100 | exist = includes.indexOf(urlId) > -1; 101 | return exist; 102 | } 103 | //获取数据 104 | fetchData(opts) { 105 | return __awaiter(this, void 0, void 0, function* () { 106 | let self = this; 107 | // let swaggerUtl = getHost(opts.url); 108 | return yield new Promise((resolve, reject) => { 109 | let header = {}; 110 | for (let prop in self.option.headers) { 111 | header(prop, self.option.headers[prop]); 112 | } 113 | axios({ 114 | url: opts.url, 115 | headers: header 116 | }) 117 | .then((data) => { 118 | resolve(data.data); 119 | }); 120 | }); 121 | }); 122 | } 123 | } 124 | exports.Server = Server; 125 | -------------------------------------------------------------------------------- /dist/server/valid.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | function validParam(params, data) { 4 | let result = ''; 5 | let errorRequired = validRequired(params, data); 6 | let errorType = validType(params, data); 7 | if (errorRequired.length > 0) { 8 | result += `【${errorRequired.join('、')}】必须传入`; 9 | } 10 | if (errorType.length > 0) { 11 | result += ` 12 | 【${errorType.join('、')}】传入类型错误 13 | `; 14 | } 15 | return result; 16 | } 17 | exports.validParam = validParam; 18 | //校验必填 19 | function validRequired(inParams, data) { 20 | let error = []; 21 | if (!data.params) 22 | return error; 23 | for (let idx = 0; idx < data.params.length; idx++) { 24 | let item = data.params[idx]; 25 | if (item.required && !inParams[item.value]) { 26 | //检查传参中是不是存在必填项 27 | error.push(item.value); 28 | } 29 | } 30 | return error; 31 | } 32 | //校验入参格式 33 | function validType(inParams, data) { 34 | let error = []; 35 | if (!data.params) 36 | return error; 37 | for (let prop in inParams) { 38 | let param = getParamByName(prop, data.params); 39 | if (!param) 40 | return error; 41 | if (typeof inParams[prop] != param.type) { 42 | //如果类型为int,但url的方式传参会自动转换为字符串,我们要转换 43 | console.log(param.type, inParams[prop], isNaN(parseInt(inParams[prop]))); 44 | if (!(param.type == 'integer' && !isNaN(parseInt(inParams[prop])))) { 45 | error.push(param.value); 46 | } 47 | } 48 | } 49 | return error; 50 | } 51 | //获取对应参数的值 52 | function getParamByName(key, params) { 53 | let result = null; 54 | for (let idx = 0; idx < params.length; idx++) { 55 | let param = params[idx]; 56 | if (key === param.value) { 57 | result = param; 58 | } 59 | } 60 | return result; 61 | } 62 | -------------------------------------------------------------------------------- /dist/utils/dict.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | exports.mockDirName = 'mock'; 4 | exports.configName = 'SMock.json'; //参数文件名 5 | exports.urlsRealName = 'urlsReal'; //真实路径文件名 6 | -------------------------------------------------------------------------------- /dist/utils/mock.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | /** 4 | * 模拟数据动态化,引入mockjs.js来处理 5 | * author: liaoyanli 6 | */ 7 | let Mock = require('mockjs'); 8 | var Random = Mock.Random; 9 | function setString() { 10 | Random.word(3, 8); 11 | return Mock.mock('@word(3, 8)'); 12 | } 13 | exports.setString = setString; 14 | function setBoolean() { 15 | Random.boolean(); 16 | return Mock.mock('@boolean'); 17 | } 18 | exports.setBoolean = setBoolean; 19 | function setInteger() { 20 | Random.integer(1, 100); 21 | return Mock.mock('@integer(1, 100)'); 22 | } 23 | exports.setInteger = setInteger; 24 | -------------------------------------------------------------------------------- /dist/utils/utils.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | let fs = require('fs'); 4 | let path = require('path'); 5 | const dict_1 = require("./dict"); 6 | function dealConfig(c) { 7 | if (c.docPath) { 8 | c.docPath = parseURL(c.docPath); 9 | } 10 | if (c.domain) { 11 | c.headers = { 12 | host: c.domain 13 | }; 14 | } 15 | if (c.customProtocol == 'https') { 16 | c.port = 443; 17 | } 18 | //mock文件夹名 19 | // c.mockDirName = `${c.projectName?c.projectName:defaultConfig.projectName}mock`; 20 | return c; // Object.assign(defaultConfig, c); 21 | } 22 | exports.dealConfig = dealConfig; 23 | function parseURL(url) { 24 | var a = url.replace("http://", "").replace("https://", ""); 25 | return a.substring(0, a.indexOf("/") > 0 ? a.indexOf("/") : a.length); 26 | } 27 | exports.parseURL = parseURL; 28 | function log(msg) { 29 | console.log(msg); 30 | } 31 | exports.log = log; 32 | //警告打印 33 | function warn(msg) { 34 | console.log(`warn:${msg}`); 35 | } 36 | exports.warn = warn; 37 | function error(msg) { 38 | console.log(`error:${msg}`); 39 | } 40 | exports.error = error; 41 | //将buffer或者字符串转换成json串 42 | function toJson(str) { 43 | return JSON.parse(str); 44 | } 45 | exports.toJson = toJson; 46 | //将对象变成格式化串 47 | function toStr(json) { 48 | return JSON.stringify(json, null, 2); 49 | } 50 | exports.toStr = toStr; 51 | //将含有花括号的url,替换成对应的数据 52 | function dealUrl(url) { 53 | return url.replace(/\{.*?\}/g, function (d) { 54 | let key = d.substring(1, d.length - 1); 55 | return `:${key}`; 56 | }); //此正则很重要 57 | } 58 | exports.dealUrl = dealUrl; 59 | function getParamByType(type, req) { 60 | let params = {}; 61 | type = type.toLowerCase(); 62 | switch (type) { 63 | case 'get': 64 | params = req.query; 65 | break; 66 | case 'post': 67 | params = req.body; 68 | break; 69 | default: 70 | break; 71 | } 72 | return params; 73 | } 74 | exports.getParamByType = getParamByType; 75 | // Data中获取对应ID的数据 76 | function getDataFromArrayById(arr, id) { 77 | for (let i = 0; i < arr.length; i++) { 78 | let item = arr[i]; 79 | if (item.id === id) { 80 | return item; 81 | } 82 | } 83 | return null; 84 | } 85 | exports.getDataFromArrayById = getDataFromArrayById; 86 | //文件操作 87 | function existsSync(url) { 88 | return fs.existsSync(url); 89 | } 90 | exports.existsSync = existsSync; 91 | //读文件 92 | function readFileSync(url) { 93 | return fs.readFileSync(url); 94 | } 95 | exports.readFileSync = readFileSync; 96 | //创建目录 97 | function createDir(dir) { 98 | var stat = fs.existsSync(dir); 99 | if (!stat) { 100 | //为true的话那么存在,如果为false不存在 101 | fs.mkdirSync(dir); 102 | } 103 | } 104 | exports.createDir = createDir; 105 | //创建文件并写入内容(异步) 106 | function makeFile(filePath, content) { 107 | return new Promise((resolve, reject) => { 108 | var stat = existsSync(filePath); 109 | if (stat) { 110 | //为true的话那么存在,如果为false不存在 111 | // utils.log(`${filePath} 已存在,内容已覆盖`); 112 | } 113 | fs.writeFile(filePath, content, (err) => { 114 | if (!err) { 115 | resolve(filePath); 116 | } 117 | else { 118 | reject(err); 119 | } 120 | }); 121 | }); 122 | } 123 | exports.makeFile = makeFile; 124 | //创建文件并写入内容(同步无回调) 125 | function makeFileSync(filePath, content) { 126 | var stat = existsSync(filePath); 127 | if (stat) { 128 | //为true的话那么存在,如果为false不存在 129 | log(`${filePath} 已存在,内容已覆盖`); 130 | } 131 | // else { 132 | fs.writeFileSync(filePath, content); 133 | } 134 | exports.makeFileSync = makeFileSync; 135 | ////判断是否存在json文件 136 | function isNew() { 137 | return !existsSync(join(process.cwd(), dict_1.mockDirName)); 138 | } 139 | exports.isNew = isNew; 140 | //合并 141 | function join(a, b) { 142 | return path.resolve(a, b); 143 | } 144 | exports.join = join; 145 | function join2(a, b, c) { 146 | return path.join(a, b, c); 147 | } 148 | exports.join2 = join2; 149 | // 判断对象中是否存在值 150 | function keyInData(id, arr) { 151 | let result = false; 152 | for (let i = 0; i < arr.length; i++) { 153 | let item = arr[i]; 154 | if (item.id == id) { 155 | result = true; 156 | } 157 | } 158 | return result; 159 | } 160 | exports.keyInData = keyInData; 161 | function getHost(url) { 162 | } 163 | exports.getHost = getHost; 164 | -------------------------------------------------------------------------------- /example/fetch.js: -------------------------------------------------------------------------------- 1 | var axios = require('axios'); 2 | var SMOCK = require('./urlsReal'); 3 | let apiDoc; 4 | function a() { 5 | console.log(url); 6 | return new Promise((resolve, reject) => { 7 | axios({ 8 | url: 'http://10.182.30.155/v2/api-docs' 9 | }) 10 | .then((data) => { 11 | // resolve(data.data); 12 | apiDoc = data.data; 13 | resolve(apiDoc); 14 | console.log(apiDoc); 15 | }) 16 | }) 17 | } 18 | a().then((data) => { 19 | console.log(apiDoc.definitions['ResultResponse«ApplyBooking»']); 20 | }) 21 | -------------------------------------------------------------------------------- /example/index.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Description: In User Settings Edit 3 | * @Author: your name 4 | * @Date: 2019-08-12 11:03:59 5 | * @LastEditTime: 2019-08-12 11:03:59 6 | * @LastEditors: your name 7 | */ 8 | var Core = require("./../dist/main.js").Core; 9 | new Core({ 10 | docPath: "10.182.52.40", 11 | realHostName: "10.182.52.40", 12 | docPort: "", 13 | mockPort: 3000, 14 | jsPath: "example/", 15 | headers: { 16 | // "host": "kudou-staff-m-fy.jd.com" 17 | }, 18 | descInclude: ["getAuthCodeUsingGET", "bindCarUsingPOST"], 19 | override: true 20 | }); 21 | -------------------------------------------------------------------------------- /example/test.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | var restfulURL = function(url, param) { 3 | let result = url; 4 | for(var prop in param) { 5 | result = result.replace('{'+prop+'}', param[prop]); 6 | } 7 | return result; 8 | } 9 | var url = '/api/service/{serviceId}'; 10 | var newUrl = restfulURL(url, { 11 | serviceId: 1 12 | }); 13 | console.log(newUrl); 14 | })() -------------------------------------------------------------------------------- /example/urlsReal.js: -------------------------------------------------------------------------------- 1 | 2 | (function (global, factory) { 3 | typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) : 4 | typeof define === 'function' && define.amd ? define(['exports'], factory) : 5 | (factory((global.SMOCK = {}))); 6 | }(this, (function(exports) { 'use strict'; 7 | var isDebug = (window.location.href).indexOf('debug') > -1; 8 | var host = isDebug?'//127.0.0.1:3000':'//192.168.128.83'; 9 | var restfulURL = function(url, param) { 10 | let result = url; 11 | for(var prop in param) { 12 | result = result.replace('{'+prop+'}', param[prop]); 13 | } 14 | return result; 15 | } 16 | var url = { 17 | 'getBannersUsingGET': { 18 | url: host + '/stryview/v1/banner', 19 | type: 'get' 20 | },'getProjectProgressUsingGET': { 21 | url: host + '/stryview/v1/jobProgress', 22 | type: 'get' 23 | },'loginInfoUsingGET': { 24 | url: host + '/stryview/v1/loginInfo', 25 | type: 'get' 26 | },'menusUsingGET': { 27 | url: host + '/stryview/v1/menus', 28 | type: 'get' 29 | },'getMyStrategyUsingGET': { 30 | url: host + '/stryview/v1/myStrategyHouse', 31 | type: 'get' 32 | },'strategyUsingPOST': { 33 | url: host + '/stryview/v1/strategy', 34 | type: 'post' 35 | },'strategy01UsingPOST': { 36 | url: host + '/stryview/v1/strategy01', 37 | type: 'post' 38 | },'strategy02UsingPOST': { 39 | url: host + '/stryview/v1/strategy02', 40 | type: 'post' 41 | },'strategy03UsingPOST': { 42 | url: host + '/stryview/v1/strategy03', 43 | type: 'post' 44 | },'strategy04UsingPOST': { 45 | url: host + '/stryview/v1/strategy04', 46 | type: 'post' 47 | },'strategy05UsingPOST': { 48 | url: host + '/stryview/v1/strategy05', 49 | type: 'post' 50 | },'strategy06UsingPOST': { 51 | url: host + '/stryview/v1/strategy06', 52 | type: 'post' 53 | },'strategy07UsingPOST': { 54 | url: host + '/stryview/v1/strategy07', 55 | type: 'post' 56 | },'getStrategyUsingGET': { 57 | url: host + '/stryview/v1/strategyHouse', 58 | type: 'get' 59 | },'strategyHouseNavUsingGET': { 60 | url: host + '/stryview/v1/strategyHouseNav', 61 | type: 'get' 62 | },'strategyHouseUserUsingGET': { 63 | url: host + '/stryview/v1/strategyHouseUser', 64 | type: 'get' 65 | },'saveStrategyPmoUsingPOST': { 66 | url: host + '/stryview/v1/strategyPmo', 67 | type: 'post' 68 | },'deleteStrategyUserUsingDELETE': { 69 | url: host + '/stryview/v1/strategyUser', 70 | type: 'delete' 71 | },'deleteStrategyUserUsingDELETE': { 72 | url: host + '/stryview/v1/strategyUser', 73 | type: 'delete' 74 | },'getStrategyUserRoleUsingGET': { 75 | url: host + '/stryview/v1/strategyUserRole', 76 | type: 'get' 77 | },'getStrategyWinDataUsingGET': { 78 | url: host + '/stryview/v1/strategyWin', 79 | type: 'get' 80 | },'getStrategyWinNavUsingGET': { 81 | url: host + '/stryview/v1/strategyWinNav', 82 | type: 'get' 83 | },'subDeptUsingGET': { 84 | url: host + '/stryview/v1/subDept', 85 | type: 'get' 86 | },'switchStrategyUsingPOST': { 87 | url: host + '/stryview/v1/switchStrategyHouse', 88 | type: 'post' 89 | },'saveTimeLineUsingPOST': { 90 | url: host + '/stryview/v1/timeLine', 91 | type: 'post' 92 | },'userUsingGET': { 93 | url: host + '/stryview/v1/user', 94 | type: 'get' 95 | },'yesOrNoUsingGET': { 96 | url: host + '/stryview/v1/yesOrNo', 97 | type: 'get' 98 | } 99 | } 100 | 101 | exports.isDebug = isDebug; 102 | exports.host = host; 103 | exports.url = url; 104 | exports.restfulURL = restfulURL; 105 | 106 | Object.defineProperty(exports, '__esModule', { value: true }); 107 | 108 | }))) -------------------------------------------------------------------------------- /gulpfile.js: -------------------------------------------------------------------------------- 1 | const { task, watch, series, src, dest } = require('gulp'); 2 | const uglify = require('gulp-uglify-es').default; 3 | const ts = require('gulp-typescript'); 4 | const sass = require('gulp-sass'); 5 | //TS解析 6 | function compilerTypeScript(cb) { 7 | return src('src/**/*.ts') 8 | .pipe(ts({ 9 | "module": "CommonJS", 10 | "esModuleInterop": true, 11 | "target": "es6", 12 | "noImplicitAny": true, 13 | "moduleResolution": "node", 14 | "sourceMap": false 15 | }) 16 | ) 17 | .pipe(dest('./dist')); 18 | } 19 | function jsCompress(cb) { 20 | return src('src/**/*.js') 21 | .pipe(uglify()) 22 | .pipe(dest('dist')); 23 | } 24 | //编译sass 25 | function compilerSass(cb) { 26 | return src('src/html/*.scss') 27 | .pipe(sass().on('error', sass.logError)) 28 | .pipe(dest('dist/html')); 29 | } 30 | 31 | // 迁移html 32 | function moveHtml(cb) { 33 | return src('src/html/**/*.html') 34 | .pipe(dest('dist/html')); 35 | } 36 | 37 | if(process.env.NODE_ENV === 'development') { 38 | task('default', series(compilerTypeScript, compilerSass, moveHtml)); 39 | watch(['src/**/*.ts', 'src/**/*.scss', 'src/html/**/*.html'], series(compilerTypeScript, compilerSass, moveHtml)); 40 | } else { 41 | task('default', series(compilerTypeScript, jsCompress, compilerSass, moveHtml)); 42 | } 43 | -------------------------------------------------------------------------------- /jest.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "collectCoverage":true, 3 | "collectCoverageFrom": [ 4 | "/src/**/*.{js,ts}" 5 | ], 6 | "transform": { 7 | "^.+\\.js$": "/node_modules/babel-jest", 8 | "^.+\\.(ts)$": "/node_modules/ts-jest/preprocessor.js" 9 | }, 10 | "moduleFileExtensions":["js","ts"] 11 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "smock-core", 3 | "version": "2.0.5", 4 | "description": "SMock核心源码", 5 | "main": "./dist/main", 6 | "repository": { 7 | "type": "git", 8 | "url": "https://github.com/jdf2e/SMock.git" 9 | }, 10 | "scripts": { 11 | "test": "jest --config=jest.config.json", 12 | "dev": "cross-env NODE_ENV=development gulp", 13 | "build": "gulp" 14 | }, 15 | "keywords": [ 16 | "swagger", 17 | "mock", 18 | "swagger-mock", 19 | "node", 20 | "server", 21 | "smock" 22 | ], 23 | "author": "jdc-fe", 24 | "license": "MIT", 25 | "dependencies": { 26 | "@types/axios": "^0.14.0", 27 | "body-parser": "^1.18.3", 28 | "clone": "^2.1.2", 29 | "express": "^4.16.3", 30 | "mockjs": "^1.0.1-beta3" 31 | }, 32 | "devDependencies": { 33 | "gulp": "^4.0.0", 34 | "typescript": "^3.2.2", 35 | "gulp-uglify-es": "^1.0.4", 36 | "gulp-typescript": "^5.0.0", 37 | "@types/jest": "^23.3.12", 38 | "@types/request": "^2.48.1", 39 | "@types/request-promise-native": "^1.0.15", 40 | "babel-env": "^2.4.1", 41 | "babel-jest": "^23.6.0", 42 | "child_process": "^1.0.2", 43 | "cross-env": "^5.2.0", 44 | "gulp-babel": "^8.0.0", 45 | "gulp-rename": "^1.4.0", 46 | "gulp-sass": "^4.0.2", 47 | "jest": "^23.6.0", 48 | "node-sass": "^4.11.0", 49 | "regenerator-runtime": "^0.13.1", 50 | "request": "^2.88.0", 51 | "request-promise-native": "^1.0.5", 52 | "ts-jest": "^23.10.5" 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/base/base.ts: -------------------------------------------------------------------------------- 1 | import {Data, Config} from './../model/dataModel'; 2 | class Base { 3 | // 配置 4 | option: Config; 5 | // 数据 6 | data: Data; 7 | constructor(opts: Config, data: Data) { 8 | this.option = opts; 9 | this.data = data; 10 | } 11 | } 12 | export { Base }; -------------------------------------------------------------------------------- /src/file/file.ts: -------------------------------------------------------------------------------- 1 | import { Base } from './../base/base'; 2 | import { mockDirName, urlsRealName } from './../utils/dict'; 3 | import { Config, Data } from './../model/dataModel'; 4 | import { makeFileSync, join, join2, toStr, createDir, existsSync, error } from './../utils/utils'; 5 | import { jsPathError } from './../model/log'; 6 | 7 | class File extends Base { 8 | constructor(opts: Config, data: Data) { 9 | super(opts, data); 10 | } 11 | //创建JSON文件 12 | async createJSONFile() { 13 | let filePromise = []; 14 | let customFileDir = join(process.cwd(), mockDirName); //默认mock相关文件目录名 15 | createDir(customFileDir); 16 | for(let fileIndex = 0; fileIndex < this.data.length; fileIndex++) { 17 | let item = this.data[fileIndex]; 18 | let data = JSON.stringify(item.data); 19 | let fileUrl = join2(process.cwd(), mockDirName, item.id+".json"); 20 | filePromise.push(makeFileSync(fileUrl, data)); 21 | } 22 | Promise.all(filePromise).then((data) => { 23 | return; 24 | }); 25 | } 26 | // 创建URL文件 27 | async createUrlFile() { 28 | return await new Promise((resolve, reject) => { 29 | let option = this.option, 30 | urlDatas = this.data; 31 | let jsContent = this.customJsTpl(urlDatas); 32 | let customFileDir = join(process.cwd(), mockDirName); //默认mock相关文件目录名 33 | let jsFilePath = join2(process.cwd(), mockDirName, `${urlsRealName}.js`); //默认生成位置,如果用户配置则生成至用户配置的位置 34 | if (option.jsPath) { 35 | //用户如果有自定义文件目录,则需要生成至用户自定义目录 36 | customFileDir = join(process.cwd(), option.jsPath); 37 | if(!existsSync(customFileDir)) return error(jsPathError); 38 | jsFilePath = join(customFileDir, `${urlsRealName}.js`); 39 | } 40 | createDir(customFileDir); 41 | makeFileSync(jsFilePath, jsContent); //生成一个js文件 42 | 43 | let jsonFilePath = join2(process.cwd(), mockDirName, `${urlsRealName}.json`); //生成一个json文件,只有全部url 44 | let jsonContent = toStr(this.jsonDeal(urlDatas)); //将要写入文件的内容串 45 | makeFileSync(jsonFilePath, jsonContent); //生成一个json文件只有url 46 | resolve(); 47 | }); 48 | } 49 | //根据用户定义的参数,生成指定格式的url聚合文件 50 | customJsTpl(data: any) { 51 | let tpl = require('./urlTpl'), 52 | option = this.option, 53 | host = option.realHostName; 54 | return tpl.getTpl(data, option.mockPort, host); 55 | } 56 | 57 | //处理json数据 58 | jsonDeal(urls: any) { 59 | let pathKey = '', 60 | obj: any = {}; 61 | for (let key in urls) { 62 | let item = urls[key]; 63 | pathKey = item.id; 64 | obj[pathKey] = { 65 | url: item.url, 66 | type: item.type 67 | }; 68 | } 69 | return obj; 70 | } 71 | } 72 | 73 | export { File }; 74 | -------------------------------------------------------------------------------- /src/file/urlTpl.ts: -------------------------------------------------------------------------------- 1 | let urls: any, port: string, hostname: string; 2 | 3 | export function getTpl(_urls: any, _port: string, _hostname: string) { 4 | urls = _urls; 5 | port = _port; 6 | hostname = _hostname; 7 | let tpl = ` 8 | (function (global, factory) { 9 | typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) : 10 | typeof define === 'function' && define.amd ? define(['exports'], factory) : 11 | (global = global || self, factory(global.SMOCK = {})); 12 | }(this, (function(exports) { 'use strict'; 13 | ${hostTpl()} 14 | ${restfulTpl()} 15 | var url = { 16 | ${urlTpl()} 17 | } 18 | ${moduleTpl()} 19 | })))`; 20 | return tpl; 21 | } 22 | 23 | //创建host部分 24 | function hostTpl() { 25 | let tpl = `var isDebug = (window.location.href).indexOf('debug') > -1; 26 | var host = isDebug?'//127.0.0.1:${port}':'//${hostname}';`; 27 | return tpl; 28 | } 29 | 30 | //创建RESTful函数 31 | function restfulTpl() { 32 | let tpl = `var restfulURL = function(url, param) { 33 | let result = url; 34 | for(var prop in param) { 35 | result = result.replace('{'+prop+'}', param[prop]); 36 | } 37 | return result; 38 | }` 39 | return tpl; 40 | } 41 | 42 | //创建url对象 43 | function urlTpl() { 44 | let urlTpl = ''; 45 | for (let key in urls) { 46 | urlTpl += `'${urls[key].id}': { 47 | url: host + '${urls[key].url}', 48 | type: '${urls[key].type}' 49 | },`; 50 | } 51 | return urlTpl.substr(0, urlTpl.length - 1); 52 | } 53 | 54 | //创建模块依赖部分 55 | function moduleTpl() { 56 | let moduleTpl = ` 57 | exports.isDebug = isDebug; 58 | exports.host = host; 59 | exports.url = url; 60 | exports.restfulURL = restfulURL; 61 | 62 | Object.defineProperty(exports, '__esModule', { value: true }); 63 | `; 64 | return moduleTpl; 65 | } 66 | -------------------------------------------------------------------------------- /src/html/desc.scss: -------------------------------------------------------------------------------- 1 | .w { 2 | width: 990px; 3 | margin: 0 auto; 4 | } 5 | .search { 6 | input { 7 | height: 40px; 8 | width: 300px; 9 | text-indent: 10px; 10 | outline: none; 11 | } 12 | } 13 | .tb { 14 | width: 100%; 15 | text-align: center; 16 | border-collapse:collapse; 17 | th:nth-child(1) { 18 | width: 200px; 19 | } 20 | th, tr { 21 | height: 40px; 22 | } 23 | th { 24 | background: #f8f8f8; 25 | } 26 | td, th { 27 | border-bottom: 1px solid #ccc; 28 | } 29 | } -------------------------------------------------------------------------------- /src/html/temp.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 文档参数描述 9 | 10 | 11 | 12 |
13 |
14 |

接口名称:{{API}}

15 |

接口描述:{{DESC}}

16 |
17 | {{CODE}} 18 |
19 |
20 |
21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /src/main.ts: -------------------------------------------------------------------------------- 1 | import { Swagger } from './model/swagger/swagger'; 2 | import { Config, Data } from './model/dataModel'; 3 | import { File } from './file/file'; 4 | import { Server } from './server/server'; 5 | import { log, isNew } from './utils/utils'; 6 | import { docPathError } from './model/log'; 7 | class Core { 8 | // 缓存参数 9 | options: Config; 10 | // 缓存数据 11 | data: Data; 12 | //数据Promise 13 | dataPromise: Promise; 14 | constructor(opts: Object) { 15 | // let config = Config; 16 | this.options = Object.assign( 17 | { 18 | type: 'swagger', 19 | docPath: '', //swagger文档访问路径 20 | docPort: 80, //swagger文档端口号 21 | path: '/v2/api-docs', //swagger模式路径 22 | method: 'GET', //文档数据请求方式 23 | realHostName: '', // 项目上线后访问的真实域名 24 | mockPort: 3000, //启动服务的端口号 25 | customProtocol: 'http', //指定协议 26 | headers: {}, 27 | jsPath: '', //指定生成的URL文件创建路径 28 | descInclude: [], 29 | override: false 30 | }, 31 | opts 32 | ); 33 | //确保地址存在 34 | if(this.options.docPath == "") { 35 | log(docPathError); 36 | return; 37 | } 38 | //不同类型跳转到不同数据层 39 | switch (this.options.type) { 40 | case 'swagger': 41 | let swagger = new Swagger(this.options); 42 | this.dataPromise = swagger.getData(); 43 | break; 44 | } 45 | this.workflow(); 46 | process.on('unhandledRejection', (error) => { 47 | console.error('unhandledRejection', error); 48 | process.exit(1); // To exit with a 'failure' code 49 | }); 50 | } 51 | // SMock主流程 52 | workflow() { 53 | this.dataPromise.then((data: any) => { 54 | let process = []; 55 | this.data = data; 56 | //声明变量 57 | var server = new Server(this.options, this.data); 58 | if(!isNew() && !this.options.override) { 59 | 60 | }else { 61 | let file = new File(this.options, this.data); 62 | //创建文件 63 | let filePromise = file.createJSONFile(); 64 | process.push(filePromise); 65 | //创建URL 66 | let urlPromise = file.createUrlFile(); 67 | process.push(urlPromise); 68 | } 69 | //插入接口 70 | let apiPromise = server.addAPI(); 71 | process.push(apiPromise); 72 | 73 | //全部过程执行完毕,执行启动服务 74 | Promise.all(process).then(() => { 75 | server.startServer(() => { 76 | log(`【${new Date()}】服务器启动!`); 77 | // log(`http://127.0.0.1:${this.options.mockPort}`); 78 | }); 79 | }); 80 | }); 81 | } 82 | } 83 | export { Core }; 84 | -------------------------------------------------------------------------------- /src/model/dataModel.ts: -------------------------------------------------------------------------------- 1 | interface Config { 2 | type: String, 3 | docPath: String, //swagger文档访问路径 4 | docPort: Number, //swagger文档端口号 5 | path: String, //swagger模式路径 6 | method: String, //文档数据请求方式 7 | realHostName: String, // 项目上线后访问的真实域名 8 | mockPort: Number, //启动服务的端口号 9 | customProtocol: String, //指定协议 10 | headers: any, //创建服务时的请求头 11 | jsPath: String, 12 | descInclude: any, //是否自动弹出接口描述 13 | override: Boolean //是否每次启动服务都覆盖数据 14 | 15 | } 16 | interface Data { 17 | id?: any; //唯一ID 18 | url?: any; //接口路径 19 | type?: any; //请求类型 20 | [propName: string]: any; 21 | } 22 | 23 | interface UrlData { 24 | url: String 25 | } 26 | export { Config, Data, UrlData }; -------------------------------------------------------------------------------- /src/model/log.ts: -------------------------------------------------------------------------------- 1 | let startServerError = '服务器启动错误'; 2 | let jsPathError = 'jsPath配置有误'; 3 | let docPathError = '文档路径配置错误,请检查docPath是否配置'; 4 | export { 5 | startServerError, 6 | jsPathError, 7 | docPathError 8 | } -------------------------------------------------------------------------------- /src/model/swagger/model.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Swagger处理数据模型 3 | * author: Yang Lei 4 | */ 5 | import { log } from './../../utils/utils'; 6 | import {setString, setInteger, setBoolean } from './../../utils/mock'; 7 | interface Definition { 8 | prevKey: String; 9 | definition: any; //当前数据格式 10 | definitionMap: any;//全部数据格式Map 11 | } 12 | //数据查找路径,针对swagger返回数据的特殊处理 13 | function queryData(hash: any) { 14 | let result = ''; 15 | if(hash) { 16 | result = (hash.substring(2, hash.length)).split('/'); 17 | } 18 | return result[1] || ''; 19 | } 20 | 21 | //递归替换 22 | export async function dealData(def: Definition): Promise { 23 | let result: any = {}; 24 | //保存接口返回参数的描述 25 | let desc: any = []; 26 | let definition =def.definition; 27 | let type = definition && definition.type ? definition.type : ''; 28 | if (type) { 29 | if (type == 'string') { 30 | result = setString(); 31 | let varDesc:any = {}; 32 | varDesc.key = def.prevKey; 33 | varDesc.desc = definition.description || '暂无定义'; 34 | varDesc.type = definition.type; 35 | desc.push(varDesc); 36 | } 37 | if (type == 'integer') { 38 | result = setInteger(); 39 | let varDesc:any = {}; 40 | varDesc.key = def.prevKey; 41 | varDesc.desc = definition.description || '暂无定义'; 42 | varDesc.type = definition.type; 43 | desc.push(varDesc); 44 | } 45 | if (type == 'boolean') { 46 | result = setBoolean(); 47 | let varDesc:any = {}; 48 | varDesc.key = def.prevKey; 49 | varDesc.desc = definition.description || '暂无定义'; 50 | varDesc.type = definition.type; 51 | desc.push(varDesc); 52 | } 53 | if (type == 'object') { 54 | if (definition.properties) { 55 | result = definition.properties; 56 | for (let key in result) { 57 | //防止递归数据造成死循环 58 | if (result[key].type && result[key].type == 'array' && result[key]['$ref'] && (queryData(result[key]['$ref']) == def.prevKey)) { 59 | result[key] = {}; 60 | } else { 61 | dealData({ 62 | prevKey: key, 63 | definition: result[key], 64 | definitionMap: def.definitionMap 65 | }).then((data) => { 66 | result[key] = data.data; 67 | desc = desc.concat(data.desc); 68 | }) 69 | } 70 | } 71 | } else { 72 | result = {}; 73 | } 74 | } 75 | if (type == 'array') { 76 | let items = def.definition.items; 77 | if (items.type) { 78 | } else { 79 | let objKey: string = queryData(items['$ref']); 80 | //防止递归数据造成死循环 81 | if (objKey != def.prevKey) { 82 | dealData({ 83 | prevKey: objKey, 84 | definition: def.definitionMap[objKey], 85 | definitionMap: def.definitionMap 86 | }).then((data) => { 87 | result = data.data; 88 | desc = desc.concat(data.desc); 89 | }) 90 | } else { 91 | result = {}; 92 | } 93 | } 94 | } 95 | } else { 96 | let goObject = def.definition && def.definition['$ref'] ? def.definition['$ref'] : ''; 97 | if (goObject) { 98 | let objKey = queryData(goObject); 99 | dealData({ 100 | prevKey: objKey, 101 | definition: def.definitionMap[objKey], 102 | definitionMap: def.definitionMap 103 | }).then((data) => { 104 | result = Object.assign(result, data.data); 105 | desc = desc.concat(data.desc); 106 | }) 107 | } else { 108 | result = setString(); 109 | } 110 | } 111 | return { 112 | data: result, 113 | desc: [] 114 | } 115 | } 116 | //解析API参数 117 | export function getParamData(params:any, definitions:any):any { 118 | let result = []; 119 | for(let i = 0; i < params.length; i++) { 120 | let param = params[i]; 121 | let p: any = {}; 122 | if(param.schema) { 123 | p.type = 'object'; 124 | p.value = param.name; 125 | if(param.schema['$ref']){ 126 | p.child = getParamSchemaData(param.schema['$ref'], definitions); 127 | }else { 128 | p.type = param.schema.type; 129 | } 130 | p.required = param.required; 131 | }else { 132 | p.type = param.type? param.type: 'string'; 133 | p.value = param.name; 134 | p.required = param.required; 135 | } 136 | p.in = param.in; 137 | p.required = param.required; 138 | p.desc = param.description; 139 | result.push(p); 140 | } 141 | return result; 142 | } 143 | 144 | function getParamSchemaData(key:string, definitionMap: any):any { 145 | let validResult:any = []; 146 | let defineKey = queryData(key); 147 | let props = definitionMap[defineKey].properties; 148 | for( let key in props) { 149 | let prop = props[key]; 150 | let validItem:any = {}; 151 | if(prop.$ref) { 152 | validItem.type = 'object'; 153 | validItem.desc = prop.description; 154 | validItem.param = getParamSchemaData(prop.$ref, definitionMap); 155 | }else { 156 | validItem.name = key; 157 | validItem.type = prop.type; 158 | validItem.desc = prop.description; 159 | } 160 | validResult.push(validItem); 161 | } 162 | return validResult; 163 | // definitionMap[key]; 164 | } 165 | //获取json数据的key 166 | export function getJsonDataKey(responses: any):any { 167 | let schema = responses['200'].schema; 168 | let mockJsonKey = schema ? schema['$ref'] : ''; 169 | return queryData(mockJsonKey); 170 | } 171 | -------------------------------------------------------------------------------- /src/model/swagger/swagger.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * 使用Swagger时,处理数据的文件 4 | */ 5 | 6 | import { Config, Data } from '../dataModel'; 7 | import { Server } from '../../server/server' 8 | import { dealConfig, log, keyInData } from '../../utils/utils'; 9 | import { dealData, getJsonDataKey, getParamData } from './model'; 10 | import { setString } from './../../utils/mock'; 11 | // 一个npm 包 12 | let clone = require('clone'); 13 | class Swagger { 14 | config: Config; 15 | constructor(opts: Config) { 16 | this.config = dealConfig(opts); 17 | } 18 | async getData(): Promise { 19 | let self = this; 20 | let d; 21 | await self.getDataFromServer() 22 | .then((data: any) => { 23 | d = self.convertData(clone(data)); 24 | console.log(d, data); 25 | 26 | }) 27 | return d; 28 | } 29 | // 从swagger中拿到数据 30 | async getDataFromServer(): Promise { 31 | let server = new Server(this.config); 32 | let result; 33 | let url = `${this.config.customProtocol}://${this.config.docPath}:${this.config.docPort}${this.config.path}`; 34 | await server.fetchData( 35 | { 36 | url: url 37 | } 38 | ) 39 | .then((data) => { 40 | // console.log(data); 41 | result = data; 42 | }) 43 | return result; 44 | } 45 | // 转化数据 46 | async convertData(data: any): Promise { 47 | return await new Promise((resolve, reject) => { 48 | let allDealDataPromises = []; 49 | // 格式规划 50 | let apis = data.paths; 51 | let definitions = data.definitions; 52 | let result: any[] = []; 53 | for (let prop in apis) { 54 | let d: Data = {}; 55 | let item = apis[prop]; 56 | d.url = prop; 57 | // if(d.url != '/api/service/{serviceId}') continue; 58 | let dataModelFlag = ''; 59 | for (let type in item) { 60 | // let descs:any = []; 61 | d.type = type; 62 | d.id = item[type].operationId; 63 | d.desc = item[type].summary; 64 | dataModelFlag = getJsonDataKey(item[type].responses); 65 | if (dataModelFlag) { 66 | let promise = dealData({ 67 | prevKey: dataModelFlag, 68 | definition: definitions[dataModelFlag], 69 | definitionMap: definitions 70 | }).then((data) => { 71 | d.data = data.data; 72 | d.responseDesc = data.desc; 73 | 74 | if (!keyInData(item[type].operationId, result)) { 75 | result.push(d); 76 | } 77 | }).then((err) => { 78 | }).catch((err) => { 79 | 80 | }); 81 | allDealDataPromises.push(promise); 82 | } else { 83 | d.data = setString(); 84 | if (!keyInData(item[type].operationId, result)) { 85 | result.push(d); 86 | } 87 | } 88 | if (item[type].parameters) { 89 | d.params = getParamData(item[type].parameters, definitions); 90 | } else { 91 | d.params = null; 92 | } 93 | } 94 | } 95 | Promise.all(allDealDataPromises).then(() => { 96 | resolve(result); 97 | }) 98 | }) 99 | } 100 | } 101 | export { Swagger }; -------------------------------------------------------------------------------- /src/server/describe.ts: -------------------------------------------------------------------------------- 1 | import { Base } from "../base/base"; 2 | import { Data, Config } from './../model/dataModel'; 3 | import { getDataFromArrayById, join } from './../utils/utils'; 4 | import { readFileSync } from "fs"; 5 | import { exec } from 'child_process'; 6 | 7 | class Describe extends Base { 8 | constructor(opts?: Config, data?: Data) { 9 | super(opts, data); 10 | } 11 | 12 | getDescribeHtmlUrl (id: any) { 13 | this.createHtml(id); 14 | return `http://127.0.0.1:${this.option.mockPort}/desc/${id}`; 15 | } 16 | 17 | createHtml(id: any) { 18 | let result:String; 19 | let data = getDataFromArrayById(this.data, id); 20 | let html: string = ''; 21 | if(data.params) { 22 | html += ` 23 |

入参

24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | `; 33 | for(let inIndex = 0; inIndex < data.params.length; inIndex ++) { 34 | let param = data.params[inIndex]; 35 | html += ` 36 | 37 | 38 | 39 | 40 | 41 | `; 42 | if(param.child) { 43 | for(let childIndex = 0; childIndex < param.child.length; childIndex++) { 44 | let childParam = param.child[childIndex]; 45 | 46 | html += ` 47 | 48 | 49 | 50 | 51 | 52 | `; 53 | } 54 | } 55 | } 56 | html += ` 57 | 58 |
参数名类型位置是否必填描述
${param.value}${param.type}${param.in}${param.required}${param.desc}
${param.value}.${childParam.name}${childParam.type}${param.in}${param.required}${param.desc}
` 59 | } 60 | if(data.responseDesc && data.responseDesc.length > 0) { 61 | html += ` 62 |

出参

63 | 64 | 65 | 66 | 67 | 68 | 69 | `; 70 | for(let resIndex =0; resIndex < data.responseDesc.length; resIndex ++) { 71 | let desc = data.responseDesc[resIndex]; 72 | html += ` 73 | 74 | 75 | 76 | 77 | ` 78 | } 79 | html += ` 80 | 81 |
参数名类型描述
${desc.key}${desc.type}${desc.desc}
82 | `; 83 | } 84 | let file = readFileSync(join(__dirname, './../html/temp.html'), 'utf8'); 85 | result = file.replace('{{CODE}}', html); 86 | result = result.replace('{{API}}', data.url); 87 | result = result.replace('{{DESC}}', data.desc); 88 | return result; 89 | } 90 | openAPIDesc(descUrl: string) { 91 | let cmd:string = '' ; 92 | if(process.platform == 'win32') { 93 | cmd = 'start'; 94 | } else if (process.platform == 'linux') { 95 | cmd = 'xdg-open'; 96 | } else if (process.platform == 'darwin') { 97 | cmd = 'open'; 98 | } 99 | exec(`${cmd} ${descUrl}`); 100 | } 101 | } 102 | 103 | export { Describe }; -------------------------------------------------------------------------------- /src/server/server.ts: -------------------------------------------------------------------------------- 1 | import { Base } from './../base/base'; 2 | import { Data, Config, UrlData } from './../model/dataModel'; 3 | import { log,dealUrl,join2, join } from './../utils/utils'; 4 | import { mockDirName } from './../utils/dict'; 5 | import { Describe } from './describe'; 6 | import { validParam } from './valid'; 7 | let express = require('express'); 8 | var bodyParser = require("body-parser"); 9 | let axios = require('axios'); 10 | let app = express(); 11 | app.use(bodyParser.urlencoded({ extended: false })); 12 | interface serverConfig { 13 | url: string, 14 | mockDir:string, 15 | type: string, 16 | typeContent: any, 17 | GlobalDefinitions: any 18 | } 19 | class Server extends Base{ 20 | constructor(opts?: Config, data?: Data) { 21 | super(opts, data); 22 | } 23 | //启动服务 24 | startServer(cal: any): any { 25 | app.listen(this.option.mockPort, () => { 26 | if (cal) cal(); //启动成功 27 | this.addStatic(); 28 | }); 29 | } 30 | // 托管静态页面,如接口描述页等 31 | addStatic() { 32 | app.use(express.static(join(__dirname, './../html'))); 33 | } 34 | 35 | // 注入接口 36 | async addAPI(): Promise { 37 | let self = this; 38 | let desc = new Describe(this.option, this.data); 39 | let data=this.data; 40 | return await new Promise((resolve, reject) => { 41 | app.get('/desc/:id', function(req:any, res:any) { 42 | let id = req.params.id; 43 | res.send(desc.createHtml(id)); 44 | }) 45 | for (let index = 0; index < data.length; index++) { 46 | const element = data[index]; 47 | let realUrl = dealUrl(element.url); 48 | app[element.type](realUrl,function(req:any, res:any){ 49 | res.header("Access-Control-Allow-Origin", req.headers.origin); 50 | res.header('Access-Control-Allow-Credentials', true); //告诉客户端可以在HTTP请求中带上Cookie 51 | res.header("Access-Control-Allow-Headers", "Origin, No-Cache, X-Requested-With, If-Modified-Since, Pragma, " + 52 | "Last-Modified, Cache-Control, Expires, Content-Type, Content-Language, Cache-Control, X-E4M-With,X_FILENAME"); 53 | res.header("Access-Control-Allow-Methods", "PUT,POST,GET,DELETE,OPTIONS"); 54 | res.header("X-Powered-By", ' 3.2.1') 55 | res.header("Content-Type", "application/json;charset=utf-8"); 56 | 57 | //创建接口描述页面 58 | let descUrl = desc.getDescribeHtmlUrl(element.id); 59 | log('调用接口的文档链接:'+ descUrl); 60 | if(self.isInclude(element.id, self.option.descInclude)) { 61 | desc.openAPIDesc(descUrl); 62 | } 63 | let params = self.getParamByType(req); 64 | let errMsg = validParam(params, element); 65 | if(errMsg === '') { 66 | let fileUrl = join2(process.cwd(), mockDirName, element.id+".json"); 67 | res.send(require(fileUrl)) 68 | }else { 69 | res.send(` 70 | ${errMsg} 71 | 接口描述参考:${descUrl} 72 | `); 73 | } 74 | }) 75 | } 76 | resolve(); 77 | }); 78 | // return 1; 79 | //TODO: 注入SMock的接口服务 80 | } 81 | getParamByType(req: any) { 82 | let params = {}; 83 | if(req.params) { 84 | params = Object.assign(params, req.params); 85 | } 86 | if(req.query) { 87 | params = Object.assign(params, req.query); 88 | } 89 | if(req.body) { 90 | params = Object.assign(params, req.body); 91 | } 92 | return params; 93 | } 94 | isInclude(urlId:any, includes:any):any { 95 | let exist = false; 96 | exist = includes.indexOf(urlId) > -1; 97 | return exist; 98 | } 99 | //获取数据 100 | async fetchData(opts:UrlData): Promise { 101 | let self = this; 102 | // let swaggerUtl = getHost(opts.url); 103 | return await new Promise((resolve, reject) => { 104 | let header:any = {}; 105 | for(let prop in self.option.headers) { 106 | header(prop, self.option.headers[prop]); 107 | } 108 | axios({ 109 | url: opts.url, 110 | headers: header 111 | }) 112 | .then((data: any) => { 113 | resolve(data.data); 114 | }) 115 | }) 116 | } 117 | } 118 | 119 | export { Server }; -------------------------------------------------------------------------------- /src/server/valid.ts: -------------------------------------------------------------------------------- 1 | export function validParam(params: any, data:any) { 2 | let result = ''; 3 | let errorRequired = validRequired(params, data); 4 | let errorType = validType(params, data); 5 | if(errorRequired.length > 0) { 6 | result += `【${errorRequired.join('、')}】必须传入`; 7 | } 8 | if (errorType.length > 0) { 9 | result += ` 10 | 【${errorType.join('、')}】传入类型错误 11 | `; 12 | } 13 | return result; 14 | } 15 | //校验必填 16 | function validRequired(inParams: any, data: any) { 17 | let error:any = []; 18 | if(!data.params) return error; 19 | for(let idx = 0; idx < data.params.length; idx++) { 20 | let item = data.params[idx]; 21 | if(item.required && !inParams[item.value]) { 22 | //检查传参中是不是存在必填项 23 | error.push(item.value); 24 | } 25 | } 26 | return error; 27 | } 28 | 29 | //校验入参格式 30 | function validType(inParams: any, data: any) { 31 | let error:any = []; 32 | if(!data.params) return error; 33 | for (let prop in inParams) { 34 | let param = getParamByName(prop, data.params); 35 | if(!param) return error; 36 | if( typeof inParams[prop] != param.type) { 37 | //如果类型为int,但url的方式传参会自动转换为字符串,我们要转换 38 | console.log(param.type, inParams[prop], isNaN(parseInt(inParams[prop]))); 39 | if(!(param.type == 'integer' && !isNaN(parseInt(inParams[prop])))) { 40 | error.push(param.value); 41 | } 42 | } 43 | } 44 | return error; 45 | } 46 | 47 | //获取对应参数的值 48 | function getParamByName(key: string, params: any) { 49 | let result:any = null; 50 | for(let idx = 0; idx < params.length; idx++) { 51 | let param = params[idx]; 52 | if(key === param.value) { 53 | result = param; 54 | } 55 | } 56 | return result; 57 | } -------------------------------------------------------------------------------- /src/utils/dict.ts: -------------------------------------------------------------------------------- 1 | export const mockDirName = 'mock'; 2 | export const configName = 'SMock.json'; //参数文件名 3 | export const urlsRealName = 'urlsReal'; //真实路径文件名 -------------------------------------------------------------------------------- /src/utils/mock.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * 模拟数据动态化,引入mockjs.js来处理 3 | * author: liaoyanli 4 | */ 5 | let Mock = require('mockjs'); 6 | var Random = Mock.Random; 7 | export function setString() { 8 | Random.word(3, 8); 9 | return Mock.mock('@word(3, 8)'); 10 | } 11 | 12 | export function setBoolean() { 13 | Random.boolean(); 14 | return Mock.mock('@boolean'); 15 | } 16 | 17 | export function setInteger() { 18 | Random.integer(1, 100); 19 | return Mock.mock('@integer(1, 100)') 20 | } -------------------------------------------------------------------------------- /src/utils/utils.ts: -------------------------------------------------------------------------------- 1 | let fs = require('fs'); 2 | let path = require('path'); 3 | import { mockDirName } from './dict'; 4 | export function dealConfig(c: any) { 5 | if(c.docPath) { 6 | c.docPath = parseURL(c.docPath); 7 | } 8 | if (c.domain) { 9 | c.headers = { 10 | host: c.domain 11 | }; 12 | } 13 | if (c.customProtocol == 'https') { 14 | c.port = 443; 15 | } 16 | //mock文件夹名 17 | // c.mockDirName = `${c.projectName?c.projectName:defaultConfig.projectName}mock`; 18 | return c; // Object.assign(defaultConfig, c); 19 | } 20 | function parseURL(url: any) { 21 | var a = url.replace("http://","").replace("https://",""); 22 | return a.substring(0,a.indexOf("/")>0?a.indexOf("/"):a.length); 23 | } 24 | exports.parseURL = parseURL; 25 | export function log(msg: any) { 26 | console.log(msg); 27 | } 28 | //警告打印 29 | export function warn(msg: any) { 30 | console.log(`warn:${msg}`); 31 | } 32 | 33 | export function error(msg: any) { 34 | console.log(`error:${msg}`); 35 | } 36 | 37 | //将buffer或者字符串转换成json串 38 | export function toJson(str: any) { 39 | return JSON.parse(str); 40 | } 41 | 42 | //将对象变成格式化串 43 | export function toStr(json: any) { 44 | return JSON.stringify(json, null, 2); 45 | } 46 | 47 | //将含有花括号的url,替换成对应的数据 48 | export function dealUrl(url: any) { 49 | return url.replace(/\{.*?\}/g, function(d: any) { 50 | let key = d.substring(1, d.length - 1); 51 | return `:${key}`; 52 | }); //此正则很重要 53 | } 54 | 55 | export function getParamByType(type: string, req: any) { 56 | let params = {}; 57 | type = type.toLowerCase(); 58 | switch (type) { 59 | case 'get': 60 | params = req.query; 61 | break; 62 | case 'post': 63 | params = req.body; 64 | break; 65 | default: 66 | break; 67 | } 68 | return params; 69 | } 70 | 71 | // Data中获取对应ID的数据 72 | export function getDataFromArrayById(arr: any, id: any) { 73 | for (let i = 0; i < arr.length; i++) { 74 | let item = arr[i]; 75 | if (item.id === id) { 76 | return item; 77 | } 78 | } 79 | return null; 80 | } 81 | 82 | //文件操作 83 | export function existsSync(url: string) { 84 | return fs.existsSync(url); 85 | } 86 | 87 | //读文件 88 | export function readFileSync(url: string) { 89 | return fs.readFileSync(url); 90 | } 91 | 92 | //创建目录 93 | export function createDir(dir: string) { 94 | var stat = fs.existsSync(dir); 95 | if (!stat) { 96 | //为true的话那么存在,如果为false不存在 97 | fs.mkdirSync(dir); 98 | } 99 | } 100 | 101 | //创建文件并写入内容(异步) 102 | export function makeFile(filePath: string, content: any) { 103 | return new Promise((resolve, reject) => { 104 | var stat = existsSync(filePath); 105 | if (stat) { 106 | //为true的话那么存在,如果为false不存在 107 | // utils.log(`${filePath} 已存在,内容已覆盖`); 108 | } 109 | fs.writeFile(filePath, content, (err: any) => { 110 | if (!err) { 111 | resolve(filePath); 112 | } else { 113 | reject(err); 114 | } 115 | }); 116 | }); 117 | } 118 | 119 | //创建文件并写入内容(同步无回调) 120 | export function makeFileSync(filePath: string, content: any) { 121 | var stat = existsSync(filePath); 122 | if (stat) { 123 | //为true的话那么存在,如果为false不存在 124 | log(`${filePath} 已存在,内容已覆盖`); 125 | } 126 | // else { 127 | fs.writeFileSync(filePath, content); 128 | } 129 | ////判断是否存在json文件 130 | export function isNew() { 131 | return !existsSync(join(process.cwd(), mockDirName)); 132 | } 133 | 134 | //合并 135 | export function join(a: any, b: any) { 136 | return path.resolve(a, b); 137 | } 138 | 139 | export function join2(a: any, b: any, c: any) { 140 | return path.join(a, b, c); 141 | } 142 | 143 | // 判断对象中是否存在值 144 | export function keyInData(id: string, arr: any) { 145 | let result = false; 146 | for(let i=0; i