@magicbe/api-generator
Version:
api 生成器
528 lines (527 loc) • 27.8 kB
JavaScript
"use strict";
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
var __generator = (this && this.__generator) || function (thisArg, body) {
var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g;
return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
function verb(n) { return function (v) { return step([n, v]); }; }
function step(op) {
if (f) throw new TypeError("Generator is already executing.");
while (g && (g = 0, op[0] && (_ = 0)), _) try {
if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
if (y = 0, t) op = [op[0] & 2, t.value];
switch (op[0]) {
case 0: case 1: t = op; break;
case 4: _.label++; return { value: op[1], done: false };
case 5: _.label++; y = op[1]; op = [0]; continue;
case 7: op = _.ops.pop(); _.trys.pop(); continue;
default:
if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
if (t[2]) _.ops.pop();
_.trys.pop(); continue;
}
op = body.call(thisArg, _);
} catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
}
};
var __spreadArray = (this && this.__spreadArray) || function (to, from, pack) {
if (pack || arguments.length === 2) for (var i = 0, l = from.length, ar; i < l; i++) {
if (ar || !(i in from)) {
if (!ar) ar = Array.prototype.slice.call(from, 0, i);
ar[i] = from[i];
}
}
return to.concat(ar || Array.prototype.slice.call(from));
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
var path_1 = __importDefault(require("path"));
var fs_1 = require("fs");
var lodash_1 = __importDefault(require("lodash"));
var prettier_1 = __importDefault(require("prettier"));
var GeneratorYapiV3 = /** @class */ (function () {
function GeneratorYapiV3(options) {
this.options = options;
}
GeneratorYapiV3.prototype.gen = function () {
return __awaiter(this, void 0, void 0, function () {
var _a, service, token, project, list_menu, res, res, _loop_1, this_1, _i, _b, menu;
var _c;
return __generator(this, function (_d) {
switch (_d.label) {
case 0:
_a = this.options, service = _a.service, token = _a.token;
return [4 /*yield*/, fetch("".concat(service, "/api/project/get?token=").concat(token))];
case 1:
res = _d.sent();
return [4 /*yield*/, res.json()];
case 2:
project = _d.sent();
return [4 /*yield*/, fetch("".concat(service, "/api/interface/list_menu?token=").concat(token, "&project_id=").concat(project.data._id))];
case 3:
res = _d.sent();
return [4 /*yield*/, res.json()];
case 4:
list_menu = _d.sent();
_loop_1 = function (menu) {
var file_name, text, _e, _f, item, details, res, parser, prettier_options, dir;
return __generator(this, function (_g) {
switch (_g.label) {
case 0:
if (!!((_c = this_1.options.cats) === null || _c === void 0 ? void 0 : _c.length)) {
if (!lodash_1.default.includes(this_1.options.cats, menu._id)) {
return [2 /*return*/, "continue"];
}
}
file_name = "gen_api_".concat(menu._id);
if (this_1.options.target === 'typescript') {
file_name = file_name.concat('.ts');
}
else {
file_name = file_name.concat('.js');
}
text = '';
// 生成文件头部
text += "import request from \"@/request\";\n\n";
text += "/**\n";
text += " * \u6587\u4EF6\u7531 gen-api \u81EA\u52A8\u751F\u6210\uFF0C\u5982\u6709\u7591\u95EE\uFF0C\u8BF7\u8054\u7CFB teacher-wang \n";
text += " * \u63A5\u53E3 [".concat(menu.name, "] ").concat(service, "/project/").concat(project.data._id, "/interface/api/cat_").concat(menu._id, "\n");
text += " */\n\n";
_e = 0, _f = menu.list;
_g.label = 1;
case 1:
if (!(_e < _f.length)) return [3 /*break*/, 5];
item = _f[_e];
details = void 0;
return [4 /*yield*/, fetch("".concat(service, "/api/interface/get?token=").concat(token, "&id=").concat(item._id))];
case 2:
res = _g.sent();
return [4 /*yield*/, res.json()];
case 3:
details = _g.sent();
if (this_1.options.target === 'typescript') {
text += this_1.genTypescript(project.data, menu, item, details.data);
}
else {
text += this_1.genJavascript(project.data, menu, item, details.data);
}
_g.label = 4;
case 4:
_e++;
return [3 /*break*/, 1];
case 5:
parser = 'babel';
if (this_1.options.target === 'typescript')
parser = 'babel-ts';
prettier_options = {
parser: parser,
singleQuote: true,
trailingComma: 'es5',
tabWidth: 4,
semi: true,
printWidth: 2000,
};
return [4 /*yield*/, prettier_1.default.format(text, prettier_options)];
case 6:
text = _g.sent();
return [4 /*yield*/, GeneratorYapiV3.mkDir(this_1.options.output)];
case 7:
dir = _g.sent();
(0, fs_1.writeFile)(path_1.default.join(dir, file_name), text, function (err) {
if (!err)
console.log("".concat(dir, "/").concat(file_name, " \u751F\u6210\u6210\u529F!"));
else
console.error("".concat(dir, "/").concat(file_name, " \u751F\u6210\u5931\u8D25!"));
});
return [2 /*return*/];
}
});
};
this_1 = this;
_i = 0, _b = list_menu.data;
_d.label = 5;
case 5:
if (!(_i < _b.length)) return [3 /*break*/, 8];
menu = _b[_i];
return [5 /*yield**/, _loop_1(menu)];
case 6:
_d.sent();
_d.label = 7;
case 7:
_i++;
return [3 /*break*/, 5];
case 8: return [2 /*return*/];
}
});
});
};
GeneratorYapiV3.prototype.genJavascript = function (project, menu, item, detail) {
var content = '';
// 生成函数名称
var functionName = this.getFunctionName(detail.title, detail.method, detail.path);
// 解析请求参数
var queryParams = this.parseQueryParams(detail.req_query);
var pathParams = this.parsePathParams(detail.req_params);
var bodyParamsResult = this.parseBodyParams(detail.req_body_other, detail.req_body_type, functionName);
var bodyParams = bodyParamsResult.params;
// 生成 JSDoc 注释
content += "/** \n";
content += " * ".concat(detail.title, " \n");
content += " * ".concat(this.options.service, "/project/").concat(project._id, "/interface/api/").concat(item._id, " \n");
content += " */\n";
// 生成函数
content += "export const ".concat(functionName, " = (");
var functionParams = [];
if (pathParams.length > 0) {
functionParams.push('params');
}
if (queryParams.length > 0) {
functionParams.push('query');
}
if (bodyParams.length > 0) {
functionParams.push('body');
}
content += functionParams.join(', ');
content += ") => request.".concat(detail.method.toLowerCase(), "(");
// 构建 URL
var url = this.buildUrl(detail.path, pathParams);
content += "`".concat(url, "`");
if (bodyParams.length > 0) {
content += ", body";
}
if (queryParams.length > 0) {
content += ", { params: query }";
}
content += ");\n\n";
return content;
};
GeneratorYapiV3.prototype.genTypescript = function (project, menu, item, detail) {
var content = '';
var allInterfaces = []; // 收集所有嵌套接口
// 生成函数名称和基础接口名称
var functionName = this.getFunctionName(detail.title, detail.method, detail.path);
var baseInterfaceName = functionName.charAt(0).toUpperCase() + functionName.slice(1);
// 解析请求参数
var queryParams = this.parseQueryParams(detail.req_query);
var pathParams = this.parsePathParams(detail.req_params);
var bodyParamsResult = this.parseBodyParams(detail.req_body_other, detail.req_body_type, baseInterfaceName);
var bodyParams = bodyParamsResult.params;
var bodyInterfaces = bodyParamsResult.interfaces;
// 收集所有嵌套接口
allInterfaces.push.apply(allInterfaces, bodyInterfaces);
// 生成请求参数接口
if (queryParams.length > 0) {
var queryInterfaceName = this.getInterfaceName(baseInterfaceName, 'Query');
content += "/** ".concat(detail.title, " \u8BF7\u6C42\u53C2\u6570 */\n");
content += "export interface ".concat(queryInterfaceName, " {\n");
queryParams.forEach(function (param) {
content += "\t".concat(param.name).concat(param.required ? '' : '?', ": ").concat(param.type, ";\n");
});
content += "}\n\n";
}
if (pathParams.length > 0) {
var paramsInterfaceName = this.getInterfaceName(baseInterfaceName, 'Params');
content += "/** ".concat(detail.title, " \u8BF7\u6C42\u8DEF\u5F84\u53C2\u6570 */\n");
content += "export interface ".concat(paramsInterfaceName, " {\n");
pathParams.forEach(function (param) {
content += "\t/** ".concat(param.desc, " */\n");
content += "\t".concat(param.name, ": ").concat(param.type, ";\n");
});
content += "}\n\n";
}
// 解析响应体
var responseInterface = this.parseResponseBody(detail.res_body, detail.res_body_type, baseInterfaceName);
if (responseInterface) {
// 收集响应体的嵌套接口
allInterfaces.push.apply(allInterfaces, responseInterface.interfaces);
}
// 首先生成所有嵌套接口
if (allInterfaces.length > 0) {
content += allInterfaces.join('');
}
// 然后生成主接口
if (bodyParams.length > 0) {
var bodyInterfaceName = this.getInterfaceName(baseInterfaceName, 'Body');
content += "/** ".concat(detail.title, " \u8BF7\u6C42\u4E3B\u4F53 */\n");
content += "export interface ".concat(bodyInterfaceName, " {\n");
bodyParams.forEach(function (param) {
if (param.desc) {
content += "\t/** ".concat(param.desc, " */\n");
}
content += "\t".concat(param.name).concat(param.required ? '' : '?', ": ").concat(param.type, ";\n");
});
content += "}\n\n";
}
if (responseInterface) {
content += "/** ".concat(detail.title, " \u54CD\u5E94\u4E3B\u4F53 */\n");
content += "export interface ".concat(responseInterface.name, " {\n");
responseInterface.properties.forEach(function (prop) {
content += "\t".concat(prop.name).concat(prop.required ? '' : '?', ": ").concat(prop.type, ";\n");
});
content += "}\n\n";
}
// 生成请求函数
var url = this.buildUrl(detail.path, pathParams);
content += "/** \n";
content += " * ".concat(detail.title, " \n");
content += " * ".concat(this.options.service, "/project/").concat(project._id, "/interface/api/").concat(item._id, " \n");
content += " */\n";
content += "export const ".concat(functionName, " = (");
var functionParams = [];
if (pathParams.length > 0) {
functionParams.push("params: ".concat(this.getInterfaceName(baseInterfaceName, 'Params')));
}
if (queryParams.length > 0) {
functionParams.push("query: ".concat(this.getInterfaceName(baseInterfaceName, 'Query')));
}
if (bodyParams.length > 0) {
functionParams.push("body: ".concat(this.getInterfaceName(baseInterfaceName, 'Body')));
}
content += functionParams.join(', ');
content += ") => request.".concat(detail.method.toLowerCase(), "<").concat((responseInterface === null || responseInterface === void 0 ? void 0 : responseInterface.name) || 'any', ">(`").concat(url, "`");
if (bodyParams.length > 0) {
content += ", body";
}
if (queryParams.length > 0) {
content += ", { params: query }";
}
content += ");\n\n";
return content;
};
GeneratorYapiV3.prototype.parseQueryParams = function (reqQuery) {
return reqQuery.map(function (param) { return ({
name: param.name,
type: 'string', // 默认类型,可以根据实际需要调整
required: param.required === '1',
desc: param.desc
}); });
};
GeneratorYapiV3.prototype.parsePathParams = function (reqParams) {
return reqParams.map(function (param) { return ({
name: param.name,
type: 'string', // 默认类型,可以根据实际需要调整
required: param.required === '1',
desc: param.desc
}); });
};
GeneratorYapiV3.prototype.parseBodyParams = function (reqBodyOther, reqBodyType, functionName) {
var _this = this;
if (!reqBodyOther || reqBodyType !== 'json') {
return { params: [], interfaces: [] };
}
try {
var bodySchema_1 = JSON.parse(reqBodyOther);
var interfaces_1 = [];
if (bodySchema_1.properties) {
// 生成请求主体接口名称
var bodyInterfaceName_1 = this.getInterfaceName(functionName, 'Body');
var params = Object.entries(bodySchema_1.properties).map(function (_a) {
var _b;
var name = _a[0], prop = _a[1];
// 使用请求主体接口名称作为父级接口名称
var typeInfo = _this.getTypeFromSchema(prop, bodyInterfaceName_1, interfaces_1, name);
return {
name: name,
type: typeInfo.type,
required: ((_b = bodySchema_1.required) === null || _b === void 0 ? void 0 : _b.includes(name)) || false,
desc: prop.description || ''
};
});
return { params: params, interfaces: interfaces_1 };
}
}
catch (e) {
// 解析失败,返回空数组
}
return { params: [], interfaces: [] };
};
GeneratorYapiV3.prototype.parseResponseBody = function (resBody, resBodyType, functionName) {
var _this = this;
if (!resBody || resBodyType !== 'json') {
return null;
}
try {
var responseSchema_1 = JSON.parse(resBody);
var interfaces_2 = [];
if (responseSchema_1.properties) {
// 先确定响应接口的名称
var responseInterfaceName_1 = this.getInterfaceName(functionName, 'Response');
var properties = Object.entries(responseSchema_1.properties).map(function (_a) {
var _b;
var name = _a[0], prop = _a[1];
// 对于响应体的第一层属性,使用响应接口名称作为父级接口名称
var typeInfo = _this.getTypeFromSchema(prop, responseInterfaceName_1, interfaces_2, name);
return {
name: name,
type: typeInfo.type,
required: ((_b = responseSchema_1.required) === null || _b === void 0 ? void 0 : _b.includes(name)) || false
};
});
return {
name: responseInterfaceName_1,
properties: properties,
interfaces: interfaces_2
};
}
}
catch (e) {
// 解析失败,返回 null
console.error(e);
}
return null;
};
GeneratorYapiV3.prototype.getTypeFromSchema = function (prop, parentInterfaceName, interfaces, keyName) {
if (prop.type === 'string')
return { type: 'string', interfaces: [] };
if (prop.type === 'number')
return { type: 'number', interfaces: [] };
if (prop.type === 'boolean')
return { type: 'boolean', interfaces: [] };
if (prop.type === 'integer')
return { type: 'number', interfaces: [] };
if (prop.type === 'array') {
if (prop.items) {
var itemTypeInfo = this.getTypeFromSchema(prop.items, parentInterfaceName, interfaces, keyName);
return {
type: "".concat(itemTypeInfo.type, "[]"),
interfaces: __spreadArray(__spreadArray([], interfaces, true), itemTypeInfo.interfaces, true)
};
}
return { type: 'any[]', interfaces: [] };
}
if (prop.type === 'object') {
if (prop.properties) {
// 生成嵌套接口,优先使用key名称
var interfaceName = this.generateNestedInterfaceName(parentInterfaceName, prop, keyName);
// 生成嵌套接口时,使用新生成的接口名称作为父接口名称,用于进一步的嵌套
var nestedInterfaces = this.generateNestedInterface(interfaceName, prop, interfaceName);
// 将嵌套接口添加到传入的interfaces数组中
interfaces.push.apply(interfaces, nestedInterfaces);
return {
type: interfaceName,
interfaces: interfaces
};
}
return { type: 'any', interfaces: [] };
}
return { type: 'any', interfaces: [] };
};
GeneratorYapiV3.prototype.generateNestedInterfaceName = function (parentInterfaceName, prop, keyName) {
// 优先使用key名称,如果没有则使用父级接口名称+后缀
if (keyName) {
// 将key名称转换为驼峰命名(处理下划线、连字符等)
var camelCaseKeyName = keyName.replace(/[-_.]/g, ' ')
.split(' ')
.filter(function (word) { return word.length > 0; })
.map(function (word) { return word.charAt(0).toUpperCase() + word.slice(1).toLowerCase(); })
.join('');
// 使用父级接口名称 + 驼峰命名后的key名称
return "".concat(parentInterfaceName).concat(camelCaseKeyName);
}
// 如果没有key名称,使用父级接口名称+后缀
var timestamp = Date.now().toString().slice(-6);
return "".concat(parentInterfaceName, "Nested").concat(timestamp);
};
GeneratorYapiV3.prototype.generateNestedInterface = function (interfaceName, prop, parentInterfaceName) {
var _this = this;
var interfaces = [];
var interfaceContent = "export interface ".concat(interfaceName, " {\n");
if (prop.properties) {
Object.entries(prop.properties).forEach(function (_a) {
var _b;
var name = _a[0], nestedProp = _a[1];
var typeInfo = _this.getTypeFromSchema(nestedProp, parentInterfaceName, interfaces, name);
var required = ((_b = prop.required) === null || _b === void 0 ? void 0 : _b.includes(name)) || false;
var desc = nestedProp.description || '';
// 处理属性名,如果以数字开头,则使用引号包裹
var validPropertyName = /^[0-9]/.test(name) ? "\"".concat(name, "\"") : name;
if (desc) {
interfaceContent += "\t/** ".concat(desc, " */\n");
}
interfaceContent += "\t".concat(validPropertyName).concat(required ? '' : '?', ": ").concat(typeInfo.type, ";\n");
});
}
interfaceContent += "}\n\n";
interfaces.push(interfaceContent);
return interfaces;
};
GeneratorYapiV3.prototype.getInterfaceName = function (parentInterfaceName, suffix) {
// 基于父级接口名称生成子接口名称
return "".concat(parentInterfaceName).concat(suffix);
};
GeneratorYapiV3.prototype.getFunctionName = function (title, method, path) {
var methodPrefix = method.toLowerCase();
// 将路径转换为驼峰命名,处理空格、连字符、下划线、点号等
var pathParts = path.split('/').filter(function (part) { return part && !part.startsWith('{'); });
var pathCamelCase = pathParts.map(function (part) {
// 替换连字符、下划线、点号为空格,然后转换为驼峰命名
return part.replace(/[-_.]/g, ' ')
.split(' ')
.filter(function (word) { return word.length > 0; })
.map(function (word) { return word.charAt(0).toUpperCase() + word.slice(1).toLowerCase(); })
.join('');
}).join('');
// 提取路径参数名并转换为驼峰命名
var pathParams = path.split('/')
.filter(function (part) { return part.startsWith('{') && part.endsWith('}'); })
.map(function (part) {
var paramName = part.slice(1, -1); // 移除 { 和 }
// 将参数名转换为驼峰命名(处理下划线等)
return paramName.replace(/[-_.]/g, ' ')
.split(' ')
.filter(function (word) { return word.length > 0; })
.map(function (word) { return word.charAt(0).toUpperCase() + word.slice(1).toLowerCase(); })
.join('');
});
// 将路径参数名添加到函数名末尾
var pathParamSuffix = pathParams.join('');
return "".concat(methodPrefix).concat(pathCamelCase).concat(pathParamSuffix);
};
GeneratorYapiV3.prototype.buildUrl = function (path, pathParams) {
var url = path;
pathParams.forEach(function (param) {
url = url.replace("{".concat(param.name, "}"), "${params.".concat(param.name, "}"));
});
return url;
};
/**
* 创建目录
*/
GeneratorYapiV3.mkDir = function (output, _path_) {
return new Promise(function (resolve, reject) {
var dir = path_1.default.join.apply(path_1.default, lodash_1.default.compact([output, _path_]));
(0, fs_1.access)(dir, fs_1.constants.F_OK, function (err) {
if (err) {
(0, fs_1.mkdir)(dir, { recursive: true }, function (err) {
if (err) {
reject(err);
console.error(err);
}
else {
resolve(dir);
console.log("".concat(output, " \u521B\u5EFA\u76EE\u5F55\u6210\u529F"));
}
});
}
else {
resolve(dir);
}
});
});
};
return GeneratorYapiV3;
}());
exports.default = GeneratorYapiV3;