优化
This commit is contained in:
29
server/packages/swagger-jsdoc-generator/package.json
Normal file
29
server/packages/swagger-jsdoc-generator/package.json
Normal file
@@ -0,0 +1,29 @@
|
||||
{
|
||||
"name": "tailchat-service-swagger-generator",
|
||||
"private": true,
|
||||
"version": "1.0.0",
|
||||
"description": "",
|
||||
"main": "index.js",
|
||||
"bin": "./dist/index.js",
|
||||
"scripts": {
|
||||
"dev": "tsc --watch",
|
||||
"prepare": "tsc",
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
},
|
||||
"keywords": [
|
||||
"msgbyte",
|
||||
"moonrailgun",
|
||||
"tailchat"
|
||||
],
|
||||
"author": "moonrailgun <moonrailgun@gmail.com>",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"globby": "11.1.0",
|
||||
"ts-morph": "^16.0.0",
|
||||
"ts-node": "^10.9.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^18.11.18",
|
||||
"typescript": "^4.9.4"
|
||||
}
|
||||
}
|
||||
31
server/packages/swagger-jsdoc-generator/src/index.ts
Normal file
31
server/packages/swagger-jsdoc-generator/src/index.ts
Normal file
@@ -0,0 +1,31 @@
|
||||
import { Project } from 'ts-morph';
|
||||
import path from 'path';
|
||||
import globby from 'globby';
|
||||
import { processService } from './processService';
|
||||
|
||||
/**
|
||||
* https://ts-morph.com/setup/
|
||||
*/
|
||||
|
||||
/**
|
||||
* 扫描服务
|
||||
*/
|
||||
async function scanServices() {
|
||||
const serviceFiles = await globby('./services/**/*.service.ts');
|
||||
|
||||
console.time('parse project usage');
|
||||
const project = new Project({
|
||||
tsConfigFilePath: path.resolve(process.cwd(), './tsconfig.json'),
|
||||
});
|
||||
console.timeEnd('parse project usage');
|
||||
|
||||
console.time('parse source usage');
|
||||
|
||||
// 单个测试
|
||||
const sourceFile = project.getSourceFileOrThrow(serviceFiles[0]);
|
||||
processService(sourceFile);
|
||||
|
||||
console.timeEnd('parse source usage');
|
||||
}
|
||||
|
||||
scanServices();
|
||||
205
server/packages/swagger-jsdoc-generator/src/processService.ts
Normal file
205
server/packages/swagger-jsdoc-generator/src/processService.ts
Normal file
@@ -0,0 +1,205 @@
|
||||
import { SourceFile, SyntaxKind, TypeReferenceNode } from 'ts-morph';
|
||||
import { getMethodParameters } from './utils';
|
||||
|
||||
/**
|
||||
* 解析并处理 service 文件, 把所有的 Action 都自动加上jsdoc
|
||||
*/
|
||||
export async function processService(sourceFile: SourceFile) {
|
||||
const serviceNameDeclaration = sourceFile
|
||||
.getDescendantsOfKind(SyntaxKind.GetAccessor)
|
||||
.find((item) => item.getSymbol().getName() === 'serviceName');
|
||||
|
||||
if (!serviceNameDeclaration) {
|
||||
// 没有定义 serviceName, 不是一个正确的服务,跳过
|
||||
return;
|
||||
}
|
||||
|
||||
// 目前只先视为一个文件只有一个service,不考虑多个
|
||||
const serviceName = serviceNameDeclaration
|
||||
.getFirstDescendantByKind(SyntaxKind.ReturnStatement)
|
||||
.getExpression()
|
||||
.asKindOrThrow(SyntaxKind.StringLiteral)
|
||||
.getLiteralText();
|
||||
|
||||
console.log('process service:', serviceName);
|
||||
|
||||
const actions = findActionMethods(sourceFile);
|
||||
|
||||
for (const action of actions) {
|
||||
const jsdocs = action.getJsDocs();
|
||||
const len = jsdocs.length;
|
||||
const lastJsDoc = jsdocs[len - 1]; // 最后一条记录
|
||||
if (lastJsDoc && lastJsDoc.getText().includes('@swagger')) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const actionName = action.getSymbol().getEscapedName();
|
||||
|
||||
const requestTypeReferenceNode = action
|
||||
.getFirstChildByKind(SyntaxKind.Parameter)
|
||||
.getFirstChildByKind(SyntaxKind.TypeReference);
|
||||
|
||||
const text = generateOpenapiSchemaText(
|
||||
serviceName,
|
||||
actionName,
|
||||
lastJsDoc?.getCommentText().replaceAll('\n', ' ') ?? '',
|
||||
getPropertySignatureToSwagger(requestTypeReferenceNode)
|
||||
);
|
||||
|
||||
if (lastJsDoc) {
|
||||
// 移除以前的注释
|
||||
lastJsDoc.remove();
|
||||
}
|
||||
|
||||
// 将之前的描述填写到里面
|
||||
action.addJsDoc(text);
|
||||
}
|
||||
|
||||
await sourceFile.save(); // 将改动保存到原文件中
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取Action方法列表
|
||||
*/
|
||||
function findActionMethods(sourceFile: SourceFile) {
|
||||
const actions = sourceFile
|
||||
.getDescendantsOfKind(SyntaxKind.MethodDeclaration)
|
||||
.filter((item) => {
|
||||
const parameters = getMethodParameters(item);
|
||||
if (
|
||||
parameters.length === 1 &&
|
||||
['TcPureContext', 'TcContext'].includes(
|
||||
parameters[0].getType().getSymbol().getEscapedName()
|
||||
)
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
});
|
||||
|
||||
return actions;
|
||||
}
|
||||
|
||||
interface SwaggerParamType {
|
||||
name: string;
|
||||
type: 'string' | 'integer' | 'string[]' | 'integer[]'; // https://graphql-faas.github.io/OpenAPI-Specification/versions/2.0.html#data-types
|
||||
}
|
||||
|
||||
function generateOpenapiSchemaText(
|
||||
serviceName: string,
|
||||
actionName: string,
|
||||
description: string,
|
||||
requestParams: SwaggerParamType[],
|
||||
responseParams?: 'boolean' | SwaggerParamType[]
|
||||
) {
|
||||
actionName = actionName.replaceAll('.', '/');
|
||||
|
||||
const responseData = responseParams
|
||||
? responseParams === 'boolean'
|
||||
? `data:
|
||||
type: boolean`
|
||||
: `data:
|
||||
type: object
|
||||
properties:
|
||||
${generateProperties(responseParams, 4)}`
|
||||
: '';
|
||||
|
||||
return `@swagger
|
||||
|
||||
/api/${actionName}/${serviceName}:
|
||||
post:
|
||||
tags:
|
||||
- ${actionName}
|
||||
description: ${description}
|
||||
requestBody:
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
${generateProperties(requestParams, 14)}
|
||||
responses:
|
||||
200:
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
code:
|
||||
type: integer
|
||||
example: 200
|
||||
message:
|
||||
type: string
|
||||
example: ok
|
||||
${paddingWithIndent(responseData, 16)}
|
||||
`;
|
||||
}
|
||||
|
||||
function generateProperties(
|
||||
params: SwaggerParamType[],
|
||||
indent: number
|
||||
): string {
|
||||
return paddingWithIndent(
|
||||
params
|
||||
.map((p) => {
|
||||
if (p.type.endsWith('[]')) {
|
||||
const t = p.type.substring(0, p.type.length - 2);
|
||||
return `${p.name}:
|
||||
type: array
|
||||
items:
|
||||
type: ${t}`;
|
||||
} else {
|
||||
return `${p.name}:
|
||||
type: ${p.type}`;
|
||||
}
|
||||
})
|
||||
.join('\n'),
|
||||
indent
|
||||
);
|
||||
}
|
||||
|
||||
function paddingWithIndent(text: string, indent: number) {
|
||||
let indentText = '';
|
||||
Array.from({ length: indent }).forEach(() => {
|
||||
indentText += ' ';
|
||||
});
|
||||
|
||||
return text.split('\n').join('\n' + indentText);
|
||||
}
|
||||
|
||||
/**
|
||||
* 将ts的类型转换为swagger可以认识的类型
|
||||
*/
|
||||
function getPropertySignatureToSwagger(
|
||||
typeReferenceNode: TypeReferenceNode
|
||||
): SwaggerParamType[] {
|
||||
if (!typeReferenceNode) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return typeReferenceNode
|
||||
.getDescendantsOfKind(SyntaxKind.PropertySignature)
|
||||
.map((item) => {
|
||||
const name = item.getName();
|
||||
const type = item.getType();
|
||||
|
||||
let typeText: SwaggerParamType['type'] = 'string';
|
||||
if (type.isArray()) {
|
||||
typeText = 'string[]';
|
||||
|
||||
if (type.isNumber()) {
|
||||
typeText = 'integer[]';
|
||||
}
|
||||
} else {
|
||||
if (type.isNumber()) {
|
||||
typeText = 'integer';
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
name,
|
||||
type: typeText,
|
||||
};
|
||||
});
|
||||
}
|
||||
10
server/packages/swagger-jsdoc-generator/src/utils.ts
Normal file
10
server/packages/swagger-jsdoc-generator/src/utils.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
import { MethodDeclaration, ParameterDeclaration, SyntaxKind } from 'ts-morph';
|
||||
|
||||
/**
|
||||
* 获取函数参数列表
|
||||
*/
|
||||
export function getMethodParameters(
|
||||
methodDeclaration: MethodDeclaration
|
||||
): ParameterDeclaration[] {
|
||||
return methodDeclaration.getChildrenOfKind(SyntaxKind.Parameter);
|
||||
}
|
||||
11
server/packages/swagger-jsdoc-generator/tsconfig.json
Normal file
11
server/packages/swagger-jsdoc-generator/tsconfig.json
Normal file
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"extends": "../../tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"declaration": true,
|
||||
"rootDir": "./src",
|
||||
"outDir": "./dist",
|
||||
"typeRoots": ["./node_modules/@types"],
|
||||
},
|
||||
"include": ["./src/**/*"],
|
||||
"exclude": ["./node_modules/**/*", "./dist/**/*"]
|
||||
}
|
||||
Reference in New Issue
Block a user