UNPKG

swagger-ts-api_zwj

Version:

根据swagger文档生成typescript客户端调用代码

589 lines (588 loc) 22.2 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || (function () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __setModuleDefault(result, mod); return result; }; })(); var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.Swagger = void 0; const lodash_1 = __importStar(require("lodash")); const path_1 = __importDefault(require("path")); const ejs_1 = __importDefault(require("ejs")); const fs_1 = __importDefault(require("fs")); const fs_2 = require("./fs"); const prettier_1 = __importDefault(require("prettier")); /** * swagger 代码生成处理类 */ class Swagger { // 接口配置 apiConfig; // swagger-json swaggerData; // prettier prettierOptions; constructor(basePath, apiConfig, swaggerData, prettier) { this.apiConfig = Object.assign({ OutPath: path_1.default.join(basePath, 'src/api/' + apiConfig.ApiName), TplPath: path_1.default.join(basePath, 'nswag/tpl'), FormatControllerName: this.formatControllerName, // 格式化模块名称(默认:接口名称+Api) FormatMethodName: this.formatMethodName, // 格式化接口名称(默认:小驼峰命名) FormatModelName: this.formatModelName, // 格式化dto对象、枚举名称(默认:只会去除特殊字符) FormatMock: this.formatMock // 格式化模拟数据 }, apiConfig); this.swaggerData = swaggerData; this.prettierOptions = prettier; } /** * 格式化模块名称(默认:接口名称+Api) * @param name 名称 */ formatControllerName(name) { return name.indexOf('Api') !== -1 ? name : name + 'Api'; } /** * 格式化接口名称(默认:小驼峰命名) * @param name 名称 */ formatMethodName(name) { if (name === '/' || name === '') { return ''; } const fnName = name.substring(name.lastIndexOf('/')); return lodash_1.default.camelCase(fnName); } /** * 格式化dto对象、枚举名称(默认:只会去除特殊字符) * @param name 名称 */ formatModelName(name) { return name.substring(name.lastIndexOf('/') + 1).replace(/[.,\[\]]/g, '_'); } /** * 去掉换行 * @param str 字符串 */ removeLineBreak(str) { return str ? str.replace(/[\r\n]/g, '') : ''; } /** * 处理重名问题 * @param name 当前名称 * @param list 列表,对象必须有Name属性才行 */ reName(name, list) { // 方法名称-重名处理 if (lodash_1.default.findIndex(list, { methodName: name }) !== -1) { let i = 1; while (true) { if (lodash_1.default.findIndex(list, { methodName: name + '_' + i }) !== -1) { i++; } else { name = name + '_' + i; break; } } } return name; } /** * 查找对象节点 * @param sourceObj 对象 * @param targObjs 依赖对象 * @param key 条件字段 */ findObjs(sourceObj, targObjs, key) { if ((0, lodash_1.isObjectLike)(sourceObj)) { lodash_1.default.forEach(sourceObj, (prop, name) => { if (name == key) { targObjs.push(prop); } this.findObjs(prop, targObjs, key); }); } return targObjs; } /** * 对schema对象进行类型判断 * @param obj */ convertType(obj) { if (!obj) { return 'void'; } if (obj.hasOwnProperty('schema')) { return this.convertType(obj.schema); } if (obj.hasOwnProperty('oneOf')) { return this.convertType(obj.oneOf[0]); } if (obj.hasOwnProperty('allOf')) { let _Types = []; lodash_1.default.forEach(obj.allOf, (pp, nm) => { _Types.push(this.convertType(pp)); }); return _Types.join(' & '); } if (obj.hasOwnProperty('properties')) { return this.convertType(obj.properties); } if (obj.hasOwnProperty('$ref')) { return this.formatModelName(obj.$ref); } if (obj.type === 'array') { const iType = this.convertType(obj.items); return 'Array<' + iType + '>'; } else { switch (obj.type) { case 'string': if (obj.format === 'binary') { return 'string | Blob'; } return 'string'; case 'number': case 'integer': return 'number'; case 'boolean': return 'boolean'; case 'file': return 'string | Blob'; default: if ((0, lodash_1.isObjectLike)(obj)) { let objType = {}; lodash_1.default.forEach(obj, (prop, name) => { objType[name] = this.convertType(prop); }); return JSON.stringify(objType).replace(/["]/g, ''); } else { return 'any'; } } } } /** * 获取全部tag * @param swaggerData 数据 */ getTags() { let tags = { // auth: { // tagName:'', // description: '', // methods: [{methodName:'',url:'',requestName:'',summary:'',parameters:'',responses:''}] // } }; lodash_1.default.forEach(this.swaggerData.paths, (api, url) => { lodash_1.default.forEach(api, (md, requestName) => { // 接口Controller let tagName = md.tags[0]; if (!tags[tagName]) { tags[tagName] = { tagName: tagName, description: this.swaggerData.ControllerDesc ? this.swaggerData.ControllerDesc[tagName] : '太懒没写注释', methods: [] }; } // 参数处理 if (!md.parameters) { md.parameters = []; } if (md.requestBody) { let schema = this.findObjs(md.requestBody, [], 'schema')[0] || this.findObjs(md.requestBody, [], 'multipart/form-data')[0].schema; if (schema) { md.parameters.push(Object.assign({ name: 'data', in: 'body' }, { schema, required: md.requestBody.required })); } } // 方法 let method = { methodName: this.reName(this.apiConfig.FormatMethodName(url), tags[tagName].methods), url: url, requestName: requestName, summary: this.removeLineBreak(md.summary) || '太懒没写注释', parameters: md.parameters, responses: md.responses }; tags[tagName].methods.push(method); }); }); // 调整方法顺序,因为mock时 有可能匹配错误的mock拦截 lodash_1.default.each(tags, (c) => { c.methods = lodash_1.default.orderBy(c.methods, ['methodName'], ['desc']); }); // 清理无方法空模块 lodash_1.default.remove(tags, (c) => { return c.methods.length <= 0; }); return tags; } /** * 获取全部依赖对象 * @param tag 标签/Controller */ getTagModels(tag) { let models = this.findObjs(tag, [], '$ref'); let modelsNames = []; models.forEach((item) => { const name = this.apiConfig.FormatModelName(item); if (!modelsNames.includes(name)) modelsNames.push(name); }); return modelsNames; } // 格式化属性方法 fmProperties(properties, model, requireds) { lodash_1.default.forEach(properties, (propertie, name) => { const newp = { name: name, description: this.removeLineBreak(propertie.description), type: this.convertType(propertie), required: requireds && requireds.length > 0 ? requireds.includes(name) : undefined }; model.properties.push(newp); }); } // 获取全部请求参数对象 getRequestBodys() { let requestBodyList = []; lodash_1.default.forEach(this.swaggerData.paths, (api, url) => { lodash_1.default.forEach(api, (md, requestName) => { if (md.requestBody) { let schema = this.findObjs(md.requestBody, [], 'schema')[0] || this.findObjs(md.requestBody, [], 'multipart/form-data')[0].schema; if (schema) { requestBodyList.push(this.convertType(schema)); } } }); }); return requestBodyList; } /** * 获取全部 Model \ Enum * @param node 指定对象节点,可空,如果不传则按默认规则查找 */ getModelsAndEnums(node) { if (!node) { node = this.swaggerData?.components?.schemas || this.swaggerData?.definitions; } let enums = []; let models = []; let requestBodyList = this.getRequestBodys(); lodash_1.default.forEach(node, (definition, name) => { if (definition.hasOwnProperty('enum')) { const e = { name: this.apiConfig.FormatModelName(name), description: this.removeLineBreak(definition.description), items: [] }; const ems = lodash_1.default.zipObject(definition['x-enumNames'], definition.enum); lodash_1.default.forEach(ems, function (enumValue, enumName) { const item = { name: enumName, value: Number(enumValue) }; e.items.push(item); }); enums.push(e); } else { const modelName = this.apiConfig.FormatModelName(name); let isParameter = requestBodyList.includes(modelName); const m = { name: modelName, description: this.removeLineBreak(definition.description), baseModel: '', isParameter, properties: [] }; // 格式化属性 if (definition.hasOwnProperty('allOf')) { lodash_1.default.forEach(definition.allOf, (propertie) => { if (propertie.hasOwnProperty('$ref')) { m.baseModel = this.apiConfig.FormatModelName(propertie.$ref.substring(propertie.$ref.lastIndexOf('/') + 1)); } else { if (propertie.hasOwnProperty('properties')) { this.fmProperties(propertie.properties, m, propertie.required || []); } } }); } else { this.fmProperties(definition.properties, m, definition.required || []); } models.push(m); } }); return { models, enums }; } /** * 获取请求参数 * @param parameters 参数数组,不同版本swagger有差异 * @param inTypes 参数类型数组,可同时查多种类型 query、path、body、header * @param format 格式化数据 */ getParameter(parameters, inTypes, format) { let pas = lodash_1.default.filter(parameters, (item) => { return inTypes.includes(item.in); }).map((item) => { return { ...item, type: this.convertType(item) }; }); // 格式化 if (format) { return pas.map((item) => { return format(item); }); } // 排序一下参数,把非必填参数排后面 return pas.sort((a, b) => { if (a.required && !b.required) { return -1; // a在b之前 } else if (!a.required && b.required) { return 1; // b在a之前 } return 0; // 顺序不变 }); } /** * 获取请求参数 * @param responses 返回对象 */ getResponses(responses) { let schema = this.findObjs(responses, [], 'schema')[0]; return this.convertType(schema); } /** * 格式化模拟值 * @param val 默认格式化后的值 * @param p 对应的属性 * @param mock 最终模拟数据 */ formatMock(val, p, mock) { switch (p.type) { case 'string': switch (p.name) { case 'name': val = '@cname'; break; case 'title': val = '@ctitle(10, 20)'; break; case 'mobile': val = '@natural(10000)'; break; case 'email': val = '@email'; break; case 'province': val = '@province'; break; case 'city': val = '@city'; break; case 'area': val = '@county'; break; default: val = '@ctitle(10, 20)'; break; } mock[p.name] = val; break; case 'number': switch (p.name) { case 'result_code': val = 0; break; case 'page_index': val = 1; break; case 'page_size': val = 15; break; case 'total_count': val = 30; break; default: val = '@integer(0, 100)'; break; } mock[p.name] = val; break; case 'array': mock[p.name + '|20'] = val; break; default: mock[p.name] = val; break; } return mock; } /** * 格式化模拟数据 * @param responses 当前需要模拟的对象 * @param models 接口全部对象 * @param fm 格式化模拟数据函数 * @param deep 递归层级,防止对象父子嵌套导致死循环 默认递归5级 */ mock(responses, models, deep = 1) { let responsesList; if (responses.indexOf('&') > 0) { responsesList = responses.split('&'); } else { responsesList = [responses]; } let mock = {}; responsesList.forEach((item) => { let _name = item.trim().replace('{data:', '').replace('}', ''); let model = lodash_1.default.find(models, { name: _name }); if (model) { model.properties.forEach((p) => { // 'string' | 'number' | 'boolean' | 'file' | 'array' | 'enum' | 'schema' let v = ''; switch (p.type) { case 'string': v = '@ctitle(10, 20)'; break; case 'number': v = '@integer(0, 100)'; break; case 'boolean': v = '@boolean'; break; case 'file': v = ''; break; case 'array': v = deep > 5 ? [] : [this.mock(p.type, models, deep + 1)]; break; case 'enum': v = p.type; break; case 'schema': v = deep > 5 ? null : this.mock(p.type, models, deep + 1); break; } mock = this.apiConfig.FormatMock(v, p, mock); }); return mock; } else { switch (_name) { case 'string': mock['data'] = '@ctitle(10, 20)'; break; case 'number': mock['data'] = '@integer(0, 100)'; break; case 'boolean': mock['data'] = '@boolean'; break; } } }); return mock; } /** * 生成代码 * @param TplPath 模板绝对地址 * @param OutPath 文件存放绝对地址 * @param fileName 文件名称 * @param data.tag 当前控制器标签(仅生成控制器代码时有值) * @param data.swaggerData 接口数据 * @param data.apiConfig 接口配置 */ async codeRender(TplPath, OutPath, fileName, data) { if ((0, fs_2.markDirsSync)(OutPath)) { const fileText = ejs_1.default.render(fs_1.default.readFileSync(TplPath, 'utf-8'), data, { context: this }); const savePath = path_1.default.join(OutPath, fileName); const content = this.prettierOptions ? await prettier_1.default.format(fileText, { parser: 'babel-ts', singleQuote: true, printWidth: 180, tabWidth: 2, semi: false, trailingComma: 'none' }) : fileText; fs_1.default.writeFileSync(savePath, content); } } /** * 生成代码 */ generate() { const saveBaseDir = path_1.default.join(this.apiConfig.OutPath, 'base'); const saveModelsDir = path_1.default.join(this.apiConfig.OutPath, 'model'); const saveMockDir = path_1.default.join(this.apiConfig.OutPath, 'mock'); const tplBasePath = path_1.default.join(this.apiConfig.TplPath, 'base.ejs'); const tplMethodPath = path_1.default.join(this.apiConfig.TplPath, 'method.ejs'); const tplModelsPath = path_1.default.join(this.apiConfig.TplPath, 'model.ejs'); const tplMockPath = path_1.default.join(this.apiConfig.TplPath, 'mock.ejs'); const tplMockMethodPath = path_1.default.join(this.apiConfig.TplPath, 'mock-method.ejs'); // 清理旧代码 (0, fs_2.removeDirSync)(this.apiConfig.OutPath); // 生成-基类 this.codeRender(tplBasePath, saveBaseDir, 'index.ts', { swaggerData: this.swaggerData, apiConfig: this.apiConfig }); // 生成-dto对象 this.codeRender(tplModelsPath, saveModelsDir, 'index.ts', { swaggerData: this.swaggerData, apiConfig: this.apiConfig }); // 生成-接口 const tags = this.getTags(); lodash_1.default.forEach(tags, (tag, tagName) => { this.codeRender(tplMethodPath, this.apiConfig.OutPath, this.apiConfig.FormatControllerName(tagName) + '.ts', { tag, swaggerData: this.swaggerData, apiConfig: this.apiConfig }); // 生成-Mock-接口 if (this.apiConfig.Mock) { this.codeRender(tplMockMethodPath, saveMockDir, this.apiConfig.FormatControllerName(tagName) + '.ts', { tag, swaggerData: this.swaggerData, apiConfig: this.apiConfig }); } }); // 生成-Mock if (this.apiConfig.Mock) { this.codeRender(tplMockPath, saveMockDir, 'index.ts', { swaggerData: this.swaggerData, apiConfig: this.apiConfig }); } } } exports.Swagger = Swagger;