node-web-mvc
Version:
node spring mvc
303 lines (302 loc) • 14.2 kB
JavaScript
;
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
/**
* @module OpenApiModel
* @description 用于构建当前环境的openapi.json
*/
const path_1 = __importDefault(require("path"));
const fs_1 = __importDefault(require("fs"));
const schemas_1 = __importDefault(require("./schemas"));
const RequestMapping_1 = __importDefault(require("../../servlets/annotations/mapping/RequestMapping"));
const RuntimeAnnotation_1 = __importDefault(require("../../servlets/annotations/annotation/RuntimeAnnotation"));
const ApiImplicitParams_1 = __importDefault(require("../annotations/ApiImplicitParams"));
const ParamAnnotation_1 = __importDefault(require("../../servlets/annotations/params/ParamAnnotation"));
const ApiOperation_1 = __importDefault(require("../annotations/ApiOperation"));
const Controller_1 = __importDefault(require("../../servlets/annotations/Controller"));
const Api_1 = __importDefault(require("../annotations/Api"));
const ElementType_1 = __importDefault(require("../../servlets/annotations/annotation/ElementType"));
const ApiIgnore_1 = __importDefault(require("../annotations/ApiIgnore"));
const ServletResponse_1 = __importDefault(require("../../servlets/annotations/params/ServletResponse"));
const ServletRequest_1 = __importDefault(require("../../servlets/annotations/params/ServletRequest"));
const Javascript_1 = __importDefault(require("../../interface/Javascript"));
const HttpEntity_1 = __importDefault(require("../../servlets/models/HttpEntity"));
const metadata_1 = require("../../servlets/annotations/annotation/metadata");
const emptyOf = (v, defaultValue) => (v === null || v === undefined || v === '') ? defaultValue : v;
class OpenApiModel {
/**
* 驼峰命名转换成 - 符号链接
*/
clampToJoinName(name) {
const k = name.length;
let newName = '';
for (let i = 0; i < k; i++) {
const code = name.charCodeAt(i);
const isUpperCase = code >= 65 && code <= 90;
const joinChar = isUpperCase ? '-' : '';
newName = newName + joinChar + (name[i]).toLowerCase();
}
return newName.replace(/^-/, '');
}
/**
* 将 -转换成小驼峰命名
*/
toClamp(name) {
const segments = name.split('-');
return segments.map((m, i) => {
if (i === 0) {
return m[0].toLowerCase() + m.slice(1);
}
return m[0].toUpperCase() + m.slice(1);
}).join('');
}
createTags(annotation) {
var _a;
const apiAnno = (_a = RuntimeAnnotation_1.default.getClassAnnotation(annotation.ctor, Api_1.default)) === null || _a === void 0 ? void 0 : _a.nativeAnnotation;
const name = this.clampToJoinName(annotation.ctor.name);
return ((apiAnno === null || apiAnno === void 0 ? void 0 : apiAnno.tags) || [{
name: name,
description: (apiAnno === null || apiAnno === void 0 ? void 0 : apiAnno.description) || name,
externalDocs: apiAnno === null || apiAnno === void 0 ? void 0 : apiAnno.externalDocs,
}]);
}
readPkg(id) {
if (fs_1.default.existsSync(id)) {
return JSON.parse(fs_1.default.readFileSync(id).toString('utf-8'));
}
else {
return {};
}
}
/**
* 获取完整的swaager openapi.json
*/
build(contextPath) {
var _a, _b;
const operationIds = {};
const id = require.resolve(path_1.default.resolve('package.json'));
delete require.cache[id];
// TODO: low 如果要支持mjs场景这里需要考虑如何改造
// eslint-disable-next-line @typescript-eslint/no-var-requires
const pkg = this.readPkg(id);
const tags = [];
const paths = {};
const contributor = (pkg.contributors || [])[0] || {};
const controllers = RuntimeAnnotation_1.default.getAnnotations(Controller_1.default);
const definition = new schemas_1.default();
for (const controller of controllers) {
if (RuntimeAnnotation_1.default.hasClassAnnotation(controller.ctor, ApiIgnore_1.default)) {
// 如果忽略该控制器
continue;
}
// 创建tags
const controllerTags = this.createTags(controller);
const actions = RuntimeAnnotation_1.default.getAnnotations(RequestMapping_1.default, controller.ctor).filter((m) => m.elementType === ElementType_1.default.METHOD);
for (const action of actions) {
// 构建操作
this.buildOperation(paths, action, controllerTags, definition, operationIds);
}
tags.push(...controllerTags);
}
return {
info: {
'contact': {
email: contributor.email || pkg.author || '',
},
'license': (_b = (_a = pkg.licenses) === null || _a === void 0 ? void 0 : _a.map) === null || _b === void 0 ? void 0 : _b.call(_a, (k) => {
return {
name: k.type,
url: k.url,
};
}),
'title': pkg.name || '',
'version': pkg.version || '1.0.0',
'description': pkg.description || '',
},
tags: tags.sort((a, b) => {
if (a.name > b.name) {
return 1;
}
else if (a.name == b.name) {
return 0;
}
else {
return -1;
}
}),
paths: paths,
servers: [
{ url: contextPath },
],
components: {
schemas: definition.build(),
},
openapi: '3.0.1',
};
}
/**
* 创建api操作的所有paths
*/
buildOperation(paths, action, tags, definition, operationIds) {
var _a;
const apiOperation = (_a = RuntimeAnnotation_1.default.getMethodAnnotation(action.ctor, action.methodName, ApiOperation_1.default)) === null || _a === void 0 ? void 0 : _a.nativeAnnotation;
const mapping = RequestMapping_1.default.getMappingInfo(action.ctor, action.methodName);
if (!mapping) {
return;
}
const utags = (apiOperation === null || apiOperation === void 0 ? void 0 : apiOperation.tags) || tags.map((tag) => tag.name);
let operationId = action.methodName;
if (operationIds[operationId]) {
operationId = this.toClamp(`${utags[0]}-${operationId}`);
}
operationIds[action.methodName] = true;
const returnType = action.returnType;
const code = (apiOperation === null || apiOperation === void 0 ? void 0 : apiOperation.code) || '200';
const consumes = (apiOperation === null || apiOperation === void 0 ? void 0 : apiOperation.consumes) || mapping.consumes || [];
const parameters = this.buildOperationParameters(action, definition);
const operationDoc = {
deprecated: false,
operationId: operationId,
tags: utags,
summary: apiOperation === null || apiOperation === void 0 ? void 0 : apiOperation.value,
description: apiOperation === null || apiOperation === void 0 ? void 0 : apiOperation.notes,
parameters: parameters.filter((m) => this.isNotBodyParameter(m)),
requestBody: this.buildOperationConsumes(action, parameters, consumes),
responses: {
'201': { 'description': 'Created' },
'401': { 'description': 'Unauthorized' },
'403': { 'description': 'Forbidden' },
'404': { 'description': 'Not Found' },
[code]: {
'description': 'OK',
'content': this.buildOperationProduces(returnType, mapping, definition),
},
},
};
mapping.value.forEach((url) => {
Object.keys(mapping.method).forEach((method) => {
const path = (paths[url] = paths[url] || {});
path[method.toLowerCase()] = operationDoc;
});
});
}
isNotBodyParameter(m) {
const paramIn = m.in;
return !this.isMultipartFile(m.schema) && paramIn !== 'body' && paramIn !== 'part';
}
/**
* 构建api接口操作参数
* @param operation
*/
buildOperationParameters(action, definition) {
var _a;
const apiImplicitAnno = RuntimeAnnotation_1.default.getMethodAnnotation(action.ctor, action.methodName, ApiImplicitParams_1.default);
const parameters = ((_a = apiImplicitAnno === null || apiImplicitAnno === void 0 ? void 0 : apiImplicitAnno.nativeAnnotation) === null || _a === void 0 ? void 0 : _a.parameters) || [];
const parameterNames = action.parameters;
const finalParameters = parameterNames.map((name, i) => {
const parameter = (parameters.find((m) => m.name === name) || {});
const parameterAnno = RuntimeAnnotation_1.default.getMethodParamAnnotation(action.ctor, action.methodName, name, ParamAnnotation_1.default);
const isRequest = !!RuntimeAnnotation_1.default.getMethodParamAnnotation(action.ctor, action.methodName, name, ServletRequest_1.default);
const isResponse = !!RuntimeAnnotation_1.default.getMethodParamAnnotation(action.ctor, action.methodName, name, ServletResponse_1.default);
const parameter2 = parameterAnno === null || parameterAnno === void 0 ? void 0 : parameterAnno.nativeAnnotation;
const dataType = action.paramTypes[i];
if (isRequest || isResponse || Javascript_1.default.createTyper(dataType.clazz).isType(HttpEntity_1.default)) {
return;
}
const value = emptyOf(parameter.value, parameter2 === null || parameter2 === void 0 ? void 0 : parameter2.value) || emptyOf(parameter.name, parameter2 === null || parameter2 === void 0 ? void 0 : parameter2.value) || name;
const useType = parameter2 === null || parameter2 === void 0 ? void 0 : parameter2.paramAt;
return {
name: value || name,
required: emptyOf(parameter.required, parameter2 === null || parameter2 === void 0 ? void 0 : parameter2.required),
example: emptyOf(parameter.example, parameter2 === null || parameter2 === void 0 ? void 0 : parameter2.defaultValue),
description: parameter.description || undefined,
in: useType || 'query',
dataType: dataType,
type: '',
schema: {
$ref: null,
},
};
}).filter(Boolean);
return finalParameters.map((parameter) => {
var _a;
const info = parameter.example && (0, metadata_1.buildRuntimeType)((_a = parameter.example) === null || _a === void 0 ? void 0 : _a.constructor, null);
const typeInfo = definition.typemappings.make(parameter.dataType || info);
return {
name: parameter.name,
required: parameter.required,
description: parameter.description,
in: parameter.in,
example: emptyOf(parameter.example, undefined),
schema: typeInfo,
};
}).filter(Boolean);
}
isMultipartFile(schema) {
var _a;
const ref = schema === null || schema === void 0 ? void 0 : schema.$ref;
const type = schema;
if (!type) {
return false;
}
else if (ref) {
return ref.indexOf('MultipartFile') > -1;
}
else {
return type.type == 'file' || ((_a = type.items) === null || _a === void 0 ? void 0 : _a.type) == 'file';
}
}
buildOperationConsumes(action, parameters, consumes) {
const requestBody = { content: {} };
const body = parameters.find((m) => m.in == 'body');
const multiparts = parameters.filter((m) => this.isMultipartFile(m.schema) || m.in == 'part');
if (multiparts.length > 0) {
const meta = requestBody.content['multipart/form-data'] = {
schema: {
required: multiparts.filter((m) => m.required).map((m) => m.name),
properties: {},
type: 'object',
},
};
multiparts.forEach((m) => {
meta.schema.properties[m.name] = Object.assign({}, m.schema);
});
}
if ((consumes === null || consumes === void 0 ? void 0 : consumes.length) > 0) {
consumes.forEach((m) => {
if (requestBody.content[m])
return;
requestBody.content[m] = {
schema: {
type: 'string',
},
};
});
}
else if (body) {
requestBody.content['application/json'] = {
example: emptyOf(body.example, undefined),
schema: body.schema,
};
}
if (Object.keys(requestBody.content).length < 1) {
return undefined;
}
return requestBody;
}
buildOperationProduces(returnType, mapping, definition) {
var _a;
const produces = ((_a = mapping.produces) === null || _a === void 0 ? void 0 : _a.length) < 1 ? ['*/*'] : mapping.produces;
const contents = {};
produces.forEach((name) => {
const schema = returnType ? definition.typemappings.make(returnType) : undefined;
contents[name] = {
schema: schema || { type: 'string' },
};
});
return Object.keys(contents).length < 1 ? undefined : contents;
}
}
exports.default = OpenApiModel;