Merge branch 'dev-mock' into dev

This commit is contained in:
suxiaoxin 2017-10-23 10:21:51 +08:00
commit 474658c3b7
28 changed files with 893 additions and 77 deletions

View File

@ -194,6 +194,23 @@ function handleMockWord(word) {
return Mock.mock(word);
}
/**
* 合并后新的对象属性与 Obj 一致nextObj 有对应属性则取 nextObj 属性值否则取 Obj 属性值
* @param {Object} Obj 旧对象
* @param {Object} nextObj 新对象
* @return {Object} 合并后的对象
*/
exports.safeAssign = (Obj, nextObj) => {
let keys = Object.keys(nextObj);
return Object.keys(Obj).reduce((result, value) => {
if (keys.indexOf(value) >= 0) {
result[value] = nextObj[value];
} else {
result[value] = Obj[value];
}
return result;
}, {});
};
exports.simpleJsonPathParse = simpleJsonPathParse;
exports.handleMockWord = handleMockWord;

View File

@ -0,0 +1,30 @@
import axios from 'axios'
// Actions
const FETCH_MOCK_COL = 'yapi/mockCol/FETCH_MOCK_COL';
// Reducer
const initialState = {
list: []
}
export default (state = initialState, action) => {
switch (action.type) {
case FETCH_MOCK_COL:
return {
...state,
list: action.payload.data.data
}
default:
return state
}
}
// Action Creators
export async function fetchMockCol(interfaceId) {
let result = await axios.get('/api/plugin/advmock/case/list?interface_id=' + interfaceId);
return {
type: FETCH_MOCK_COL,
payload: result.data
}
}

View File

