This commit is contained in:
喻希里 2017-09-19 14:15:16 +08:00
commit 98e8af85d6
10 changed files with 148 additions and 57 deletions

View File

@ -19,24 +19,24 @@ function json_parse(data) {
} }
} }
function isValidJson(json){ function isValidJson(json) {
if(!json) return false; if (!json) return false;
if(typeof json === 'object') return true; if (typeof json === 'object') return true;
try{ try {
if(typeof json === 'string'){ if (typeof json === 'string') {
json5.parse(json); json5.parse(json);
return true; return true;
} }
}catch(e){ } catch (e) {
return false; return false;
} }
} }
function isJsonData(headers, res){ function isJsonData(headers, res) {
if(isValidJson(res)){ if (isValidJson(res)) {
return true; return true;
} }
if(!headers || typeof headers !== 'object') return false; if (!headers || typeof headers !== 'object') return false;
let isResJson = false; let isResJson = false;
Object.keys(headers).map(key => { Object.keys(headers).map(key => {
if (/content-type/i.test(key) && /application\/json/i.test(headers[key])) { if (/content-type/i.test(key) && /application\/json/i.test(headers[key])) {
@ -192,7 +192,7 @@ export default class Run extends Component {
const href = URL.format({ const href = URL.format({
protocol: urlObj.protocol || 'http', protocol: urlObj.protocol || 'http',
host: urlObj.host, host: urlObj.host,
pathname: urlObj.pathname? urlObj.pathname + path : path, pathname: urlObj.pathname ? urlObj.pathname + path : path,
query: this.getQueryObj(query) query: this.getQueryObj(query)
}); });
@ -207,7 +207,7 @@ export default class Run extends Component {
file: bodyType === 'file' ? 'single-file' : null, file: bodyType === 'file' ? 'single-file' : null,
success: (res, header) => { success: (res, header) => {
try { try {
if(isJsonData(header)){ if (isJsonData(header)) {
res = json_parse(res); res = json_parse(res);
} }
@ -243,9 +243,9 @@ export default class Run extends Component {
}, },
error: (err, header) => { error: (err, header) => {
try { try {
if(isJsonData(header)){ if (isJsonData(header)) {
err = json_parse(err); err = json_parse(err);
} }
} catch (e) { } catch (e) {
message.error(e.message) message.error(e.message)
} }
@ -555,20 +555,24 @@ export default class Run extends Component {
} }
</div> </div>
<Card title="请求部分" noHovering className="req-part"> <Card title={<Tooltip placement="top" title="在项目设置配置domain">请求部分&nbsp;<Icon type="question-circle-o" /></Tooltip>} noHovering className="req-part">
<div className="url"> <div className="url">
<InputGroup compact style={{ display: 'flex' }}> <InputGroup compact style={{ display: 'flex' }}>
<Select disabled value={method} style={{ flexBasis: 60 }} onChange={this.changeMethod} > <Select disabled value={method} style={{ flexBasis: 60 }} onChange={this.changeMethod} >
<Option value="GET">GET</Option> <Option value="GET">GET</Option>
<Option value="POST">POST</Option> <Option value="POST">POST</Option>
</Select> </Select>
<Select value={caseEnv} style={{ flexBasis: 180, flexGrow: 1 }} onSelect={this.selectDomain}> <Select value={caseEnv} style={{ flexBasis: 180, flexGrow: 1 }} onSelect={this.selectDomain}>
{ {
domains.map((item, index) => (<Option value={item.name} key={index}>{item.name + '' + item.domain}</Option>)) domains.map((item, index) => (<Option value={item.name} key={index}>{item.name + '' + item.domain}</Option>))
} }
</Select> </Select>
<Input disabled value={path + search} onChange={this.changePath} spellCheck="false" style={{ flexBasis: 180, flexGrow: 1 }} /> <Input disabled value={path + search} onChange={this.changePath} spellCheck="false" style={{ flexBasis: 180, flexGrow: 1 }} />
</InputGroup> </InputGroup>
<Tooltip placement="bottom" title="请求真实接口"> <Tooltip placement="bottom" title="请求真实接口">
<Button <Button
disabled={!hasPlugin} disabled={!hasPlugin}
@ -636,21 +640,21 @@ export default class Run extends Component {
<Panel <Panel
header={ header={
<div style={{ display: 'flex', justifyContent: 'space-between' }}> <div style={{ display: 'flex', justifyContent: 'space-between' }}>
<div>BODY</div> <div>BODY</div>
</div> </div>
} }
key="3" key="3"
className={HTTP_METHOD[method].request_body?'POST':'hidden'} className={HTTP_METHOD[method].request_body ? 'POST' : 'hidden'}
> >
<div style={{ display: HTTP_METHOD[method].request_body && bodyType !== 'form' && bodyType !== 'file'? 'block': 'none' }}> <div style={{ display: HTTP_METHOD[method].request_body && bodyType !== 'form' && bodyType !== 'file' ? 'block' : 'none' }}>
<div id="body-other-edit" style={{ marginTop: 10, minHeight:150 }} className="pretty-editor"></div> <div id="body-other-edit" style={{ marginTop: 10, minHeight: 150 }} className="pretty-editor"></div>
</div> </div>
{ {
HTTP_METHOD[method].request_body && bodyType === 'form' && HTTP_METHOD[method].request_body && bodyType === 'form' &&
<div> <div>
{ {
bodyForm.map((item, index) => { bodyForm.map((item, index) => {
return ( return (
<div key={index} className="key-value-wrap"> <div key={index} className="key-value-wrap">

View File

@ -131,7 +131,9 @@ class ProjectList extends Component {
}] }]
})( })(
<Select> <Select>
{this.state.groupList.map((item, index) => <Option value={item._id.toString()} key={index}>{item.group_name}</Option>)} {this.state.groupList.map((item, index) => (
<Option disabled={!(item.role === 'dev' || item.role === 'owner')} value={item._id.toString()} key={index}>{item.group_name}</Option>
))}
</Select> </Select>
)} )}
</FormItem> </FormItem>

View File

@ -247,8 +247,8 @@ export default class InterfaceColMenu extends Component {
style={{width: '100%'}} style={{width: '100%'}}
key={'case_' + interfaceCase._id} key={'case_' + interfaceCase._id}
title={ title={
<div className="menu-title"> <div className="menu-title" title={interfaceCase.casename}>
<span>{interfaceCase.casename}</span> <span className="casename">{interfaceCase.casename}</span>
<Icon type='delete' className="case-delete-icon" onClick={() => { this.showDelCaseConfirm(interfaceCase._id) }} /> <Icon type='delete' className="case-delete-icon" onClick={() => { this.showDelCaseConfirm(interfaceCase._id) }} />
</div> </div>
} }

View File

@ -6,7 +6,11 @@
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
overflow: hidden; overflow: hidden;
.casename {
overflow: hidden;
}
.case-delete-icon{ .case-delete-icon{
margin-left: 5px;
display: none; display: none;
} }
i:before{ i:before{

View File

@ -198,6 +198,7 @@ class InterfaceEditForm extends Component {
let editor = this.editor = new Editor('#desc'); let editor = this.editor = new Editor('#desc');
editor.create(); editor.create();
editor.txt.html(this.state.desc)
} }
addParams = (name, data) => { addParams = (name, data) => {
@ -281,7 +282,7 @@ class InterfaceEditForm extends Component {
{getFieldDecorator('req_query[' + index + '].desc', { {getFieldDecorator('req_query[' + index + '].desc', {
initialValue: data.desc initialValue: data.desc
})( })(
<Input placeholder="备注" /> <Input.TextArea autosize={true} placeholder="备注" />
)} )}
</Col> </Col>
<Col span="1" > <Col span="1" >
@ -308,14 +309,14 @@ class InterfaceEditForm extends Component {
{getFieldDecorator('req_headers[' + index + '].value', { {getFieldDecorator('req_headers[' + index + '].value', {
initialValue: data.value initialValue: data.value
})( })(
<Input placeholder="参数值" /> <Input placeholder="参数值" />
)} )}
</Col> </Col>
<Col span="10" > <Col span="10" >
{getFieldDecorator('req_headers[' + index + '].desc', { {getFieldDecorator('req_headers[' + index + '].desc', {
initialValue: data.desc initialValue: data.desc
})( })(
<Input placeholder="备注" /> <Input.TextArea autosize={true} placeholder="备注" />
)} )}
</Col> </Col>
<Col span="2" > <Col span="2" >
@ -358,7 +359,7 @@ class InterfaceEditForm extends Component {
{getFieldDecorator('req_body_form[' + index + '].desc', { {getFieldDecorator('req_body_form[' + index + '].desc', {
initialValue: data.desc initialValue: data.desc
})( })(
<Input placeholder="备注" /> <Input.TextArea autosize={true} placeholder="备注" />
)} )}
</Col> </Col>
<Col span="1" > <Col span="1" >
@ -380,7 +381,7 @@ class InterfaceEditForm extends Component {
{getFieldDecorator('req_params[' + index + '].desc', { {getFieldDecorator('req_params[' + index + '].desc', {
initialValue: data.desc initialValue: data.desc
})( })(
<Input placeholder="备注" /> <Input.TextArea autosize={true} placeholder="备注" />
)} )}
</Col> </Col>

View File

@ -8,24 +8,34 @@
// .item-all-interface { // .item-all-interface {
// background-color: red; // background-color: red;
// } // }
.ant-tabs-bar{ // .ant-tabs-bar{
border-bottom: none; // border-bottom: none;
margin-bottom: 0 // margin-bottom: 0
} // }
.ant-tabs-nav{ .ant-tabs-nav{
width:100%; width:100%;
background-color: #ececec
} }
.ant-tabs-tab{ .ant-tabs-tab{
min-width: 50%; min-width: 50%;
} }
.ant-tabs.ant-tabs-card > .ant-tabs-bar .ant-tabs-tab{ .ant-tabs.ant-tabs-card > .ant-tabs-bar .ant-tabs-tab{
background-color: #fff; height: 39px;
background: #fff;
border: 1px solid #d9d9d9;
border-bottom: 0;
}
.ant-tabs.ant-tabs-card > .ant-tabs-bar .ant-tabs-tab:nth-of-type(2) {
border-left: 0;
}
.ant-tabs.ant-tabs-card > .ant-tabs-bar .ant-tabs-tab:last-of-type {
border-right: 0;
} }
.ant-tabs.ant-tabs-card > .ant-tabs-bar .ant-tabs-tab-active{ .ant-tabs.ant-tabs-card > .ant-tabs-bar .ant-tabs-tab-active{
height: 40px;
background-color: #efefef; background-color: #efefef;
color:#021b2d; color:#021b2d;
font-weight: 500; font-weight: 500;
// margin-bottom: 0;
} }
.ant-tabs.ant-tabs-card > .ant-tabs-bar .ant-tabs-nav-container{ .ant-tabs.ant-tabs-card > .ant-tabs-bar .ant-tabs-nav-container{
@ -34,13 +44,13 @@
.ant-tabs.ant-tabs-card > .ant-tabs-bar{ .ant-tabs.ant-tabs-card > .ant-tabs-bar{
text-align: center; text-align: center;
line-height: 40px; background: #ececec;
height:40px;
} }
.ant-tabs-nav-wrap{ .ant-tabs-nav-wrap{
height: 40px; height: 40px;
line-height: 31px; line-height: 31px;
// border-bottom: 1px solid #d9d9d9;
} }
.ant-input { .ant-input {
width: 100%; width: 100%;

View File

@ -1,6 +1,6 @@
{ {
"name": "yapi", "name": "yapi",
"version": "1.0.0", "version": "1.0.1",
"description": "YAPI", "description": "YAPI",
"main": "index.js", "main": "index.js",
"scripts": { "scripts": {

View File

@ -79,20 +79,22 @@ class interfaceController extends baseController {
let http_path = url.parse(params.path, true); let http_path = url.parse(params.path, true);
params.path = http_path.pathname;
if (!yapi.commons.verifyPath(http_path.pathname)) { if (!yapi.commons.verifyPath(http_path.pathname)) {
return ctx.body = yapi.commons.resReturn(null, 400, '接口path第一位必须是/,最后一位不能为/'); return ctx.body = yapi.commons.resReturn(null, 400, '接口path第一位必须是/,最后一位不能为/');
} }
if (!params.req_query) {
params.req_query = []; params.query_path = {};
Object.keys(http_path.query).forEach((item) => { params.query_path.path = http_path.pathname;
params.req_query.push({ params.query_path.params = [];
name: item Object.keys(http_path.query).forEach((item) => {
}) params.query_path.params.push({
name: item,
value: http_path.query[item]
}) })
} })
let checkRepeat = await this.Model.checkRepeat(params.project_id, params.path, params.method); let checkRepeat = await this.Model.checkRepeat(params.project_id, params.path, params.method);
@ -109,6 +111,7 @@ class interfaceController extends baseController {
path: params.path, path: params.path,
desc: params.desc, desc: params.desc,
method: params.method, method: params.method,
query_path: params.query_path,
req_headers: params.req_headers, req_headers: params.req_headers,
req_body_type: params.req_body_type, req_body_type: params.req_body_type,
res_body: params.res_body, res_body: params.res_body,
@ -118,11 +121,11 @@ class interfaceController extends baseController {
up_time: yapi.commons.time() up_time: yapi.commons.time()
}; };
if (params.req_query) { if (!_.isUndefined(params.req_query)) {
data.req_query = params.req_query; data.req_query = params.req_query;
} }
if (params.req_body_form) { if (!_.isUndefined(params.req_body_form)) {
data.req_body_form = params.req_body_form; data.req_body_form = params.req_body_form;
} }
@ -147,7 +150,7 @@ class interfaceController extends baseController {
} else { } else {
data.type = 'static' data.type = 'static'
} }
if (params.req_body_other) { if (!_.isUndefined(params.req_body_other)) {
data.req_body_other = params.req_body_other; data.req_body_other = params.req_body_other;
} }
@ -350,10 +353,24 @@ class interfaceController extends baseController {
return ctx.body = yapi.commons.resReturn(null, 400, '没有权限'); return ctx.body = yapi.commons.resReturn(null, 400, '没有权限');
} }
if (params.path && !yapi.commons.verifyPath(params.path)) {
let http_path = url.parse(params.path, true);
if (!yapi.commons.verifyPath(http_path.pathname)) {
return ctx.body = yapi.commons.resReturn(null, 400, '接口path第一位必须是/,最后一位不能为/'); return ctx.body = yapi.commons.resReturn(null, 400, '接口path第一位必须是/,最后一位不能为/');
} }
params.query_path = {};
params.query_path.path = http_path.pathname;
params.query_path.params = [];
Object.keys(http_path.query).forEach((item) => {
params.query_path.params.push({
name: item,
value: http_path.query[item]
})
})
if (params.path && (params.path !== interfaceData.path || params.method !== interfaceData.method)) { if (params.path && (params.path !== interfaceData.path || params.method !== interfaceData.method)) {
let checkRepeat = await this.Model.checkRepeat(interfaceData.project_id, params.path, params.method); let checkRepeat = await this.Model.checkRepeat(interfaceData.project_id, params.path, params.method);
if (checkRepeat > 0) { if (checkRepeat > 0) {
@ -362,7 +379,8 @@ class interfaceController extends baseController {
} }
let data = { let data = {
up_time: yapi.commons.time() up_time: yapi.commons.time(),
query_path: params.query_path
}; };
if (!_.isUndefined(params.path)) { if (!_.isUndefined(params.path)) {
@ -400,6 +418,7 @@ class interfaceController extends baseController {
if (!_.isUndefined(params.req_query)) { if (!_.isUndefined(params.req_query)) {
data.req_query = params.req_query; data.req_query = params.req_query;
} }
if (!_.isUndefined(params.req_body_other)) { if (!_.isUndefined(params.req_body_other)) {
data.req_body_other = params.req_body_other; data.req_body_other = params.req_body_other;
} }

View File

@ -8,6 +8,7 @@ const Mock = require('mockjs');
function matchApi(apiPath, apiRule) { function matchApi(apiPath, apiRule) {
let apiRules = apiRule.split("/"); let apiRules = apiRule.split("/");
let apiPaths = apiPath.split("/");
if (apiPaths.length !== apiRules.length) { if (apiPaths.length !== apiRules.length) {
return false; return false;
} }
@ -59,20 +60,51 @@ module.exports = async (ctx, next) => {
try { try {
newpath = path.substr(project.basepath.length); newpath = path.substr(project.basepath.length);
interfaceData = await interfaceInst.getByPath(project._id, newpath, ctx.method); interfaceData = await interfaceInst.getByPath(project._id, newpath, ctx.method);
//处理query_path情况
if (!interfaceData || interfaceData.length === 0) { if (!interfaceData || interfaceData.length === 0) {
//非正常跨域预检请求回应 interfaceData = await interfaceInst.getByQueryPath(project._id, newpath, ctx.method);
if (ctx.method === 'OPTIONS') {
ctx.set("Access-Control-Allow-Origin", "*")
ctx.set("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE")
return ctx.body = 'ok'
}
let newData = await interfaceInst.getVar(project._id, ctx.method);
let i, l, j, len, curQuery, match = false;
for (i = 0, l = interfaceData.length; i < l; i++) {
match = false;
currentInterfaceData = interfaceData[i];
curQuery = currentInterfaceData.query_path;
if (!curQuery || typeof curQuery !== 'object' || !curQuery.path) {
continue;
}
for (j = 0, len = curQuery.params.length; j < len; j++) {
if (ctx.query[curQuery.params[j].name] !== curQuery.params[j].value) {
continue;
}
if(j === len -1){
match = true;
}
}
if (match) {
interfaceData = [currentInterfaceData];
break;
}
if(i === l -1){
interfaceData = [];
}
}
}
//处理动态路由
if (!interfaceData || interfaceData.length === 0) {
let newData = await interfaceInst.getVar(project._id, ctx.method);
let findInterface = _.find(newData, (item) => { let findInterface = _.find(newData, (item) => {
return matchApi(newpath, item.path) return matchApi(newpath, item.path)
}); });
if (!findInterface) { if (!findInterface) {
//非正常跨域预检请求回应
if (ctx.method === 'OPTIONS') {
ctx.set("Access-Control-Allow-Origin", "*")
ctx.set("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE")
return ctx.body = 'ok'
}
return ctx.body = yapi.commons.resReturn(null, 404, '不存在的api'); return ctx.body = yapi.commons.resReturn(null, 404, '不存在的api');
} }
interfaceData = [ interfaceData = [
@ -82,10 +114,14 @@ module.exports = async (ctx, next) => {
} }
if (interfaceData.length > 1) { if (interfaceData.length > 1) {
return ctx.body = yapi.commons.resReturn(null, 405, '存在多个api请检查数据库'); return ctx.body = yapi.commons.resReturn(null, 405, '存在多个api请检查数据库');
} else {
interfaceData = interfaceData[0];
} }
interfaceData = interfaceData[0];
ctx.set("Access-Control-Allow-Origin", "*") ctx.set("Access-Control-Allow-Origin", "*")
if (interfaceData.res_body_type === 'json') { if (interfaceData.res_body_type === 'json') {
try { try {

View File

@ -20,6 +20,12 @@ class interfaceModel extends baseModel {
add_time: Number, add_time: Number,
up_time: Number, up_time: Number,
type: {type: String, enum: ['static', 'var'], default:'static'}, type: {type: String, enum: ['static', 'var'], default:'static'},
query_path: {
path: String,
params: [{
name: String, value: String
}],
},
req_query:[{ req_query:[{
name: String, value: String, desc: String, required: { name: String, value: String, desc: String, required: {
type:String, type:String,
@ -83,6 +89,15 @@ class interfaceModel extends baseModel {
}).select('_id path').exec() }).select('_id path').exec()
} }
getByQueryPath(project_id, path, method) {
return this.model.find({
project_id: project_id,
"query_path.path": path,
method: method
})
.exec();
}
getByPath(project_id, path, method) { getByPath(project_id, path, method) {
return this.model.find({ return this.model.find({
project_id: project_id, project_id: project_id,