egg-swagger-doc-fixbug
Version:
472 lines (411 loc) • 12.8 kB
JavaScript
;
const path = require('path');
const fs = require('fs');
const contract = require('../contract/index');
const comment = require('../comment/index');
const _ = require('../constant/index');
/**
* swagger Document
*/
let DOCUMENT;
let FUNCTIONBUNDLER = [];
/**
* 构建Document
* @controller {tagName} {description} 接受swaggerDoc扫描,并命名该controller名字,默认为文件名
* @deprecated 未完成接口
* @summary {content} 接口标题
* @description {content} 接口描述
* @router {method} {routerkey} 接口请求地址
* @request {position} {type} {name} {description} 请求体
* @response {http_status} {type} {description} 响应体
* @consume {consume} 例 @consume application/json
* @produce {produce} 例 @consume application/json
* @ignore 实验性功能,自动生成路由时(需遵循一定规则),跳过指定非 API function扫描
*/
function buildDocument(app) {
// config
let swagger = app.config.swaggerdoc;
let securitys = [];
let tag_path = {
tags: [],
paths: {},
};
// 允许使用验证
if (swagger.enableSecurity) {
// 获取定义的安全验证名称
for (let security in swagger.securityDefinitions) {
securitys.push(security);
}
}
// 遍历contract,组装swagger.definitions
let definitions = contract.getDefinitions(app);
let filepath = path.join(app.config.baseDir, swagger.dirScanner);
// 递归获取 tags&paths
tag_path = getTag_Path(filepath, securitys, swagger, definitions);
// build document
DOCUMENT = {
host: '',
swagger: _.SWAGGERVERSION,
basePath: swagger.basePath,
info: swagger.apiInfo,
schemes: swagger.schemes,
tags: tag_path.tags,
paths: tag_path.paths,
securityDefinitions: swagger.securityDefinitions,
definitions,
};
return DOCUMENT;
}
function getTag_Path(fileDir, securitys, swagger, definitions) {
// 已存在tag集合
let tagNames = [];
let tags = [];
let paths = {};
const names = fs.readdirSync(fileDir);
for (let name of names) {
const filepath = path.join(fileDir, name);
const stat = fs.statSync(filepath);
if (stat.isDirectory()) {
const subPath = getTag_Path(filepath, securitys, swagger, definitions);
// 合并子目录的扫描结果
tags.concat(subPath.tags);
Object.assign(paths, subPath.paths);
continue;
}
if (stat.isFile() && ['.js', '.ts'].indexOf(path.extname(name)) !== -1) {
let blocks = comment.generateCommentBlocks(filepath);
// 如果第一个注释块不包含@controller不对该文件注释解析
if (!blocks || !hasController(blocks[0])) continue;
// 当前注释块集合的所属tag-group, 并添加至swagger.tags中
let controller = comment.getComment(blocks[0], _.CONTROLLER)[0];
let tagName = controller[1] ? controller[1] : name.split(/\.(js|ts)/)[0];
if (tagNames.includes(tagName)) {
tagName = tagName + '_' + tagNames.length;
}
tagNames.push(tagName);
tags.push({ name: tagName, description: controller[2] ? controller[2] : '' });
// 获取所有的有效方法
let func = generateAPIFunc(filepath);
let bundler = {
filePath: filepath,
routers: [],
};
let routerlist = [];
for (let i = 1; i < blocks.length; i++) {
if (isIgnore(blocks[i])) continue;
let direct = `${filepath.split(/\.(js|ts)/)[0].split('app')[1].substr(1)}`;
// 解析路由
let routers = comment.getComment(blocks[i], _.ROUTER);
if (routers) {
let path_method = {};
path_method.tags = [tagName];
path_method.summary = generateSummary(blocks[i]);
path_method.description = generateDescription(blocks[i]);
path_method.operationId = `${direct.replace(path.sep, '-')}-${func[i - 1]}`;
path_method.consumes = generateConsumes(blocks[i], swagger);
path_method.produces = generateProduces(blocks[i], swagger);
path_method.parameters = generateRequest(blocks[i], routers, definitions);
path_method.security = generateSecurity(blocks[i], securitys, swagger);
path_method.responses = generateResponse(blocks[i], routers, definitions);
path_method.deprecated = isDeprecated(blocks[i]);
if (!routerlist.includes(routers[0][2])) {
paths[routers[0][2]] = {};
}
routerlist.push(routers[0][2]);
paths[routers[0][2]][routers[0][1].toLowerCase()] = path_method;
// 绑定route和function
let contractName = getContractInBody(blocks[i], definitions);
let router = {
method: routers[0][1].toLowerCase(),
route: routers[0][2],
func: func[i - 1],
ruleName: contractName,
};
bundler.routers.push(router);
}
}
FUNCTIONBUNDLER.push(bundler);
}
}
return {
tags,
paths,
};
}
/**
* 判断是否包含@Controller标签
* @param {String} block 注释块
* @return {Boolean} 是否包含@Controller标签
*/
function hasController(block) {
return block.indexOf('@Controller') > -1 || block.indexOf('@controller') > -1;
}
/**
* 获取controller的方法, 按定义顺序
* @param {String} filepath controller文件地址
*/
function generateAPIFunc(filepath) {
let func = [];
let obj = require(filepath);
if (path.extname(filepath) === '.ts') {
obj = obj.default;
}
let instance = obj.prototype || obj;
func = Object.getOwnPropertyNames(instance).map(key => {
return key;
});
if (func[0] === 'constructor') {
func.shift();
}
return func;
}
/**
* 是否跳过该方法
* @param {string} block 注释块
*/
function isIgnore(block) {
return block.indexOf('@Ignore') > -1 || block.indexOf('@ignore') > -1;
}
/**
* 是否无效接口
* @param {string} block 注释块
*/
function isDeprecated(block) {
return block.indexOf('@Deprecated') > -1 || block.indexOf('@deprecated') > -1;
}
/**
* 解析安全验证
* @param {String} block 注释块
* @param {Array} securitys 设定的安全验证名称
* @param {Object} swagger swagger配置
*/
function generateSecurity(block, securitys, swagger) {
let securityDoc = [];
for (let security of securitys) {
if (block.indexOf(`@${security}`) > -1) {
let securityItem = {};
if (swagger.securityDefinitions[security].type === 'apiKey') {
securityItem[security] = [];
}
if (swagger.securityDefinitions[security].type === 'oauth2') {
securityItem[security] = [];
Object.keys(swagger.securityDefinitions[security].scopes).forEach(i => {
securityItem[security].push(i);
});
}
securityDoc.push(securityItem);
}
}
return securityDoc;
}
/**
* 获取api标题
* @param {String} block 注释块
*/
function generateSummary(block) {
let summary = '';
let summarys = comment.getComment(block, _.SUMMARY);
if (summarys) {
let m = 1;
while (summarys[0][m]) {
summary = summary + summarys[0][m] + ' ';
m++;
}
}
return summary;
}
/**
* 获取api接口描述
* @param {String} block 注释块
*/
function generateDescription(block) {
let description = '';
let descriptions = comment.getComment(block.replace(/^\s+\*\s+^/gm, '\n').replace(/^\s+\*\s*/gm, ''), _.DESCRIPTION);
if (descriptions) {
let m = 1;
while (descriptions[0][m]) {
description = description + descriptions[0][m] + ' ';
m++;
}
}
return description;
}
/**
* 获取请求的produces
* @param {String} block comment block
* @param {Object} swagger config of swagger
*/
function generateProduces(block, swagger) {
let produces = [];
let produceComments = comment.getComment(block, _.PRODUCE);
if (produceComments) {
for (let item of produceComments) {
for (let key in item) {
if (Number(key) === 0) continue;
produces.push(item[key]);
}
}
} else {
produces = swagger.produces;
}
return produces;
}
/**
* 获取请求的consumes
* @param {String} block comment block
* @param {Object} swagger config of swagger
*/
function generateConsumes(block, swagger) {
let consumes = [];
let consumeComments = comment.getComment(block, _.CONSUME);
if (consumeComments) {
for (let item of consumeComments) {
for (let key in item) {
if (Number(key) === 0) continue;
consumes.push(item[key]);
}
}
} else {
consumes = swagger.consumes;
}
return consumes;
}
/**
* 获取请求参数
* @param {String} block 注释块
* @param {Array} routers 路由列表
* @param {Object} definitions contract信息
*/
function generateRequest(block, routers, definitions) {
let parameters = [];
let requests = comment.getComment(block, _.REQUEST);
if (requests) {
for (let request of requests) {
let parameter = generateParameters(request, routers, definitions);
parameters.push(parameter);
}
}
return parameters;
}
/**
* 获取request in body
* @param {String} block comment
* @param {Object} definitions contract定义
*/
function getContractInBody(block, definitions) {
let requests = comment.getComment(block, _.REQUEST);
if (requests) {
for (let request of requests) {
if (request[1] === 'body' && definitions.hasOwnProperty(request[2])) {
return request[2];
}
}
}
}
/**
* 获取响应参数
* @param {String} block 注释块
* @param {Array} routers 路由列表
* @param {Object} definitions contract信息
*/
function generateResponse(block, routers, definitions) {
let responseDoc = {};
let responses = comment.getComment(block, _.RESPONSE);
if (responses) {
for (let response of responses) {
let res = {};
let schema = {};
if (response[2]) {
if (!definitions.hasOwnProperty(response[2])) {
throw new Error(`[egg-swagger-doc] error at ${routers[0][1].toLowerCase()}:${routers[0][2]} ,the type of response parameter does not exit`);
}
schema.$ref = `#/definitions/${response[2]}`;
res.schema = schema;
}
res.description = '';
if (response[3]) {
let m = 3;
while (response[m]) {
res.description = res.description + response[m] + ' ';
m++;
}
}
responseDoc[response[1]] = res;
}
} else {
responseDoc.default = { description: 'successful operation' };
}
return responseDoc;
}
/**
* 获取请求参数
* @param {String} request 包含@Request的注释行,以空格分割的得到的数组
* @param {Array} routers 路由信息
* @param {Object} definitions contract信息
*/
function generateParameters(request, routers, definitions) {
let parameter = {};
parameter.in = request[1];
if (parameter.in.toLowerCase() === 'body' && !_.itemType.includes(request[2])) {
let schema = {};
if (!request[2].startsWith('array')) {
if (!definitions.hasOwnProperty(request[2])) {
throw new Error(`[egg-swagger-doc] error at ${routers[0][1].toLowerCase()}:${routers[0][2]} ,the type of request parameter does not exit`);
}
schema.$ref = `#/definitions/${request[2]}`;
} else {
schema.type = 'array';
let ObjectType = ['boolean', 'integer', 'number', 'string'];
let items = {};
let itemsType = request[2].substring(6, request[2].length - 1);
if (ObjectType.includes(itemsType)) {
items.type = itemsType;
} else {
if (!definitions.hasOwnProperty(itemsType)) {
throw new Error(`[egg-swagger-doc] error at ${routers[0][1].toLowerCase()}:${routers[0][2]} ,the type of request parameter does not exit`);
}
items.$ref = `#/definitions/${itemsType}`;
}
schema.items = items;
}
parameter.schema = schema;
} else {
parameter.type = request[2];
}
if (request[3]) {
parameter.name = request[3].replace('*', '');
parameter.required = false;
if (request[3].indexOf('*') > -1 || parameter.in === 'path') {
parameter.required = true;
}
}
if (parameter.in.toLowerCase() === 'body') {
parameter.name = 'body';
parameter.required = true;
}
parameter.description = '';
let i = 4;
while (request[i]) {
if (request[i].indexOf('eg:') > -1) {
parameter.example = request[i].replace('eg:', '');
} else {
parameter.description = parameter.description + request[i] + ' ';
}
i++;
}
return parameter;
}
module.exports = {
documentInit: app => {
if (!DOCUMENT) {
buildDocument(app);
}
return DOCUMENT;
},
getFuncBundler: app => {
if (!FUNCTIONBUNDLER) {
buildDocument(app);
}
return FUNCTIONBUNDLER;
},
};