@ -8,6 +8,7 @@ import news from './news.js'
import addInterface from './addInterface.js'
import menu from './menu.js'
import follow from './follow.js'
import mockCol from './mockCol.js'
export default combineReducers({
group,
@ -18,5 +19,6 @@ export default combineReducers({
news,
addInterface,
menu,
follow
follow,
mockCol
})

View File

@ -0,0 +1,249 @@
import React, { Component } from 'react'
// import axios from 'axios'
import PropTypes from 'prop-types'
import { Button, Form, Input, Switch, Select, Icon, Modal } from 'antd';
import { safeAssign } from '../../../client/common.js';
import { httpCodes } from '../index.js';
const Option = Select.Option;
const FormItem = Form.Item;
const formItemLayout = {
labelCol: {
xs: { span: 24 },
sm: { span: 5 }
},
wrapperCol: {
xs: { span: 24 },
sm: { span: 12 }
}
};
@Form.create()
export default class CaseDesModal extends Component {
static propTypes = {
form: PropTypes.object,
caseData: PropTypes.object,
onCancel: PropTypes.func,
onOk: PropTypes.func,
isAdd: PropTypes.bool,
visible: PropTypes.bool
}
constructor(props) {
super(props);
}
preProcess = caseData => {
// const a = {
// interface_id: { type: Number, required: true },
// project_id: {type: Number, required: true},
// ip: {type: String},
// ip_enable: {type: Boolean, default: false},
// name: {type: String, required: true},
// code: {type: Number, default: 200},
// deplay: {type: Number, default: 0},
// headers: [{
// name: {type: String, required: true},
// value: {type: String}
// }],
// params: mongoose.Schema.Types.Mixed,
// uid: String,
// up_time: Number,
// res_body: {type: String, required: true}
// }
const initCaseData = {
ip: '',
ip_enable: false,
name: '',
code: '200',
deplay: 0,
headers: [{name: '', value: ''}],
paramsArr: [{name: '', value: ''}],
res_body: ''
}
caseData.paramsArr = caseData.params && caseData.params.length ? Object.keys(caseData.params).map(key => {
return { name: key, value: caseData.params[key] }
}) : [{name: '', value: ''}];
caseData.headers = caseData.headers && caseData.headers.length ? caseData.headers : [{name: '', value: ''}];
caseData = safeAssign(initCaseData, caseData);
return caseData;
}
endProcess = caseData => {
const headers = [];
const params = {};
caseData.headers.forEach(item => {
if (item.name) {
headers.push({
name: item.name,
value: item.value
})
}
});
caseData.paramsArr.forEach(item => {
if (item.name) {
params[item.name] = item.value
}
})
caseData.headers = headers;
caseData.params = params;
delete caseData.paramsArr;
return caseData;
}
componentDidMount() {
this.props.form.setFieldsValue(this.preProcess(this.props.caseData))
}
componentWillReceiveProps(nextProps) {
if (this.props.caseData !== nextProps.caseData) {
this.props.form.setFieldsValue(this.preProcess(this.props.caseData))
}
}
handleOk = () => {
const form = this.props.form;
this.props.onOk(this.endProcess(form.getFieldsValue()));
}
render() {
const { getFieldDecorator, getFieldValue } = this.props.form;
const { isAdd, visible, onCancel } = this.props;
return (
<Modal
title={isAdd ? '添加期望' : '编辑期望'}
visible={visible}
maskClosable={false}
onOk={this.handleOk}
onCancel={() => onCancel()}
>
<Form>
<FormItem
{...formItemLayout}
label="期望名称"
>
{getFieldDecorator('name', {
rules: [{ required: true, message: '请输入期望名称!' }]
})(
<Input placeholder="请输入期望名称" />
)}
</FormItem>
<FormItem {...formItemLayout} label="IP 过滤">
{getFieldDecorator('ip_enable', {
valuePropName: 'checked',
rules: [{ type: 'boolean' }]
})(
<Switch />
)}
{getFieldDecorator('ip')(
<Input placeholder="请输入过滤的 IP 地址" />
)}
</FormItem>
<FormItem
{...formItemLayout}
label="HTTP CODE"
>
{getFieldDecorator('code')(
<Select search>
{
httpCodes.map(code => <Option key={''+code} value={''+code}>{''+code}</Option>)
}
</Select>
)}
</FormItem>
<FormItem
{...formItemLayout}
label="延时"
>
{getFieldDecorator('deplay', {
initialValue: 0,
rules: [{ required: true, message: '请输入延时时间!', type: 'integer' }]
})(
<Input placeholder="请输入延时时间" />
)}
</FormItem>
{
getFieldDecorator('headers', { initialValue: [] }) &&
getFieldValue('headers').map((item, index) => (
<div key={index}>
<FormItem
{...formItemLayout}
label={index ? '' : 'HTTP 头'}
>
{getFieldDecorator(`headers[${index}].name`)(
<Input />
)}
</FormItem>
<FormItem
{...formItemLayout}
>
{getFieldDecorator(`headers[${index}].value`)(
<Input />
)}
{getFieldValue('headers').length > 1 ? (
<Icon
className="dynamic-delete-button"
type="minus-circle-o"
onClick={() => this.removeHeaders(index)}
/>
) : null}
</FormItem>
</div>
))
}
<FormItem>
<Button type="dashed" onClick={this.addHeaders} style={{ width: '60%' }}>
<Icon type="plus" /> 添加 HTTP
</Button>
</FormItem>
{
getFieldDecorator('paramsArr', { initialValue: [] }) &&
getFieldValue('paramsArr').map((item, index) => (
<div key={index}>
<FormItem
{...formItemLayout}
label={index ? '' : '参数'}
>
{getFieldDecorator(`paramsArr[${index}].name`)(
<Input />
)}
</FormItem>
<FormItem
>
{getFieldDecorator(`paramsArr[${index}].value`)(
<Input />
)}
{getFieldValue('paramsArr').length > 1 ? (
<Icon
className="dynamic-delete-button"
type="minus-circle-o"
onClick={() => this.removeParams(index)}
/>
) : null}
</FormItem>
</div>
))
}
<FormItem>
<Button type="dashed" onClick={this.addParams} style={{ width: '60%' }}>
<Icon type="plus" /> 添加参数
</Button>
</FormItem>
<FormItem
{...formItemLayout}
label="返回 JSON"
>
{getFieldDecorator('res_body', {
rules: [{ required: true, message: '请输入期望名称!' }]
})(
<Input placeholder="返回 JSON" />
)}
</FormItem>
</Form>
</Modal>
)
}
}

View File

@ -0,0 +1,111 @@
import React, { Component } from 'react'
import { connect } from 'react-redux'
import axios from 'axios'
import PropTypes from 'prop-types'
import { withRouter } from 'react-router-dom';
import { Table, Button, message } from 'antd';
import { fetchMockCol } from '../../../client/reducer/modules/mockCol'
import { formatTime } from '../../../client/common.js';
import CaseDesModal from './CaseDesModal';
@connect(
state => {
return {
list: state.mockCol.list
}
},
{
fetchMockCol
}
)
@withRouter
export default class MockCol extends Component {
static propTypes = {
list: PropTypes.array,
match: PropTypes.object,
fetchMockCol: PropTypes.func
}
state = {
caseData: {},
caseDesModalVisible: false,
isAdd: false
}
constructor(props) {
super(props);
}
componentWillMount() {
const interfaceId = this.props.match.params.actionId
this.props.fetchMockCol(interfaceId);
}
handleOk = async (caseData) => {
const interface_id = this.props.match.params.action;
const project_id = this.props.match.params.id;
caseData = Object.assign({
...caseData,
interface_id: interface_id,
project_id: project_id
})
if (!this.state.isAdd) {
caseData.id = 0;
}
axios.post('/api/plugin/advmock/case/save', caseData).then(res => {
if (res.data.errcode === 0) {
message.success(this.state.isAdd ? '添加成功' : '保存成功');
} else {
message.error(res.data.errmsg);
}
})
}
saveFormRef = (form) => {
this.form = form;
}
render() {
const data = this.props.list;
const { isAdd, caseData, caseDesModalVisible } = this.state;
const columns = [{
title: '期望名称',
dataIndex: 'name',
key: 'name',
render: text => <a href="#">{text}</a>
}, {
title: 'ip',
dataIndex: 'ip',
key: 'ip'
}, {
title: '创建人',
dataIndex: 'username',
key: 'username'
}, {
title: '编辑时间',
key: 'action',
render: text => formatTime(text)
}, {
title: '操作',
dataIndex: 'address',
key: 'address'
}];
return (
<div style={{ padding: '20px 10px' }}>
<Button type="primary" onClick={() => this.setState({isAdd: true, caseDesModalVisible: true})}>添加期望</Button>
<Table columns={columns} dataSource={data} />
<CaseDesModal
visible={caseDesModalVisible}
isAdd={isAdd}
caseData={caseData}
onOk={this.handleOk}
onCancel={() => this.setState({caseDesModalVisible: false})}
ref={this.saveFormRef}
></CaseDesModal>
</div>
)
}
}

View File

@ -25,13 +25,13 @@ class advMockModel extends baseModel {
}
delByInterfaceId(interface_id) {
return this.model.deleteOne({
return this.model.remove({
interface_id: interface_id
});
}
delByProjectId(project_id){
return this.model.deleteMany({
return this.model.remove({
project_id: project_id
})
}

View File

@ -0,0 +1,75 @@
const yapi = require('yapi.js');
const baseModel = require('models/base.js');
const mongoose = require('mongoose');
class caseModel extends baseModel {
getName() {
return 'adv_mock_case';
}
getSchema() {
return {
interface_id: { type: Number, required: true },
project_id: {type: Number, required: true},
ip: {type: String},
ip_enable: {type: Boolean, default: false},
name: {type: String, required: true},
code: {type: Number, default: 200},
delay: {type: Number, default: 0},
headers: [{
name: {type: String, required: true},
value: {type: String}
}],
params: mongoose.Schema.Types.Mixed,
uid: String,
up_time: Number,
res_body: {type: String, required: true}
};
}
get(data) {
return this.model.findOne(data);
}
list(id){
return this.model.find({
interface_id: id
})
}
delByInterfaceId(interface_id) {
return this.model.remove({
interface_id: interface_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();
}
up(data) {
let id = data.id;
delete data.id;
data.up_time = yapi.commons.time();
return this.model.update({
_id: id
}, data)
}
del(id){
return this.model.remove({
_id: id
})
}
}
module.exports = caseModel;

View File

@ -1,4 +1,4 @@
import AdvMock from './AdvMock.js'
import AdvMock from './MockCol/MockCol.js'
module.exports = function(){
this.bindHook('interface_tab', function(tabs){

View File

@ -1,11 +1,14 @@
const baseController = require('controllers/base.js');
const advModel = require('./model.js');
const advModel = require('./advMockModel.js');
const yapi = require('yapi.js');
const caseModel = require('./caseModel.js');
const config = require('./index.js');
class advMockController extends baseController{
constructor(ctx){
super(ctx);
this.Model = yapi.getInst(advModel);
this.caseModel = yapi.getInst(caseModel);
}
async getMock(ctx){
@ -27,7 +30,6 @@ class advMockController extends baseController{
return ctx.body =yapi.commons.resReturn(null, 408, '缺少project_id');
}
let data = {
interface_id: params.interface_id,
mock_script: params.mock_script || '',
@ -47,6 +49,99 @@ class advMockController extends baseController{
return ctx.body = yapi.commons.resReturn(null, 400, e.message);
}
}
async list(ctx){
let id = ctx.query.interface_id;
if(!id){
return ctx.body = yapi.commons.resReturn(null, 400, '缺少 interface_id');
}
let result = await this.caseModel.list(id);
ctx.body = yapi.commons.resReturn(result);
}
async getCase(ctx){
let id = ctx.query.id;
if(!id){
return ctx.body = yapi.commons.resReturn(null, 400, '缺少 id');
}
let result = await this.caseModel.get({
_id: id
})
ctx.body = yapi.commons.resReturn(result);
}
async saveCase(ctx){
let params = ctx.request.body;
if(!params.interface_id){
return ctx.body =yapi.commons.resReturn(null, 408, '缺少interface_id');
}
if(!params.project_id){
return ctx.body =yapi.commons.resReturn(null, 408, '缺少project_id');
}
let data = {
interface_id: params.interface_id,
project_id: params.project_id,
ip_enable: params.ip_enable,
name: params.name,
params: params.params || [],
uid: this.getUid(),
code: params.code || 200,
delay: params.delay || 0,
headers: params.headers || [],
up_time: yapi.commons.time(),
res_body: params.res_body || '',
ip: params.ip
}
data.code = isNaN(data.code) ? 200 : +data.code;
data.delay = isNaN(data.delay) ? 0 : +data.delay;
if(config.httpCodes.indexOf(data.code) === -1){
return ctx.body =yapi.commons.resReturn(null, 408, '非法的 httpCode');
}
let findRepeat, findRepeatParams;
findRepeatParams = {
project_id: data.project_id,
interface_id: data.interface_id,
ip_enable: data.ip_enable
}
if(data.params && typeof data.params === 'object'){
for(let i in data.params){
findRepeatParams['params.' + i] = data.params[i];
}
}
if(data.ip_enable){
findRepeatParams.ip = data.ip;
}
findRepeat = await this.caseModel.get(findRepeatParams);
if(findRepeat){
return ctx.body = yapi.commons.resReturn(null,400, '已存在的期望');
}
let result;
if(params.id && !isNaN(params.id)){
data.id = +params.id;
result = await this.caseModel.up(data);
}else{
result = await this.caseModel.save(data);
}
return ctx.body = yapi.commons.resReturn(result);
}
async delCase(ctx){
let id = ctx.request.body.id;
if(!id){
return ctx.body =yapi.commons.resReturn(null, 408, '缺少 id');
}
ctx.body = await this.caseModel.del(id);
}
}
module.exports = advMockController;

View File

@ -1,4 +1,7 @@
module.exports = {
server: true,
client: true
client: true,
httpCodes: [
100,101,102,200,201,202,203,204,205,206,207,208,226,300,301,302,303,304,305,307,308,400,401,402,403,404,405,406,407,408,409,410,411,412,413,414,415,416,417,418,422,423,424,426,428,429,431,500,501,502,503,504,505,506,507,508,510,511
]
}

View File

@ -1,19 +1,84 @@
const controller = require('./controller');
const advModel = require('./model.js');
const advModel = require('./advMockModel.js');
const caseModel = require('./caseModel.js');
const yapi = require('yapi.js');
const mongoose = require('mongoose');
const _ = require('underscore');
function arrToObj(arr){
let obj = {};
arr.forEach(item=>{
obj[item.name] = item.value;
})
return obj;
}
module.exports = function(){
yapi.connect.then(function () {
let Col = mongoose.connection.db.collection('adv_mock')
Col.ensureIndex({
Col.createIndex({
interface_id: 1
})
Col.ensureIndex({
Col.createIndex({
project_id: 1
})
let caseCol = mongoose.connection.db.collection('adv_mock_case')
caseCol.createIndex({
interface_id: 1
})
caseCol.createIndex({
project_id: 1
})
})
async function checkCase(ctx, interfaceId){
let reqParams = Object.assign({}, ctx.query, ctx.body);
let caseInst = yapi.getInst(caseModel);
let ip = ctx.ip.match(/\d+.\d+.\d+.\d+/)[0];
let listWithIp =await caseInst.model.find({
interface_id: interfaceId,
ip_enable: true,
ip: ip
}).select('_id params');
let matchList = [];
listWithIp.forEach(item=>{
let params = item.params;
if(_.isMatch(reqParams, params)){
matchList.push(item);
}
})
if(matchList.length === 0){
let list =await caseInst.model.find({
interface_id: interfaceId,
ip_enable: false
}).select('_id params')
list.forEach(item=>{
let params = item.params;
if(_.isMatch(reqParams, item.params)){
matchList.push(item);
}
})
}
if(matchList.length > 0){
let maxItem = _.max(matchList, item=> Object.keys(item.params).length);
return maxItem;
}
return null;
}
async function handleByCase(caseData, context){
let caseInst = yapi.getInst(caseModel);
let result = await caseInst.get({
_id: caseData._id
})
return result;
}
this.bindHook('add_router', function(addRouter){
addRouter({
controller: controller,
@ -27,6 +92,45 @@ module.exports = function(){
path: 'advmock/save',
action: 'upMock'
})
addRouter({
/**
* 保存期望
*/
controller: controller,
method: 'post',
path: 'advmock/case/save',
action: 'saveCase'
})
addRouter({
/**
* 保存期望
*/
controller: controller,
method: 'get',
path: 'advmock/case/get',
action: 'getCase'
})
addRouter({
/**
* 获取期望列表
*/
controller: controller,
method: 'get',
path: 'advmock/case/list',
action: 'list'
})
addRouter({
/**
* 获取期望列表
*/
controller: controller,
method: 'post',
path: 'advmock/case/del',
action: 'delCase'
})
})
this.bindHook('interface_del', async function(id){
let inst = yapi.getInst(advModel);
@ -46,6 +150,15 @@ module.exports = function(){
*/
this.bindHook('mock_after', async function(context){
let interfaceId = context.interfaceData._id;
let caseData = await checkCase(context.ctx, interfaceId);
if(caseData){
let data = await handleByCase(caseData, context);
context.mockJson = data.res_body;
context.resHeader = arrToObj(data.headers);
context.httpCode = data.code;
context.delay = data.delay;
return true;
}
let inst = yapi.getInst(advModel);
let data = await inst.get(interfaceId);
if(!data || !data.enable || !data.mock_script){
@ -56,7 +169,11 @@ module.exports = function(){
header: context.ctx.header,
query: context.ctx.query,
body: context.ctx.request.body,
mockJson: context.mockJson
mockJson: context.mockJson,
params: Object.assign({}, context.ctx.query, context.ctx.request.body),
resHeader: context.resHeader,
httpCode: context.httpCode,
delay: context.httpCode
}
sandbox.cookie = {};
@ -65,6 +182,11 @@ module.exports = function(){
sandbox.cookie[ parts[ 0 ].trim() ] = ( parts[ 1 ] || '' ).trim();
});
sandbox = yapi.commons.sandbox(sandbox, script);
sandbox.delay = isNaN(sandbox.delay) ? 0 : +sandbox.delay;
context.mockJson = sandbox.mockJson;
context.resHeader = sandbox.resHeader;
context.httpCode = sandbox.httpCode;
context.delay = sandbox.delay;
})
}

View File

@ -8,11 +8,12 @@
"install-server": " node server/install.js",
"dev-client": "ykit s -p 4000",
"dev": "npm run dev-server & npm run dev-client",
"start": " node server/app.js"
"start": " node server/app.js",
"test": "ava"
},
"repository": {
"type": "git",
"url": "git@gitlab.corp.qunar.com:mfe/yapi.git"
"url": "git@github.com:YMFE/yapi.git"
},
"author": "",
"license": "Apache2.0",
@ -25,6 +26,7 @@
"json-schema-ref-parser": "^4.0.0",
"json5": "^0.5.1",
"jsonwebtoken": "^7.4.1",
"kerberos": "0.0.23",
"koa": "^2.0.0",
"koa-bodyparser": "^3.2.0",
"koa-multer": "^1.0.2",
@ -37,7 +39,7 @@
"koa-websocket": "^4.0.0",
"mockjs": "^1.0.1-beta3",
"moment": "^2.18.1",
"mongoose": "^4.7.0",
"mongoose": "^4.12.3",
"mongoose-auto-increment": "^5.0.1",
"nodemailer": "^4.0.1",
"ora": "^1.3.0",
@ -50,11 +52,8 @@
"url": "^0.11.0"
},
"devDependencies": {
"happypack": "^4.0.0-beta.5",
"prop-types": "^15.5.10",
"rc-queue-anim": "^1.2.0",
"rc-scroll-anim": "^1.0.7",
"assets-webpack-plugin": "^3.5.1",
"ava": "^0.22.0",
"babel": "^6.5.2",
"babel-core": "^6.8.0",
"babel-eslint": "^7.2.3",
@ -81,10 +80,14 @@
"extract-text-webpack-plugin": "2.0.0",
"fast-sass-loader-china": "^1.2.5",
"ghooks": "^2.0.0",
"happypack": "^4.0.0-beta.5",
"less": "^2.7.2",
"less-loader": "^4.0.5",
"node-sass-china": "^4.5.0",
"nodemon": "^1.11.0",
"prop-types": "^15.5.10",
"rc-queue-anim": "^1.2.0",
"rc-scroll-anim": "^1.0.7",
"react": "^15.6.1",
"react-dnd": "^2.5.1",
"react-dnd-html5-backend": "^2.5.1",
@ -100,6 +103,7 @@
"redux-devtools-log-monitor": "^1.3.0",
"redux-promise": "^0.5.3",
"redux-thunk": "^2.2.0",
"rewire": "^2.5.2",
"string-replace-webpack-plugin": "^0.1.3",
"style-loader": "^0.18.2",
"table-resolver": "^3.2.0",
@ -135,5 +139,10 @@
"engines": {
"node": ">= 7.6.0",
"npm": ">= 4.1.2"
},
"ava": {
"files": [
"test/**/*.js"
]
}
}

View File

@ -178,20 +178,6 @@ class groupController extends baseController {
}
}
// params.role = ['owner', 'dev', 'guest'].find(v => v === params.role) || 'dev';
// var check = await groupInst.checkMemberRepeat(params.id, params.member_uid);
// if (check > 0) {
// return ctx.body = yapi.commons.resReturn(null, 400, '成员已存在');
// }
// let groupUserdata = await this.getUserdata(params.member_uid, params.role);
// if (groupUserdata === null) {
// return ctx.body = yapi.commons.resReturn(null, 400, '组长uid不存在')
// }
// if (groupUserdata._role === 'admin') {
// return ctx.body = yapi.commons.resReturn(null, 400, '不能邀请管理员')
// }
// delete groupUserdata._role;
try {
let result = await groupInst.addMember(params.id, add_members);
let username = this.getUsername();
@ -448,10 +434,14 @@ class groupController extends baseController {
await interfaceCaseInst.delByProjectId(p._id)
await interfaceColInst.delByProjectId(p._id)
})
await projectInst.delByGroupid(id);
if(projectList.length > 0){
await projectInst.delByGroupid(id);
}
let result = await groupInst.del(id);
ctx.body = yapi.commons.resReturn(result);
} catch (err) {
console.error(err);
ctx.body = yapi.commons.resReturn(null, 402, err.message);
}
}

View File

@ -300,6 +300,9 @@ class interfaceController extends baseController {
}
let project = await this.projectModel.getBaseInfo(project_id);
if(!project){
return ctx.body = yapi.commons.resReturn(null, 406, '不存在的项目');
}
if (project.project_type === 'private') {
if (await this.checkAuth(project._id, 'project', 'view') !== true) {
return ctx.body = yapi.commons.resReturn(null, 406, '没有权限');

View File

@ -34,93 +34,93 @@ function setupSql() {
yapi.connect.then(function () {
let userCol = mongoose.connection.db.collection('user')
userCol.ensureIndex({
userCol.createIndex({
username: 1
})
userCol.ensureIndex({
userCol.createIndex({
email: 1
}, {
unique: true
})
let projectCol = mongoose.connection.db.collection('project')
projectCol.ensureIndex({
projectCol.createIndex({
uid: 1
})
projectCol.ensureIndex({
projectCol.createIndex({
name: 1
})
projectCol.ensureIndex({
projectCol.createIndex({
group_id: 1
})
let logCol = mongoose.connection.db.collection('log')
logCol.ensureIndex({
logCol.createIndex({
uid: 1
})
logCol.ensureIndex({
logCol.createIndex({
typeid: 1,
type: 1
})
let interfaceColCol = mongoose.connection.db.collection('interface_col')
interfaceColCol.ensureIndex({
interfaceColCol.createIndex({
uid: 1
})
interfaceColCol.ensureIndex({
interfaceColCol.createIndex({
project_id: 1
})
let interfaceCatCol = mongoose.connection.db.collection('interface_cat')
interfaceCatCol.ensureIndex({
interfaceCatCol.createIndex({
uid: 1
})
interfaceCatCol.ensureIndex({
interfaceCatCol.createIndex({
project_id: 1
})
let interfaceCaseCol = mongoose.connection.db.collection('interface_case')
interfaceCaseCol.ensureIndex({
interfaceCaseCol.createIndex({
uid: 1
})
interfaceCaseCol.ensureIndex({
interfaceCaseCol.createIndex({
col_id: 1
})
interfaceCaseCol.ensureIndex({
interfaceCaseCol.createIndex({
project_id: 1
})
let interfaceCol = mongoose.connection.db.collection('interface')
interfaceCol.ensureIndex({
interfaceCol.createIndex({
uid: 1
})
interfaceCol.ensureIndex({
interfaceCol.createIndex({
path: 1,
method: 1
})
interfaceCol.ensureIndex({
interfaceCol.createIndex({
project_id: 1
})
let groupCol = mongoose.connection.db.collection('group')
groupCol.ensureIndex({
groupCol.createIndex({
uid: 1
})
groupCol.ensureIndex({
groupCol.createIndex({
group_name: 1
})
let avatarCol = mongoose.connection.db.collection('avatar')
avatarCol.ensureIndex({
avatarCol.createIndex({
uid: 1
})
let followCol = mongoose.connection.db.collection('follow')
followCol.ensureIndex({
followCol.createIndex({
uid: 1
})
followCol.ensureIndex({
followCol.createIndex({
project_id: 1
})

View File

@ -5,7 +5,11 @@ const mockExtra = require('../../common/mock-extra.js');
const _ = require('underscore');
const Mock = require('mockjs');
/**
*
* @param {*} apiPath /user/tom
* @param {*} apiRule /user/:username
*/
function matchApi(apiPath, apiRule) {
let apiRules = apiRule.split("/");
let apiPaths = apiPath.split("/");
@ -61,13 +65,12 @@ module.exports = async (ctx, next) => {
return ctx.body = yapi.commons.resReturn(null, 403, e.message);
}
if (project === false) {
if (!project) {
return ctx.body = yapi.commons.resReturn(null, 400, '不存在的项目');
}
let interfaceData, newpath;
let interfaceInst = yapi.getInst(interfaceModel);
try {
newpath = path.substr(project.basepath.length);
interfaceData = await interfaceInst.getByPath(project._id, newpath, ctx.method);
@ -149,9 +152,25 @@ module.exports = async (ctx, next) => {
projectData: project,
interfaceData: interfaceData,
ctx: ctx,
mockJson: res
mockJson: res,
resHeader: {},
httpCode: 200,
delay: 0
}
await yapi.emitHook('mock_after', context);
let handleMock = new Promise(resolve=>{
setTimeout(()=>{
resolve(true)
}, context.delay)
})
await handleMock;
if(context.resHeader && typeof context.resHeader === 'object'){
for(let i in context.resHeader){
ctx.set(i, context.resHeader[i]);
}
}
ctx.status = context.httpCode;
return ctx.body = context.mockJson;
} catch (e) {
yapi.commons.log(e, 'error')

View File

@ -35,7 +35,7 @@ class followModel extends baseModel {
}
del(projectid, uid){
return this.model.deleteOne({
return this.model.remove({
projectid: projectid,
uid: uid
});

View File

@ -114,7 +114,7 @@ class groupModel extends baseModel {
}
del(id) {
return this.model.deleteOne({
return this.model.remove({
_id: id
});
}

View File

@ -140,19 +140,19 @@ class interfaceModel extends baseModel {
}
del(id) {
return this.model.deleteOne({
return this.model.remove({
_id: id
});
}
delByCatid(id){
return this.model.deleteMany({
return this.model.remove({
catid: id
})
}
delByProjectId(id){
return this.model.deleteMany({
return this.model.remove({
project_id: id
})
}

View File

@ -63,25 +63,25 @@ class interfaceCase extends baseModel {
}
del(id) {
return this.model.deleteOne({
return this.model.remove({
_id: id
});
}
delByProjectId(id) {
return this.model.deleteMany({
return this.model.remove({
project_id: id
})
}
delByInterfaceId(id){
return this.model.deleteMany({
return this.model.remove({
interface_id: id
})
}
delByCol(id) {
return this.model.deleteMany({
return this.model.remove({
col_id: id
})
}

View File

@ -44,13 +44,13 @@ class interfaceCat extends baseModel {
}
del(id) {
return this.model.deleteOne({
return this.model.remove({
_id: id
});
}
delByProjectId(id){
return this.model.deleteMany({
return this.model.remove({
project_id: id
})
}

View File

@ -41,13 +41,13 @@ class interfaceCol extends baseModel {
}
del(id) {
return this.model.deleteOne({
return this.model.remove({
_id: id
});
}
delByProjectId(id){
return this.model.deleteMany({
return this.model.remove({
project_id: id
})
}

View File

@ -41,7 +41,7 @@ class logModel extends baseModel {
}
del(id) {
return this.model.deleteOne({
return this.model.remove({
_id: id
});
}

View File

@ -115,13 +115,13 @@ class projectModel extends baseModel {
}
del(id) {
return this.model.deleteOne({
return this.model.remove({
_id: id
});
}
delByGroupid(groupId){
return this.model.deleteMany({
return this.model.remove({
group_id: groupId
})
}

View File

@ -70,7 +70,7 @@ class userModel extends baseModel {
}
del(id) {
return this.model.deleteOne({
return this.model.remove({
_id: id
});
}

View File

@ -160,6 +160,10 @@ exports.filterRes = (list, rules) => {
});
};
/**
* 验证一个 path 是否合法
* path第一位必需为 /, path只容许由 字母数字-/_:. 组成, 最后一位不容许为 /
*/
exports.verifyPath = (path) => {
if (/^\/[a-zA-Z0-9\-\/_:\.]+$/.test(path)) {
if (path[path.length - 1] === '/') {
@ -172,6 +176,15 @@ exports.verifyPath = (path) => {
}
};
/**
* 沙盒执行 js 代码
* @sandbox Object context
* @script String script
* @return sandbox
*
* @example let a = sandbox({a: 1}, 'a=2')
* a = {a: 2}
*/
exports.sandbox = (sandbox, script) => {
const vm = require('vm');
sandbox = sandbox || {};
@ -215,6 +228,12 @@ exports.trim = trim;
exports.ltrim = ltrim;
exports.rtrim = rtrim;
/**
* 处理请求参数类型String 字符串去除两边空格Number 使用parseInt 转换为数字
* @params Object {a: ' ab ', b: ' 123 '}
* @keys Object {a: 'string', b: 'number'}
* @return Object {a: 'ab', b: 123}
*/
exports.handleParams = (params, keys) => {
if (!params || typeof params !== 'object' || !keys || typeof keys !== 'object') {
return false;
@ -226,7 +245,7 @@ exports.handleParams = (params, keys) => {
switch (filter) {
case 'string': params[key] = trim(params[key] + '');
break;
case 'number': params[key] = parseInt(params[key], 10);
case 'number': params[key] =!isNaN(params[key])? parseInt(params[key], 10) : 0;
break;
default: params[key] = trim(params + '');
}

View File

@ -0,0 +1,49 @@
import test from 'ava';
import {
ltrim,
rtrim,
trim,
handleParams,
verifyPath,
sandbox
} from '../../server/utils/commons.js';
test('trim', t => {
t.is(trim(" a b ksjdfk "), 'a b ksjdfk');
t.is(trim(1), '1')
});
test('ltrim', t => {
t.is(ltrim(" a b ksjdfk "), 'a b ksjdfk ');
t.is(ltrim(1), '1')
});
test('rtrim', t => {
t.is(rtrim(" a b ksjdfk "), ' a b ksjdfk');
t.is(rtrim(1), '1')
});
test('handleParams', t=>{
t.deepEqual(handleParams({
a: ' s k ',
b: " a123456 "
}, {
a: 'string',
b: 'number'
}), {
a: 's k',
b: 0
})
})
test('verifyPath', t=>{
t.false(verifyPath('a/b'));
t.true(verifyPath('/a:b/t/.api/k_-/tt'))
t.false(verifyPath('/a:b/t/.api/k_-/tt/'))
})
test('sandbox', t=>{
t.deepEqual(sandbox({
a: 1
}, 'a=2'), {a : 2});
})

View File

@ -0,0 +1,23 @@
import test from 'ava';
const rewire = require("rewire");
const mockServer = rewire('../../server/middleware/mockServer.js');
const matchApi = mockServer.__get__('matchApi');
test('matchApi', t => {
const apiRule = '/user/:username';
t.true(matchApi('/user/tom', apiRule));
t.true(matchApi('/user/111$$%#$##$#2222222222!!!!!!!', apiRule))
t.false(matchApi('/user/a/', apiRule))
t.false(matchApi('/use/a', apiRule))
const apiRule_2 = '/user/:username/kk';
t.true(matchApi('/user/aa/kk', apiRule_2));
t.true(matchApi('/user/!!!###kksdjfks***/kk', apiRule_2));
t.false(matchApi('/user/aa/aa', apiRule_2));
const apiRule_3 = '/user/:sdfsdfj/ttt/:sdkfjkj';
t.true(matchApi('/user/a/ttt/b', apiRule_3));
t.false(matchApi('/user/a/ttt2/b', apiRule_3))
});