markdown渲染功能

This commit is contained in:
2024-07-13 13:25:06 +08:00
parent eb50ad480e
commit 7aa5f5643b
6 changed files with 334 additions and 169 deletions

View File

@@ -1,4 +1,5 @@
import mdTemplate from './mdTemplate/mdTemplate.js' import mdTemplate from './mdTemplate/mdTemplate.js'
import Services from "./mdTemplate/Services";
function exportData(exportDataModule, pid) { function exportData(exportDataModule, pid) {
exportDataModule.markdown = { exportDataModule.markdown = {
@@ -17,7 +18,18 @@ function hander(routers) {
}; };
} }
module.exports = function() { module.exports = function() {
this.bindHook('export_data', exportData); this.bindHook('export_data', exportData);
this.bindHook('sub_setting_nav', hander); this.bindHook('sub_setting_nav', hander);
this.bindHook('interface_tab', function (tabs) {
tabs.mdGen = {
name: 'Markdown',
component: Services
}
})
}; };

View File

@@ -2,6 +2,7 @@ const baseController = require('controllers/base.js');
const interfaceModel = require('models/interface.js'); const interfaceModel = require('models/interface.js');
const projectModel = require('models/project.js'); const projectModel = require('models/project.js');
const interfaceCatModel = require('models/interfaceCat.js'); const interfaceCatModel = require('models/interfaceCat.js');
const userModel = require("models/user.js");
const yapi = require('yapi.js'); const yapi = require('yapi.js');
const uuid = require('uuid'); const uuid = require('uuid');
const configModel = require("./configModel"); const configModel = require("./configModel");
@@ -15,6 +16,7 @@ class exportMarkdownController extends baseController {
this.interModel = yapi.getInst(interfaceModel); this.interModel = yapi.getInst(interfaceModel);
this.projectModel = yapi.getInst(projectModel); this.projectModel = yapi.getInst(projectModel);
this.configModel = yapi.getInst(configModel); this.configModel = yapi.getInst(configModel);
this.userModel = yapi.getInst(userModel);
} }
/* /*
@@ -71,7 +73,6 @@ class exportMarkdownController extends baseController {
} }
async exportData(ctx) { async exportData(ctx) {
let pid = ctx.request.query.pid; let pid = ctx.request.query.pid;
let status = ctx.request.query.status; let status = ctx.request.query.status;
@@ -80,13 +81,13 @@ class exportMarkdownController extends baseController {
ctx.body = yapi.commons.resReturn(null, 200, 'pid 不为空'); ctx.body = yapi.commons.resReturn(null, 200, 'pid 不为空');
} }
let result = await this.configModel.getByProjectId(pid); let templateData = await this.configModel.getByProjectId(pid);
// console.log(result); // console.log(result);
if (!result.is_export_by_interface){ if (!templateData.is_export_by_interface) {
console.log("重定向") console.log("重定向")
ctx.status = 302; ctx.status = 302;
ctx.redirect(`/api/plugin/export?type=markdown&pid=${pid}`); ctx.redirect(`/api/plugin/export?type=markdown&pid=${pid}`);
return ; return;
} }
let curProject; let curProject;
try { try {
@@ -96,6 +97,8 @@ class exportMarkdownController extends baseController {
const list = await this.handleListClass(pid, status); const list = await this.handleListClass(pid, status);
let data = this.handleExistId(list); let data = this.handleExistId(list);
let userData = await this.userModel.findById(this.getUid());
// console.log("curProject", curProject) // console.log("curProject", curProject)
// console.log("list", list) // console.log("list", list)
@@ -106,41 +109,19 @@ class exportMarkdownController extends baseController {
// const tmpPath = fs.mkdtempSync(path.join(os.tmpdir(),uuid.v4())); // const tmpPath = fs.mkdtempSync(path.join(os.tmpdir(),uuid.v4()));
// console.log("tmpPath=", tmpPath) // console.log("tmpPath=", tmpPath)
const zip = JSZip(); const zip = JSZip();
let errMsg = []; let allErrMsg = [];
for (let item of data) { for (let item of data) {
for(let interfaceItem of item.list){ for (let interfaceItem of item.list) {
const safeVm = new Safeify({ let {result, errMsg} = await this.executeJsRender(templateData.template_data, curProject, interfaceItem, item, userData)
timeout: 3000, //超时时间 zip.file(`${item.name}/${interfaceItem.title}.md`, result.toString())
asyncTimeout: 10000, //包含异步操作的超时时间 if (errMsg){
unrestricted: true, allErrMsg.push(errMsg)
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(`${item.name}/${interfaceItem.title}.md`, model.toString())
safeVm.destroy();
} catch (e) {
errMsg.push({
error: e.toString(),
context: vmContext
});
} }
} }
} }
if (errMsg.length){ if (allErrMsg.length) {
ctx.body = yapi.commons.resReturn(errMsg, 502, '下载出错'); ctx.body = yapi.commons.resReturn(allErrMsg, 502, '下载出错');
return ; return;
} }
ctx.set('Content-Disposition', `attachment; filename=${encodeURIComponent(curProject.name)}.zip`); ctx.set('Content-Disposition', `attachment; filename=${encodeURIComponent(curProject.name)}.zip`);
@@ -202,6 +183,67 @@ class exportMarkdownController extends baseController {
let result = await this.configModel.getByProjectId(projectId); let result = await this.configModel.getByProjectId(projectId);
return (ctx.body = yapi.commons.resReturn(result)); return (ctx.body = yapi.commons.resReturn(result));
} }
/**
* 查询配置信息
* @param {*} ctx
*/
async mdGen(ctx) {
let interfaceId = ctx.query.interfaceId;
let userData = await this.userModel.findById(this.getUid());
let interfaceData = await this.interModel.get(interfaceId);
if (!interfaceData) {
return (ctx.body = yapi.commons.resReturn(null, 200, ''));
}
let templateData = await this.configModel.getByProjectId(interfaceData.project_id);
if (!templateData || !templateData.template_data) {
return (ctx.body = yapi.commons.resReturn(null, 200, ''));
}
let interfaceCat = await this.catModel.get(interfaceData.catid);
let project = await this.projectModel.get(interfaceData.project_id);
let {result, errMsg} = await this.executeJsRender(templateData.template_data, project, interfaceData, interfaceCat, userData);
if (errMsg){
ctx.body = yapi.commons.resReturn(errMsg, 502, '渲染出错');
return;
}
return (ctx.body = yapi.commons.resReturn(result));
}
async executeJsRender(template_data, curProject, interfaceItem, categoryItem, useItem) {
let errMsg;
let result;
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 = {
project: JSON.stringify(curProject),
interface: JSON.stringify(interfaceItem),
category: JSON.stringify(categoryItem),
user: JSON.stringify(useItem)
};
try {
safeVm.preset("project = JSON.parse(project);interface = JSON.parse(interface);category=JSON.parse(category);user=JSON.parse(user)")
result = await safeVm.run(template_data, vmContext);
// fs.writeFileSync(`${tmpPath}/${interfaceItem.title}.md`, model.toString())
safeVm.destroy();
} catch (e) {
errMsg = {
error: e.toString(),
context: vmContext
};
}
return {result: result, errMsg}
}
} }
module.exports = exportMarkdownController; module.exports = exportMarkdownController;

