Merge branch 'dev-rewrite' of gitlab.corp.qunar.com:mfe/yapi into dev-rewrite

This commit is contained in:
gaoxiaolin.gao 2018-02-08 17:53:07 +08:00
commit 322ec5aa8e
11 changed files with 351 additions and 256 deletions

View File

@ -1,9 +1,8 @@
import moment from 'moment';
import constants from './constants/variable'
import Mock from 'mockjs'
import json5 from 'json5'
import MockExtra from 'common/mock-extra.js'
import {filter} from 'common/power-string.js'
const moment = require('moment');
const constants = require ('./constants/variable')
const Mock = require('mockjs')
const json5 = require('json5')
const MockExtra = require('common/mock-extra.js')
const Roles = {
0 : 'admin',
@ -165,112 +164,6 @@ exports.entries = (obj) => {
return res;
}
/**
* 作用解析规则串 key 然后根据规则串的规则以及路径找到在 json 中对应的数据
* 规则串$.{key}.{body||params}.{dataPath} 其中 body 为返回数据params 为请求数据datapath 为数据的路径
* 数组$.key.body.data.arr[0]._id (获取 key 所指向请求的返回数据的 arr 数组的第 0 项元素的 _id 属性)
* 对象$.key.body.data.obj._id ((获取 key 所指向请求的返回数据的 obj 对象的 _id 属性))
*
* @param String key 规则串
* @param Object json 数据
* @returns
*/
function simpleJsonPathParse(key, json){
if(!key || typeof key !== 'string' || key.indexOf('$.') !== 0 || key.length <= 2){
return null;
}
let keys = key.substr(2).split(".");
keys = keys.filter(item=>{
return item;
})
for(let i=0, l = keys.length; i< l; i++){
try{
let m = keys[i].match(/(.*?)\[([0-9]+)\]/)
if(m){
json = json[m[1]][m[2]];
}else{
json = json[keys[i]];
}
}catch(e){
json = '';
break;
}
}
return json;
}
function handleMockWord(word) {
if(!word || typeof word !== 'string' || word[0] !== '@') return word;
return Mock.mock(word);
}
/**
*
* @param {*} data
* @param {*} handleValueFn 处理参数值函数
*/
function handleJson(data, handleValueFn) {
if (!data) {
return data;
}
if (typeof data === 'string') {
return handleValueFn(data);
} else if (typeof data === 'object') {
for (let i in data) {
data[i] = handleJson(data[i], handleValueFn);
}
} else {
return data;
}
return data;
}
function handleValueWithFilter(context){
return function(match){
if (match[0] === '@') {
return handleMockWord(match);
} else if (match.indexOf('$.') === 0) {
return simpleJsonPathParse(match, context);
} else{
return match;
}
}
}
function handleFilter(str, match, context){
match = match.trim();
try{
let a= filter(match, handleValueWithFilter(context))
return a;
}catch(err){
return str;
}
}
function handleParamsValue (val, context={}){
const variableRegexp = /\{\{\s*([^}]+?)\}\}/g;
if (!val || typeof val !== 'string') {
return val;
}
val = val.trim()
let match = val.match(/^\{\{([^\}]+)\}\}$/);
if (!match){
if(val[0] ==='@' || val[0] === '$'){
return handleFilter(val, val, context);
}
}else{
return handleFilter(val, match[1], context);
}
return val.replace(variableRegexp, handleFilter)
}
exports.handleJson = handleJson;
exports.handleParamsValue = handleParamsValue;
exports.getMockText = (mockTpl) => {
try{
@ -297,22 +190,4 @@ exports.safeAssign = (Obj, nextObj) => {
}
return result;
}, {});
};
exports.simpleJsonPathParse = simpleJsonPathParse;
exports.handleMockWord = handleMockWord;
exports.joinPath = (domain, joinPath) =>{
let l = domain.length;
if(domain[l - 1] === '/'){
domain = domain.substr(0, l - 1)
}
if(joinPath[0] !== '/'){
joinPath = joinPath.substr(1);
}
return domain + joinPath;
}
exports.safeArray = (arr) => {
return Array.isArray(arr) ? arr : [];
}
};

