From 92a3130392cf2eda2972a2d134942b7901dad54c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=A6=BE=E5=87=A0=E6=B5=B7?= Date: Sat, 2 Mar 2024 01:38:26 +0800 Subject: [PATCH] =?UTF-8?q?markdown=20=E6=A8=A1=E6=9D=BF=E5=AF=BC=E5=87=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- client/plugin-module.js | 2 +- common/config.js | 2 + .../client.js | 23 ++ .../configModel.js | 83 ++++++++ .../controller.js | 200 ++++++++++++++++++ .../index.js | 4 + .../mdTemplate/mdTemplate.js | 172 +++++++++++++++ .../server.js | 27 +++ package-lock.json | 12 +- package.json | 3 +- 10 files changed, 520 insertions(+), 8 deletions(-) create mode 100644 exts/yapi-plugin-export-markdown-template/client.js create mode 100644 exts/yapi-plugin-export-markdown-template/configModel.js create mode 100644 exts/yapi-plugin-export-markdown-template/controller.js create mode 100644 exts/yapi-plugin-export-markdown-template/index.js create mode 100644 exts/yapi-plugin-export-markdown-template/mdTemplate/mdTemplate.js create mode 100644 exts/yapi-plugin-export-markdown-template/server.js diff --git a/client/plugin-module.js b/client/plugin-module.js index 3dc2505..ebec237 100644 --- a/client/plugin-module.js +++ b/client/plugin-module.js @@ -1 +1 @@ -module.exports = {"import-postman" : {module: require('exts/yapi-plugin-import-postman/client.js'),options: null},"import-har" : {module: require('exts/yapi-plugin-import-har/client.js'),options: null},"advanced-mock" : {module: require('exts/yapi-plugin-advanced-mock/client.js'),options: null},"import-swagger" : {module: require('exts/yapi-plugin-import-swagger/client.js'),options: null},"statistics" : {module: require('exts/yapi-plugin-statistics/client.js'),options: null},"export-data" : {module: require('exts/yapi-plugin-export-data/client.js'),options: null},"gen-services" : {module: require('exts/yapi-plugin-gen-services/client.js'),options: null},"export-swagger2-data" : {module: require('exts/yapi-plugin-export-swagger2-data/client.js'),options: null},"import-yapi-json" : {module: require('exts/yapi-plugin-import-yapi-json/client.js'),options: null},"wiki" : {module: require('exts/yapi-plugin-wiki/client.js'),options: null},"swagger-auto-sync" : {module: require('exts/yapi-plugin-swagger-auto-sync/client.js'),options: null},"export-postman" : {module: require('exts/yapi-plugin-export-postman/client.js'),options: null}} \ No newline at end of file +module.exports = {"import-postman" : {module: require('exts/yapi-plugin-import-postman/client.js'),options: null},"import-har" : {module: require('exts/yapi-plugin-import-har/client.js'),options: null},"advanced-mock" : {module: require('exts/yapi-plugin-advanced-mock/client.js'),options: null},"import-swagger" : {module: require('exts/yapi-plugin-import-swagger/client.js'),options: null},"statistics" : {module: require('exts/yapi-plugin-statistics/client.js'),options: null},"export-data" : {module: require('exts/yapi-plugin-export-data/client.js'),options: null},"gen-services" : {module: require('exts/yapi-plugin-gen-services/client.js'),options: null},"export-swagger2-data" : {module: require('exts/yapi-plugin-export-swagger2-data/client.js'),options: null},"import-yapi-json" : {module: require('exts/yapi-plugin-import-yapi-json/client.js'),options: null},"wiki" : {module: require('exts/yapi-plugin-wiki/client.js'),options: null},"swagger-auto-sync" : {module: require('exts/yapi-plugin-swagger-auto-sync/client.js'),options: null},"export-postman" : {module: require('exts/yapi-plugin-export-postman/client.js'),options: null},"export-markdown-template" : {module: require('exts/yapi-plugin-export-markdown-template/client.js'),options: null}} \ No newline at end of file diff --git a/common/config.js b/common/config.js index ce795a7..ad575d7 100644 --- a/common/config.js +++ b/common/config.js @@ -23,6 +23,8 @@ module.exports = { name: 'swagger-auto-sync' }, { name: 'export-postman' + }, { + name: 'export-markdown-template' } // { // name: 'test' diff --git a/exts/yapi-plugin-export-markdown-template/client.js b/exts/yapi-plugin-export-markdown-template/client.js new file mode 100644 index 0000000..09604b9 --- /dev/null +++ b/exts/yapi-plugin-export-markdown-template/client.js @@ -0,0 +1,23 @@ +import mdTemplate from './mdTemplate/mdTemplate.js' + +function exportData(exportDataModule, pid) { + exportDataModule.markdown = { + name: 'markdown', + route: `/api/plugin/exportMarkdown?pid=${pid}`, + desc: '根据模板导出项目接口文档' + }; +} + + + +function hander(routers) { + routers.mdExport = { + name: 'Markdown导出', + component: mdTemplate + }; +} + +module.exports = function() { + this.bindHook('export_data', exportData); + this.bindHook('sub_setting_nav', hander); +}; diff --git a/exts/yapi-plugin-export-markdown-template/configModel.js b/exts/yapi-plugin-export-markdown-template/configModel.js new file mode 100644 index 0000000..5b512d3 --- /dev/null +++ b/exts/yapi-plugin-export-markdown-template/configModel.js @@ -0,0 +1,83 @@ +const yapi = require('yapi.js'); +const baseModel = require('models/base.js'); +const mongoose = require('mongoose'); + +class configModel extends baseModel { + getName() { + return 'markdown_template_config'; + } + + getSchema() { + return { + uid: { type: Number}, + project_id: { type: Number, required: true }, + //是否开启自动同步 + is_export_by_interface: { type: Boolean, default: false }, + // 模板 + template_data: String, + add_time: Number, + up_time: Number, + }; + } + + getByProjectId(id) { + return this.model.findOne({ + project_id: id + }) + } + + delByProjectId(project_id){ + return this.model.remove({ + project_id: project_id + }) + } + + save(data) { + data.up_time = yapi.commons.time(); + let m = new this.model(data); + return m.save(); + } + + listAll() { + return this.model + .find({}) + .select( + '_id uid project_id add_time up_time is_sync_open sync_cron sync_json_url sync_mode old_swagger_content last_sync_time' + ) + .sort({ _id: -1 }) + .exec(); + } + + up(data) { + console.log(data) + let id = data.id; + delete data.id; + data.up_time = yapi.commons.time(); + return this.model.update({ + _id: id + }, data) + } + + upById(id, data) { + delete data.id; + data.up_time = yapi.commons.time(); + return this.model.update({ + _id: id + }, data) + } + + del(id){ + return this.model.remove({ + _id: id + }) + } + + delByProjectId(projectId){ + return this.model.remove({ + project_id: projectId + }) + } + +} + +module.exports = configModel; diff --git a/exts/yapi-plugin-export-markdown-template/controller.js b/exts/yapi-plugin-export-markdown-template/controller.js new file mode 100644 index 0000000..3d3c57c --- /dev/null +++ b/exts/yapi-plugin-export-markdown-template/controller.js @@ -0,0 +1,200 @@ +const baseController = require('controllers/base.js'); +const interfaceModel = require('models/interface.js'); +const projectModel = require('models/project.js'); +const interfaceCatModel = require('models/interfaceCat.js'); +const yapi = require('yapi.js'); +const uuid = require('uuid'); +const configModel = require("./configModel"); +const Safeify = require('safeify').default; +const JSZip = require("jszip"); + +class exportMarkdownController extends baseController { + constructor(ctx) { + super(ctx); + this.catModel = yapi.getInst(interfaceCatModel); + this.interModel = yapi.getInst(interfaceModel); + this.projectModel = yapi.getInst(projectModel); + this.configModel = yapi.getInst(configModel); + } + + /* + handleListClass,handleExistId is same as the exportController(yapi-plugin-export-data). + No DRY,but i have no idea to optimize it. + */ + + async handleListClass(pid, status) { + let result = await this.catModel.list(pid), + newResult = []; + for (let i = 0, item, list; i < result.length; i++) { + item = result[i].toObject(); + list = await this.interModel.listByInterStatus(item._id, status); + list = list.sort((a, b) => { + return a.index - b.index; + }); + if (list.length > 0) { + item.list = list; + newResult.push(item); + } + } + + return newResult; + } + + handleExistId(data) { + function delArrId(arr, fn) { + if (!Array.isArray(arr)) return; + arr.forEach(item => { + delete item._id; + delete item.__v; + delete item.uid; + delete item.edit_uid; + delete item.catid; + delete item.project_id; + + if (typeof fn === 'function') fn(item); + }); + } + + delArrId(data, function (item) { + delArrId(item.list, function (api) { + delArrId(api.req_body_form); + delArrId(api.req_params); + delArrId(api.req_query); + delArrId(api.req_headers); + if (api.query_path && typeof api.query_path === 'object') { + delArrId(api.query_path.params); + } + }); + }); + + return data; + } + + + + async exportData(ctx) { + let pid = ctx.request.query.pid; + let status = ctx.request.query.status; + + if (!pid) { + ctx.body = yapi.commons.resReturn(null, 200, 'pid 不为空'); + } + + let result = await this.configModel.getByProjectId(pid); + // console.log(result); + if (!result.is_export_by_interface){ + console.log("重定向") + ctx.status = 302; + ctx.redirect(`/api/plugin/export?type=markdown&pid=${pid}`); + return ; + } + let curProject; + try { + curProject = await this.projectModel.get(pid); + // ctx.set('Content-Type', 'application/json'); + // ctx.set('Content-Type', 'application/octet-stream'); + const list = await this.handleListClass(pid, status); + + let data = this.handleExistId(list); + + // console.log("curProject", curProject) + // console.log("list", list) + + + let model; + + // const tmpPath = fs.mkdtempSync(path.join(os.tmpdir(),uuid.v4())); + // console.log("tmpPath=", tmpPath) + const zip = JSZip(); + + for (let item of data) { + for(let interfaceItem of item.list){ + const safeVm = new Safeify({ + timeout: 3000, //超时时间 + asyncTimeout: 10000, //包含异步操作的超时时间 + unrestricted: true, + quantity: 4, //沙箱进程数量,默认同 CPU 核数 + memoryQuota: 500, //沙箱最大能使用的内存(单位 m),默认 500m + cpuQuota: 0.5, //沙箱的 cpu 资源配额(百分比),默认 50% + }); + // console.log("curProject", curProject) + const vmContext = { + projectTmp: JSON.stringify(curProject), + interfaceTmp: JSON.stringify(interfaceItem), + categoryTmp: JSON.stringify(item) + }; + try { + safeVm.preset("const project = JSON.parse(projectTmp);const interface = JSON.parse(interfaceTmp);const category=JSON.parse(categoryTmp)") + model = await safeVm.run(result.template_data, vmContext); + // fs.writeFileSync(`${tmpPath}/${interfaceItem.title}.md`, model.toString()) + zip.file(`${interfaceItem.title}.md`, model.toString()) + safeVm.destroy(); + } catch (e) { + console.log(e) + return ; + } + } + } + + ctx.set('Content-Disposition', `attachment; filename=${encodeURIComponent(curProject.name)}.zip`); + let dataaa = await zip.generateAsync({ + type: "nodebuffer", + // 压缩算法 + compression: "DEFLATE", + streamFiles: true, + compressionOptions: { + level: 9 + } + }); + ctx.body = dataaa + // ctx.set('Content-Length', dataaa.length); + + // dataaa.copy(ctx.body) + + // fs.unlinkSync(tmpPath) + } catch (error) { + yapi.commons.log(error, 'error'); + ctx.body = yapi.commons.resReturn(null, 502, '下载出错'); + } + } + + /** + * 保存配置信息 + * @param {*} ctx + */ + async upConfig(ctx) { + let requestBody = ctx.request.body; + let projectId = requestBody.project_id; + if (!projectId) { + return (ctx.body = yapi.commons.resReturn(null, 408, '缺少项目Id')); + } + + if ((await this.checkAuth(projectId, 'project', 'edit')) !== true) { + return (ctx.body = yapi.commons.resReturn(null, 405, '没有权限')); + } + + let result; + if (requestBody.id) { + result = await this.configModel.up(requestBody); + } else { + result = await this.configModel.save(requestBody); + } + + return (ctx.body = yapi.commons.resReturn(result)); + } + + /** + * 查询配置信息 + * @param {*} ctx + */ + async getConfig(ctx) { + let projectId = ctx.query.project_id; + if (!projectId) { + return (ctx.body = yapi.commons.resReturn(null, 408, '缺少项目Id')); + } + let result = await this.configModel.getByProjectId(projectId); + return (ctx.body = yapi.commons.resReturn(result)); + } +} + +module.exports = exportMarkdownController; diff --git a/exts/yapi-plugin-export-markdown-template/index.js b/exts/yapi-plugin-export-markdown-template/index.js new file mode 100644 index 0000000..080b4b6 --- /dev/null +++ b/exts/yapi-plugin-export-markdown-template/index.js @@ -0,0 +1,4 @@ +module.exports = { + server: true, + client: true + } \ No newline at end of file diff --git a/exts/yapi-plugin-export-markdown-template/mdTemplate/mdTemplate.js b/exts/yapi-plugin-export-markdown-template/mdTemplate/mdTemplate.js new file mode 100644 index 0000000..2a09640 --- /dev/null +++ b/exts/yapi-plugin-export-markdown-template/mdTemplate/mdTemplate.js @@ -0,0 +1,172 @@ +import React, { Component } from 'react'; +import { connect } from 'react-redux'; +import PropTypes from 'prop-types'; +import {Form, Switch, Button, message} from 'antd'; +import {handleSwaggerUrlData} from 'client/reducer/modules/project'; +const FormItem = Form.Item; +import axios from 'axios'; +import AceEditor from "../../../client/components/AceEditor/AceEditor"; +// layout +const formItemLayout = { + labelCol: { + lg: { span: 5 }, + xs: { span: 24 }, + sm: { span: 10 } + }, + wrapperCol: { + lg: { span: 16 }, + xs: { span: 24 }, + sm: { span: 12 } + }, + className: 'form-item' +}; +const tailFormItemLayout = { + wrapperCol: { + sm: { + span: 16, + offset: 11 + } + } +}; + +@connect( + state => { + return { + projectMsg: state.project.currProject + }; + }, + { + handleSwaggerUrlData + } +) +@Form.create() +export default class ProjectInterfaceSync extends Component { + static propTypes = { + form: PropTypes.object, + match: PropTypes.object, + projectId: PropTypes.number, + projectMsg: PropTypes.object, + handleSwaggerUrlData: PropTypes.func + }; + + constructor(props) { + super(props); + this.state = { + config_data: { is_export_by_interface: false, template_data:"",_id:null } + }; + } + + handleSubmit = async () => { + const { form, projectId } = this.props; + let params = { + project_id: projectId, + is_export_by_interface: this.state.config_data.is_export_by_interface, + template_data: this.state.config_data.template_data, + uid: this.props.projectMsg.uid + }; + if (this.state.config_data._id) { + params.id = this.state.config_data._id; + } + if (!this.state.config_data.is_export_by_interface){ + params.template_data = null; + this.setState({ + config_data: { + template_data: null, + is_export_by_interface: this.state.config_data.is_export_by_interface + } + }); + } + form.validateFields(async (err, values) => { + if (!err) { + let assignValue = Object.assign(params, values); + await axios.post('/api/plugin/mdConfig/save', assignValue).then(res => { + if (res.data.errcode === 0) { + message.success('保存成功'); + } else { + message.error(res.data.errmsg); + } + }); + } + }); + + }; + + + UNSAFE_componentWillMount() { + //查询同步任务 + this.setState({ + config_data: {} + }); + this.getSyncData(); + } + + async getSyncData() { + let projectId = this.props.projectMsg._id; + let result = await axios.get('/api/plugin/mdConfig/get?project_id=' + projectId); + if (result.data.errcode === 0) { + if (result.data.data) { + this.setState({ + config_data: result.data.data + }); + } + } + } + + // 是否开启 + onChange = v => { + let config_data = this.state.config_data; + config_data.is_export_by_interface = v; + this.setState({ + config_data: config_data + }); + }; + + + handleTemplateInput = e => { + let config_data = this.state.config_data; + config_data.template_data = e.text; + this.setState({ + config_data: config_data + }); + }; + + + render() { + const templateEditor = ( + + + + + ) + return ( +
+
+ + + + +
+ {this.state.config_data.is_export_by_interface ? templateEditor : null} +
+ + + +
+
+ ); + } +} diff --git a/exts/yapi-plugin-export-markdown-template/server.js b/exts/yapi-plugin-export-markdown-template/server.js new file mode 100644 index 0000000..ff09c91 --- /dev/null +++ b/exts/yapi-plugin-export-markdown-template/server.js @@ -0,0 +1,27 @@ +const exportMarkdownController = require('./controller'); +const controller = require("../yapi-plugin-swagger-auto-sync/controller/syncController"); + +module.exports = function(){ + this.bindHook('add_router', function(addRouter){ + addRouter({ + controller: exportMarkdownController, + method: 'get', + path: 'exportMarkdown', + action: 'exportData' + }) + + // 配置信息 + addRouter({ + controller: exportMarkdownController, + method: 'get', + path: 'mdConfig/get', + action: 'getConfig' + }); + addRouter({ + controller: exportMarkdownController, + method: 'post', + path: 'mdConfig/save', + action: 'upConfig' + }); + }) +} diff --git a/package-lock.json b/package-lock.json index 6b22eb7..ff853c0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -296,9 +296,9 @@ } }, "acorn": { - "version": "8.10.0", - "resolved": "https://registry.npmmirror.com/acorn/-/acorn-8.10.0.tgz", - "integrity": "sha512-F0SAmZ8iUtS//m8DmCTA0jlh6TDKkHQyK6xc6V4KDTyZKA9dnvX9/3sRTVQrWm79glUAZbnmmNcdYwUIHWVybw==" + "version": "8.11.3", + "resolved": "https://registry.npmmirror.com/acorn/-/acorn-8.11.3.tgz", + "integrity": "sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==" }, "acorn-dynamic-import": { "version": "2.0.2", @@ -352,9 +352,9 @@ } }, "acorn-walk": { - "version": "8.2.0", - "resolved": "https://registry.npmmirror.com/acorn-walk/-/acorn-walk-8.2.0.tgz", - "integrity": "sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==" + "version": "8.3.2", + "resolved": "https://registry.npmmirror.com/acorn-walk/-/acorn-walk-8.3.2.tgz", + "integrity": "sha512-cjkyv4OtNCIeqhHrfS81QWXoCBPExR/J62oyEqepVw8WaQeSqpW2uhuLPh1m9eWhDuOo/jUXVTlifvesOWp/4A==" }, "add": { "version": "2.0.6", diff --git a/package.json b/package.json index 0b0e99f..93f1e93 100644 --- a/package.json +++ b/package.json @@ -51,6 +51,7 @@ "jsondiffpatch": "0.3.11", "jsonwebtoken": "7.4.1", "jsrsasign": "^8.0.12", + "jszip": "^3.10.1", "koa": "2.0.0", "koa-body": "^2.5.0", "koa-bodyparser": "3.2.0", @@ -78,7 +79,7 @@ "qs": "^6.7.0", "react-slick": "^0.17.0", "request": "2.81.0", - "safeify": "^5.0.5", + "safeify": "^5.0.6", "sha.js": "2.4.9", "sha1": "1.1.1", "swagger-client": "3.10.0",