markdown渲染功能
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
import mdTemplate from './mdTemplate/mdTemplate.js'
|
||||
import Services from "./mdTemplate/Services";
|
||||
|
||||
function exportData(exportDataModule, pid) {
|
||||
exportDataModule.markdown = {
|
||||
@@ -17,7 +18,18 @@ function hander(routers) {
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
module.exports = function() {
|
||||
this.bindHook('export_data', exportData);
|
||||
this.bindHook('sub_setting_nav', hander);
|
||||
|
||||
|
||||
this.bindHook('interface_tab', function (tabs) {
|
||||
tabs.mdGen = {
|
||||
name: 'Markdown',
|
||||
component: Services
|
||||
}
|
||||
})
|
||||
};
|
||||
|
||||
@@ -2,6 +2,7 @@ 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 userModel = require("models/user.js");
|
||||
const yapi = require('yapi.js');
|
||||
const uuid = require('uuid');
|
||||
const configModel = require("./configModel");
|
||||
@@ -9,199 +10,240 @@ 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);
|
||||
constructor(ctx) {
|
||||
super(ctx);
|
||||
this.catModel = yapi.getInst(interfaceCatModel);
|
||||
this.interModel = yapi.getInst(interfaceModel);
|
||||
this.projectModel = yapi.getInst(projectModel);
|
||||
this.configModel = yapi.getInst(configModel);
|
||||
this.userModel = yapi.getInst(userModel);
|
||||
}
|
||||
|
||||
/*
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
handleListClass,handleExistId is same as the exportController(yapi-plugin-export-data).
|
||||
No DRY,but i have no idea to optimize it.
|
||||
*/
|
||||
return newResult;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
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;
|
||||
|
||||
return newResult;
|
||||
if (typeof fn === 'function') fn(item);
|
||||
});
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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 templateData = await this.configModel.getByProjectId(pid);
|
||||
// console.log(result);
|
||||
if (!templateData.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);
|
||||
let userData = await this.userModel.findById(this.getUid());
|
||||
|
||||
|
||||
async exportData(ctx) {
|
||||
let pid = ctx.request.query.pid;
|
||||
let status = ctx.request.query.status;
|
||||
// console.log("curProject", curProject)
|
||||
// console.log("list", list)
|
||||
|
||||
if (!pid) {
|
||||
ctx.body = yapi.commons.resReturn(null, 200, 'pid 不为空');
|
||||
|
||||
let model;
|
||||
|
||||
// const tmpPath = fs.mkdtempSync(path.join(os.tmpdir(),uuid.v4()));
|
||||
// console.log("tmpPath=", tmpPath)
|
||||
const zip = JSZip();
|
||||
let allErrMsg = [];
|
||||
for (let item of data) {
|
||||
for (let interfaceItem of item.list) {
|
||||
let {result, errMsg} = await this.executeJsRender(templateData.template_data, curProject, interfaceItem, item, userData)
|
||||
zip.file(`${item.name}/${interfaceItem.title}.md`, result.toString())
|
||||
if (errMsg){
|
||||
allErrMsg.push(errMsg)
|
||||
}
|
||||
}
|
||||
}
|
||||
if (allErrMsg.length) {
|
||||
ctx.body = yapi.commons.resReturn(allErrMsg, 502, '下载出错');
|
||||
return;
|
||||
}
|
||||
|
||||
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 ;
|
||||
ctx.set('Content-Disposition', `attachment; filename=${encodeURIComponent(curProject.name)}.zip`);
|
||||
let dataaa = await zip.generateAsync({
|
||||
type: "nodebuffer",
|
||||
// 压缩算法
|
||||
compression: "DEFLATE",
|
||||
streamFiles: true,
|
||||
compressionOptions: {
|
||||
level: 9
|
||||
}
|
||||
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);
|
||||
});
|
||||
ctx.body = dataaa
|
||||
// ctx.set('Content-Length', dataaa.length);
|
||||
|
||||
let data = this.handleExistId(list);
|
||||
// dataaa.copy(ctx.body)
|
||||
|
||||
// console.log("curProject", curProject)
|
||||
// console.log("list", list)
|
||||
// fs.unlinkSync(tmpPath)
|
||||
} catch (error) {
|
||||
yapi.commons.log(error, 'error');
|
||||
ctx.body = yapi.commons.resReturn(null, 502, '下载出错');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
let model;
|
||||
|
||||
// const tmpPath = fs.mkdtempSync(path.join(os.tmpdir(),uuid.v4()));
|
||||
// console.log("tmpPath=", tmpPath)
|
||||
const zip = JSZip();
|
||||
let errMsg = [];
|
||||
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(`${item.name}/${interfaceItem.title}.md`, model.toString())
|
||||
safeVm.destroy();
|
||||
} catch (e) {
|
||||
|
||||
errMsg.push({
|
||||
error: e.toString(),
|
||||
context: vmContext
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
if (errMsg.length){
|
||||
ctx.body = yapi.commons.resReturn(errMsg, 502, '下载出错');
|
||||
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'));
|
||||
}
|
||||
|
||||
/**
|
||||
* 保存配置信息
|
||||
* @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));
|
||||
if ((await this.checkAuth(projectId, 'project', 'edit')) !== true) {
|
||||
return (ctx.body = yapi.commons.resReturn(null, 405, '没有权限'));
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询配置信息
|
||||
* @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));
|
||||
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));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 查询配置信息
|
||||
* @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;
|
||||
|
||||
@@ -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);
|
||||
@@ -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;
|
||||
}
|
||||
@@ -17,6 +17,13 @@ module.exports = function(){
|
||||
path: 'mdConfig/get',
|
||||
action: 'getConfig'
|
||||
});
|
||||
// 配置信息
|
||||
addRouter({
|
||||
controller: exportMarkdownController,
|
||||
method: 'get',
|
||||
path: 'mdConfig/gen',
|
||||
action: 'mdGen'
|
||||
});
|
||||
addRouter({
|
||||
controller: exportMarkdownController,
|
||||
method: 'post',
|
||||
|
||||
@@ -6,6 +6,7 @@ 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 => {
|
||||
@@ -47,7 +48,7 @@ class Services extends Component {
|
||||
}
|
||||
}
|
||||
async preCopy(code) {
|
||||
await navigator.clipboard.writeText(code)
|
||||
copy(code)
|
||||
message.success("复制成功")
|
||||
}
|
||||
render() {
|
||||
|
||||
Reference in New Issue
Block a user