View File

@ -5,7 +5,8 @@ import { Alert, Modal, Row, Col, Icon, Collapse, Input, Tooltip } from 'antd'
import MockList from './MockList.js'
import MethodsList from './MethodsList.js'
import VariablesSelect from './VariablesSelect.js'
import { handleParamsValue } from '../../common.js'
const {handleParamsValue} = require('common/utils.js')
const Panel = Collapse.Panel;
// 深拷贝

View File

@ -4,15 +4,17 @@ import { Button, Input, Checkbox, Modal, Select, Spin, Icon, Collapse, Tooltip,
import constants from '../../constants/variable.js'
import AceEditor from 'client/components/AceEditor/AceEditor'
import _ from 'underscore'
import { isJson, handleParamsValue, deepCopyJson } from '../../common.js'
import { isJson, deepCopyJson } from '../../common.js'
import ModalPostman from '../ModalPostman/index.js'
import CheckCrossInstall, { initCrossRequest } from './CheckCrossInstall.js'
import './Postman.scss';
import ProjectEnv from '../../containers/Project/Setting/ProjectEnv/index.js';
import { handleParams, checkRequestBodyIsRaw, handleContentType, crossRequest, checkNameIsExistInArray } from './postmanLib.js'
// import { isRegExp } from 'util';
const {handleParamsValue} = require('common/utils.js')
const { handleParams, checkRequestBodyIsRaw, handleContentType, crossRequest, checkNameIsExistInArray } = require('common/postmanLib.js')
const HTTP_METHOD = constants.HTTP_METHOD;
const InputGroup = Input.Group;

View File

@ -1,4 +1,4 @@
export default {
module.exports = {
PAGE_LIMIT: 10, // 默认每页展示10条数据
NAME_LIMIT: 100, // 限制名称的字符长度(中文算两个长度)
HTTP_METHOD: {

View File

@ -8,7 +8,6 @@ import { Tooltip, Icon, Button, Spin, Modal, message, Select, Switch } from 'ant
import { fetchInterfaceColList, fetchCaseList, setColData } from '../../../../reducer/modules/interfaceCol'
import HTML5Backend from 'react-dnd-html5-backend';
import { DragDropContext } from 'react-dnd';
import { json_parse, handleParamsValue } from '../../../../common.js'
import AceEditor from 'client/components/AceEditor/AceEditor';
import * as Table from 'reactabular-table';
import * as dnd from 'reactabular-dnd';
@ -16,9 +15,11 @@ import * as resolve from 'table-resolver';
import axios from 'axios'
import CaseReport from './CaseReport.js'
import _ from 'underscore'
import { handleParams, crossRequest, handleCurrDomain, checkNameIsExistInArray } from 'client/components/Postman/postmanLib.js'
import { initCrossRequest } from 'client/components/Postman/CheckCrossInstall.js'
const { handleParams, crossRequest, handleCurrDomain, checkNameIsExistInArray } = require('common/postmanLib.js')
const {handleParamsValue, json_parse} = require('common/utils.js')
const Option = Select.Option;
@ -145,9 +146,6 @@ class InterfaceColContent extends Component {
item.req_headers = this.handleReqHeader(item.req_headers)
return item;
})
newRows = newRows.sort((n, o) => {
return n.index - o.index;
})
this.setState({
rows: newRows
@ -229,16 +227,7 @@ class InterfaceColContent extends Component {
}
let validRes = [];
// 弃用 mock 字段验证功能
// if (res && typeof res === 'object') {
// if (interfaceData.mock_verify) {
// let tpl = MockExtra(json_parse(interfaceData.res_body), {
// query: interfaceData.req_query,
// body: interfaceData.req_body_form
// })
// validRes = Mock.valid(tpl, res);
// }
// }
let responseData = Object.assign({}, {
status: data.res.status,
body: res,
@ -319,32 +308,6 @@ class InterfaceColContent extends Component {
return obj;
}
getQueryObj = (query, requestParams) => {
query = query || [];
const queryObj = {};
query.forEach(item => {
if (item.name && item.enable) {
queryObj[item.name] = this.handleValue(item.value);
if (requestParams) {
requestParams[item.name] = queryObj[item.name];
}
}
})
return queryObj;
}
getHeadersObj = (headers) => {
headers = headers || [];
const headersObj = {};
headers.forEach(item => {
if (item.name && item.value) {
headersObj[item.name] = this.handleValue(item.value);
}
})
return headersObj;
}
onRow(row) {
return {
rowId: row.id,
@ -555,7 +518,6 @@ class InterfaceColContent extends Component {
return <Button onClick={() => this.openReport(rowData.id)}>测试报告</Button>
}
return <div className="interface-col-table-action">
{/* <Button onClick={() => this.openAdv(rowData.id)} type="primary">高级</Button> */}
{reportFun()}
</div>
}]

View File

@ -1,17 +1,10 @@
import { json_parse, isJson5, handleJson, joinPath, safeArray } from '../../common.js'
import constants from '../../constants/variable.js'
import _ from "underscore"
import URL from 'url';
const utils = require('common/power-string.js').utils;
const { isJson5, json_parse, handleJson, joinPath, safeArray } = require('./utils')
const constants = require('../client/constants/variable.js')
const _ = require("underscore")
const URL = require('url')
const utils = require('./power-string.js').utils;
const HTTP_METHOD = constants.HTTP_METHOD;
exports.checkRequestBodyIsRaw = checkRequestBodyIsRaw;
exports.handleParams = handleParams;
exports.handleContentType = handleContentType;
exports.crossRequest = crossRequest;
exports.handleCurrDomain = handleCurrDomain;
exports.checkNameIsExistInArray = checkNameIsExistInArray;
let httpRequest = () => { };
const ContentTypeMap = {
'application/json': 'json',
@ -20,9 +13,7 @@ const ContentTypeMap = {
'application/html': 'html'
}
// function isNode(){
// return typeof module !== 'undefined' && module.exports
// }
const isNode = typeof global == 'object' && global.global === global;
function handleContentType(headers) {
if (!headers || typeof headers !== 'object') return ContentTypeMap.other;
@ -91,6 +82,9 @@ function sandbox(context = {}, script) {
function crossRequest(defaultOptions, preScript, afterScript) {
let options = Object.assign({}, defaultOptions);
let urlObj = URL.parse(options.url, true), query = {};
if (!isNode) {
httpRequest = window.crossRequest
}
query = Object.assign(query, urlObj.query);
let context = {
pathname: urlObj.pathname,
@ -146,11 +140,13 @@ function crossRequest(defaultOptions, preScript, afterScript) {
}
resolve(data);
}
window.crossRequest(options);
httpRequest(options);
})
}
function handleParams(interfaceData, handleValue, requestParams) {
function paramsToObjectWithEnable(arr) {
const obj = {};
@ -241,4 +237,11 @@ function handleParams(interfaceData, handleValue, requestParams) {
return requestOptions;
}
}
exports.checkRequestBodyIsRaw = checkRequestBodyIsRaw;
exports.handleParams = handleParams;
exports.handleContentType = handleContentType;
exports.crossRequest = crossRequest;
exports.handleCurrDomain = handleCurrDomain;
exports.checkNameIsExistInArray = checkNameIsExistInArray;

155
common/utils.js Normal file
View File

@ -0,0 +1,155 @@
const Mock = require('mockjs')
const filter = require('./power-string.js').filter;
const json5 = require('json5')
/**
* 作用解析规则串 key 然后根据规则串的规则以及路径找到在 json 中对应的数据
* 规则串$.{key}.{body||params}.{dataPath} 其中 body 为返回数据params 为请求数据datapath 为数据的路径
* 数组$.key.body.data.arr[0]._id (获取 key 所指向请求的返回数据的 arr 数组的第 0 项元素的 _id 属性)
* 对象$.key.body.data.obj._id ((获取 key 所指向请求的返回数据的 obj 对象的 _id 属性))
*
* @param String key 规则串
* @param Object json 数据
* @returns
*/
function simpleJsonPathParse(key, json){
if(!key || typeof key !== 'string' || key.indexOf('$.') !== 0 || key.length <= 2){
return null;
}
let keys = key.substr(2).split(".");
keys = keys.filter(item=>{
return item;
})
for(let i=0, l = keys.length; i< l; i++){
try{
let m = keys[i].match(/(.*?)\[([0-9]+)\]/)
if(m){
json = json[m[1]][m[2]];
}else{
json = json[keys[i]];
}
}catch(e){
json = '';
break;
}
}
return json;
}
function handleMockWord(word) {
if(!word || typeof word !== 'string' || word[0] !== '@') return word;
return Mock.mock(word);
}
/**
*
* @param {*} data
* @param {*} handleValueFn 处理参数值函数
*/
function handleJson(data, handleValueFn) {
if (!data) {
return data;
}
if (typeof data === 'string') {
return handleValueFn(data);
} else if (typeof data === 'object') {
for (let i in data) {
data[i] = handleJson(data[i], handleValueFn);
}
} else {
return data;
}
return data;
}
function handleValueWithFilter(context){
return function(match){
if (match[0] === '@') {
return handleMockWord(match);
} else if (match.indexOf('$.') === 0) {
return simpleJsonPathParse(match, context);
} else{
return match;
}
}
}
function handleFilter(str, match, context){
match = match.trim();
try{
let a= filter(match, handleValueWithFilter(context))
return a;
}catch(err){
return str;
}
}
function handleParamsValue (val, context={}){
const variableRegexp = /\{\{\s*([^}]+?)\}\}/g;
if (!val || typeof val !== 'string') {
return val;
}
val = val.trim()
let match = val.match(/^\{\{([^\}]+)\}\}$/);
if (!match){
if(val[0] ==='@' || val[0] === '$'){
return handleFilter(val, val, context);
}
}else{
return handleFilter(val, match[1], context);
}
return val.replace(variableRegexp, handleFilter)
}
exports.handleJson = handleJson;
exports.handleParamsValue = handleParamsValue;
exports.simpleJsonPathParse = simpleJsonPathParse;
exports.handleMockWord = handleMockWord;
exports.joinPath = (domain, joinPath) =>{
let l = domain.length;
if(domain[l - 1] === '/'){
domain = domain.substr(0, l - 1)
}
if(joinPath[0] !== '/'){
joinPath = joinPath.substr(1);
}
return domain + joinPath;
}
exports.safeArray = (arr) => {
return Array.isArray(arr) ? arr : [];
}
exports.isJson5 = function isJson5(json){
if(!json) return false;
try{
json = json5.parse(json);
return json;
}catch(e){
return false;
}
}
function isJson(json){
if(!json) return false;
try{
json = JSON.parse(json);
return json;
}catch(e){
return false;
}
}
exports.isJson = isJson
exports.json_parse = function(json){
try{
return JSON.parse(json);
}catch(err){
return json;
}
}

View File

@ -124,42 +124,16 @@ class interfaceColController extends baseController {
if (!id || id == 0) {
return ctx.body = yapi.commons.resReturn(null, 407, 'col_id不能为空')
}
let resultList = await this.caseModel.list(id, 'all');
let colData = await this.colModel.get(id);
let project = await this.projectModel.getBaseInfo(colData.project_id);
if (project.project_type === 'private') {
if (await this.checkAuth(project._id, 'project', 'view') !== true) {
return ctx.body = yapi.commons.resReturn(null, 406, '没有权限');
}
}
for (let index = 0; index < resultList.length; index++) {
let result = resultList[index].toObject();
let data = await this.interfaceModel.get(result.interface_id);
if (!data) {
await this.caseModel.del(result._id);
continue;
}
let projectData = await this.projectModel.getBaseInfo(data.project_id);
result.path = projectData.basepath + data.path;
result.method = data.method;
result.req_body_type = data.req_body_type;
result.req_headers = this.handleParamsValue(data.req_headers, result.req_headers);
result.res_body_type = data.res_body_type;
result.req_body_form = this.handleParamsValue(data.req_body_form, result.req_body_form)
result.req_query = this.handleParamsValue(data.req_query, result.req_query)
result.req_params = this.handleParamsValue(data.req_params, result.req_params)
resultList[index] = result;
}
resultList = resultList.sort((a, b) => {
return a.index - b.index;
});
let ctxBody = yapi.commons.resReturn(resultList);
ctxBody.colData = colData;
ctx.body = ctxBody;
ctx.body = await yapi.commons.getCaseList(id);
} catch (e) {
ctx.body = yapi.commons.resReturn(null, 402, e.message);
}
@ -577,12 +551,12 @@ class interfaceColController extends baseController {
result.path = projectData.basepath + data.path;
result.method = data.method;
result.req_body_type = data.req_body_type;
result.req_headers = this.handleParamsValue(data.req_headers, result.req_headers);
result.req_headers = yapi.commons.handleParamsValue(data.req_headers, result.req_headers);
result.res_body = data.res_body;
result.res_body_type = data.res_body_type;
result.req_body_form = this.handleParamsValue(data.req_body_form, result.req_body_form)
result.req_query = this.handleParamsValue(data.req_query, result.req_query)
result.req_params = this.handleParamsValue(data.req_params, result.req_params)
result.req_body_form = yapi.commons.handleParamsValue(data.req_body_form, result.req_body_form)
result.req_query = yapi.commons.handleParamsValue(data.req_query, result.req_query)
result.req_params = yapi.commons.handleParamsValue(data.req_params, result.req_params)
result.interface_up_time = data.up_time;
ctx.body = yapi.commons.resReturn(result);
} catch (e) {
@ -590,31 +564,7 @@ class interfaceColController extends baseController {
}
}
/**
*
* @param {*} params 接口定义的参数
* @param {*} val 接口case 定义的参数值
*/
handleParamsValue(params, val) {
let value = {};
try {
params = params.toObject();
} catch (e) { }
if (params.length === 0 || val.length === 0) {
return params;
}
val.forEach((item, index) => {
value[item.name] = item;
})
params.forEach((item, index) => {
if (!value[item.name] || typeof value[item.name] !== 'object') return null;
params[index].value = value[item.name].value;
if (!_.isUndefined(value[item.name].enable)) {
params[index].enable = value[item.name].enable
}
})
return params;
}
/**
* 更新一个接口集name或描述

View File

@ -0,0 +1,66 @@
const projectModel = require('../models/project.js');
const interfaceColModel = require('../models/interfaceCol.js');
const interfaceCaseModel = require('../models/interfaceCase.js');
const interfaceModel = require('../models/interface.js');
const yapi = require('../yapi.js');
const baseController = require('./base.js');
const { handleParams, crossRequest, handleCurrDomain, checkNameIsExistInArray } = require('../../common/postmanLib')
class openController extends baseController{
constructor(ctx){
super(ctx)
this.projectModel = yapi.getInst(projectModel)
this.interfaceColModel = yapi.getInst(interfaceColModel)
this.interfaceCaseModel = yapi.getInst(interfaceCaseModel)
this.interfaceModel = yapi.getInst(interfaceModel)
this.schemaMap = {
runAutoTest: {
'*id': 'number'
}
}
}
async projectInterfaceData(ctx){
ctx.body = 'projectInterfaceData'
}
async runAutoTest(ctx){
let id = ctx.params.id;
let curEnv = ctx.params.env_name;
let colData = await this.interfaceColModel.get(id);
let projectId = colData.project_id;
let projectData = await this.projectModel.get(projectId);
let caseList = await yapi.commons.getCaseList(id);
if(caseList.errcode !== 0){
ctx.body = caseList
}
caseList = caseList.data;
caseList = caseList.map(item=>{
item.id = item._id;
item.case_env = curEnv || item.case_env;
item.req_headers = this.handleReqHeader(item.req_headers, projectData.env, curEnv)
return item;
})
}
handleReqHeader(req_header, envData, curEnvName){
// console.log('env', env);
let currDomain = handleCurrDomain(envData, curEnvName);
let header = currDomain.header;
header.forEach(item => {
if (!checkNameIsExistInArray(item.name, req_header)) {
item.abled = true;
req_header.push(item)
}
})
return req_header
}
}
module.exports = openController;

View File

@ -9,7 +9,8 @@ const yapi = require('./yapi.js');
const projectController = require('./controllers/project.js');
const logController = require('./controllers/log.js');
const followController = require('./controllers/follow.js');
const { createAction } = require("./utils/commons.js")
const openController = require('./controllers/open.js');
const { createAction } = require("./utils/commons.js");
const router = koaRouter();
@ -45,6 +46,10 @@ let INTERFACE_CONFIG = {
test: {
prefix: '/test/',
controller: testController
},
open: {
prefix: '/open/',
controller: openController
}
};
@ -435,7 +440,16 @@ let routerConfig = {
path: "http/code",
method: "post"
}
]
],
open: [{
action: "projectInterfaceData",
path: "project_interface_data",
method: "get"
},{
action: "runAutoTest",
path: "run_auto_test",
method: "get"
}]
}
let pluginsRouterPath = [];

View File

@ -3,6 +3,10 @@ const path = require('path');
const yapi = require('../yapi.js');
const sha1 = require('sha1');
const logModel = require('../models/log.js');
const projectModel = require('../models/project.js');
const interfaceColModel = require('../models/interfaceCol.js');
const interfaceCaseModel = require('../models/interfaceCase.js');
const interfaceModel = require('../models/interface.js');
const json5 = require('json5');
const _ = require('underscore');
const Ajv = require('ajv');
@ -376,4 +380,67 @@ exports.createAction = (router, baseurl, routerController, action, path, method,
}
});
}
/**
*
* @param {*} params 接口定义的参数
* @param {*} val 接口case 定义的参数值
*/
function handleParamsValue (params, val){
let value = {};
try {
params = params.toObject();
} catch (e) { }
if (params.length === 0 || val.length === 0) {
return params;
}
val.forEach((item) => {
value[item.name] = item;
})
params.forEach((item, index) => {
if (!value[item.name] || typeof value[item.name] !== 'object') return null;
params[index].value = value[item.name].value;
if (!_.isUndefined(value[item.name].enable)) {
params[index].enable = value[item.name].enable
}
})
return params;
}
exports.handleParamsValue = handleParamsValue;
exports.getCaseList = async function getCaseList(id) {
const caseInst = yapi.getInst(interfaceCaseModel)
const colInst = yapi.getInst(interfaceColModel)
const projectInst = yapi.getInst(projectModel)
const interfaceInst = yapi.getInst(interfaceModel)
let resultList = await caseInst.list(id, 'all');
let colData = await colInst.get(id);
for (let index = 0; index < resultList.length; index++) {
let result = resultList[index].toObject();
let data = await interfaceInst.get(result.interface_id);
if (!data) {
await caseInst.del(result._id);
continue;
}
let projectData = await projectInst.getBaseInfo(data.project_id);
result.path = projectData.basepath + data.path;
result.method = data.method;
result.req_body_type = data.req_body_type;
result.req_headers = handleParamsValue(data.req_headers, result.req_headers);
result.res_body_type = data.res_body_type;
result.req_body_form = handleParamsValue(data.req_body_form, result.req_body_form)
result.req_query = handleParamsValue(data.req_query, result.req_query)
result.req_params = handleParamsValue(data.req_params, result.req_params)
resultList[index] = result;
}
resultList = resultList.sort((a, b) => {
return a.index - b.index;
});
let ctxBody = yapi.commons.resReturn(resultList);
ctxBody.colData = colData;
return ctxBody;
}