mirror of
https://github.com/YMFE/yapi.git
synced 2025-01-24 13:14:16 +08:00
Merge branch 'dev' of http://gitlab.corp.qunar.com/mfe/yapi into dev
This commit is contained in:
commit
98e8af85d6
@ -19,24 +19,24 @@ function json_parse(data) {
|
||||
}
|
||||
}
|
||||
|
||||
function isValidJson(json){
|
||||
if(!json) return false;
|
||||
if(typeof json === 'object') return true;
|
||||
try{
|
||||
if(typeof json === 'string'){
|
||||
function isValidJson(json) {
|
||||
if (!json) return false;
|
||||
if (typeof json === 'object') return true;
|
||||
try {
|
||||
if (typeof json === 'string') {
|
||||
json5.parse(json);
|
||||
return true;
|
||||
}
|
||||
}catch(e){
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function isJsonData(headers, res){
|
||||
if(isValidJson(res)){
|
||||
function isJsonData(headers, res) {
|
||||
if (isValidJson(res)) {
|
||||
return true;
|
||||
}
|
||||
if(!headers || typeof headers !== 'object') return false;
|
||||
if (!headers || typeof headers !== 'object') return false;
|
||||
let isResJson = false;
|
||||
Object.keys(headers).map(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({
|
||||
protocol: urlObj.protocol || 'http',
|
||||
host: urlObj.host,
|
||||
pathname: urlObj.pathname? urlObj.pathname + path : path,
|
||||
pathname: urlObj.pathname ? urlObj.pathname + path : path,
|
||||
query: this.getQueryObj(query)
|
||||
});
|
||||
|
||||
@ -207,7 +207,7 @@ export default class Run extends Component {
|
||||
file: bodyType === 'file' ? 'single-file' : null,
|
||||
success: (res, header) => {
|
||||
try {
|
||||
if(isJsonData(header)){
|
||||
if (isJsonData(header)) {
|
||||
res = json_parse(res);
|
||||
}
|
||||
|
||||
@ -243,9 +243,9 @@ export default class Run extends Component {
|
||||
},
|
||||
error: (err, header) => {
|
||||
try {
|
||||
if(isJsonData(header)){
|
||||
if (isJsonData(header)) {
|
||||
err = json_parse(err);
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
message.error(e.message)
|
||||
}
|
||||
@ -555,20 +555,24 @@ export default class Run extends Component {
|
||||
}
|
||||
</div>
|
||||
|
||||
<Card title="请求部分" noHovering className="req-part">
|
||||
<Card title={<Tooltip placement="top" title="在项目设置配置domain">请求部分 <Icon type="question-circle-o" /></Tooltip>} noHovering className="req-part">
|
||||
<div className="url">
|
||||
|
||||
<InputGroup compact style={{ display: 'flex' }}>
|
||||
<Select disabled value={method} style={{ flexBasis: 60 }} onChange={this.changeMethod} >
|
||||
<Option value="GET">GET</Option>
|
||||
<Option value="POST">POST</Option>
|
||||
</Select>
|
||||
|
||||
<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>))
|
||||
}
|
||||
</Select>
|
||||
|
||||
<Input disabled value={path + search} onChange={this.changePath} spellCheck="false" style={{ flexBasis: 180, flexGrow: 1 }} />
|
||||
</InputGroup>
|
||||
|
||||
<Tooltip placement="bottom" title="请求真实接口">
|
||||
<Button
|
||||
disabled={!hasPlugin}
|
||||
@ -636,21 +640,21 @@ export default class Run extends Component {
|
||||
<Panel
|
||||
header={
|
||||
<div style={{ display: 'flex', justifyContent: 'space-between' }}>
|
||||
<div>BODY</div>
|
||||
<div>BODY</div>
|
||||
</div>
|
||||
}
|
||||
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 id="body-other-edit" style={{ marginTop: 10, minHeight:150 }} className="pretty-editor"></div>
|
||||
<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>
|
||||
|
||||
{
|
||||
HTTP_METHOD[method].request_body && bodyType === 'form' &&
|
||||
<div>
|
||||
{
|
||||
{
|
||||
bodyForm.map((item, index) => {
|
||||
return (
|
||||
<div key={index} className="key-value-wrap">
|
||||
|
@ -131,7 +131,9 @@ class ProjectList extends Component {
|
||||
}]
|
||||
})(
|
||||
<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>
|
||||
)}
|
||||
</FormItem>
|
||||
|
@ -247,8 +247,8 @@ export default class InterfaceColMenu extends Component {
|
||||
style={{width: '100%'}}
|
||||
key={'case_' + interfaceCase._id}
|
||||
title={
|
||||
<div className="menu-title">
|
||||
<span>{interfaceCase.casename}</span>
|
||||
<div className="menu-title" title={interfaceCase.casename}>
|
||||
<span className="casename">{interfaceCase.casename}</span>
|
||||
<Icon type='delete' className="case-delete-icon" onClick={() => { this.showDelCaseConfirm(interfaceCase._id) }} />
|
||||
</div>
|
||||
}
|
||||
|
@ -6,7 +6,11 @@
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
overflow: hidden;
|
||||
.casename {
|
||||
overflow: hidden;
|
||||
}
|
||||
.case-delete-icon{
|
||||
margin-left: 5px;
|
||||
display: none;
|
||||
}
|
||||
i:before{
|
||||
|
@ -198,6 +198,7 @@ class InterfaceEditForm extends Component {
|
||||
|
||||
let editor = this.editor = new Editor('#desc');
|
||||
editor.create();
|
||||
editor.txt.html(this.state.desc)
|
||||
}
|
||||
|
||||
addParams = (name, data) => {
|
||||
@ -281,7 +282,7 @@ class InterfaceEditForm extends Component {
|
||||
{getFieldDecorator('req_query[' + index + '].desc', {
|
||||
initialValue: data.desc
|
||||
})(
|
||||
<Input placeholder="备注" />
|
||||
<Input.TextArea autosize={true} placeholder="备注" />
|
||||
)}
|
||||
</Col>
|
||||
<Col span="1" >
|
||||
@ -308,14 +309,14 @@ class InterfaceEditForm extends Component {
|
||||
{getFieldDecorator('req_headers[' + index + '].value', {
|
||||
initialValue: data.value
|
||||
})(
|
||||
<Input placeholder="参数值" />
|
||||
<Input placeholder="参数值" />
|
||||
)}
|
||||
</Col>
|
||||
<Col span="10" >
|
||||
{getFieldDecorator('req_headers[' + index + '].desc', {
|
||||
initialValue: data.desc
|
||||
})(
|
||||
<Input placeholder="备注" />
|
||||
<Input.TextArea autosize={true} placeholder="备注" />
|
||||
)}
|
||||
</Col>
|
||||
<Col span="2" >
|
||||
@ -358,7 +359,7 @@ class InterfaceEditForm extends Component {
|
||||
{getFieldDecorator('req_body_form[' + index + '].desc', {
|
||||
initialValue: data.desc
|
||||
})(
|
||||
<Input placeholder="备注" />
|
||||
<Input.TextArea autosize={true} placeholder="备注" />
|
||||
)}
|
||||
</Col>
|
||||
<Col span="1" >
|
||||
@ -380,7 +381,7 @@ class InterfaceEditForm extends Component {
|
||||
{getFieldDecorator('req_params[' + index + '].desc', {
|
||||
initialValue: data.desc
|
||||
})(
|
||||
<Input placeholder="备注" />
|
||||
<Input.TextArea autosize={true} placeholder="备注" />
|
||||
)}
|
||||
</Col>
|
||||
|
||||
|
@ -8,24 +8,34 @@
|
||||
// .item-all-interface {
|
||||
// background-color: red;
|
||||
// }
|
||||
.ant-tabs-bar{
|
||||
border-bottom: none;
|
||||
margin-bottom: 0
|
||||
}
|
||||
// .ant-tabs-bar{
|
||||
// border-bottom: none;
|
||||
// margin-bottom: 0
|
||||
// }
|
||||
.ant-tabs-nav{
|
||||
width:100%;
|
||||
background-color: #ececec
|
||||
}
|
||||
.ant-tabs-tab{
|
||||
min-width: 50%;
|
||||
}
|
||||
.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{
|
||||
height: 40px;
|
||||
background-color: #efefef;
|
||||
color:#021b2d;
|
||||
font-weight: 500;
|
||||
// margin-bottom: 0;
|
||||
}
|
||||
|
||||
.ant-tabs.ant-tabs-card > .ant-tabs-bar .ant-tabs-nav-container{
|
||||
@ -34,13 +44,13 @@
|
||||
|
||||
.ant-tabs.ant-tabs-card > .ant-tabs-bar{
|
||||
text-align: center;
|
||||
line-height: 40px;
|
||||
height:40px;
|
||||
background: #ececec;
|
||||
}
|
||||
|
||||
.ant-tabs-nav-wrap{
|
||||
height: 40px;
|
||||
line-height: 31px;
|
||||
// border-bottom: 1px solid #d9d9d9;
|
||||
}
|
||||
.ant-input {
|
||||
width: 100%;
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "yapi",
|
||||
"version": "1.0.0",
|
||||
"version": "1.0.1",
|
||||
"description": "YAPI",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
|
@ -79,20 +79,22 @@ class interfaceController extends baseController {
|
||||
|
||||
|
||||
let http_path = url.parse(params.path, true);
|
||||
params.path = http_path.pathname;
|
||||
|
||||
if (!yapi.commons.verifyPath(http_path.pathname)) {
|
||||
return ctx.body = yapi.commons.resReturn(null, 400, '接口path第一位必须是/,最后一位不能为/');
|
||||
}
|
||||
|
||||
if (!params.req_query) {
|
||||
params.req_query = [];
|
||||
Object.keys(http_path.query).forEach((item) => {
|
||||
params.req_query.push({
|
||||
name: item
|
||||
})
|
||||
|
||||
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]
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
|
||||
let checkRepeat = await this.Model.checkRepeat(params.project_id, params.path, params.method);
|
||||
@ -109,6 +111,7 @@ class interfaceController extends baseController {
|
||||
path: params.path,
|
||||
desc: params.desc,
|
||||
method: params.method,
|
||||
query_path: params.query_path,
|
||||
req_headers: params.req_headers,
|
||||
req_body_type: params.req_body_type,
|
||||
res_body: params.res_body,
|
||||
@ -118,11 +121,11 @@ class interfaceController extends baseController {
|
||||
up_time: yapi.commons.time()
|
||||
};
|
||||
|
||||
if (params.req_query) {
|
||||
if (!_.isUndefined(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;
|
||||
}
|
||||
|
||||
@ -147,7 +150,7 @@ class interfaceController extends baseController {
|
||||
} else {
|
||||
data.type = 'static'
|
||||
}
|
||||
if (params.req_body_other) {
|
||||
if (!_.isUndefined(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, '没有权限');
|
||||
}
|
||||
|
||||
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第一位必须是/,最后一位不能为/');
|
||||
}
|
||||
|
||||
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)) {
|
||||
let checkRepeat = await this.Model.checkRepeat(interfaceData.project_id, params.path, params.method);
|
||||
if (checkRepeat > 0) {
|
||||
@ -362,7 +379,8 @@ class interfaceController extends baseController {
|
||||
}
|
||||
|
||||
let data = {
|
||||
up_time: yapi.commons.time()
|
||||
up_time: yapi.commons.time(),
|
||||
query_path: params.query_path
|
||||
};
|
||||
|
||||
if (!_.isUndefined(params.path)) {
|
||||
@ -400,6 +418,7 @@ class interfaceController extends baseController {
|
||||
if (!_.isUndefined(params.req_query)) {
|
||||
data.req_query = params.req_query;
|
||||
}
|
||||
|
||||
if (!_.isUndefined(params.req_body_other)) {
|
||||
data.req_body_other = params.req_body_other;
|
||||
}
|
||||
|
@ -8,6 +8,7 @@ const Mock = require('mockjs');
|
||||
|
||||
function matchApi(apiPath, apiRule) {
|
||||
let apiRules = apiRule.split("/");
|
||||
let apiPaths = apiPath.split("/");
|
||||
if (apiPaths.length !== apiRules.length) {
|
||||
return false;
|
||||
}
|
||||
@ -59,20 +60,51 @@ module.exports = async (ctx, next) => {
|
||||
try {
|
||||
newpath = path.substr(project.basepath.length);
|
||||
interfaceData = await interfaceInst.getByPath(project._id, newpath, ctx.method);
|
||||
|
||||
//处理query_path情况
|
||||
if (!interfaceData || interfaceData.length === 0) {
|
||||
//非正常跨域预检请求回应
|
||||
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);
|
||||
interfaceData = await interfaceInst.getByQueryPath(project._id, newpath, 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) => {
|
||||
return matchApi(newpath, item.path)
|
||||
});
|
||||
|
||||
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');
|
||||
}
|
||||
interfaceData = [
|
||||
@ -82,10 +114,14 @@ module.exports = async (ctx, next) => {
|
||||
}
|
||||
|
||||
if (interfaceData.length > 1) {
|
||||
|
||||
|
||||
return ctx.body = yapi.commons.resReturn(null, 405, '存在多个api,请检查数据库');
|
||||
} else {
|
||||
interfaceData = interfaceData[0];
|
||||
}
|
||||
|
||||
interfaceData = interfaceData[0];
|
||||
|
||||
ctx.set("Access-Control-Allow-Origin", "*")
|
||||
if (interfaceData.res_body_type === 'json') {
|
||||
try {
|
||||
|
@ -20,6 +20,12 @@ class interfaceModel extends baseModel {
|
||||
add_time: Number,
|
||||
up_time: Number,
|
||||
type: {type: String, enum: ['static', 'var'], default:'static'},
|
||||
query_path: {
|
||||
path: String,
|
||||
params: [{
|
||||
name: String, value: String
|
||||
}],
|
||||
},
|
||||
req_query:[{
|
||||
name: String, value: String, desc: String, required: {
|
||||
type:String,
|
||||
@ -83,6 +89,15 @@ class interfaceModel extends baseModel {
|
||||
}).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) {
|
||||
return this.model.find({
|
||||
project_id: project_id,
|
||||
|
Loading…
Reference in New Issue
Block a user