集成swagger,自动生成文档

This commit is contained in:
cool 2023-11-18 20:42:16 +08:00
parent b2ed3f44d6
commit 9be0261a2b
14 changed files with 422 additions and 113 deletions

View File

@ -3,7 +3,7 @@ window.onload = function() {
// the following lines will be replaced by docker/configurator, when it runs in a docker-container
window.ui = SwaggerUIBundle({
url: "https://petstore.swagger.io/v2/swagger.json",
url: "/swagger/json",
dom_id: '#swagger-ui',
deepLinking: true,
presets: [

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -28,7 +28,7 @@ export default {
},
},
cool: {
// 实体与路径跟生成代码、前端请求、swagger文档相关 注意:线上不建议开启,以免保留敏感信息
// 实体与路径跟生成代码、前端请求、swagger文档相关 注意:线上不建议开启,以免暴露敏感信息
eps: true,
// 是否自动导入模块数据库
initDB: true,

View File

@ -1,10 +1,9 @@
import { CoolEvent, Event } from '@cool-midway/core';
import { App, Config, ILogger, Inject, Logger } from '@midwayjs/core';
import { App, Config, ILogger, Logger } from '@midwayjs/core';
import { IMidwayKoaApplication } from '@midwayjs/koa';
import * as fs from 'fs';
import * as path from 'path';
import { v1 as uuid } from 'uuid';
import { BaseSwaggerIndex } from '../swagger.ts';
/**
* jwt.secret
@ -20,12 +19,8 @@ export class BaseAppEvent {
@App()
app: IMidwayKoaApplication;
@Inject()
baseSwaggerIndex: BaseSwaggerIndex;
@Event('onServerReady')
async onServerReady() {
this.baseSwaggerIndex.init();
if (this.config.base.jwt.secret == 'cool-admin-xxxxxx') {
const filePath = path.join(
this.app.getBaseDir(),

View File

@ -1,84 +0,0 @@
import {
App,
ILogger,
IMidwayApplication,
Logger,
Provide,
Scope,
ScopeEnum,
} from '@midwayjs/core';
import * as path from 'path';
import * as fs from 'fs';
/**
* swagger
*/
@Provide()
@Scope(ScopeEnum.Singleton)
export class BaseSwaggerIndex {
@Logger()
coreLogger: ILogger;
@App()
app: IMidwayApplication;
basePath = '';
lockPath = '';
/**
*
*/
async init() {
this.basePath = this.app.getBaseDir();
this.lockPath = path.join(this.basePath, '..', 'lock');
//this.copyToPublic();
}
/**
* swagger静态资源包复制到public目录下
*/
async copyToPublic() {
const lockFile = path.join(this.lockPath, 'static.swagger.lock');
if (fs.existsSync(lockFile)) return;
// swagger静态资源包
const swaggerUiAssetPath = require('swagger-ui-dist').getAbsoluteFSPath();
console.log('swaggerUiAssetPath', swaggerUiAssetPath);
// 复制整个目录到public的swagger目录下 没有则创建
const publicPath = path.join(this.basePath, '..', 'public');
const swaggerPath = path.join(publicPath, 'swagger');
if (!fs.existsSync(swaggerPath)) {
fs.mkdirSync(swaggerPath);
}
this.copyDirectory(swaggerUiAssetPath, swaggerPath);
fs.writeFileSync(lockFile, 'static');
this.coreLogger.info(
'\x1B[36m [cool:module:base] midwayjs cool module base swagger static copy to public\x1B[0m'
);
}
/**
*
* @param src
* @param dest
*/
copyDirectory(src, dest) {
// 确保目标文件夹存在
fs.mkdirSync(dest, { recursive: true });
// 读取源文件夹中的所有文件和子文件夹
let entries = fs.readdirSync(src, { withFileTypes: true });
for (let entry of entries) {
let srcPath = path.join(src, entry.name);
let destPath = path.join(dest, entry.name);
// 递归复制子文件夹
if (entry.isDirectory()) {
this.copyDirectory(srcPath, destPath);
} else {
// 同步复制文件
fs.copyFileSync(srcPath, destPath);
}
}
}
}

View File

@ -0,0 +1,301 @@
import { CoolEps } from '@cool-midway/core';
import { Config, Inject, Provide, Scope, ScopeEnum } from '@midwayjs/core';
import * as _ from 'lodash';
/**
*
*/
@Provide()
@Scope(ScopeEnum.Singleton)
export class SwaggerBuilder {
@Config('module.swagger.base')
swaggerBase;
@Inject()
eps: CoolEps;
json = {};
@Config('cool.eps')
epsConfig: boolean;
/**
*
*/
async init() {
if (this.epsConfig) {
await this.build();
}
}
/**
*
*/
async build() {
const epsData = {
app: this.eps.app || [],
admin: this.eps.admin || [],
module: this.eps.module || {},
};
this.json = this.convertToSwagger(epsData);
}
/**
* Epss转换为Swagger
* @param dataJson
* @returns
*/
convertToSwagger(dataJson) {
const swagger = {
...this.swaggerBase,
paths: {},
tags: Object.keys(dataJson.module)
.filter(item => item != 'swagger')
.map(moduleKey => {
return {
key: moduleKey,
name: dataJson.module[moduleKey].name || '',
description: dataJson.module[moduleKey].description || '',
};
}),
};
// 添加组件
function addComponentSchemas(data) {
if (_.isEmpty(data.name)) return;
const schema = {
type: 'object',
properties: {},
required: [],
};
data.columns.forEach(column => {
const swaggerType = mapTypeToSwagger(column.type);
schema.properties[column.propertyName] = {
type: swaggerType,
description: column.comment,
};
if (!column.nullable) {
schema.required.push(column.propertyName);
}
});
swagger.components.schemas[data.name] = schema;
return data.name;
}
// 转换类型
function mapTypeToSwagger(type) {
const typeMapping = {
string: 'string',
number: 'number',
bigint: 'integer',
datetime: 'string', // assuming datetime is formatted as ISO8601 string
};
return typeMapping[type] || 'string';
}
// 添加请求体
function addRequest(path, schemas, data) {
if (path == '/info' || path == '/list' || path == '/page') {
if (path == '/info') {
data.parameters = [
{
name: 'id',
in: 'query',
description: 'ID',
required: true,
schema: {
type: 'integer',
},
},
];
} else {
data.requestBody = {
description: '动态请求体',
required: true,
content: {
'application/json': {
schema: {
type: 'object',
properties:
path == '/page'
? {
page: {
type: 'integer',
description: '第几页',
default: 1,
},
size: {
type: 'integer',
description: '每页大小',
default: 20,
},
}
: {},
},
},
},
};
}
data.responses = {
'200': {
description: '成功响应',
content: {
'application/json': {
schema: {
type: 'object',
properties: {
code: {
type: 'integer',
description: '状态码',
},
message: {
type: 'string',
description: '响应消息',
},
data: {
$ref: `#/components/schemas/${schemas}`,
},
},
},
},
},
},
};
}
if (path == '/add' || path == '/update') {
data.requestBody = {
description: schemas,
required: true,
content: {
'application/json': {
schema: {
$ref: `#/components/schemas/${schemas}`,
},
},
},
};
data.responses = {
'200': {
description: '成功响应',
content: {
'application/json': {
example: {
code: 1000,
message: 'success',
data: {
id: 6,
},
},
},
},
},
};
}
if (path == '/delete') {
data.requestBody = {
description: schemas,
required: true,
content: {
'application/json': {
schema: {
type: 'object',
properties: {
ids: {
type: 'array',
description: 'ID数组',
items: {
type: 'integer',
},
},
},
},
},
},
};
data.responses = {
'200': {
description: '成功响应',
content: {
'application/json': {
example: {
code: 1000,
message: 'success',
},
},
},
},
};
}
}
// 处理每个模块下的API接口
function processModuleApis(moduleApis, moduleName) {
moduleApis.forEach(module => {
const schemas = addComponentSchemas({
name: module.name,
columns: module.columns,
});
if (Array.isArray(module.api)) {
module.api.forEach(api => {
const fullPath = `${api.prefix == '/' ? '' : api.prefix}${
api.path
}`;
const method = api.method.toLowerCase();
if (!swagger.paths[fullPath]) {
swagger.paths[fullPath] = {};
}
swagger.paths[fullPath][method] = {
summary:
`${module.info.type.description || module.info.type.name}` +
api.summary || '',
security: api.ignoreToken
? []
: [
{
ApiKeyAuth: [],
},
],
tags: [moduleName || '其他'],
responses: schemas
? {
'200': {
description: 'Success response',
content: {
'application/json': {
schema: {
$ref: `#/components/schemas/${schemas}`,
},
},
},
},
}
: {},
};
addRequest(api.path, schemas, swagger.paths[fullPath][method]);
});
}
});
}
// 遍历app和admin中的所有模块
Object.keys(dataJson.app).forEach(moduleKey => {
if (Array.isArray(dataJson.app[moduleKey])) {
processModuleApis(
dataJson.app[moduleKey],
dataJson.module[moduleKey]?.name
);
}
});
Object.keys(dataJson.admin).forEach(moduleKey => {
if (Array.isArray(dataJson.admin[moduleKey])) {
processModuleApis(
dataJson.admin[moduleKey],
dataJson.module[moduleKey]?.name
);
}
});
return swagger;
}
}

View File

@ -0,0 +1,50 @@
import { ModuleConfig } from '@cool-midway/core';
/**
*
*/
export default ({ app }) => {
return {
// 模块名称
name: 'Swagger',
// 模块描述
description: '处理和生成swagger文档',
// 中间件,只对本模块有效
middlewares: [],
// 中间件,全局有效
globalMiddlewares: [],
// 模块加载顺序默认为0值越大越优先加载
order: 0,
// swagger基本配置
base: {
openapi: '3.1.0',
info: {
title: 'Cool Admin 在线API文档',
version: '7.x',
description: '本文档是由Cool Admin内部自动构建完成',
contact: {
name: '开发文档',
url: 'https://cool-js.com',
},
},
// 请求地址
servers: [
{
url: `http://127.0.0.1:${app?.getConfig('koa.port') || 8001}`,
description: '本地后台地址',
},
],
paths: {},
components: {
schemas: {},
securitySchemes: {
Auth: {
type: 'apiKey',
name: 'Authorization',
in: 'header',
},
},
},
},
} as ModuleConfig;
};

View File

@ -0,0 +1,35 @@
import { Config, Controller, Get, Inject } from '@midwayjs/decorator';
import { Context } from '@midwayjs/koa';
import { SwaggerBuilder } from '../builder';
import { BaseController } from '@cool-midway/core';
/**
*
*/
@Controller('/swagger')
export class SwaggerIndexController extends BaseController {
@Inject()
ctx: Context;
@Inject()
swaggerBuilder: SwaggerBuilder;
@Config('cool.eps')
epsConfig: boolean;
@Get('/', { summary: 'swagger界面' })
public async index() {
if (!this.epsConfig) {
return this.fail('Eps未开启');
}
await this.ctx.render('swagger', {});
}
@Get('/json', { summary: '获得Swagger JSON数据' })
public async json() {
if (!this.epsConfig) {
return this.fail('Eps未开启');
}
return this.swaggerBuilder.json;
}
}

View File

@ -0,0 +1,28 @@
import { CoolEvent, Event } from '@cool-midway/core';
import { App, ILogger, Inject, Logger } from '@midwayjs/core';
import { IMidwayKoaApplication } from '@midwayjs/koa';
import { SwaggerBuilder } from '../builder';
/**
* jwt.secret
*/
@CoolEvent()
export class SwaggerAppEvent {
@Logger()
coreLogger: ILogger;
@App()
app: IMidwayKoaApplication;
@Inject()
swaggerBuilder: SwaggerBuilder;
@Event('onServerReady')
async onServerReady() {
this.swaggerBuilder.init().then(() => {
this.coreLogger.info(
'\x1B[36m [cool:module:swagger] midwayjs cool module swagger build success\x1B[0m'
);
});
}
}

View File

@ -1,16 +0,0 @@
import { Controller, Get, Inject } from '@midwayjs/decorator';
import { Context } from '@midwayjs/koa';
/**
*
*/
@Controller('/')
export class WelcomeController {
@Inject()
ctx: Context;
@Get('/swagger', { summary: 'swagger界面' })
public async swagger() {
await this.ctx.render('swagger', {});
}
}