Skip to content

Commit 4512765

Browse files
Merge pull request YMFE#726 from johnfwtest/master
feat: export data as swagger json YMFE#714 YMFE#724
2 parents e2dfc78 + c4ccb8c commit 4512765

5 files changed

Lines changed: 317 additions & 0 deletions

File tree

common/config.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ module.exports = {
1111
name: 'statistics'
1212
},{
1313
name: 'export-data'
14+
},{
15+
name: 'export-swagger2-data'
1416
},{
1517
name: 'import-yapi-json'
1618
},{
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
function exportData(exportDataModule, pid) {
2+
exportDataModule.swaggerjson = {
3+
name: 'swaggerjson',
4+
route: `/api/plugin/exportSwagger?type=OpenAPIV2&pid=${pid}`,
5+
desc: '导出项目接口文档为(Swagger 2.0)Json文件'
6+
};
7+
}
8+
9+
module.exports = function() {
10+
this.bindHook('export_data', exportData);
11+
};
Lines changed: 288 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,288 @@
1+
const baseController = require('controllers/base.js');
2+
const interfaceModel = require('models/interface.js');
3+
const projectModel = require('models/project.js');
4+
const interfaceCatModel = require('models/interfaceCat.js');
5+
const yapi = require('yapi.js');
6+
7+
8+
class exportSwaggerController extends baseController {
9+
constructor(ctx) {
10+
super(ctx);
11+
this.catModel = yapi.getInst(interfaceCatModel);
12+
this.interModel = yapi.getInst(interfaceModel);
13+
this.projectModel = yapi.getInst(projectModel);
14+
}
15+
16+
/*
17+
handleListClass,handleExistId is same as the exportController(yapi-plugin-export-data).
18+
No DRY,but i have no idea to optimize it.
19+
*/
20+
21+
async handleListClass(pid, status) {
22+
let result = await this.catModel.list(pid),
23+
newResult = [];
24+
for (let i = 0, item, list; i < result.length; i++) {
25+
item = result[i].toObject();
26+
list = await this.interModel.listByInterStatus(item._id, status);
27+
list = list.sort((a, b) => {
28+
return a.index - b.index;
29+
});
30+
if (list.length > 0) {
31+
item.list = list;
32+
newResult.push(item);
33+
}
34+
}
35+
36+
return newResult;
37+
}
38+
39+
handleExistId(data) {
40+
function delArrId(arr, fn) {
41+
if (!Array.isArray(arr)) return;
42+
arr.forEach(item => {
43+
delete item._id;
44+
delete item.__v;
45+
delete item.uid;
46+
delete item.edit_uid;
47+
delete item.catid;
48+
delete item.project_id;
49+
50+
if (typeof fn === 'function') fn(item);
51+
});
52+
}
53+
54+
delArrId(data, function (item) {
55+
delArrId(item.list, function (api) {
56+
delArrId(api.req_body_form);
57+
delArrId(api.req_params);
58+
delArrId(api.req_query);
59+
delArrId(api.req_headers);
60+
if (api.query_path && typeof api.query_path === 'object') {
61+
delArrId(api.query_path.params);
62+
}
63+
});
64+
});
65+
66+
return data;
67+
}
68+
69+
async exportData(ctx) {
70+
let pid = ctx.request.query.pid;
71+
let type = ctx.request.query.type;
72+
let status = ctx.request.query.status;
73+
74+
if (!pid) {
75+
ctx.body = yapi.commons.resReturn(null, 200, 'pid 不为空');
76+
}
77+
let curProject;
78+
let tp = '';
79+
try {
80+
curProject = await this.projectModel.get(pid);
81+
ctx.set('Content-Type', 'application/octet-stream');
82+
const list = await this.handleListClass(pid, status);
83+
84+
switch (type) {
85+
case 'OpenAPIV2':
86+
{ //in this time, only implemented OpenAPI V2.0
87+
let data = this.handleExistId(list);
88+
let model = await convertToSwaggerV2Model(data);
89+
tp = JSON.stringify(model, null, 2);
90+
ctx.set('Content-Disposition', `attachment; filename=swaggerApi.json`);
91+
return (ctx.body = tp);
92+
}
93+
default:
94+
{
95+
ctx.body = yapi.commons.resReturn(null, 400, 'type 无效参数')
96+
}
97+
}
98+
} catch (error) {
99+
yapi.commons.log(error, 'error');
100+
ctx.body = yapi.commons.resReturn(null, 502, '下载出错');
101+
}
102+
103+
//Convert to SwaggerV2.0 (OpenAPI 2.0)
104+
async function convertToSwaggerV2Model(list) {
105+
const swaggerObj = {
106+
swagger: '2.0',
107+
info: {
108+
title: curProject.name,
109+
version: 'last', // last version
110+
description: curProject.desc
111+
},
112+
//host: "", // No find any info of host in this point :-)
113+
basePath: curProject.basepath ? curProject.basepath : '/', //default base path is '/'(root)
114+
tags: (() => {
115+
let tagArray = [];
116+
list.forEach(t => {
117+
tagArray.push({
118+
name: t.name,
119+
description: t.desc
120+
/*externalDocs:{
121+
descroption:"",
122+
url:""
123+
} */
124+
});
125+
});
126+
return tagArray;
127+
})(),
128+
schemes: [
129+
"http" //Only http
130+
],
131+
paths: (() => {
132+
let apisObj = {};
133+
for (let aptTag of list) { //list of category
134+
for (let api of aptTag.list) //list of api
135+
{
136+
if (apisObj[api.path] == null) {
137+
apisObj[api.path] = {};
138+
}
139+
apisObj[api.path][api.method.toLowerCase()] = (() => {
140+
let apiItem = {};
141+
apiItem['tags'] = [aptTag.name];
142+
apiItem['summary'] = api.title;
143+
apiItem['description'] = api.markdown;
144+
switch (api.req_body_type) {
145+
case 'form':
146+
case 'file':
147+
apiItem['consumes'] = ['multipart/form-data']; //form data required
148+
break;
149+
case 'json':
150+
apiItem['consumes'] = ['application/json'];
151+
break;
152+
case 'raw':
153+
apiItem['consumes'] = ['text/plain'];
154+
break;
155+
default:
156+
break;
157+
}
158+
apiItem['parameters'] = (() => {
159+
let paramArray = [];
160+
for (let p of api.req_headers) //Headers parameters
161+
{
162+
//swagger has consumes proprety, so skip proprety "Content-Type"
163+
if (p.name === 'Content-Type') {
164+
continue;
165+
}
166+
paramArray.push({
167+
name: p.name,
168+
in: 'header',
169+
description: `${p.name} (Only:${p.value})`,
170+
required: p.required === 1,
171+
type: 'string', //always be type string
172+
default: p.value
173+
});
174+
}
175+
for (let p of api.req_params) //Path parameters
176+
{
177+
paramArray.push({
178+
name: p.name,
179+
in: 'path',
180+
description: p.desc,
181+
required: true, //swagger path parameters required proprety must be always true,
182+
type: 'string' //always be type string
183+
});
184+
}
185+
for (let p of api.req_query) //Query parameters
186+
{
187+
paramArray.push({
188+
name: p.name,
189+
in: 'query',
190+
required: p.required === 1,
191+
description: p.desc,
192+
type: 'string' //always be type string
193+
});
194+
}
195+
switch (api.req_body_type) //Body parameters
196+
{
197+
case 'form':
198+
{
199+
for (let p of api.req_body_form) {
200+
paramArray.push({
201+
name: p.name,
202+
in: 'formData',
203+
required: p.required === 1,
204+
description: p.desc,
205+
type: p.type === 'text' ? 'string' : 'file' //in this time .formData type have only text or file
206+
});
207+
}
208+
break;
209+
}
210+
case 'json':
211+
{
212+
if (api.req_body_other) {
213+
let jsonParam = JSON.parse(api.req_body_other);
214+
if (jsonParam) {
215+
paramArray.push({
216+
name: 'root',
217+
in: 'body',
218+
description: jsonParam.description,
219+
schema: jsonParam //as same as swagger's format
220+
});
221+
}
222+
}
223+
break;
224+
}
225+
case 'file':
226+
{
227+
paramArray.push({
228+
name: 'upfile',
229+
in: 'formData', //use formData
230+
description: api.req_body_other,
231+
type: 'file'
232+
});
233+
break;
234+
}
235+
case 'raw':
236+
{
237+
paramArray.push({
238+
name: 'raw',
239+
in: 'body',
240+
description: 'raw paramter',
241+
schema: {
242+
type: 'string',
243+
format: 'binary',
244+
default: api.req_body_other
245+
}
246+
});
247+
break;
248+
}
249+
default:
250+
break;
251+
}
252+
return paramArray;
253+
})();
254+
apiItem['responses'] = {
255+
'200': {
256+
description: 'successful operation',
257+
schema: (() => {
258+
let schemaObj = {};
259+
if (api.res_body_type === 'raw') {
260+
schemaObj['type'] = 'string';
261+
schemaObj['format'] = 'binary';
262+
schemaObj['default'] = api.res_body;
263+
} else if (api.res_body_type === 'json') {
264+
if (api.res_body) {
265+
let resBody = JSON.parse(api.res_body);
266+
if (resBody !== null) {
267+
//schemaObj['type']=resBody.type;
268+
schemaObj = resBody; //as the parameters,
269+
}
270+
}
271+
}
272+
return schemaObj;
273+
})()
274+
}
275+
};
276+
return apiItem;
277+
})();
278+
}
279+
}
280+
return apisObj;
281+
})()
282+
};
283+
return swaggerObj;
284+
}
285+
}
286+
}
287+
288+
module.exports = exportSwaggerController;
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
module.exports = {
2+
server: true,
3+
client: true
4+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
const exportSwaggerController = require('./controller');
2+
3+
module.exports = function(){
4+
this.bindHook('add_router', function(addRouter){
5+
addRouter({
6+
controller: exportSwaggerController,
7+
method: 'get',
8+
path: 'exportSwagger',
9+
action: 'exportData'
10+
})
11+
})
12+
}

0 commit comments

Comments
 (0)