mirror of
https://github.com/cool-team-official/cool-admin-midway.git
synced 2024-11-01 22:20:30 +08:00
集成swagger,自动生成文档
This commit is contained in:
parent
b2ed3f44d6
commit
9be0261a2b
@ -3,7 +3,7 @@ window.onload = function() {
|
|||||||
|
|
||||||
// the following lines will be replaced by docker/configurator, when it runs in a docker-container
|
// the following lines will be replaced by docker/configurator, when it runs in a docker-container
|
||||||
window.ui = SwaggerUIBundle({
|
window.ui = SwaggerUIBundle({
|
||||||
url: "https://petstore.swagger.io/v2/swagger.json",
|
url: "/swagger/json",
|
||||||
dom_id: '#swagger-ui',
|
dom_id: '#swagger-ui',
|
||||||
deepLinking: true,
|
deepLinking: true,
|
||||||
presets: [
|
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
@ -28,7 +28,7 @@ export default {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
cool: {
|
cool: {
|
||||||
// 实体与路径,跟生成代码、前端请求、swagger文档相关 注意:线上不建议开启,以免保留敏感信息
|
// 实体与路径,跟生成代码、前端请求、swagger文档相关 注意:线上不建议开启,以免暴露敏感信息
|
||||||
eps: true,
|
eps: true,
|
||||||
// 是否自动导入模块数据库
|
// 是否自动导入模块数据库
|
||||||
initDB: true,
|
initDB: true,
|
||||||
|
@ -1,10 +1,9 @@
|
|||||||
import { CoolEvent, Event } from '@cool-midway/core';
|
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 { IMidwayKoaApplication } from '@midwayjs/koa';
|
||||||
import * as fs from 'fs';
|
import * as fs from 'fs';
|
||||||
import * as path from 'path';
|
import * as path from 'path';
|
||||||
import { v1 as uuid } from 'uuid';
|
import { v1 as uuid } from 'uuid';
|
||||||
import { BaseSwaggerIndex } from '../swagger.ts';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 修改jwt.secret
|
* 修改jwt.secret
|
||||||
@ -20,12 +19,8 @@ export class BaseAppEvent {
|
|||||||
@App()
|
@App()
|
||||||
app: IMidwayKoaApplication;
|
app: IMidwayKoaApplication;
|
||||||
|
|
||||||
@Inject()
|
|
||||||
baseSwaggerIndex: BaseSwaggerIndex;
|
|
||||||
|
|
||||||
@Event('onServerReady')
|
@Event('onServerReady')
|
||||||
async onServerReady() {
|
async onServerReady() {
|
||||||
this.baseSwaggerIndex.init();
|
|
||||||
if (this.config.base.jwt.secret == 'cool-admin-xxxxxx') {
|
if (this.config.base.jwt.secret == 'cool-admin-xxxxxx') {
|
||||||
const filePath = path.join(
|
const filePath = path.join(
|
||||||
this.app.getBaseDir(),
|
this.app.getBaseDir(),
|
||||||
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
301
src/modules/swagger/builder.ts
Normal file
301
src/modules/swagger/builder.ts
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
50
src/modules/swagger/config.ts
Normal file
50
src/modules/swagger/config.ts
Normal 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;
|
||||||
|
};
|
35
src/modules/swagger/controller/index.ts
Normal file
35
src/modules/swagger/controller/index.ts
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
28
src/modules/swagger/event/app.ts
Normal file
28
src/modules/swagger/event/app.ts
Normal 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'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
@ -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', {});
|
|
||||||
}
|
|
||||||
}
|
|
Loading…
Reference in New Issue
Block a user