feat: pre-script 支持持久化数据存储,兼容浏览器和服务端

This commit is contained in:
sean 2019-04-21 17:45:14 +08:00
parent 708d88a5a8
commit dedb73bad9
11 changed files with 195 additions and 34 deletions

View File

@ -1,4 +1,5 @@
import React, { PureComponent as Component } from 'react';
import { connect } from 'react-redux';
import PropTypes from 'prop-types';
import {
Button,
@ -35,6 +36,8 @@ const {
checkNameIsExistInArray
} = require('common/postmanLib.js');
const createContext = require('common/createContext')
const HTTP_METHOD = constants.HTTP_METHOD;
const InputGroup = Input.Group;
const Option = Select.Option;
@ -105,11 +108,21 @@ ParamsNameComponent.propTypes = {
name: PropTypes.string
};
@connect(
state => {
return {
curUid: state.user.uid
};
}
)
export default class Run extends Component {
static propTypes = {
data: PropTypes.object, //接口原有数据
save: PropTypes.func, //保存回调方法
type: PropTypes.string //enum[case, inter], 判断是在接口页面使用还是在测试集
type: PropTypes.string, //enum[case, inter], 判断是在接口页面使用还是在测试集
curUid: PropTypes.number.isRequired,
interfaceId: PropTypes.number.isRequired,
projectId: PropTypes.number.isRequired
};
constructor(props) {
@ -300,7 +313,12 @@ export default class Run extends Component {
result;
try {
result = await crossRequest(options, this.state.pre_script, this.state.after_script);
options.taskId = this.props.curUid;
result = await crossRequest(options, this.state.pre_script, this.state.after_script, createContext(
this.props.curUid,
this.props.projectId,
this.props.interfaceId
));
result = {
header: result.res.header,
body: result.res.body,

View File

@ -221,6 +221,8 @@ export default class InterfaceCaseContent extends Component {
saveTip="更新保存修改"
save={this.updateCase}
ref={this.savePostmanRef}
interfaceId={currCase.interface_id}
projectId={currCase.project_id}
/>
)}
</div>

View File

@ -36,6 +36,7 @@ import CaseEnv from 'client/components/CaseEnv';
import Label from '../../../../components/Label/Label.js';
const Option = Select.Option;
const createContext = require('common/createContext')
import copy from 'copy-to-clipboard';
@ -64,7 +65,8 @@ function handleReport(json) {
token: state.project.token,
envList: state.interfaceCol.envList,
curProjectRole: state.project.currProject.role,
projectEnv: state.project.projectEnv
projectEnv: state.project.projectEnv,
curUid: state.user.uid
};
},
{
@ -98,7 +100,8 @@ class InterfaceColContent extends Component {
getEnv: PropTypes.func,
projectEnv: PropTypes.object,
fetchCaseEnvList: PropTypes.func,
envList: PropTypes.array
envList: PropTypes.array,
curUid: PropTypes.number
};
constructor(props) {
@ -309,7 +312,12 @@ class InterfaceColContent extends Component {
};
try {
let data = await crossRequest(options, interfaceData.pre_script, interfaceData.after_script);
let data = await crossRequest(options, interfaceData.pre_script, interfaceData.after_script, createContext(
this.props.curUid,
this.props.match.params.id,
interfaceData.interface_id
));
options.taskId = this.props.curUid;
let res = (data.res.body = json_parse(data.res.body));
result = {
...options,

View File

@ -95,6 +95,8 @@ export default class Run extends Component {
saveTip="保存到集合"
save={() => this.setState({ saveCaseModalVisible: true })}
ref={this.savePostmanRef}
interfaceId={currInterface._id}
projectId={currInterface.project_id}
/>
<AddColModal
visible={this.state.saveCaseModalVisible}

14
common/createContext.js Normal file
View File

@ -0,0 +1,14 @@
module.exports = function (uid, projectId,interfaceId) {
if(!uid || !projectId || !interfaceId){
console.error('uid projectId interfaceId 不能为空', uid, projectId,interfaceId)
}
/**
* 统一转换为number
*/
return {
uid: +uid,
projectId: +projectId,
interfaceId: +interfaceId
}
}

View File

@ -20,6 +20,32 @@ const ContentTypeMap = {
other: 'text'
};
const getStorage = async (id)=>{
try{
if(isNode){
let storage = global.storageCreator(id);
let data = await storage.getItem();
return {
getItem: (name)=> data[name],
setItem: (name, value)=>{
data[name] = value;
storage.setItem(name, value)
}
}
}else{
return {
getItem: (name)=> window.localStorage.getItem(name),
setItem: (name, value)=> window.localStorage.setItem(name, value)
}
}
}catch(e){
console.error(e)
return (...args)=>{
console.error(...args)
}
}
}
async function httpRequestByNode(options) {
function handleRes(response) {
if (!response || typeof response !== 'object') {
@ -207,12 +233,21 @@ function sandboxByBrowser(context = {}, script) {
return context;
}
async function crossRequest(defaultOptions, preScript, afterScript) {
/**
*
* @param {*} defaultOptions
* @param {*} preScript
* @param {*} afterScript
* @param {*} commonContext 负责传递一些业务信息crossRequest 不关注具体传什么只负责当中间人
*/
async function crossRequest(defaultOptions, preScript, afterScript, commonContext = {}) {
let options = Object.assign({}, defaultOptions);
const taskId = options.taskId || Math.random() + '';
let urlObj = URL.parse(options.url, true),
query = {};
query = Object.assign(query, urlObj.query);
let context = {
...commonContext,
get href() {
return urlObj.href;
},
@ -239,7 +274,8 @@ async function crossRequest(defaultOptions, preScript, afterScript) {
query: query,
requestHeader: options.headers || {},
requestBody: options.data,
promise: false
promise: false,
storage: await getStorage(taskId)
};
context.utils = Object.freeze({
@ -395,7 +431,7 @@ function handleParams(interfaceData, handleValue, requestParams) {
}
}
} catch (e) {
console.log('err', e);
console.error('err', e);
}
if (HTTP_METHOD[interfaceRunData.method].request_body) {

View File

@ -10,6 +10,7 @@ const mockServer = require('./middleware/mockServer.js');
const plugins = require('./plugin.js');
const websockify = require('koa-websocket');
const websocket = require('./websocket.js');
const storageCreator = require('./utils/storage')
const Koa = require('koa');
const koaStatic = require('koa-static');
@ -17,6 +18,7 @@ const koaStatic = require('koa-static');
const koaBody = require('koa-body');
const router = require('./router.js');
global.storageCreator = storageCreator;
let indexFile = process.argv[2] === 'dev' ? 'dev.html' : 'index.html';
const app = websockify(new Koa());

View File

@ -18,6 +18,7 @@ const renderToHtml = require('../utils/reportHtml');
const axios = require('axios');
const HanldeImportData = require('../../common/HandleImportData');
const _ = require('underscore');
const createContex = require('../../common/createContext')
/**
* {
@ -307,7 +308,12 @@ class openController extends baseController {
validRes: []
};
try {
let data = await crossRequest(options, interfaceData.pre_script, interfaceData.after_script);
options.taskId = this.getUid();
let data = await crossRequest(options, interfaceData.pre_script, interfaceData.after_script,createContex(
this.getUid(),
interfaceData.project_id,
interfaceData.interface_id
));
let res = data.res;
result = Object.assign(result, {

70
server/models/storage.js Normal file
View File

@ -0,0 +1,70 @@
const baseModel = require('./base.js');
const mongoose = require('mongoose');
class stroageModel extends baseModel {
constructor() {
super()
let storageCol = mongoose.connection.db.collection('storage');
storageCol.createIndex(
{
key: 1
},
{
unique: true
}
);
}
getName() {
return 'storage';
}
getSchema() {
return {
key: { type: Number, required: true },
data: {
type: String,
default: ''
} //用于原始数据存储
};
}
save(key, data = {}, isInsert = false) {
let saveData = {
key,
data: JSON.stringify(data, null, 2)
};
if(isInsert){
let r = new this.model(saveData);
return r.save();
}
return this.model.updateOne({
key
}, saveData)
}
del(key) {
return this.model.remove({
key
});
}
get(key) {
return this.model
.findOne({
key
})
.exec().then(data => {
this.save(key, {})
if (!data) return null;
data = data.toObject().data;
try {
return JSON.parse(data)
} catch (e) {
return {}
}
});
}
}
module.exports = stroageModel;

View File

@ -521,33 +521,10 @@ function convertString(variable) {
}
}
const _cache = {}
function creater(key){
const _storage ={
getItem(key){
return _storage[key]
},
setItem(key, value){
_storage[key] = value;
}
}
if(!_cache[key]){
_cache[key] = _storage;
setTimeout(()=>{
delete _cache[key];
}, 1000* 3600) //一个小时后释放掉,防止长时间调用内存导致内存占用过大
return _storage;
}else{
return _cache[key]
}
}
exports.runCaseScript = async function runCaseScript(params, colId, interfaceId, userId) {
exports.runCaseScript = async function runCaseScript(params, colId, interfaceId) {
const colInst = yapi.getInst(interfaceColModel);
let colData = await colInst.get(colId);
let localStorage = creater(colId + '/' +userId);
const logs = [];
const context = {
assert: require('assert'),
@ -556,7 +533,6 @@ exports.runCaseScript = async function runCaseScript(params, colId, interfaceId,
header: params.response.header,
records: params.records,
params: params.params,
localStorage,
log: msg => {
logs.push('log: ' + convertString(msg));
}

27
server/utils/storage.js Normal file
View File

@ -0,0 +1,27 @@
module.exports = function storageCreator(id) {
const storageModel = require('../models/storage.js');
const yapi = require('../yapi.js');
const defaultData = {}
return {
getItem: async (name = '') => {
let inst = yapi.getInst(storageModel);
let data = await inst.get(id);
data = data || defaultData;
if (name) return data[name];
return data;
},
setItem: async (name, value) => {
let inst = yapi.getInst(storageModel);
let data = await inst.get(id) || defaultData;
let result;
data[name] = value;
if(!data){
result = await inst.save(id, data, true)
}else{
result = await inst.save(id, data, false)
}
return result;
}
}
}