说明内容支持markdown格式,文件名、方法名、注释优化

This commit is contained in:
暮光:城中城
2021-11-23 22:58:10 +08:00
parent b32c9a8f9e
commit f9fb9c2f70
10 changed files with 209 additions and 72 deletions

View File

@@ -8,7 +8,7 @@
"version": "0.0.0",
"dependencies": {
"@ant-design/icons-vue": "^6.0.1",
"ant-design-vue": "^3.0.0-alpha.8",
"ant-design-vue": "^3.0.0-alpha.12",
"axios": "^0.19.2",
"brace": "^0.11.1",
"highlight.js": "^11.3.1",
@@ -258,10 +258,9 @@
}
},
"node_modules/ant-design-vue": {
"version": "3.0.0-alpha.8",
"resolved": "https://registry.npmmirror.com/ant-design-vue/download/ant-design-vue-3.0.0-alpha.8.tgz",
"integrity": "sha1-ZYFtH0S4PpkntHAQWjDTH56SyRo=",
"license": "MIT",
"version": "3.0.0-alpha.12",
"resolved": "https://r.cnpmjs.org/ant-design-vue/download/ant-design-vue-3.0.0-alpha.12.tgz",
"integrity": "sha512-kwmfnK2hC5NZW5QbkdJ7bzogXWlt9YvKTU3bw53OP4BBUep2OcSbxwR8txWLQ3A9HFeb4O1oeWkSh70LoDvpZg==",
"dependencies": {
"@ant-design/colors": "^6.0.0",
"@ant-design/icons-vue": "^6.0.0",
@@ -2494,9 +2493,9 @@
}
},
"ant-design-vue": {
"version": "3.0.0-alpha.8",
"resolved": "https://registry.npmmirror.com/ant-design-vue/download/ant-design-vue-3.0.0-alpha.8.tgz",
"integrity": "sha1-ZYFtH0S4PpkntHAQWjDTH56SyRo=",
"version": "3.0.0-alpha.12",
"resolved": "https://r.cnpmjs.org/ant-design-vue/download/ant-design-vue-3.0.0-alpha.12.tgz",
"integrity": "sha512-kwmfnK2hC5NZW5QbkdJ7bzogXWlt9YvKTU3bw53OP4BBUep2OcSbxwR8txWLQ3A9HFeb4O1oeWkSh70LoDvpZg==",
"requires": {
"@ant-design/colors": "^6.0.0",
"@ant-design/icons-vue": "^6.0.0",

View File

@@ -8,7 +8,7 @@
},
"dependencies": {
"@ant-design/icons-vue": "^6.0.1",
"ant-design-vue": "^3.0.0-alpha.8",
"ant-design-vue": "^3.0.0-alpha.12",
"axios": "^0.19.2",
"brace": "^0.11.1",
"highlight.js": "^11.3.1",

View File

@@ -0,0 +1,9 @@
import logUtil from '../utils/logUtil.js';
/**
* openApi格式的参数解析
* @author 暮光:城中城
* @since 2017年5月7日
*/
export default {}

View File

@@ -1,12 +1,33 @@
import {message} from 'ant-design-vue';
import logUtil from '../utils/logUtil.js';
// 无需特殊处理的参数类型
let notNeedHandleTypeArr = ['file', 'string', 'integer', 'long', 'double', 'object', 'number', 'boolean', 'ref'];
/**
* 参数解析
* swagger格式的参数解析参数解析
* @author 暮光城中城
* @since 2017年5月7日
*/
export default {
/**
* 解析请求的参数
* @param parameters swagger.parameters
* @param definitionsDataMap 解析的path里对应的数据map{url + "." + method: swagger.paths.post}
* @returns [] 参数列表[{
* type: '',
* key: '',
* in: '',
* name: '',
* subType: '',
* required: '否',
* format: '',
* enum: '',
* example: '',
* collectionFormat: 'multi',
* description: '',
* additional: '',
* children: [],
* }]
*/
getRequestParamList(parameters, definitionsDataMap) {
if (!parameters) {
return [];
@@ -23,7 +44,7 @@ export default {
let children = undefined;
let additional = undefined;
if (type === 'array') {
// 解析parameter.items.$ref 或 parameter.items.originalRef {$ref: "#/definitions/Model", originalRef: "Model"}
// 解析parameter.items.$ref 或 parameter.items.$ref {$ref: "#/definitions/Model"}
// 解析parameter.items.type {type: 'file'}
if (this.isSchemaRef(parameter.items)) {
subType = this.getSchemaRef(parameter.items);
@@ -38,12 +59,12 @@ export default {
} else if (parameter.items && parameter.items.type) {
subType = parameter.items.type;
} else {
this.logMessage('001', type, parameter);
logUtil.logMessage('001', type, parameter);
}
} else if (!type) {
if (parameter.schema) {
if (this.isSchemaRef(parameter.schema)) {
// 解析parameter.schema {originalRef: "Model", $ref: "#/definitions/Model"}
// 解析parameter.schema {$ref: "#/definitions/Model"}
type = this.getSchemaRef(parameter.schema);
children = this.getParamDefinitions(type, definitionsDataMap, indexKey, {}, 0);
} else if (parameter.schema.type) {
@@ -59,13 +80,13 @@ export default {
} else if (parameter.schema.items.type) {
subType = parameter.schema.items.type;
} else {
this.log('0014', type, parameter);
logUtil.log('0014', type, parameter);
}
} else {
this.log('0011', type, parameter);
logUtil.log('0011', type, parameter);
}
} else {
this.logMessage('0013', type, parameter);
logUtil.logMessage('0013', type, parameter);
}
} else if (parameter.items && parameter.items.type) {
// 解析parameter.items {type: "object", $ref: "#/definitions/Model"}
@@ -75,16 +96,16 @@ export default {
children = this.getAdditionalProperties(parameter.items.additionalProperties, additional, definitionsDataMap, indexKey, {}, 0);
format = additional.type;
} else {
this.logMessage('0012', type, parameter);
logUtil.logMessage('0012', type, parameter);
}
} else {
this.logMessage('002', type, parameter);
logUtil.logMessage('002', type, parameter);
}
} else {
if (notNeedHandleTypeArr.indexOf(type) >= 0) {
// 无需特殊处理的类型
} else {
this.logMessage('003', type, parameter);
logUtil.logMessage('003', type, parameter);
}
}
if (example) {
@@ -114,6 +135,18 @@ export default {
}
return requestParamList;
},
/**
* 解析请求返回的结果集
* @param responses swagger.parameters
* @param definitionsDataMap 解析的path里对应的数据map{url + "." + method: swagger.paths.post}
* @returns [] 参数列表[{
* code: '',
* type: '',
* key: '',
* desc: '',
* schemas: [],
* }]
*/
getResponseParamList(responses, definitionsDataMap) {
let responsesList = [];
let indexKey = 1;
@@ -136,15 +169,46 @@ export default {
});
return responsesList;
},
/**
* 判断是否包含$ref属性
* @param schema
* @returns {boolean}
*/
isSchemaRef(schema) {
return !!(schema && (schema.originalRef || schema['$ref']));
return !!(schema && schema['$ref']);
},
/**
* 获取对象的$ref属性
* @param schema
* @returns {string}
*/
getSchemaRef(schema) {
if (schema.originalRef) return schema.originalRef;
if (schema['$ref'] && schema['$ref'].indexOf('#/definitions/') === 0) return schema['$ref'].replace('#/definitions/', '');
this.logMessage('9467', '', schema);
logUtil.logMessage('9467', '', schema);
return '';
},
/**
* 获取swagger.definitions里的对象信息
* @param ref 对象名
* @param definitionsDataMap 解析的path里对应的数据map{url + "." + method: swagger.paths.post}
* @param indexKey 层级key
* @param parentRef 父级已用过的ref防止无限递归
* @param deep 层级深度大于10层则不再解析防止层级太深或无线递归
* @returns {undefined|
* {
* type: '',
* name: '',
* key: '',
* subType: '',
* format: '',
* description: '',
* enum: '',
* additional: '',
* example: '',
* children: [],
* }
* }
*/
getParamDefinitions(ref, definitionsDataMap, indexKey, parentRef, deep) {
let definition = definitionsDataMap[ref];
// 层级大于5层 或者 没有类型定义
@@ -178,14 +242,14 @@ export default {
parentRef[currentLevelType] = undefined;
});
if (type === 'array') {
// 解析parameter.items {originalRef: "Model", $ref: "#/definitions/Model"}
// 解析parameter.items {$ref: "#/definitions/Model"}
if (this.isSchemaRef(parameter.items)) {
subType = this.getSchemaRef(parameter.items);
children = this.getParamDefinitions(subType, definitionsDataMap, keySub, parentRef, deep + 1);
} else if (parameter.items && parameter.items.type) {
subType = parameter.items.type;
} else {
this.logMessage('004', type, parameter);
logUtil.logMessage('004', type, parameter);
}
} else if (type === 'object') {
if (parameter.additionalProperties) {
@@ -193,20 +257,20 @@ export default {
children = this.getAdditionalProperties(parameter.additionalProperties, additional, definitionsDataMap, keySub, parentRef, deep + 1);
format = additional.type;
} else {
this.log('0041', type, parameter);
logUtil.log('0041', type, parameter);
}
} else if (!type) {
if (this.isSchemaRef(parameter)) {
type = this.getSchemaRef(parameter);
children = this.getParamDefinitions(type, definitionsDataMap, keySub, parentRef, deep + 1);
} else {
this.logMessage('005', type, parameter);
logUtil.logMessage('005', type, parameter);
}
} else {
if (notNeedHandleTypeArr.indexOf(type) >= 0) {
// 无需特殊处理的类型
} else {
this.logMessage('006', type, parameter);
logUtil.logMessage('006', type, parameter);
}
}
if (example) {
@@ -236,6 +300,21 @@ export default {
}
return paramList.length > 0 ? paramList : undefined;
},
/**
* parameter.schema.additionalProperties 类型的参数值处理
* @param additionalProperties
* @param additional
* @param definitionsDataMap
* @param keySub
* @param parentRef
* @param deep
* @returns {
* undefined
* |{type: "", name: "", key: "", subType: "", format: "", description: "", enum: "", additional: "", example: "", children: *[]}
* |{}
* |{children: (undefined|{type: "", name: "", key: "", subType: "", format: "", description: "", enum: "", additional: "", example: "", children: *[]}), type: string}
* }
*/
getAdditionalProperties(additionalProperties, additional, definitionsDataMap, keySub, parentRef, deep) {
if (this.isSchemaRef(additionalProperties)) {
additional.type = this.getSchemaRef(additionalProperties);
@@ -256,24 +335,17 @@ export default {
};
return children;
} else {
this.logMessage('007', '', additionalProperties);
logUtil.logMessage('007', '', additionalProperties);
}
} else {
additional.type = additionalProperties.type;
if (notNeedHandleTypeArr.indexOf(additional.type) >= 0) {
// 无需特殊处理的类型
} else {
this.logMessage('008', '', additionalProperties);
logUtil.logMessage('008', '', additionalProperties);
}
}
return undefined;
},
log(code, type, parameter) {
console.log(code + '-遇到未处理的类型,请联系开发人员修改:' + type, parameter);
},
logMessage(code, type, parameter) {
console.log(code + '-遇到未处理的类型,请联系开发人员修改:' + type, parameter);
message.error(code + '-遇到未处理的类型,请联系开发人员修改:' + type);
}
}

View File

@@ -1,15 +1,28 @@
const methodArray = ["get", "head", "post", "put", "patch", "delete", "options", "trace"];
/**
* 通过tag创建文档树
* 解析swagger文档paths信息生成后续需要的关键信息
* @param swagger 文档内容
* @returns {{pathDataMap: {}, pathData: {}}}
* @returns {{urlMethodMap: {}, tagPathMap: {}, methodStatistic: {}}}
* {
* urlMethodMap: {
* 'url + "." + method': {swagger.paths.url.method对象信息}
* },
* tagPathMap: {
* 分组名: {url: {...接口信息, path: '', url: '', method: ''}}
* },
* methodStatistic: {
* POST: 132,
* GET: 146,
* TOTAL: 999
* }
* }
*/
export function createTreeViewByTag(swagger) {
let pathData = {}, pathDataMap = {}, methodStatistic = {};
export function analysisSwaggerData(swagger) {
let tagPathMap = {}, urlMethodMap = {}, methodStatistic = {};
let swaggerPaths = swagger.paths;
if (!swaggerPaths) {
return {pathDataMap, pathData, methodStatistic};
return {urlMethodMap, tagPathMap, methodStatistic};
}
//console.log(swaggerPaths);
Object.keys(swaggerPaths).forEach(url => {
@@ -19,13 +32,13 @@ export function createTreeViewByTag(swagger) {
if (!pathMethods[method] || !pathMethods[method].tags) {
continue;
}
let methodUp = method.toUpperCase();
methodStatistic[methodUp] = (methodStatistic[methodUp] || 0) + 1;
methodStatistic['TOTAL'] = (methodStatistic['TOTAL'] || 0) + 1;
let methodLower = method.toLowerCase();
methodStatistic[methodLower] = (methodStatistic[methodLower] || 0) + 1;
methodStatistic['total'] = (methodStatistic['total'] || 0) + 1;
pathMethods[method].tags.forEach(tag => {
let pathTag = pathData[tag];
let pathTag = tagPathMap[tag];
if (!pathTag) {
pathTag = pathData[tag] = {};
pathTag = tagPathMap[tag] = {};
}
let pathTagUrl = pathTag[url];
if (!pathTagUrl) {
@@ -37,21 +50,30 @@ export function createTreeViewByTag(swagger) {
pathTagUrl[method].url = url;
pathTagUrl[method].method = method;
// url对应文档的映射
pathDataMap[tempPath] = pathMethods[method];
urlMethodMap[tempPath] = pathMethods[method];
});
}
});
return {pathData, pathDataMap, methodStatistic};
return {urlMethodMap, tagPathMap, methodStatistic};
}
export function getTreeDataForTag(swagger, pathData, keywords, metaInfo) {
/**
* 按tag分组获取左侧菜单目录树
* @param swagger 原始文档信息
* @param tagPathMap 分组信息{分组名: {url: {...接口信息, path: '', url: '', method: ''}}}
* @param keywords 过滤关键字
* @param metaInfo 接口元信息点击时放入URL的参数
* @returns {{children: [], title: (*|string), key: string}[]}
*/
export function getTreeDataForTag(swagger, tagPathMap, keywords, metaInfo) {
let treeData = [];
let indexTag = 1;
// 遍历分组
swagger.tags.forEach(tag => {
let swaggerTags = swagger.tags || [];
swaggerTags.forEach(tag => {
let indexUrl = 1;
let urlTree = [];
let pathTagNode = pathData[tag.name];
let pathTagNode = tagPathMap[tag.name];
if (!pathTagNode) {
return;
}
@@ -91,12 +113,19 @@ export function getTreeDataForTag(swagger, pathData, keywords, metaInfo) {
return [
{
key: 'main',
title: swagger.title || 'Swagger接口文档',
title: swagger.info.title || 'Swagger接口文档',
children: treeData
}
];
}
/**
* 搜索接口是否包含某关键字将匹配URLpathmethodsummarydescriptiontags 属性
* @param url 接口URL
* @param methodNode 接口基本信息
* @param keywords 关键字
* @returns {*|boolean} 是否包含
*/
function searchInPathMethods(url, methodNode, keywords) {
if (!keywords || !url) {
return true;

View File

@@ -0,0 +1,23 @@
import {message} from 'ant-design-vue';
export default {
/**
* 输出一个log到console
* @param code 错误码,用于定位包错行
* @param type 参数类型
* @param parameter 参数对象
*/
log(code, type, parameter) {
console.log(code + '-遇到未处理的类型,请联系开发人员修改:' + type, parameter);
},
/**
* 输出一个log到console并且message输出信息用于比较严重必须要处理的异常使用
* @param code 错误码,用于定位包错行
* @param type 参数类型
* @param parameter 参数对象
*/
logMessage(code, type, parameter) {
console.log(code + '-遇到未处理的类型,请联系开发人员修改:' + type, parameter);
message.error(code + '-遇到未处理的类型,请联系开发人员修改:' + type);
}
}

View File

@@ -36,7 +36,7 @@
<script>
import MenuChildrenLayout from './MenuChildrenLayout.vue'
import {customApi, zyplayerApi} from '../../api'
import {createTreeViewByTag, getTreeDataForTag} from '../../store/TreeViewByTag'
import {analysisSwaggerData, getTreeDataForTag} from '../../assets/core/SwaggerTreeAnalysis.js'
export default {
name: 'MenuLayout',
@@ -53,7 +53,7 @@
openKeys: [],
// 文档树
treeDataLoading: false,
pathData: {},
tagPathMap: {},
swaggerDoc: {},
treeData: [],
expandedKeys: [],
@@ -138,10 +138,10 @@
}
this.swaggerDoc = v2Doc;
this.$store.commit('setSwaggerDoc', this.swaggerDoc);
let treeData = createTreeViewByTag(this.swaggerDoc);
this.$store.commit('setSwaggerTreePathMap', treeData.pathDataMap);
let treeData = analysisSwaggerData(this.swaggerDoc);
this.$store.commit('setUrlMethodMap', treeData.urlMethodMap);
this.$store.commit('setMethodStatistic', treeData.methodStatistic);
this.pathData = treeData.pathData;
this.tagPathMap = treeData.tagPathMap;
this.loadTreeData();
setTimeout(() => {
this.treeDataLoading = false;
@@ -154,7 +154,7 @@
loadTreeData() {
this.expandedKeys = ['main'];
let metaInfo = {url: this.swaggerDocChoice};
this.treeData = getTreeDataForTag(this.swaggerDoc, this.pathData, this.searchKeywords, metaInfo);
this.treeData = getTreeDataForTag(this.swaggerDoc, this.tagPathMap, this.searchKeywords, metaInfo);
},
toJsonObj(value) {
if (typeof value !== 'string') {

View File

@@ -8,7 +8,7 @@ export default createStore({
pageTabNameMap: {},
swaggerDoc: {},
swaggerDefinitions: {},
swaggerTreePathMap: {},
urlMethodMap: {},
methodStatistic: {},
// 数据库存储的地址信息
swaggerResource: {},
@@ -33,8 +33,8 @@ export default createStore({
state.swaggerDoc = swaggerDoc;
state.swaggerDefinitions = swaggerDoc.definitions || {};
},
setSwaggerTreePathMap(state, swaggerTreePathMap) {
state.swaggerTreePathMap = swaggerTreePathMap;
setUrlMethodMap(state, urlMethodMap) {
state.urlMethodMap = urlMethodMap;
},
setMethodStatistic(state, methodStatistic) {
state.methodStatistic = methodStatistic;

View File

@@ -4,8 +4,6 @@
<a-form-item label="标题">{{swaggerDocInfo.title}}</a-form-item>
<a-form-item label="版本">{{swaggerDocInfo.version}}</a-form-item>
<a-form-item label="作者" v-if="swaggerDocInfo.contact">
{{swaggerDocInfo.contact.name}} {{swaggerDocInfo.contact.email}}
<a :href="swaggerDocInfo.contact.url" target="_blank" v-if="swaggerDocInfo.contact.url">{{swaggerDocInfo.contact.url}}</a>
<template v-if="swaggerDocInfo.contact.name">
{{swaggerDocInfo.contact.name}}
</template>
@@ -25,14 +23,14 @@
<a :href="swaggerDocInfo.termsOfService" target="_blank">{{swaggerDocInfo.termsOfService}}</a>
</a-form-item>
<a-form-item label="文档说明">
<span v-html="swaggerDocInfo.description"></span>
<div class="markdown-body" v-html="getDescription(swaggerDocInfo.description)"></div>
</a-form-item>
<a-form-item label="接口统计">
<a-row :gutter="[16, 16]">
<template v-for="method in ['GET', 'POST', 'PUT', 'DELETE', 'HEAD', 'PATCH', 'OPTIONS', 'TRACE', 'TOTAL']">
<template v-for="method in ['get', 'post', 'put', 'delete', 'head', 'patch', 'options', 'trace', 'total']">
<a-col :span="6" v-if="methodStatistic[method]">
<a-card size="small">
<a-statistic :title="method === 'TOTAL'?'总计':method + '方法'" :value="methodStatistic[method]" suffix="个"></a-statistic>
<a-statistic :title="method === 'total'?'总计':method.toUpperCase() + '方法'" :value="methodStatistic[method]" suffix="个"></a-statistic>
</a-card>
</a-col>
</template>
@@ -46,6 +44,9 @@
<script>
import { toRefs, ref, reactive, onMounted, computed } from 'vue';
import {useStore} from 'vuex';
import {markdownIt} from 'mavon-editor'
import 'mavon-editor/dist/markdown/github-markdown.min.css'
import 'mavon-editor/dist/css/index.css'
export default {
setup() {
@@ -53,10 +54,14 @@
const swaggerDoc = computed(() => store.state.swaggerDoc);
const swaggerDocInfo = computed(() => store.state.swaggerDoc.info);
const methodStatistic = computed(() => store.state.methodStatistic);
const getDescription = description => {
return markdownIt.render(description || '');
};
return {
swaggerDoc,
swaggerDocInfo,
methodStatistic
methodStatistic,
getDescription,
};
},
};

View File

@@ -20,7 +20,7 @@
import {useStore} from 'vuex';
import { message } from 'ant-design-vue';
import { onBeforeRouteLeave, onBeforeRouteUpdate } from 'vue-router'
import swaggerAnalysis from '../../assets/utils/SwaggerAnalysisV2'
import swaggerAnalysis from '../../assets/core/SwaggerAnalysis.js'
import DocContent from './docView/DocContent.vue'
import DocDebugger from './docView/DocDebugger.vue'
import {markdownIt} from 'mavon-editor'
@@ -47,7 +47,7 @@
let intervalTimer = undefined;
const initLoadDocument = () => {
let path = route.query.path + '.' + route.query.method;
if (Object.keys(store.state.swaggerTreePathMap).length <= 0) {
if (Object.keys(store.state.urlMethodMap).length <= 0) {
console.log('文档尚未加载,等待加载完成');
if (!intervalTimer) {
intervalTimer = setInterval(() => {
@@ -55,7 +55,7 @@
clearInterval(intervalTimer);
return;
}
if (Object.keys(store.state.swaggerTreePathMap).length > 0) {
if (Object.keys(store.state.urlMethodMap).length > 0) {
console.log('文档内容改变,重新加载文档');
initLoadDocument();
}
@@ -63,7 +63,7 @@
}
return;
}
let docInfo = store.state.swaggerTreePathMap[path];
let docInfo = store.state.urlMethodMap[path];
if (!docInfo) {
message.error('没有找到对应的文档');
return;