View File

@@ -0,0 +1,73 @@
import React, {PureComponent as Component} from 'react'
import PropTypes from 'prop-types'
import {connect} from 'react-redux';
import {getToken} from '../../../client/reducer/modules/project.js'
import './Services.scss';
import {withRouter} from "react-router-dom";
import axios from "axios";
import {message} from "antd";
import copy from "copy-to-clipboard";
@connect(
state => {
return {
token: state.project.token
}
},
{
getToken
}
)
class MDTemplateServices extends Component {
static propTypes = {
projectId: PropTypes.number,
token: PropTypes.string,
getToken: PropTypes.func,
match: PropTypes.object
}
constructor(props, context) {
super(props, context);
this.state = {
render_data: {}
}
}
componentDidMount() {
this.getSyncData();
}
async getSyncData() {
let interfaceId = this.props.match.params.actionId;
let result = await axios.get(`/api/plugin/mdConfig/gen?interfaceId=${interfaceId}`);
if (result.data) {
this.setState({
render_data: result.data
});
}
}
async preCopy(code) {
copy(code)
message.success("复制成功")
}
render() {
let render_vide = [];
if (this.state.render_data) {
render_vide.push(<pre key="md"><span className='btn-pre-copy' onClick={()=>this.preCopy(this.state.render_data.data)}>复制代码</span>{this.state.render_data.data + "\n"}</pre>)
}
console.log(render_vide)
return (
<div className="project-services">
<section className="news-box m-panel">
<div className="token">
{render_vide}
</div>
</section>
</div>
);
}
}
module.exports = withRouter(MDTemplateServices);

View File

@@ -0,0 +1,30 @@
.project-services {
margin: 0;
pre {
background: #efefef;
}
}
pre{
position: relative;
background-color: #f5f5f5;
border: 1px solid #ccc;
border-radius: 4px;
padding: 10px;
}
pre .btn-pre-copy{
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
-khtml-user-select: none;
user-select: none;
position: absolute;
top: 10px;
right: 12px;
font-size: 12px;
line-height: 1;
cursor: pointer;
color: hsla(0,0%,54.9%,.8);
transition: color .1s;
}

View File

@@ -17,6 +17,13 @@ module.exports = function(){
path: 'mdConfig/get', path: 'mdConfig/get',
action: 'getConfig' action: 'getConfig'
}); });
// 配置信息
addRouter({
controller: exportMarkdownController,
method: 'get',
path: 'mdConfig/gen',
action: 'mdGen'
});
addRouter({ addRouter({
controller: exportMarkdownController, controller: exportMarkdownController,
method: 'post', method: 'post',

View File

@@ -6,6 +6,7 @@ import './Services.scss';
import {withRouter} from "react-router-dom"; import {withRouter} from "react-router-dom";
import axios from "axios"; import axios from "axios";
import {message} from "antd"; import {message} from "antd";
import copy from "copy-to-clipboard";
@connect( @connect(
state => { state => {
@@ -47,7 +48,7 @@ class Services extends Component {
} }
} }
async preCopy(code) { async preCopy(code) {
await navigator.clipboard.writeText(code) copy(code)
message.success("复制成功") message.success("复制成功")
} }
render() { render() {