Merge branch 'master' into login
19
CHANGELOG.md
@ -1,3 +1,20 @@
|
||||
### v1.3.23
|
||||
|
||||
* 接口tag功能
|
||||
* 数据导入增加 merge 功能
|
||||
* 增加参数的批量导入功能
|
||||
* json schema 可视化编辑器增加 mock 功能
|
||||
|
||||
|
||||
#### Bug Fixed
|
||||
|
||||
* 接口path中写入 ?name=xxx bug
|
||||
* 高级mock 匹配 data: [{item: XXX}] 时匹配不成功
|
||||
* 接口运行 query params 自动勾选
|
||||
* mock get 带 cookie 时跨域
|
||||
* json schema 嵌套多层 array 预览不展示 bug
|
||||
* swagger URL 导入 跨域问题
|
||||
|
||||
### v1.3.22
|
||||
|
||||
* json schema number和integer支持枚举
|
||||
@ -5,7 +22,7 @@
|
||||
* 增加 mock 接口请求字段参数验证
|
||||
* 增加返回数据验证
|
||||
|
||||
### Bug Fixed
|
||||
#### Bug Fixed
|
||||
|
||||
* 命令行导入成员信息为 undefined
|
||||
* 修复form 参数为空时 接口无法保存的问题
|
||||
|
@ -45,6 +45,9 @@ YApi 是<strong>高效</strong>、<strong>易用</strong>、<strong>功能强大
|
||||
* [yapi sso 登录插件](https://github.com/YMFE/yapi-plugin-qsso)
|
||||
* [yapi cas 登录插件](https://github.com/wsfe/yapi-plugin-cas) By wsfe
|
||||
|
||||
### YApi 一些工具
|
||||
* [mysql服务http工具,可配合做自动化测试](https://github.com/hellosean1025/http-mysql-server)
|
||||
|
||||
### YApi docker部署(非官方)
|
||||
* [使用 alpine 版 docker 镜像快速部署 yapi](https://www.jianshu.com/p/a97d2efb23c5)
|
||||
|
||||
@ -61,6 +64,7 @@ YApi 是<strong>高效</strong>、<strong>易用</strong>、<strong>功能强大
|
||||
* 快手
|
||||
* 便利蜂
|
||||
* 中商惠民
|
||||
* 新浪
|
||||
|
||||
### Authors
|
||||
* [hellosean1025](https://github.com/hellosean1025)
|
||||
|
@ -45,6 +45,9 @@ function isJson5(json) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
exports.safeArray = function(arr) {
|
||||
return Array.isArray(arr) ? arr : [];
|
||||
};
|
||||
|
||||
exports.json5_parse = function(json) {
|
||||
try {
|
||||
@ -173,6 +176,13 @@ exports.nameLengthLimit = type => {
|
||||
];
|
||||
};
|
||||
|
||||
// 去除所有html标签只保留文字
|
||||
|
||||
exports.htmlFilter = html => {
|
||||
let reg = /<\/?.+?\/?>/g;
|
||||
return html.replace(reg, '') || '新项目';
|
||||
};
|
||||
|
||||
// 实现 Object.entries() 方法
|
||||
exports.entries = obj => {
|
||||
let res = [];
|
||||
|
@ -367,10 +367,14 @@ export default class Run extends Component {
|
||||
};
|
||||
|
||||
changeParam = (name, v, index, key) => {
|
||||
|
||||
key = key || 'value';
|
||||
const pathParam = deepCopyJson(this.state[name]);
|
||||
|
||||
pathParam[index][key] = v;
|
||||
if (key === 'value') {
|
||||
pathParam[index].enable = !!v;
|
||||
}
|
||||
this.setState({
|
||||
[name]: pathParam
|
||||
});
|
||||
@ -380,7 +384,7 @@ export default class Run extends Component {
|
||||
const bodyForm = deepCopyJson(this.state.req_body_form);
|
||||
key = key || 'value';
|
||||
if (key === 'value') {
|
||||
bodyForm[index].enable = true;
|
||||
bodyForm[index].enable = !!v;
|
||||
if (bodyForm[index].type === 'file') {
|
||||
bodyForm[index].value = 'file_' + index;
|
||||
} else {
|
||||
|
@ -20,7 +20,8 @@ const messageMap = {
|
||||
uniqueItems: '元素是否都不同',
|
||||
itemType: 'item 类型',
|
||||
format: 'format',
|
||||
itemFormat: 'format'
|
||||
itemFormat: 'format',
|
||||
mock: 'mock'
|
||||
};
|
||||
|
||||
const columns = [
|
||||
@ -78,17 +79,20 @@ const columns = [
|
||||
title: '其他信息',
|
||||
dataIndex: 'sub',
|
||||
key: 'sub',
|
||||
width: 80,
|
||||
render: text => {
|
||||
return Object.keys(text || []).map((item, index) => {
|
||||
width: 180,
|
||||
render: (text, record) => {
|
||||
let result = text || record;
|
||||
|
||||
return Object.keys(result).map((item, index) => {
|
||||
let name = messageMap[item];
|
||||
let value = text[item];
|
||||
let value = result[item];
|
||||
let isShow = !_.isUndefined(result[item]) && !_.isUndefined(name);
|
||||
|
||||
return (
|
||||
!_.isUndefined(text[item]) && (
|
||||
isShow && (
|
||||
<p key={index}>
|
||||
<span style={{ fontWeight: '700' }}>{name}: </span>
|
||||
<span >{value.toString()}</span>
|
||||
<span>{value.toString()}</span>
|
||||
</p>
|
||||
)
|
||||
);
|
||||
|
@ -191,10 +191,10 @@ class ProjectList extends Component {
|
||||
<span className="radio-desc">只有组长和项目开发者可以索引并查看项目信息</span>
|
||||
</Radio>
|
||||
<br />
|
||||
<Radio value="public" className="radio">
|
||||
{/* <Radio value="public" className="radio">
|
||||
<Icon type="unlock" />公开<br />
|
||||
<span className="radio-desc">任何人都可以索引并查看项目信息</span>
|
||||
</Radio>
|
||||
</Radio> */}
|
||||
</RadioGroup>
|
||||
)}
|
||||
</FormItem>
|
||||
|
@ -221,17 +221,12 @@ export default class GroupList extends Component {
|
||||
<div className="curr-group">
|
||||
<div className="curr-group-name">
|
||||
<span className="name">{currGroup.group_name}</span>
|
||||
{/* this.props.curUserRole === "admin" || this.props.curUserRoleInGroup === 'owner' ? (menu) : '' */}
|
||||
{/* 只有超级管理员能添加分组 */
|
||||
this.props.curUserRole === 'admin' ? (
|
||||
<Tooltip title="添加分组">
|
||||
<a className="editSet">
|
||||
<Icon className="btn" type="folder-add" onClick={this.showModal} />
|
||||
</a>
|
||||
</Tooltip>
|
||||
) : (
|
||||
''
|
||||
)}
|
||||
<Tooltip title="添加分组">
|
||||
<a className="editSet">
|
||||
<Icon className="btn" type="folder-add" onClick={this.showModal} />
|
||||
</a>
|
||||
</Tooltip>
|
||||
|
||||
</div>
|
||||
<div className="curr-group-desc">简介: {currGroup.group_desc}</div>
|
||||
</div>
|
||||
|
@ -993,7 +993,10 @@ class InterfaceColContent extends Component {
|
||||
</Row>
|
||||
<Row type="flex" justify="space-around" className="row" align="middle">
|
||||
<Col span={21} className="autoTestUrl">
|
||||
<a href={localUrl + autoTestsUrl} target="_blank">
|
||||
<a
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
href={localUrl + autoTestsUrl} >
|
||||
{autoTestsUrl}
|
||||
</a>
|
||||
</Col>
|
||||
|
@ -1,26 +1,33 @@
|
||||
import React, { PureComponent as Component } from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import React, { PureComponent as Component } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { connect } from 'react-redux';
|
||||
import InterfaceEditForm from './InterfaceEditForm.js'
|
||||
import { updateInterfaceData, fetchInterfaceListMenu, fetchInterfaceData } from '../../../../reducer/modules/interface.js';
|
||||
import axios from 'axios'
|
||||
import { message } from 'antd'
|
||||
import './Edit.scss'
|
||||
import InterfaceEditForm from './InterfaceEditForm.js';
|
||||
import {
|
||||
updateInterfaceData,
|
||||
fetchInterfaceListMenu,
|
||||
fetchInterfaceData
|
||||
} from '../../../../reducer/modules/interface.js';
|
||||
import { getProject } from '../../../../reducer/modules/project.js';
|
||||
import axios from 'axios';
|
||||
import { message, Modal } from 'antd';
|
||||
import './Edit.scss';
|
||||
import { withRouter, Link } from 'react-router-dom';
|
||||
import ProjectTag from '../../Setting/ProjectMessage/ProjectTag.js';
|
||||
|
||||
@connect(
|
||||
state => {
|
||||
return {
|
||||
curdata: state.inter.curdata,
|
||||
currProject: state.project.currProject
|
||||
}
|
||||
}, {
|
||||
};
|
||||
},
|
||||
{
|
||||
updateInterfaceData,
|
||||
fetchInterfaceListMenu,
|
||||
fetchInterfaceData
|
||||
fetchInterfaceData,
|
||||
getProject
|
||||
}
|
||||
)
|
||||
|
||||
class InterfaceEdit extends Component {
|
||||
static propTypes = {
|
||||
curdata: PropTypes.object,
|
||||
@ -29,125 +36,193 @@ class InterfaceEdit extends Component {
|
||||
fetchInterfaceListMenu: PropTypes.func,
|
||||
fetchInterfaceData: PropTypes.func,
|
||||
match: PropTypes.object,
|
||||
switchToView: PropTypes.func
|
||||
}
|
||||
switchToView: PropTypes.func,
|
||||
getProject: PropTypes.func
|
||||
};
|
||||
|
||||
constructor(props) {
|
||||
super(props)
|
||||
super(props);
|
||||
const { curdata, currProject } = this.props;
|
||||
this.state = {
|
||||
mockUrl: location.protocol + '//' + location.hostname + (location.port !== "" ? ":" + location.port : "") + `/mock/${currProject._id}${currProject.basepath}${curdata.path}`,
|
||||
mockUrl:
|
||||
location.protocol +
|
||||
'//' +
|
||||
location.hostname +
|
||||
(location.port !== '' ? ':' + location.port : '') +
|
||||
`/mock/${currProject._id}${currProject.basepath}${curdata.path}`,
|
||||
curdata: {},
|
||||
status: 0
|
||||
}
|
||||
status: 0,
|
||||
visible: false
|
||||
// tag: []
|
||||
};
|
||||
}
|
||||
|
||||
onSubmit = async (params) => {
|
||||
onSubmit = async params => {
|
||||
params.id = this.props.match.params.actionId;
|
||||
let result = await axios.post('/api/interface/up', params);
|
||||
this.props.fetchInterfaceListMenu(this.props.currProject._id).then();
|
||||
this.props.fetchInterfaceData(params.id).then()
|
||||
this.props.fetchInterfaceData(params.id).then();
|
||||
if (result.data.errcode === 0) {
|
||||
|
||||
this.props.updateInterfaceData(params);
|
||||
message.success('保存成功');
|
||||
} else {
|
||||
message.error(result.data.errmsg)
|
||||
message.error(result.data.errmsg);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
componentWillUnmount() {
|
||||
try {
|
||||
if (this.state.status === 1) {
|
||||
this.WebSocket.close()
|
||||
this.WebSocket.close();
|
||||
}
|
||||
} catch (e) {
|
||||
return null
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
let domain = location.hostname + (location.port !== "" ? ":" + location.port : "");
|
||||
let s, initData = false;
|
||||
let domain = location.hostname + (location.port !== '' ? ':' + location.port : '');
|
||||
let s,
|
||||
initData = false;
|
||||
//因后端 node 仅支持 ws, 暂不支持 wss
|
||||
let wsProtocol = location.protocol === 'https:' ? 'wss' : 'ws';
|
||||
|
||||
setTimeout(()=>{
|
||||
if(initData === false){
|
||||
setTimeout(() => {
|
||||
if (initData === false) {
|
||||
this.setState({
|
||||
curdata: this.props.curdata,
|
||||
status: 1
|
||||
})
|
||||
});
|
||||
initData = true;
|
||||
}
|
||||
}, 3000)
|
||||
}, 3000);
|
||||
|
||||
try {
|
||||
s = new WebSocket(wsProtocol + '://' + domain + '/api/interface/solve_conflict?id=' + this.props.match.params.actionId);
|
||||
s = new WebSocket(
|
||||
wsProtocol +
|
||||
'://' +
|
||||
domain +
|
||||
'/api/interface/solve_conflict?id=' +
|
||||
this.props.match.params.actionId
|
||||
);
|
||||
s.onopen = () => {
|
||||
this.WebSocket = s;
|
||||
}
|
||||
};
|
||||
|
||||
s.onmessage = (e) => {
|
||||
s.onmessage = e => {
|
||||
initData = true;
|
||||
let result = JSON.parse(e.data);
|
||||
if (result.errno === 0) {
|
||||
this.setState({
|
||||
curdata: result.data,
|
||||
status: 1
|
||||
})
|
||||
});
|
||||
} else {
|
||||
this.setState({
|
||||
curdata: result.data,
|
||||
status: 2
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
};
|
||||
|
||||
s.onerror = () => {
|
||||
this.setState({
|
||||
curdata: this.props.curdata,
|
||||
status: 1
|
||||
})
|
||||
console.warn('websocket 连接失败,将导致多人编辑同一个接口冲突。')
|
||||
}
|
||||
});
|
||||
console.warn('websocket 连接失败,将导致多人编辑同一个接口冲突。');
|
||||
};
|
||||
} catch (e) {
|
||||
|
||||
this.setState({
|
||||
curdata: this.props.curdata,
|
||||
status: 1
|
||||
})
|
||||
});
|
||||
console.error('websocket 连接失败,将导致多人编辑同一个接口冲突。');
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
onTagClick = () => {
|
||||
this.setState({
|
||||
visible: true
|
||||
});
|
||||
};
|
||||
|
||||
handleOk = async () => {
|
||||
let { tag } = this.tag.state;
|
||||
tag = tag.filter(val => {
|
||||
return val.name !== '';
|
||||
});
|
||||
|
||||
let id = this.props.currProject._id;
|
||||
let params = {
|
||||
id,
|
||||
tag
|
||||
};
|
||||
let result = await axios.post('/api/project/up_tag', params);
|
||||
|
||||
if (result.data.errcode === 0) {
|
||||
await this.props.getProject(id);
|
||||
message.success('保存成功');
|
||||
} else {
|
||||
message.error(result.data.errmsg);
|
||||
}
|
||||
|
||||
this.setState({
|
||||
visible: false
|
||||
});
|
||||
};
|
||||
|
||||
handleCancel = () => {
|
||||
this.setState({
|
||||
visible: false
|
||||
});
|
||||
};
|
||||
|
||||
tagSubmit = tagRef => {
|
||||
this.tag = tagRef;
|
||||
|
||||
// this.setState({tag})
|
||||
};
|
||||
|
||||
render() {
|
||||
return <div className="interface-edit">
|
||||
{this.state.status === 1 ?
|
||||
<InterfaceEditForm
|
||||
cat={this.props.currProject.cat}
|
||||
mockUrl={this.state.mockUrl}
|
||||
basepath={this.props.currProject.basepath}
|
||||
noticed={this.props.currProject.switch_notice}
|
||||
onSubmit={this.onSubmit}
|
||||
curdata={this.state.curdata} />
|
||||
:
|
||||
null}
|
||||
{
|
||||
this.state.status === 2 ?
|
||||
const { cat, basepath, switch_notice, tag } = this.props.currProject;
|
||||
return (
|
||||
<div className="interface-edit">
|
||||
{this.state.status === 1 ? (
|
||||
<InterfaceEditForm
|
||||
cat={cat}
|
||||
mockUrl={this.state.mockUrl}
|
||||
basepath={basepath}
|
||||
noticed={switch_notice}
|
||||
onSubmit={this.onSubmit}
|
||||
curdata={this.state.curdata}
|
||||
onTagClick={this.onTagClick}
|
||||
/>
|
||||
) : null}
|
||||
{this.state.status === 2 ? (
|
||||
<div style={{ textAlign: 'center', fontSize: '14px', paddingTop: '10px' }}>
|
||||
<Link to={'/user/profile/' + this.state.curdata.uid}><b>{this.state.curdata.username}</b></Link>
|
||||
<Link to={'/user/profile/' + this.state.curdata.uid}>
|
||||
<b>{this.state.curdata.username}</b>
|
||||
</Link>
|
||||
<span>正在编辑该接口,请稍后再试...</span>
|
||||
</div>
|
||||
:
|
||||
null}
|
||||
{
|
||||
this.state.status === 0 && '正在加载,请耐心等待...'
|
||||
}
|
||||
) : null}
|
||||
{this.state.status === 0 && '正在加载,请耐心等待...'}
|
||||
|
||||
</div>
|
||||
<Modal
|
||||
title="Tag 设置"
|
||||
width={680}
|
||||
visible={this.state.visible}
|
||||
onOk={this.handleOk}
|
||||
onCancel={this.handleCancel}
|
||||
okText="保存"
|
||||
>
|
||||
<div className="tag-modal-center">
|
||||
<ProjectTag tagMsg={tag} ref={this.tagSubmit} />
|
||||
</div>
|
||||
</Modal>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -77,7 +77,8 @@
|
||||
text-overflow:ellipsis;
|
||||
white-space: nowrap;
|
||||
padding-right: 24px;
|
||||
line-height: 100%;
|
||||
// line-height: 100%;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.opened {
|
||||
@ -109,3 +110,7 @@
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
.tag-modal-center {
|
||||
padding-left: 48px;
|
||||
}
|
||||
|
||||
|
@ -186,6 +186,11 @@ class InterfaceList extends Component {
|
||||
};
|
||||
|
||||
render() {
|
||||
let tag = this.props.curProject.tag;
|
||||
let filter = tag.map(item => {
|
||||
return { text: item.name, value: item.name };
|
||||
});
|
||||
|
||||
const columns = [
|
||||
{
|
||||
title: '接口名称',
|
||||
@ -232,12 +237,12 @@ class InterfaceList extends Component {
|
||||
title: '接口分类',
|
||||
dataIndex: 'catid',
|
||||
key: 'catid',
|
||||
width: 18,
|
||||
width: 28,
|
||||
render: (item, record) => {
|
||||
return (
|
||||
<Select
|
||||
value={item + ''}
|
||||
className="select"
|
||||
className="select path"
|
||||
onChange={catid => this.changeInterfaceCat(record._id, catid)}
|
||||
>
|
||||
{this.props.catList.map(cat => {
|
||||
@ -255,7 +260,7 @@ class InterfaceList extends Component {
|
||||
title: '状态',
|
||||
dataIndex: 'status',
|
||||
key: 'status',
|
||||
width: 14,
|
||||
width: 24,
|
||||
render: (text, record) => {
|
||||
const key = record.key;
|
||||
return (
|
||||
@ -284,6 +289,20 @@ class InterfaceList extends Component {
|
||||
}
|
||||
],
|
||||
onFilter: (value, record) => record.status.indexOf(value) === 0
|
||||
},
|
||||
{
|
||||
title: 'tag',
|
||||
dataIndex: 'tag',
|
||||
key: 'tag',
|
||||
width: 14,
|
||||
render: text => {
|
||||
let textMsg = text.length > 0 ? text.join('\n') : '未设置';
|
||||
return <div className="table-desc">{textMsg}</div>;
|
||||
},
|
||||
filters: filter,
|
||||
onFilter: (value, record) => {
|
||||
return record.tag.indexOf(value) >= 0;
|
||||
}
|
||||
}
|
||||
];
|
||||
let intername = '',
|
||||
@ -328,6 +347,8 @@ class InterfaceList extends Component {
|
||||
|
||||
const isDisabled = this.props.catList.length === 0;
|
||||
|
||||
// console.log(this.props.curProject.tag)
|
||||
|
||||
return (
|
||||
<div style={{ padding: '24px' }}>
|
||||
<h2 className="interface-title" style={{ display: 'inline-block', margin: 0 }}>
|
||||
|
@ -114,6 +114,7 @@ class InterfaceMenu extends Component {
|
||||
|
||||
componentWillReceiveProps(nextProps) {
|
||||
if (this.props.list !== nextProps.list) {
|
||||
// console.log('next', nextProps.list)
|
||||
this.setState({
|
||||
list: nextProps.list
|
||||
});
|
||||
@ -254,14 +255,14 @@ class InterfaceMenu extends Component {
|
||||
draftData.path = draftData.path + '_' + Date.now();
|
||||
});
|
||||
|
||||
axios.post('/api/interface/add', newData).then(res => {
|
||||
axios.post('/api/interface/add', newData).then(async res => {
|
||||
if (res.data.errcode !== 0) {
|
||||
return message.error(res.data.errmsg);
|
||||
}
|
||||
message.success('接口添加成功');
|
||||
let interfaceId = res.data.data._id;
|
||||
await this.getList();
|
||||
this.props.history.push('/project/' + this.props.projectId + '/interface/api/' + interfaceId);
|
||||
this.getList();
|
||||
this.setState({
|
||||
visible: false
|
||||
});
|
||||
@ -329,10 +330,38 @@ class InterfaceMenu extends Component {
|
||||
this.props.fetchInterfaceListMenu(this.props.projectId);
|
||||
}
|
||||
};
|
||||
// 数据过滤
|
||||
filterList = (list, arr) => {
|
||||
let that = this;
|
||||
let menuList = produce(list, draftList => {
|
||||
draftList.filter(item => {
|
||||
let interfaceFilter = false;
|
||||
if (item.name.indexOf(that.state.filter) === -1) {
|
||||
item.list = item.list.filter(inter => {
|
||||
if (
|
||||
inter.title.indexOf(that.state.filter) === -1 &&
|
||||
inter.path.indexOf(that.state.filter) === -1
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
//arr.push('cat_' + inter.catid)
|
||||
interfaceFilter = true;
|
||||
return true;
|
||||
});
|
||||
arr.push('cat_' + item._id);
|
||||
return interfaceFilter === true;
|
||||
}
|
||||
arr.push('cat_' + item._id);
|
||||
return true;
|
||||
});
|
||||
});
|
||||
|
||||
return menuList;
|
||||
};
|
||||
|
||||
render() {
|
||||
const matchParams = this.props.match.params;
|
||||
let menuList = this.state.list;
|
||||
// let menuList = this.state.list;
|
||||
const searchBox = (
|
||||
<div className="interface-filter">
|
||||
<Input onChange={this.onFilter} value={this.state.filter} placeholder="搜索接口" />
|
||||
@ -398,9 +427,6 @@ class InterfaceMenu extends Component {
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
if (menuList.length === 0) {
|
||||
return searchBox;
|
||||
}
|
||||
const defaultExpandedKeys = () => {
|
||||
const { router, inter, list } = this.props,
|
||||
rNull = { expands: [], selects: [] };
|
||||
@ -432,26 +458,6 @@ class InterfaceMenu extends Component {
|
||||
};
|
||||
|
||||
const itemInterfaceCreate = item => {
|
||||
// let color;
|
||||
// switch (item.method) {
|
||||
// case 'GET': color = "green"; break;
|
||||
// case 'POST': color = "blue"; break;
|
||||
// case 'PUT': color = "yellow"; break;
|
||||
// case 'DELETE': color = 'red'; break;
|
||||
// default: color = "yellow";
|
||||
// }
|
||||
// const menu = (item) => {
|
||||
// return <Menu>
|
||||
// <Menu.Item>
|
||||
// <span onClick={() => { this.showConfirm(item._id) }}>删除接口</span>
|
||||
// </Menu.Item>
|
||||
// <Menu.Item>
|
||||
// <span onClick={() => {
|
||||
// this.copyInterface(item)
|
||||
// }}>复制接口</span>
|
||||
// </Menu.Item>
|
||||
// </Menu>
|
||||
// };
|
||||
|
||||
return (
|
||||
<TreeNode
|
||||
@ -501,62 +507,14 @@ class InterfaceMenu extends Component {
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
// const menu = (item) => {
|
||||
// return <Menu>
|
||||
// <Menu.Item>
|
||||
// <span onClick={() => {
|
||||
// this.changeModal('visible', true);
|
||||
// this.setState({
|
||||
// curCatid: item._id
|
||||
// })
|
||||
// }}>添加接口</span>
|
||||
// </Menu.Item>
|
||||
// <Menu.Item>
|
||||
// <span onClick={() => {
|
||||
// this.changeModal('change_cat_modal_visible', true);
|
||||
// this.setState({
|
||||
// curCatdata: item
|
||||
// })
|
||||
// }}>修改分类</span>
|
||||
// </Menu.Item>
|
||||
// <Menu.Item>
|
||||
// <span onClick={() => {
|
||||
// this.showDelCatConfirm(item._id)
|
||||
// }}>删除分类</span>
|
||||
// </Menu.Item>
|
||||
// </Menu>
|
||||
// };
|
||||
|
||||
|
||||
let currentKes = defaultExpandedKeys();
|
||||
|
||||
let menuList;
|
||||
if (this.state.filter) {
|
||||
let arr = [];
|
||||
menuList = menuList.filter(item => {
|
||||
let interfaceFilter = false;
|
||||
if (item.name.indexOf(this.state.filter) === -1) {
|
||||
item.list = item.list.filter(inter => {
|
||||
if (
|
||||
inter.title.indexOf(this.state.filter) === -1 &&
|
||||
inter.path.indexOf(this.state.filter) === -1
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
//arr.push('cat_' + inter.catid)
|
||||
interfaceFilter = true;
|
||||
return true;
|
||||
});
|
||||
|
||||
arr.push('cat_' + item._id);
|
||||
return interfaceFilter === true;
|
||||
}
|
||||
arr.push('cat_' + item._id);
|
||||
return true;
|
||||
});
|
||||
// console.log('arr', arr);
|
||||
if (arr.length > 0) {
|
||||
currentKes.expands = arr;
|
||||
}
|
||||
menuList = this.filterList(this.state.list, arr);
|
||||
} else {
|
||||
menuList = this.state.list;
|
||||
}
|
||||
|
||||
return (
|
||||
|
@ -5,7 +5,7 @@ import PropTypes from 'prop-types';
|
||||
import { Table, Icon, Row, Col, Tooltip, message } from 'antd';
|
||||
import { Link } from 'react-router-dom';
|
||||
import AceEditor from 'client/components/AceEditor/AceEditor';
|
||||
import { formatTime } from '../../../../common.js';
|
||||
import { formatTime, safeArray } from '../../../../common.js';
|
||||
import ErrMsg from '../../../../components/ErrMsg/ErrMsg.js';
|
||||
import variable from '../../../../constants/variable';
|
||||
import constants from '../../../../constants/variable.js';
|
||||
@ -244,7 +244,7 @@ class View extends Component {
|
||||
flagMsg = (mock, strice) => {
|
||||
if (mock && strice) {
|
||||
return <span>( 全局mock & 严格模式 )</span>;
|
||||
} else if (!mock && strice) {
|
||||
} else if (!mock && strice) {
|
||||
return <span>( 严格模式 )</span>;
|
||||
} else if (mock && !strice) {
|
||||
return <span>( 全局mock )</span>;
|
||||
@ -372,6 +372,8 @@ class View extends Component {
|
||||
methodColor = 'get';
|
||||
}
|
||||
|
||||
const { tag, up_time, title, uid, username } = this.props.curData;
|
||||
|
||||
let res = (
|
||||
<div className="caseContainer">
|
||||
<h2 className="interface-title" style={{ marginTop: 0 }}>
|
||||
@ -383,15 +385,15 @@ class View extends Component {
|
||||
接口名称:
|
||||
</Col>
|
||||
<Col span={8} className="colName">
|
||||
{this.props.curData.title}
|
||||
{title}
|
||||
</Col>
|
||||
<Col span={4} className="colKey">
|
||||
创 建 人:
|
||||
</Col>
|
||||
<Col span={8} className="colValue">
|
||||
<Link className="user-name" to={'/user/profile/' + this.props.curData.uid}>
|
||||
<img src={'/api/user/avatar?uid=' + this.props.curData.uid} className="user-img" />
|
||||
{this.props.curData.username}
|
||||
<Link className="user-name" to={'/user/profile/' + uid}>
|
||||
<img src={'/api/user/avatar?uid=' + uid} className="user-img" />
|
||||
{username}
|
||||
</Link>
|
||||
</Col>
|
||||
</Row>
|
||||
@ -405,8 +407,19 @@ class View extends Component {
|
||||
<Col span={4} className="colKey">
|
||||
更新时间:
|
||||
</Col>
|
||||
<Col span={8}>{formatTime(this.props.curData.up_time)}</Col>
|
||||
<Col span={8}>{formatTime(up_time)}</Col>
|
||||
</Row>
|
||||
{safeArray(tag) &&
|
||||
safeArray(tag).length > 0 && (
|
||||
<Row className="row remark">
|
||||
<Col span={4} className="colKey">
|
||||
Tag :
|
||||
</Col>
|
||||
<Col span={18} className="colValue">
|
||||
{tag.join(' , ')}
|
||||
</Col>
|
||||
</Row>
|
||||
)}
|
||||
<Row className="row">
|
||||
<Col span={4} className="colKey">
|
||||
接口路径:
|
||||
@ -443,8 +456,6 @@ class View extends Component {
|
||||
</Col>
|
||||
<Col span={18} className="colValue">
|
||||
{this.flagMsg(this.props.currProject.is_mock_open, this.props.currProject.strice)}
|
||||
{/* {this.props.currProject.is_mock_open ? <span>( 全局mock </span> : <span>( </span>}
|
||||
{this.props.currProject.strice ? <span> & 严格模式 ) </span> : <span>) </span>} */}
|
||||
<span
|
||||
className="href"
|
||||
onClick={() =>
|
||||
|
@ -1,173 +1,169 @@
|
||||
@import '../../../styles/mixin.scss';
|
||||
|
||||
.left-menu{
|
||||
min-height: 5rem;
|
||||
// background: #FFF;
|
||||
// .item-all-interface {
|
||||
// background-color: red;
|
||||
// }
|
||||
// .ant-tabs-bar{
|
||||
// border-bottom: none;
|
||||
// margin-bottom: 0
|
||||
// }
|
||||
.ant-tag {
|
||||
margin-right: .16rem;
|
||||
.left-menu {
|
||||
min-height: 5rem;
|
||||
// background: #FFF;
|
||||
// .item-all-interface {
|
||||
// background-color: red;
|
||||
// }
|
||||
// .ant-tabs-bar{
|
||||
// border-bottom: none;
|
||||
// margin-bottom: 0
|
||||
// }
|
||||
.ant-tag {
|
||||
margin-right: 0.16rem;
|
||||
}
|
||||
.ant-tabs-nav {
|
||||
width: 100%;
|
||||
background-color: $color-bg-gray;
|
||||
}
|
||||
.ant-tabs-tab {
|
||||
min-width: 49.4%;
|
||||
}
|
||||
.ant-tabs.ant-tabs-card > .ant-tabs-bar .ant-tabs-tab {
|
||||
height: 39px;
|
||||
background: #fff;
|
||||
border-bottom: 0;
|
||||
border-radius: 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: #ddd;
|
||||
// color: $color-white;
|
||||
}
|
||||
|
||||
.ant-tabs.ant-tabs-card > .ant-tabs-bar .ant-tabs-nav-container {
|
||||
height: 40px;
|
||||
}
|
||||
|
||||
.ant-tabs.ant-tabs-card > .ant-tabs-bar {
|
||||
text-align: center;
|
||||
// background: #ececec;
|
||||
}
|
||||
|
||||
.ant-tabs-nav-wrap {
|
||||
height: 40px;
|
||||
line-height: 31px;
|
||||
// border-bottom: 1px solid #d9d9d9;
|
||||
}
|
||||
.ant-input {
|
||||
width: 100%;
|
||||
}
|
||||
.interface-filter {
|
||||
padding: 12px 16px;
|
||||
padding-right: 110px;
|
||||
line-height: 32px;
|
||||
background-color: #ddd;
|
||||
position: relative;
|
||||
}
|
||||
.btn-filter {
|
||||
position: absolute;
|
||||
right: 16px;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
}
|
||||
.ant-tree li .ant-tree-node-content-wrapper {
|
||||
width: calc(100% - 28px);
|
||||
position: relative;
|
||||
.container-title {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.ant-tabs-nav{
|
||||
width:100%;
|
||||
background-color: $color-bg-gray;
|
||||
.btns {
|
||||
background-color: #eef7fe;
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
right: 0;
|
||||
transform: translateY(-60%);
|
||||
transition: all 0.2s;
|
||||
}
|
||||
.ant-tabs-tab{
|
||||
min-width: 49.4%;
|
||||
}
|
||||
.ant-tree li .ant-tree-node-selected {
|
||||
.btns {
|
||||
background-color: #d5ebfc;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
.ant-tabs.ant-tabs-card > .ant-tabs-bar .ant-tabs-tab{
|
||||
height: 39px;
|
||||
background: #fff;
|
||||
border-bottom: 0;
|
||||
border-radius: 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: #ddd;
|
||||
// color: $color-white;
|
||||
}
|
||||
|
||||
.interface-delete-icon {
|
||||
position: relative;
|
||||
right: 0;
|
||||
float: right;
|
||||
line-height: 25px;
|
||||
width: 24px;
|
||||
font-weight: bold;
|
||||
}
|
||||
.anticon-ellipsis {
|
||||
transform: rotate(90deg);
|
||||
}
|
||||
.interface-delete-icon:hover {
|
||||
color: #2395f1;
|
||||
}
|
||||
|
||||
.interface-list {
|
||||
//max-height: 600px;
|
||||
//overflow-y: scroll;
|
||||
.cat_switch_hidden {
|
||||
.ant-tree-switcher {
|
||||
visibility: hidden;
|
||||
}
|
||||
}
|
||||
|
||||
.ant-tabs.ant-tabs-card > .ant-tabs-bar .ant-tabs-nav-container{
|
||||
height: 40px;
|
||||
a {
|
||||
color: rgba(13, 27, 62, 0.65);
|
||||
}
|
||||
|
||||
.ant-tabs.ant-tabs-card > .ant-tabs-bar{
|
||||
text-align: center;
|
||||
// background: #ececec;
|
||||
.btn-http {
|
||||
height: 23px;
|
||||
font-size: 10px;
|
||||
margin-right: 7px;
|
||||
padding: 0 5px;
|
||||
width: auto !important;
|
||||
}
|
||||
.interface-item {
|
||||
display: inline-block;
|
||||
overflow: hidden;
|
||||
top: 0px;
|
||||
}
|
||||
.interface-item-nav {
|
||||
line-height: 25px;
|
||||
}
|
||||
.interface-list {
|
||||
.cat_switch_hidden {
|
||||
.ant-tree-switcher {
|
||||
visibility: hidden;
|
||||
}
|
||||
}
|
||||
|
||||
.ant-tabs-nav-wrap{
|
||||
height: 40px;
|
||||
line-height: 31px;
|
||||
// border-bottom: 1px solid #d9d9d9;
|
||||
}
|
||||
.ant-input {
|
||||
width: 100%;
|
||||
}
|
||||
.interface-filter{
|
||||
padding: 12px 16px;
|
||||
padding-right: 110px;
|
||||
line-height: 32px;
|
||||
background-color: #ddd;
|
||||
position: relative;
|
||||
}
|
||||
.btn-filter {
|
||||
position: absolute;
|
||||
right: 16px;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
}
|
||||
.ant-tree li .ant-tree-node-content-wrapper {
|
||||
width: calc(100% - 28px);
|
||||
position: relative;
|
||||
.container-title {
|
||||
.btn-http {
|
||||
height: 23px;
|
||||
font-size: 10px;
|
||||
margin-right: 7px;
|
||||
padding: 0 5px;
|
||||
width: auto !important;
|
||||
}
|
||||
.interface-item {
|
||||
display: inline-block;
|
||||
overflow: hidden;
|
||||
text-overflow:ellipsis;
|
||||
white-space: nowrap;
|
||||
top: 0px;
|
||||
line-height: 100%;
|
||||
text-decoration: none;
|
||||
}
|
||||
.btns {
|
||||
background-color: #eef7fe;
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
right: 0;
|
||||
transform: translateY(-60%);
|
||||
transition: all .2s;
|
||||
}
|
||||
}
|
||||
.ant-tree li .ant-tree-node-selected {
|
||||
.btns {
|
||||
background-color: #d5ebfc;
|
||||
transition: all .2s;
|
||||
}
|
||||
}
|
||||
|
||||
.interface-delete-icon{
|
||||
position: relative;
|
||||
right: 0;
|
||||
float: right;
|
||||
.interface-item-nav {
|
||||
line-height: 25px;
|
||||
width: 24px;
|
||||
font-weight: bold;
|
||||
}
|
||||
.anticon-ellipsis {
|
||||
transform: rotate(90deg);
|
||||
}
|
||||
.interface-delete-icon:hover {
|
||||
color: #2395f1;
|
||||
}
|
||||
|
||||
.interface-list{
|
||||
//max-height: 600px;
|
||||
//overflow-y: scroll;
|
||||
.cat_switch_hidden{
|
||||
.ant-tree-switcher{
|
||||
visibility: hidden;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
a{
|
||||
color: rgba(13, 27, 62, 0.65);
|
||||
}
|
||||
|
||||
.btn-http{
|
||||
height: 23px;
|
||||
font-size: 10px;
|
||||
margin-right: 7px;
|
||||
padding: 0 5px;
|
||||
width:auto !important;
|
||||
}
|
||||
.interface-item{
|
||||
display: inline-block;
|
||||
overflow: hidden;
|
||||
top: 0px;
|
||||
line-height: 100%;
|
||||
}
|
||||
.interface-item-nav{
|
||||
line-height:25px;
|
||||
}
|
||||
.interface-list{
|
||||
.cat_switch_hidden{
|
||||
.ant-tree-switcher{
|
||||
visibility: hidden;
|
||||
}
|
||||
}
|
||||
|
||||
.btn-http{
|
||||
height: 23px;
|
||||
font-size: 10px;
|
||||
margin-right: 7px;
|
||||
padding: 0 5px;
|
||||
width:auto !important;
|
||||
}
|
||||
.interface-item{
|
||||
display: inline-block;
|
||||
overflow: hidden;
|
||||
top: 0px;
|
||||
line-height: 100%;
|
||||
text-decoration: none;
|
||||
}
|
||||
.interface-item-nav{
|
||||
line-height:25px;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.right-content{
|
||||
.right-content {
|
||||
min-height: 5rem;
|
||||
background: #fff;
|
||||
.caseContainer {
|
||||
@ -179,33 +175,45 @@
|
||||
text-align: left;
|
||||
background-color: #f8f8f8;
|
||||
}
|
||||
tr:nth-child(even){background: #f8f8f8;}
|
||||
tr:nth-child(even) {
|
||||
background: #f8f8f8;
|
||||
}
|
||||
}
|
||||
.interface-content{
|
||||
.ant-tabs-nav{
|
||||
width:100%;
|
||||
.interface-content {
|
||||
.ant-tabs-nav {
|
||||
width: 100%;
|
||||
// background-color: #ddd;
|
||||
// color: $color-white;
|
||||
}
|
||||
.ant-tabs-nav-wrap{
|
||||
.ant-tabs-nav-wrap {
|
||||
text-align: left;
|
||||
}
|
||||
}
|
||||
.interface-title {
|
||||
clear: both;
|
||||
font-weight: normal;
|
||||
margin-top: .48rem;
|
||||
margin-bottom: .16rem;
|
||||
margin-top: 0.48rem;
|
||||
margin-bottom: 0.16rem;
|
||||
border-left: 3px solid #2395f1;
|
||||
padding-left: 8px;
|
||||
.tooltip {
|
||||
font-size: 13px;
|
||||
font-weight: normal;
|
||||
}
|
||||
}
|
||||
.container-radiogroup {
|
||||
text-align: center;
|
||||
margin-bottom: .16rem;
|
||||
margin-bottom: 0.16rem;
|
||||
}
|
||||
.panel-sub {
|
||||
background: rgba(236, 238, 241, 0.67);
|
||||
padding: .10rem;
|
||||
padding: 0.1rem;
|
||||
.bulk-import {
|
||||
color: #2395f1;
|
||||
text-align: right;
|
||||
margin-right: 16px;
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
.ant-radio-button-wrapper-checked {
|
||||
color: #fff;
|
||||
@ -229,37 +237,41 @@
|
||||
text-align: left;
|
||||
font-weight: normal;
|
||||
background-color: #f8f8f8;
|
||||
text-indent: .4em;
|
||||
text-indent: 0.4em;
|
||||
}
|
||||
tr {
|
||||
text-indent: .4em;
|
||||
text-indent: 0.4em;
|
||||
}
|
||||
th, td {
|
||||
th,
|
||||
td {
|
||||
border: 1px solid #e9e9e9;
|
||||
}
|
||||
tr:nth-child(odd){background: #f8f8f8;}
|
||||
tr:nth-child(even){background: #fff;}
|
||||
tr:nth-child(odd) {
|
||||
background: #f8f8f8;
|
||||
}
|
||||
tr:nth-child(even) {
|
||||
background: #fff;
|
||||
}
|
||||
}
|
||||
}
|
||||
.addcatmodal{
|
||||
.ant-modal-body{
|
||||
padding: 10px 0px;
|
||||
.ant-form-item{
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
.catModalfoot{
|
||||
border-top: 1px solid #e9e9e9;
|
||||
margin-bottom: 0px;
|
||||
padding-top: 10px;
|
||||
margin-top: 0px;
|
||||
.ant-form-item-control-wrapper{
|
||||
margin-left: 0px;
|
||||
}
|
||||
.ant-form-item-control {
|
||||
float: right;
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
}
|
||||
.addcatmodal {
|
||||
.ant-modal-body {
|
||||
padding: 10px 0px;
|
||||
.ant-form-item {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
}
|
||||
.catModalfoot {
|
||||
border-top: 1px solid #e9e9e9;
|
||||
margin-bottom: 0px;
|
||||
padding-top: 10px;
|
||||
margin-top: 0px;
|
||||
.ant-form-item-control-wrapper {
|
||||
margin-left: 0px;
|
||||
}
|
||||
.ant-form-item-control {
|
||||
float: right;
|
||||
margin-right: 10px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -23,6 +23,7 @@ import URL from 'url';
|
||||
const Dragger = Upload.Dragger;
|
||||
import { saveImportData } from '../../../../reducer/modules/interface';
|
||||
import { fetchUpdateLogData } from '../../../../reducer/modules/news.js';
|
||||
import { handleSwaggerUrlData } from '../../../../reducer/modules/project';
|
||||
const Option = Select.Option;
|
||||
const confirm = Modal.confirm;
|
||||
const plugin = require('client/plugin.js');
|
||||
@ -30,7 +31,6 @@ const RadioGroup = Radio.Group;
|
||||
const importDataModule = {};
|
||||
const exportDataModule = {};
|
||||
const HandleImportData = require('common/HandleImportData');
|
||||
|
||||
function handleExportRouteParams(url, status, isWiki) {
|
||||
if (!url) {
|
||||
return;
|
||||
@ -54,12 +54,14 @@ function handleExportRouteParams(url, status, isWiki) {
|
||||
return {
|
||||
curCatid: -(-state.inter.curdata.catid),
|
||||
basePath: state.project.currProject.basepath,
|
||||
updateLogList: state.news.updateLogList
|
||||
updateLogList: state.news.updateLogList,
|
||||
swaggerUrlData: state.project.swaggerUrlData
|
||||
};
|
||||
},
|
||||
{
|
||||
saveImportData,
|
||||
fetchUpdateLogData
|
||||
fetchUpdateLogData,
|
||||
handleSwaggerUrlData
|
||||
}
|
||||
)
|
||||
class ProjectData extends Component {
|
||||
@ -84,7 +86,9 @@ class ProjectData extends Component {
|
||||
basePath: PropTypes.string,
|
||||
saveImportData: PropTypes.func,
|
||||
fetchUpdateLogData: PropTypes.func,
|
||||
updateLogList: PropTypes.array
|
||||
updateLogList: PropTypes.array,
|
||||
handleSwaggerUrlData: PropTypes.func,
|
||||
swaggerUrlData: PropTypes.string
|
||||
};
|
||||
|
||||
componentWillMount() {
|
||||
@ -95,7 +99,6 @@ class ProjectData extends Component {
|
||||
menuList: menuList,
|
||||
selectCatid: menuList[0]._id
|
||||
});
|
||||
|
||||
}
|
||||
});
|
||||
plugin.emitHook('import_data', importDataModule);
|
||||
@ -252,13 +255,14 @@ class ProjectData extends Component {
|
||||
if (this.state.selectCatid) {
|
||||
this.setState({ showLoading: true });
|
||||
try {
|
||||
let content = await axios(this.state.swaggerUrl);
|
||||
content = content.data;
|
||||
let res = await importDataModule[this.state.curImportType].run(content);
|
||||
// 处理swagger url 导入
|
||||
await this.props.handleSwaggerUrlData(this.state.swaggerUrl);
|
||||
// let result = json5_parse(this.props.swaggerUrlData)
|
||||
let res = await importDataModule[this.state.curImportType].run(this.props.swaggerUrlData);
|
||||
if (this.state.dataSync === 'merge') {
|
||||
// merge
|
||||
this.showConfirm(res);
|
||||
}else {
|
||||
} else {
|
||||
// 未开启同步
|
||||
await this.handleAddInterface(res);
|
||||
}
|
||||
@ -303,7 +307,11 @@ class ProjectData extends Component {
|
||||
this.state.curExportType &&
|
||||
exportDataModule[this.state.curExportType] &&
|
||||
exportDataModule[this.state.curExportType].route;
|
||||
let exportHref = handleExportRouteParams(exportUrl, this.state.exportContent, this.state.isWiki);
|
||||
let exportHref = handleExportRouteParams(
|
||||
exportUrl,
|
||||
this.state.exportContent,
|
||||
this.state.isWiki
|
||||
);
|
||||
|
||||
// console.log('inter', this.state.exportContent);
|
||||
return (
|
||||
@ -313,7 +321,8 @@ class ProjectData extends Component {
|
||||
<div className="dataImportCon">
|
||||
<div>
|
||||
<h3>
|
||||
数据导入 <a
|
||||
数据导入
|
||||
<a
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
href="https://yapi.ymfe.org/documents/data.html"
|
||||
@ -325,7 +334,10 @@ class ProjectData extends Component {
|
||||
</h3>
|
||||
</div>
|
||||
<div className="dataImportTile">
|
||||
<Select defaultValue="swagger" placeholder="请选择导入数据的方式" onChange={this.handleImportType}>
|
||||
<Select
|
||||
placeholder="请选择导入数据的方式"
|
||||
onChange={this.handleImportType}
|
||||
>
|
||||
{Object.keys(importDataModule).map(name => {
|
||||
return (
|
||||
<Option key={name} value={name}>
|
||||
@ -337,7 +349,7 @@ class ProjectData extends Component {
|
||||
</div>
|
||||
<div className="catidSelect">
|
||||
<Select
|
||||
value={this.state.selectCatid+''}
|
||||
value={this.state.selectCatid + ''}
|
||||
showSearch
|
||||
style={{ width: '100%' }}
|
||||
placeholder="请选择数据导入的默认分类"
|
||||
@ -358,16 +370,24 @@ class ProjectData extends Component {
|
||||
</div>
|
||||
<div className="dataSync">
|
||||
<span className="label">
|
||||
数据同步 <Tooltip title={<div>
|
||||
<h3 style={{color: "white"}}>普通模式</h3>
|
||||
<p>不导入已存在的接口</p>
|
||||
<br />
|
||||
<h3 style={{color: "white"}}>智能合并</h3>
|
||||
<p>已存在的接口,将合并返回数据的 response,适用于导入了 swagger 数据,保留对数据结构的改动</p>
|
||||
<br />
|
||||
<h3 style={{color: "white"}}>完全覆盖</h3>
|
||||
<p>不保留旧数据,完全使用新数据,适用于接口定义完全交给后端定义</p>
|
||||
</div>}>
|
||||
数据同步
|
||||
<Tooltip
|
||||
title={
|
||||
<div>
|
||||
<h3 style={{ color: 'white' }}>普通模式</h3>
|
||||
<p>不导入已存在的接口</p>
|
||||
<br />
|
||||
<h3 style={{ color: 'white' }}>智能合并</h3>
|
||||
<p>
|
||||
已存在的接口,将合并返回数据的 response,适用于导入了 swagger
|
||||
数据,保留对数据结构的改动
|
||||
</p>
|
||||
<br />
|
||||
<h3 style={{ color: 'white' }}>完全覆盖</h3>
|
||||
<p>不保留旧数据,完全使用新数据,适用于接口定义完全交给后端定义</p>
|
||||
</div>
|
||||
}
|
||||
>
|
||||
<Icon type="question-circle-o" />
|
||||
</Tooltip>{' '}
|
||||
</span>
|
||||
@ -376,13 +396,14 @@ class ProjectData extends Component {
|
||||
<Option value="good">智能合并</Option>
|
||||
<Option value="merge">完全覆盖</Option>
|
||||
</Select>
|
||||
|
||||
|
||||
{/* <Switch checked={this.state.dataSync} onChange={this.onChange} /> */}
|
||||
</div>
|
||||
{this.state.curImportType === 'swagger' && (
|
||||
<div className="dataSync">
|
||||
<span className="label">
|
||||
开启url导入 <Tooltip title="swagger url 导入">
|
||||
开启url导入
|
||||
<Tooltip title="swagger url 导入">
|
||||
<Icon type="question-circle-o" />
|
||||
</Tooltip>{' '}
|
||||
|
||||
@ -463,7 +484,10 @@ class ProjectData extends Component {
|
||||
{this.state.curExportType ? (
|
||||
<div>
|
||||
<p className="export-desc">{exportDataModule[this.state.curExportType].desc}</p>
|
||||
<a target="_blank" href={exportHref}>
|
||||
<a
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
href={exportHref}>
|
||||
<Button className="export-button" type="primary" size="large">
|
||||
{' '}
|
||||
导出{' '}
|
||||
@ -473,9 +497,10 @@ class ProjectData extends Component {
|
||||
checked={this.state.isWiki}
|
||||
onChange={this.handleWikiChange}
|
||||
className="wiki-btn"
|
||||
disabled = {this.state.curExportType === 'json'}
|
||||
disabled={this.state.curExportType === 'json'}
|
||||
>
|
||||
添加wiki <Tooltip title="开启后 html 和 markdown 数据导出会带上wiki数据">
|
||||
添加wiki
|
||||
<Tooltip title="开启后 html 和 markdown 数据导出会带上wiki数据">
|
||||
<Icon type="question-circle-o" />
|
||||
</Tooltip>{' '}
|
||||
</Checkbox>
|
||||
|
@ -34,9 +34,10 @@ const RadioGroup = Radio.Group;
|
||||
const RadioButton = Radio.Button;
|
||||
import constants from '../../../../constants/variable.js';
|
||||
const confirm = Modal.confirm;
|
||||
import { nameLengthLimit, entries, trim } from '../../../../common';
|
||||
import { nameLengthLimit, entries, trim, htmlFilter } from '../../../../common';
|
||||
import '../Setting.scss';
|
||||
import _ from 'underscore';
|
||||
import ProjectTag from './ProjectTag.js';
|
||||
// layout
|
||||
const formItemLayout = {
|
||||
labelCol: {
|
||||
@ -106,12 +107,19 @@ class ProjectMessage extends Component {
|
||||
const { form, updateProject, projectMsg, groupList } = this.props;
|
||||
form.validateFields((err, values) => {
|
||||
if (!err) {
|
||||
let assignValue = Object.assign(projectMsg, values);
|
||||
let { tag } = this.tag.state;
|
||||
// let tag = this.refs.tag;
|
||||
tag = tag.filter(val => {
|
||||
return val.name !== '';
|
||||
});
|
||||
let assignValue = Object.assign(projectMsg, values, { tag });
|
||||
|
||||
values.protocol = this.state.protocol.split(':')[0];
|
||||
const group_id = assignValue.group_id;
|
||||
const selectGroup = _.find(groupList, item => {
|
||||
return item._id == group_id;
|
||||
});
|
||||
|
||||
updateProject(assignValue)
|
||||
.then(res => {
|
||||
if (res.payload.data.errcode == 0) {
|
||||
@ -121,13 +129,14 @@ class ProjectMessage extends Component {
|
||||
// 如果如果项目所在的分组位置发生改变
|
||||
this.props.fetchGroupMsg(group_id);
|
||||
// this.props.history.push('/group');
|
||||
let projectName = htmlFilter(assignValue.name);
|
||||
this.props.setBreadcrumb([
|
||||
{
|
||||
name: selectGroup.group_name,
|
||||
href: '/group/' + group_id
|
||||
},
|
||||
{
|
||||
name: assignValue.name
|
||||
name: projectName
|
||||
}
|
||||
]);
|
||||
}
|
||||
@ -138,6 +147,10 @@ class ProjectMessage extends Component {
|
||||
});
|
||||
};
|
||||
|
||||
tagSubmit = tag => {
|
||||
this.tag = tag;
|
||||
};
|
||||
|
||||
showConfirm = () => {
|
||||
let that = this;
|
||||
confirm({
|
||||
@ -207,7 +220,6 @@ class ProjectMessage extends Component {
|
||||
|
||||
async componentWillMount() {
|
||||
await this.props.fetchGroupList();
|
||||
// await this.props.getProject(this.props.projectId);
|
||||
await this.props.fetchGroupMsg(this.props.projectMsg.group_id);
|
||||
}
|
||||
|
||||
@ -221,8 +233,28 @@ class ProjectMessage extends Component {
|
||||
(location.port !== '' ? ':' + location.port : '') +
|
||||
`/mock/${projectMsg._id}${projectMsg.basepath}+$接口请求路径`;
|
||||
let initFormValues = {};
|
||||
const { name, basepath, desc, project_type, group_id, switch_notice, strice, is_json5 } = projectMsg;
|
||||
initFormValues = { name, basepath, desc, project_type, group_id, switch_notice, strice , is_json5};
|
||||
const {
|
||||
name,
|
||||
basepath,
|
||||
desc,
|
||||
project_type,
|
||||
group_id,
|
||||
switch_notice,
|
||||
strice,
|
||||
is_json5,
|
||||
tag
|
||||
} = projectMsg;
|
||||
initFormValues = {
|
||||
name,
|
||||
basepath,
|
||||
desc,
|
||||
project_type,
|
||||
group_id,
|
||||
switch_notice,
|
||||
strice,
|
||||
is_json5,
|
||||
tag
|
||||
};
|
||||
|
||||
const colorArr = entries(constants.PROJECT_COLOR);
|
||||
const colorSelector = (
|
||||
@ -358,6 +390,21 @@ class ProjectMessage extends Component {
|
||||
]
|
||||
})(<TextArea rows={8} />)}
|
||||
</FormItem>
|
||||
|
||||
<FormItem
|
||||
{...formItemLayout}
|
||||
label={
|
||||
<span>
|
||||
tag 信息
|
||||
<Tooltip title="定义 tag 信息,过滤接口">
|
||||
<Icon type="question-circle-o" />
|
||||
</Tooltip>
|
||||
</span>
|
||||
}
|
||||
>
|
||||
<ProjectTag tagMsg={tag} ref={this.tagSubmit} />
|
||||
{/* <Tag tagMsg={tag} ref={this.tagSubmit} /> */}
|
||||
</FormItem>
|
||||
<FormItem
|
||||
{...formItemLayout}
|
||||
label={
|
||||
@ -412,10 +459,11 @@ class ProjectMessage extends Component {
|
||||
<span className="radio-desc">只有组长和项目开发者可以索引并查看项目信息</span>
|
||||
</Radio>
|
||||
<br />
|
||||
<Radio value="public" className="radio">
|
||||
{projectMsg.role === 'admin' && <Radio value="public" className="radio">
|
||||
<Icon type="unlock" />公开<br />
|
||||
<span className="radio-desc">任何人都可以索引并查看项目信息</span>
|
||||
</Radio>
|
||||
</Radio>}
|
||||
|
||||
</RadioGroup>
|
||||
)}
|
||||
</FormItem>
|
||||
|
116
client/containers/Project/Setting/ProjectMessage/ProjectTag.js
Normal file
@ -0,0 +1,116 @@
|
||||
import React, { Component } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Icon, Row, Col, Input } from 'antd';
|
||||
import './ProjectTag.scss';
|
||||
|
||||
|
||||
class ProjectTag extends Component {
|
||||
static propTypes = {
|
||||
tagMsg: PropTypes.array,
|
||||
tagSubmit: PropTypes.func
|
||||
};
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
tag: [{ name: '', desc: '' }]
|
||||
};
|
||||
}
|
||||
|
||||
initState(curdata) {
|
||||
let tag = [
|
||||
{
|
||||
name: '',
|
||||
desc: ''
|
||||
}
|
||||
];
|
||||
if (curdata && curdata.length !== 0) {
|
||||
curdata.forEach(item => {
|
||||
tag.unshift(item);
|
||||
});
|
||||
}
|
||||
|
||||
return { tag };
|
||||
}
|
||||
componentDidMount() {
|
||||
this.handleInit(this.props.tagMsg);
|
||||
}
|
||||
|
||||
handleInit(data) {
|
||||
let newValue = this.initState(data);
|
||||
this.setState({ ...newValue });
|
||||
}
|
||||
|
||||
addHeader = (val, index, name, label) => {
|
||||
let newValue = {};
|
||||
newValue[name] = [].concat(this.state[name]);
|
||||
newValue[name][index][label] = val;
|
||||
let nextData = this.state[name][index + 1];
|
||||
if (!(nextData && typeof nextData === 'object')) {
|
||||
let data = { name: '', desc: '' };
|
||||
newValue[name] = [].concat(this.state[name], data);
|
||||
}
|
||||
this.setState(newValue);
|
||||
};
|
||||
|
||||
delHeader = (key, name) => {
|
||||
let curValue = this.state[name];
|
||||
let newValue = {};
|
||||
newValue[name] = curValue.filter((val, index) => {
|
||||
return index !== key;
|
||||
});
|
||||
this.setState(newValue);
|
||||
};
|
||||
|
||||
handleChange = (val, index, name, label) => {
|
||||
let newValue = this.state;
|
||||
newValue[name][index][label] = val;
|
||||
this.setState(newValue);
|
||||
};
|
||||
|
||||
render() {
|
||||
const commonTpl = (item, index, name) => {
|
||||
const length = this.state[name].length - 1;
|
||||
return (
|
||||
<Row key={index} className="tag-item">
|
||||
<Col span={6} className="item-name">
|
||||
<Input
|
||||
placeholder={`请输入 ${name} 名称`}
|
||||
// style={{ width: '200px' }}
|
||||
value={item.name || ''}
|
||||
onChange={e => this.addHeader(e.target.value, index, name, 'name')}
|
||||
/>
|
||||
</Col>
|
||||
<Col span={12}>
|
||||
<Input
|
||||
placeholder="请输入tag 描述信息"
|
||||
style={{ width: '90%', marginRight: 8 }}
|
||||
onChange={e => this.handleChange(e.target.value, index, name, 'desc')}
|
||||
value={item.desc || ''}
|
||||
/>
|
||||
</Col>
|
||||
<Col span={2} className={index === length ? ' tag-last-row' : null}>
|
||||
{/* 新增的项中,只有最后一项没有有删除按钮 */}
|
||||
<Icon
|
||||
className="dynamic-delete-button delete"
|
||||
type="delete"
|
||||
onClick={e => {
|
||||
e.stopPropagation();
|
||||
this.delHeader(index, name);
|
||||
}}
|
||||
/>
|
||||
</Col>
|
||||
</Row>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="project-tag">
|
||||
{this.state.tag.map((item, index) => {
|
||||
return commonTpl(item, index, 'tag');
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default ProjectTag;
|
@ -0,0 +1,18 @@
|
||||
|
||||
.project-tag {
|
||||
.item-name {
|
||||
margin-right: 16px;
|
||||
}
|
||||
|
||||
.delete {
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.tag-item {
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.tag-last-row {
|
||||
display: none;
|
||||
}
|
||||
}
|
@ -1,5 +1,6 @@
|
||||
import axios from 'axios';
|
||||
import variable from '../../constants/variable';
|
||||
import {htmlFilter} from '../../common';
|
||||
|
||||
// Actions
|
||||
const FETCH_PROJECT_LIST = 'yapi/project/FETCH_PROJECT_LIST';
|
||||
@ -20,7 +21,7 @@ const CHECK_PROJECT_NAME = 'yapi/project/CHECK_PROJECT_NAME';
|
||||
const COPY_PROJECT_MSG = 'yapi/project/COPY_PROJECT_MSG';
|
||||
const PROJECT_GET_ENV = 'yapi/project/PROJECT_GET_ENV';
|
||||
const CHANGE_MEMBER_EMAIL_NOTICE = 'yapi/project/CHANGE_MEMBER_EMAIL_NOTICE';
|
||||
|
||||
const GET_SWAGGER_URL_DATA = 'yapi/project/GET_SWAGGER_URL_DATA'
|
||||
// Reducer
|
||||
const initialState = {
|
||||
isUpdateModalShow: false,
|
||||
@ -39,7 +40,8 @@ const initialState = {
|
||||
header: []
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
swaggerUrlData: ''
|
||||
};
|
||||
|
||||
export default (state = initialState, action) => {
|
||||
@ -97,6 +99,13 @@ export default (state = initialState, action) => {
|
||||
...state
|
||||
};
|
||||
}
|
||||
|
||||
case GET_SWAGGER_URL_DATA: {
|
||||
return {
|
||||
...state,
|
||||
swaggerUrlData: action.payload.data.data
|
||||
}
|
||||
}
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
@ -173,7 +182,7 @@ export function getProjectMemberList(id) {
|
||||
// }
|
||||
|
||||
export function addProject(data) {
|
||||
const {
|
||||
let {
|
||||
name,
|
||||
prd_host,
|
||||
basepath,
|
||||
@ -185,6 +194,9 @@ export function addProject(data) {
|
||||
color,
|
||||
project_type
|
||||
} = data;
|
||||
|
||||
// 过滤项目名称中有html标签存在的情况
|
||||
name = htmlFilter(name);
|
||||
const param = {
|
||||
name,
|
||||
prd_host,
|
||||
@ -205,7 +217,10 @@ export function addProject(data) {
|
||||
|
||||
// 修改项目
|
||||
export function updateProject(data) {
|
||||
const { name, project_type, basepath, desc, _id, env, group_id, switch_notice, strice, is_json5 } = data;
|
||||
let { name, project_type, basepath, desc, _id, env, group_id, switch_notice, strice, is_json5, tag } = data;
|
||||
|
||||
// 过滤项目名称中有html标签存在的情况
|
||||
name = htmlFilter(name);
|
||||
const param = {
|
||||
name,
|
||||
project_type,
|
||||
@ -216,7 +231,8 @@ export function updateProject(data) {
|
||||
env,
|
||||
group_id,
|
||||
strice,
|
||||
is_json5
|
||||
is_json5,
|
||||
tag
|
||||
};
|
||||
return {
|
||||
type: PROJECT_UPDATE,
|
||||
@ -312,3 +328,10 @@ export async function checkProjectName(name, group_id) {
|
||||
})
|
||||
};
|
||||
}
|
||||
|
||||
export async function handleSwaggerUrlData(url) {
|
||||
return {
|
||||
type: GET_SWAGGER_URL_DATA,
|
||||
payload: axios.get('/api/project/swagger_url?url='+url)
|
||||
};
|
||||
}
|
||||
|
@ -64,6 +64,7 @@ async function handle(
|
||||
let existNum = 0;
|
||||
if (len === 0) {
|
||||
messageError(`解析数据为空`);
|
||||
callback({ showLoading: false });
|
||||
return;
|
||||
}
|
||||
for (let index = 0; index < res.length; index++) {
|
||||
|
@ -115,6 +115,14 @@ module.exports = function(jsondiffpatch, formattersHtml, curDiffData) {
|
||||
content: diffText(valueMaps[old.status], valueMaps[current.status])
|
||||
});
|
||||
}
|
||||
|
||||
if (current.tag !== old.tag) {
|
||||
diffView.push({
|
||||
title: '接口tag',
|
||||
content: diffText(old.tag, current.tag)
|
||||
});
|
||||
}
|
||||
|
||||
diffView.push({
|
||||
title: 'Request Path Params',
|
||||
content: diffArray(old.req_params, current.req_params)
|
||||
|
@ -18,6 +18,9 @@ function getLength(object) {
|
||||
|
||||
function Compare(objA, objB) {
|
||||
if (!isObj(objA) && !isObj(objB)) {
|
||||
if (isArray(objA) && isArray(objB)) {
|
||||
return CompareArray(objA, objB, true);
|
||||
}
|
||||
return objA == objB;
|
||||
}
|
||||
if (!isObj(objA) || !isObj(objB)) return false;
|
||||
@ -25,6 +28,18 @@ function Compare(objA, objB) {
|
||||
return CompareObj(objA, objB, true);
|
||||
}
|
||||
|
||||
function CompareArray(objA, objB, flag) {
|
||||
if (objA.length != objB.length) return false;
|
||||
for (let i in objB) {
|
||||
if (!Compare(objA[i], objB[i])) {
|
||||
flag = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return flag;
|
||||
}
|
||||
|
||||
function CompareObj(objA, objB, flag) {
|
||||
for (var key in objA) {
|
||||
if (!flag) break;
|
||||
@ -60,6 +75,7 @@ function CompareObj(objA, objB, flag) {
|
||||
exports.jsonEqual = Compare;
|
||||
|
||||
exports.isDeepMatch = function(obj, properties) {
|
||||
|
||||
if (!properties || typeof properties !== 'object' || Object.keys(properties).length === 0) {
|
||||
return true;
|
||||
}
|
||||
|
@ -285,6 +285,7 @@ async function crossRequest(defaultOptions, preScript, afterScript) {
|
||||
}
|
||||
resolve(data);
|
||||
};
|
||||
|
||||
window.crossRequest(options);
|
||||
});
|
||||
}
|
||||
@ -421,7 +422,6 @@ function handleParams(interfaceData, handleValue, requestParams) {
|
||||
requestOptions.file = 'single-file';
|
||||
}
|
||||
}
|
||||
|
||||
return requestOptions;
|
||||
}
|
||||
|
||||
|
@ -1,4 +1,5 @@
|
||||
const _ = require('underscore');
|
||||
let fieldNum = 1;
|
||||
|
||||
exports.schemaTransformToTable = schema => {
|
||||
try {
|
||||
@ -112,7 +113,8 @@ const SchemaString = data => {
|
||||
minLength: data.minLength,
|
||||
enum: data.enum,
|
||||
enumDesc: data.enumDesc,
|
||||
format: data.format
|
||||
format: data.format,
|
||||
mock: data.mock && data.mock.mock
|
||||
};
|
||||
return item;
|
||||
};
|
||||
@ -121,6 +123,12 @@ const SchemaArray = (data, index) => {
|
||||
data.items = data.items || { type: 'string' };
|
||||
let items = checkJsonSchema(data.items);
|
||||
let optionForm = mapping(items, index);
|
||||
// 处理array嵌套array的问题
|
||||
let children =optionForm ;
|
||||
if (!_.isArray(optionForm) && !_.isUndefined(optionForm)) {
|
||||
optionForm.key = 'array-' + fieldNum++;
|
||||
children = [optionForm];
|
||||
}
|
||||
|
||||
let item = {
|
||||
desc: data.description,
|
||||
@ -129,7 +137,7 @@ const SchemaArray = (data, index) => {
|
||||
uniqueItems: data.uniqueItems,
|
||||
maxItems: data.maxItems,
|
||||
itemType: items.type,
|
||||
children: optionForm
|
||||
children
|
||||
};
|
||||
if (items.type === 'string') {
|
||||
item = Object.assign({}, item, { itemFormat: items.format });
|
||||
@ -145,7 +153,8 @@ const SchemaNumber = data => {
|
||||
default: data.default,
|
||||
format: data.format,
|
||||
enum: data.enum,
|
||||
enumDesc: data.enumDesc
|
||||
enumDesc: data.enumDesc,
|
||||
mock: data.mock && data.mock.mock
|
||||
};
|
||||
return item;
|
||||
};
|
||||
@ -158,7 +167,8 @@ const SchemaInt = data => {
|
||||
default: data.default,
|
||||
format: data.format,
|
||||
enum: data.enum,
|
||||
enumDesc: data.enumDesc
|
||||
enumDesc: data.enumDesc,
|
||||
mock: data.mock && data.mock.mock
|
||||
};
|
||||
return item;
|
||||
};
|
||||
@ -167,7 +177,8 @@ const SchemaBoolean = data => {
|
||||
let item = {
|
||||
desc: data.description,
|
||||
default: data.default,
|
||||
enum: data.enum
|
||||
enum: data.enum,
|
||||
mock: data.mock && data.mock.mock
|
||||
};
|
||||
return item;
|
||||
};
|
||||
@ -175,7 +186,8 @@ const SchemaBoolean = data => {
|
||||
const SchemaOther = data => {
|
||||
let item = {
|
||||
desc: data.description,
|
||||
default: data.default
|
||||
default: data.default,
|
||||
mock: data.mock && data.mock.mock
|
||||
};
|
||||
return item;
|
||||
};
|
||||
|
@ -3,7 +3,11 @@
|
||||
* [安装](index.md#安装)
|
||||
* [服务器管理](index.md#服务器管理)
|
||||
* [升级](index.md#升级)
|
||||
|
||||
---
|
||||
|
||||
* [mongodb集群](index.md#mongodb集群)
|
||||
* [配置邮箱](index.md#配置邮箱)
|
||||
* [配置LDAP登录](index.md#配置LDAP登录)
|
||||
* [禁止注册](index.md#禁止注册)
|
||||
* [版本通知](index.md#版本通知)
|
||||
* [版本通知](index.md#版本通知)
|
||||
|
@ -112,7 +112,7 @@ node server/app.js //启动服务器后,请访问 127.0.0.1:{config.json配置
|
||||
"mail": {...},
|
||||
"ldapLogin": {
|
||||
"enable": true,
|
||||
"server": "ldap://l-ldapt1.ops.dev.cn0.qunar.com",
|
||||
"server": "ldap://l-ldapt1.com",
|
||||
"baseDn": "CN=Admin,CN=Users,DC=test,DC=com",
|
||||
"bindPassword": "password123",
|
||||
"searchDn": "OU=UserContainer,DC=test,DC=com",
|
||||
@ -128,8 +128,8 @@ node server/app.js //启动服务器后,请访问 127.0.0.1:{config.json配置
|
||||
|
||||
- `enable` 表示是否配置 LDAP 登录,true(支持 LDAP登录 )/false(不支持LDAP登录);
|
||||
- `server ` LDAP 服务器地址,前面需要加上 ldap:// 前缀,也可以是 ldaps:// 表示是通过 SSL 连接;
|
||||
- `baseDn` LDAP 服务器的登录用户名,必须是从根结点到用户节点的全路径;
|
||||
- `bindPassword` 登录该 LDAP 服务器的密码;
|
||||
- `baseDn` LDAP 服务器的登录用户名,必须是从根结点到用户节点的全路径(非必须);
|
||||
- `bindPassword` 登录该 LDAP 服务器的密码(非必须);
|
||||
- `searchDn` 查询用户数据的路径,类似数据库中的一张表的地址,注意这里也必须是全路径;
|
||||
- `searchStandard` 查询条件,这里是 mail 表示查询用户信息是通过邮箱信息来查询的。注意,该字段信息与LDAP数据库存储数据的字段相对应,如果如果存储用户邮箱信息的字段是 email, 这里就需要修改成 email.(1.3.18+支持)自定义filter表达式,基本形式为:&(objectClass=user)(cn=%s), 其中%s会被username替换
|
||||
- `emailPostfix` 登陆邮箱后缀(非必须)
|
||||
@ -163,4 +163,24 @@ node server/app.js //启动服务器后,请访问 127.0.0.1:{config.json配置
|
||||
"versionNotify": true
|
||||
}
|
||||
|
||||
```
|
||||
```
|
||||
|
||||
|
||||
### 如何配置mongodb集群
|
||||
|
||||
请升级到 yapi >= **1.4.0**以上版本,然后在 config.json db项,配置 connectString:
|
||||
|
||||
```json
|
||||
|
||||
{
|
||||
"port": "***",
|
||||
"db": {
|
||||
"connectString": "mongodb://127.0.0.100:8418,127.0.0.101:8418,127.0.0.102:8418/yapidb?slaveOk=true",
|
||||
"user": "******",
|
||||
"pass": "******"
|
||||
},
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
详细配置参考: [wiki](https://mongoosejs.com/docs/connections.html#multiple_connections)
|
||||
|
@ -8,6 +8,7 @@
|
||||
### 进阶篇
|
||||
* [权限](manage.md)
|
||||
* [项目操作](project.md)
|
||||
* [基本设置](project.md#基本设置)
|
||||
* [新建项目](project.md#新建项目)
|
||||
* [修改项目](project.md#修改项目)
|
||||
* [项目迁移](project.md#项目迁移)
|
||||
|
@ -11,9 +11,9 @@ Mock 期望就是根据设置的请求过滤规则,返回期望数据。
|
||||
<div class="doc-img-wrapper"><img class="doc-img-r" src="./images/usage/adv-mock-case1.png"/></div>
|
||||
2. 点击『添加期望』,填写过滤规则以及期望返回数据,点击『确定』保存。
|
||||
<div class="doc-img-wrapper"><img class="doc-img-r" src="./images/usage/adv-mock-case3.png"/></div>
|
||||
<div class="doc-img-wrapper"><img class="doc-img-r" src="./images/usage/adv-mock-case4.png"/></div>
|
||||
<div class="doc-img-wrapper"><img class="doc-img-r" src="./images/usage/adv-mock-case4-new.png"/></div>
|
||||
3. 然后尝试在浏览器里发送符合规则的请求,查看返回的数据是否符合期望。
|
||||
<div class="doc-img-wrapper"><img class="doc-img-r" src="./images/usage/adv-mock-case5.png"/></div>
|
||||
<div class="doc-img-wrapper"><img class="doc-img-r" src="./images/usage/adv-mock-case5-new.png"/></div>
|
||||
|
||||
### 期望填写
|
||||
|
||||
|
@ -15,6 +15,7 @@
|
||||
- 接口路径:可以更改 HTTP 请求方式,并且支持 restful 动态路由,例如 /api/{id}/{name}, id和name是动态参数
|
||||
- 选择分类:可以更改接口所在分类
|
||||
- 状态:用于标识接口是否开发完成。
|
||||
- Tag:用于标识接口tag信息(v1.3.23+),在接口list页可以根据tag过滤接口
|
||||
|
||||
<img src="./images/baseSet.png" />
|
||||
|
||||
|
@ -2,35 +2,46 @@
|
||||
|
||||
在数据管理可快速导入其他格式的接口数据,方便快速添加接口。YApi 目前支持 postman, swagger, har 数据导入。
|
||||
|
||||
v1.3.23+ 增加数据导入的3种同步方式 normal, good, mergin
|
||||
|
||||
1. 普通模式(normal):不导入已存在的接口;
|
||||
2. 智能合并(good):已存在的接口,将合并返回数据的 response,适用于导入了 swagger 数据,保留对数据结构的改动;例如,用户对字段code 添加了mock信息, 当再次数据导入的时候 mock 字段将不会被覆盖
|
||||
3. 完全覆盖(mergin):不保留旧数据,完全使用新数据,适用于接口定义完全交给后端定义, 默认为 normal
|
||||
|
||||
## Postman 数据导入
|
||||
1.首先在postman导出接口
|
||||
|
||||
1.首先在 postman 导出接口
|
||||
|
||||
<div><img class="doc-img" style="width:50%" src="./images/usage/postman-1.jpg" /></div>
|
||||
|
||||
2.选择collection_v1,点击export导出接口到文件xxx
|
||||
2.选择 collection_v1,点击 export 导出接口到文件 xxx
|
||||
|
||||
<div><img class="doc-img" style="width:70%" src="./images/usage/postman-2.jpg" /></div>
|
||||
|
||||
3.打开yapi平台,进入到项目页面,点击数据管理,选择相应的分组和postman导入方式,选择刚才保存的文件路径,开始导入数据
|
||||
3.打开 yapi 平台,进入到项目页面,点击数据管理,选择相应的分组和 postman 导入 方式, 选择刚才保存的文件路径,开始导入数据
|
||||
|
||||
<div><img class="doc-img" style="width:90%" src="./images/usage/postman-3.jpg" /></div>
|
||||
|
||||
## HAR 数据导入
|
||||
|
||||
<p>可用 chrome 实现录制接口数据的功能,方便开发者快速导入项目接口</p>
|
||||
|
||||
1.打开 Chrome 浏览器开发者工具,点击network,首次使用请先clear所有请求信息,确保录制功能开启(红色为开启状态)
|
||||
1.打开 Chrome 浏览器开发者工具,点击 network,首次使用请先 clear 所有请求信息,确保录制功能开启(红色为开启状态)
|
||||
|
||||
<div><img class="doc-img" style="width:70%" src="./images/usage/chrome-1.jpg" /></div>
|
||||
|
||||
2.操作页面实际功能,完成后点击save as HAR with content,将数据保存到文件xxx
|
||||
2.操作页面实际功能,完成后点击 save as HAR with content,将数据保存到文件 xxx
|
||||
|
||||
<div><img class="doc-img" style="width:70%" src="./images/usage/chrome-2.jpg" /></div>
|
||||
|
||||
3.打开yapi平台,进入到项目页面,点击数据管理,选择相应的分组和har导入方式,选择刚才保存的文件路径,开始导入数据
|
||||
3.打开 yapi 平台,进入到项目页面,点击数据管理,选择相应的分组和 har 导入 方式, 选择刚才保存的文件路径,开始导入数据
|
||||
|
||||
<div><img class="doc-img" style="width:50%" src="./images/usage/chrome-3.jpg" /></div>
|
||||
|
||||
> Tips: har 数据导入只支持 response.content.mimeType 为 application/json 类型的数据
|
||||
|
||||
## Swagger 数据导入
|
||||
|
||||
<p>什么是 Swagger ?</p>
|
||||
<div>[Swagger从入门到精通](https://www.gitbook.com/book/huangwenchao/swagger/details)</div>
|
||||
|
||||
@ -38,9 +49,9 @@
|
||||
1.生成 JSON 语言编写的 Swagger API 文档文件<div> 例如这样的数据 (<a href="http://petstore.swagger.io/v2/swagger.json" target="blank">http://petstore.swagger.io/v2/swagger.json</a>),可以将其内容复制到 JSON 文件中。</div>
|
||||
<br />
|
||||
|
||||
> Tips: v1.3.19 版本开始支持swagger url 导入功能
|
||||
> Tips: v1.3.19 版本开始支持 swagger url 导入功能
|
||||
|
||||
2.打开yapi平台,进入到项目页面,点击数据管理,选择相应的分组和swagger导入方式,选择刚才的文件,开始导入数据
|
||||
2.打开 yapi 平台,进入到项目页面,点击数据管理,选择相应的分组和 swagger 导入 方式, 选择刚才的文件,开始导入数据
|
||||
|
||||
<div><img class="doc-img" style="width:50%" src="./images/usage/chrome-4.jpg" /></div>
|
||||
|
||||
@ -48,16 +59,14 @@
|
||||
|
||||
<div><img class="doc-img" style="width:90%" src="./images/usage/chrome-6.jpg" /></div>
|
||||
|
||||
## YApi 接口 JSON 数据导入
|
||||
|
||||
|
||||
|
||||
## YApi接口JSON数据导入
|
||||
该功能在 v1.3.12 版本上线,可导入在 yapi 平台导出的 json 接口数据。
|
||||
|
||||

|
||||
|
||||
|
||||
## 通过命令行导入接口数据
|
||||
|
||||
YApi 支持通过命令行导入接口数据,他的应用场景是做自动化集成,比如配合 swagger ,接口文档前端不用维护,交由后端生成。
|
||||
|
||||
### 使用方法
|
||||
@ -69,25 +78,29 @@ npm install -g yapi-cli
|
||||
```
|
||||
|
||||
第二步,在任意一个目录下新建配置文件 `yapi-import.json`,内容如下:
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "swagger",
|
||||
"token": "17fba0027f300248b804",
|
||||
"file": "swagger.json",
|
||||
"merge": false,
|
||||
"merge": "normal",
|
||||
"server": "http://yapi.local.qunar.com:3000"
|
||||
}
|
||||
```
|
||||
|
||||
`type` 是数据数据方式,目前官方只支持 swagger
|
||||
|
||||
`token` 是项目token,在 `项目设置 -> token` 设置获取
|
||||
`token` 是项目 token,在 `项目设置 -> token` 设置获取
|
||||
|
||||
`file` 是 swagger 接口文档文件,可使用绝对路径或 url
|
||||
|
||||
`merge` 是否覆盖旧的接口,默认不开启,配置 `true` 开启
|
||||
`merge` 有三种导入方式(v1.3.23+支持) normal, good, mergin
|
||||
1. 普通模式(normal):不导入已存在的接口;
|
||||
2. 智能合并(good):已存在的接口,将合并返回数据的 response,适用于导入了 swagger 数据,保留对数据结构的改动;
|
||||
3. 完全覆盖(mergin):不保留旧数据,完全使用新数据,适用于接口定义完全交给后端定义, 默认为 normal
|
||||
|
||||
`server` 是yapi服务器地址
|
||||
`server` 是 yapi 服务器地址
|
||||
|
||||
第三步,在`新建配置文件的当前目录`,执行下面指令
|
||||
|
||||
|
Before ![]() (image error) Size: 35 KiB After ![]() (image error) Size: 46 KiB ![]() ![]() |
BIN
docs/documents/images/schema-mock-1.png
Normal file
After ![]() (image error) Size: 11 KiB |
BIN
docs/documents/images/schema-mock-2.png
Normal file
After ![]() (image error) Size: 22 KiB |
BIN
docs/documents/images/usage/adv-mock-case4-new.png
Normal file
After ![]() (image error) Size: 42 KiB |
BIN
docs/documents/images/usage/adv-mock-case5-new.png
Normal file
After ![]() (image error) Size: 102 KiB |
BIN
docs/documents/images/usage/project-message.png
Normal file
After ![]() (image error) Size: 46 KiB |
@ -67,19 +67,29 @@
|
||||
|
||||
|
||||
|
||||
详细使用文档请查看:<a href="http://mockjs.com/examples.html">Mockjs 官网</a>
|
||||
详细使用文档请查看:<a href="http://mockjs.com/examples.html" target="_blank">Mockjs 官网</a>
|
||||
|
||||
## 方式2. json-schema
|
||||
<img src="./images/usage/json-schema-demo.jpg" />
|
||||
|
||||
开启 json-schema 功能后,将不再使用 mockjs 解析定义的返回数据,而是根据 json-schema 定义的数据结构,生成随机数据。
|
||||
开启 json-schema 功能后,根据 json-schema 定义的数据结构,生成随机数据。
|
||||
|
||||
### 如何生成随机的邮箱或 ip?
|
||||
### 如何生成随机的邮箱或 ip(该方法在v1.3.22之后不再适用)?
|
||||
|
||||
<img src="./images/usage/json-schema-mock.jpg" />
|
||||
|
||||
点击高级设置,选择 `format` 选项,比如选择 `email` 则该字段生成随机邮箱字符串。
|
||||
|
||||
### 集成 mockjs
|
||||
|
||||
基本书写方式为 mock 的数据占位符@xxx, 具体字段详见<a href="http://mockjs.com/examples.html" target="_blank">Mockjs 官网</a>
|
||||
<img src="./images/schema-mock-2.png" />
|
||||
<img src="./images/schema-mock-1.png" />
|
||||
|
||||
> 如果不是以@字符开头的话或者匹配不到Mockjs中的占位符就会直接生成输入的值
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## 如何使用 Mock
|
||||
|
@ -26,7 +26,7 @@ var hooks = {
|
||||
},
|
||||
/**
|
||||
* 客户端删除接口成功后触发
|
||||
* @param data 删除接口的详细信息
|
||||
* @param data 接口id
|
||||
*/
|
||||
interface_del: {
|
||||
type: 'multi',
|
||||
|
@ -1,5 +1,13 @@
|
||||
# 项目操作
|
||||
|
||||
## 基本设置
|
||||
|
||||
- tag 信息:可自定义tag名称和tag描述,tag信息可用在接口tag标识中;
|
||||
- mock 严格模式:开启后 mock 请求会对 query,body form 的必须字段和 json schema 进行校验;
|
||||
- 开启json5:开启后允许接口请求body 和返回值中写 json 字段。yapi建议用户关闭 json5, 因为json-schema 格式可以进行接口格式校验。
|
||||
|
||||
<img src="./images/usage/project-message.png" />
|
||||
|
||||
## 新建项目
|
||||
|
||||
点击右上角的 `+` 新建项目,进入新建项目页面。
|
||||
|
@ -69,13 +69,13 @@ class advMockController extends baseController {
|
||||
let userinfo = await this.userModel.findById(result[i].uid);
|
||||
result[i] = result[i].toObject();
|
||||
// if (userinfo) {
|
||||
result[i].username = userinfo.username;
|
||||
result[i].username = userinfo.username;
|
||||
// }
|
||||
}
|
||||
|
||||
ctx.body = yapi.commons.resReturn(result);
|
||||
} catch (err) {
|
||||
ctx.body = yapi.commons.resReturn(null, 400, err.message)
|
||||
ctx.body = yapi.commons.resReturn(null, 400, err.message);
|
||||
}
|
||||
}
|
||||
|
||||
@ -137,8 +137,6 @@ class advMockController extends baseController {
|
||||
for (let i in data.params) {
|
||||
findRepeatParams['params.' + i] = data.params[i];
|
||||
}
|
||||
} else {
|
||||
findRepeatParams.params = null;
|
||||
}
|
||||
|
||||
if (data.ip_enable) {
|
||||
@ -146,6 +144,7 @@ class advMockController extends baseController {
|
||||
}
|
||||
|
||||
findRepeat = await this.caseModel.get(findRepeatParams);
|
||||
|
||||
if (findRepeat && findRepeat._id !== params.id) {
|
||||
return (ctx.body = yapi.commons.resReturn(null, 400, '已存在的期望'));
|
||||
}
|
||||
@ -171,17 +170,16 @@ class advMockController extends baseController {
|
||||
|
||||
async hideCase(ctx) {
|
||||
let id = ctx.request.body.id;
|
||||
let enable = ctx.request.body.enable
|
||||
let enable = ctx.request.body.enable;
|
||||
if (!id) {
|
||||
return (ctx.body = yapi.commons.resReturn(null, 408, '缺少 id'));
|
||||
}
|
||||
let data = {
|
||||
id,
|
||||
case_enable: enable
|
||||
}
|
||||
};
|
||||
let result = await this.caseModel.up(data);
|
||||
return (ctx.body = yapi.commons.resReturn(result));
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,34 +1,28 @@
|
||||
|
||||
// import {message} from 'antd'
|
||||
|
||||
|
||||
function exportData(exportDataModule,pid){
|
||||
|
||||
|
||||
exportDataModule.html = {
|
||||
name: 'html',
|
||||
route: `/api/plugin/export?type=html&pid=${pid}`,
|
||||
desc: '导出项目接口文档为 html 文件'
|
||||
}
|
||||
exportDataModule.markdown = {
|
||||
name: 'markdown',
|
||||
route: `/api/plugin/export?type=markdown&pid=${pid}`,
|
||||
desc: '导出项目接口文档为 markdown 文件'
|
||||
},
|
||||
exportDataModule.json = {
|
||||
name: 'json',
|
||||
route: `/api/plugin/export?type=json&pid=${pid}`,
|
||||
desc: '导出项目接口文档为 json 文件,可使用该文件导入接口数据'
|
||||
}
|
||||
// exportDataModule.pdf = {
|
||||
// name: 'pdf',
|
||||
// route: `/api/plugin/export?type=pdf&pid=${pid}`,
|
||||
// desc: '导出项目接口文档为 pdf 文件'
|
||||
// }
|
||||
function exportData(exportDataModule, pid) {
|
||||
exportDataModule.html = {
|
||||
name: 'html',
|
||||
route: `/api/plugin/export?type=html&pid=${pid}`,
|
||||
desc: '导出项目接口文档为 html 文件'
|
||||
};
|
||||
(exportDataModule.markdown = {
|
||||
name: 'markdown',
|
||||
route: `/api/plugin/export?type=markdown&pid=${pid}`,
|
||||
desc: '导出项目接口文档为 markdown 文件'
|
||||
}),
|
||||
(exportDataModule.json = {
|
||||
name: 'json',
|
||||
route: `/api/plugin/export?type=json&pid=${pid}`,
|
||||
desc: '导出项目接口文档为 json 文件,可使用该文件导入接口数据'
|
||||
});
|
||||
// exportDataModule.pdf = {
|
||||
// name: 'pdf',
|
||||
// route: `/api/plugin/export?type=pdf&pid=${pid}`,
|
||||
// desc: '导出项目接口文档为 pdf 文件'
|
||||
// }
|
||||
}
|
||||
|
||||
|
||||
|
||||
module.exports = function(){
|
||||
this.bindHook('export_data', exportData)
|
||||
}
|
||||
module.exports = function() {
|
||||
this.bindHook('export_data', exportData);
|
||||
};
|
||||
|
@ -65,7 +65,11 @@ const swagger = require('swagger-client');
|
||||
async function run(res) {
|
||||
let interfaceData = { apis: [], cats: [] };
|
||||
if(typeof res === 'string' && res){
|
||||
res = JSON.parse(res);
|
||||
try{
|
||||
res = JSON.parse(res);
|
||||
} catch (e) {
|
||||
console.error('json 解析出错',e.message)
|
||||
}
|
||||
}
|
||||
|
||||
isOAS3 = res.openapi && res.openapi === '3.0.0';
|
||||
|
@ -21,7 +21,7 @@ class statisMockModel extends baseModel {
|
||||
}
|
||||
|
||||
countByGroupId(id){
|
||||
return this.model.count({
|
||||
return this.model.countDocuments({
|
||||
group_id: id
|
||||
})
|
||||
}
|
||||
@ -32,7 +32,7 @@ class statisMockModel extends baseModel {
|
||||
}
|
||||
|
||||
getTotalCount() {
|
||||
return this.model.count({});
|
||||
return this.model.countDocuments({});
|
||||
}
|
||||
|
||||
getDayCount(timeInterval) {
|
||||
@ -62,7 +62,7 @@ class statisMockModel extends baseModel {
|
||||
|
||||
up(id, data) {
|
||||
data.up_time = yapi.commons.time();
|
||||
return this.model.update({
|
||||
return this.model.updateOne({
|
||||
_id: id
|
||||
}, data, { runValidators: true });
|
||||
}
|
||||
|
10042
package-lock.json
generated
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "yapi",
|
||||
"version": "1.3.22",
|
||||
"version": "1.4.0",
|
||||
"description": "YAPI",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
@ -62,8 +62,9 @@
|
||||
"markdown-it-table-of-contents": "0.3.2",
|
||||
"md5": "2.2.1",
|
||||
"mockjs": "1.0.1-beta3",
|
||||
"moment": "2.18.1",
|
||||
"mongoose": "4.7.0",
|
||||
"moment": "^2.19.3",
|
||||
"mongodb": "^3.1.8",
|
||||
"mongoose": "5.3.2",
|
||||
"mongoose-auto-increment": "5.0.1",
|
||||
"moox": "^1.0.2",
|
||||
"nodemailer": "4.0.1",
|
||||
@ -109,7 +110,7 @@
|
||||
"extract-text-webpack-plugin": "2.0.0",
|
||||
"ghooks": "^2.0.0",
|
||||
"happypack": "^4.0.0-beta.5",
|
||||
"json-schema-editor-visual": "^1.0.20",
|
||||
"json-schema-editor-visual": "^1.0.21",
|
||||
"less": "^2.7.2",
|
||||
"less-loader": "^4.0.5",
|
||||
"markdown-it-include": "^1.0.0",
|
||||
|
@ -24,7 +24,7 @@ app.proxy = true;
|
||||
yapi.app = app;
|
||||
|
||||
// app.use(bodyParser({multipart: true}));
|
||||
app.use(koaBody({ multipart: true }));
|
||||
app.use(koaBody({ multipart: true, jsonLimit: '2mb', formLimit: '1mb', textLimit: '1mb' }));
|
||||
app.use(mockServer);
|
||||
app.use(router.routes());
|
||||
app.use(router.allowedMethods());
|
||||
@ -55,5 +55,7 @@ app.use(koaStatic(yapi.path.join(yapi.WEBROOT, 'static'), { index: indexFile, gz
|
||||
|
||||
app.listen(yapi.WEBCONFIG.port);
|
||||
commons.log(
|
||||
`服务已启动,请打开下面链接访问: \nhttp://127.0.0.1${yapi.WEBCONFIG.port == '80' ? '' : ':' + yapi.WEBCONFIG.port}/`
|
||||
`服务已启动,请打开下面链接访问: \nhttp://127.0.0.1${
|
||||
yapi.WEBCONFIG.port == '80' ? '' : ':' + yapi.WEBCONFIG.port
|
||||
}/`
|
||||
);
|
||||
|
@ -125,12 +125,20 @@ class groupController extends baseController {
|
||||
async add(ctx) {
|
||||
let params = ctx.params;
|
||||
|
||||
if (this.getRole() !== 'admin') {
|
||||
return (ctx.body = yapi.commons.resReturn(null, 401, '没有权限'));
|
||||
}
|
||||
// 新版每个人都有权限添加分组
|
||||
|
||||
// if (this.getRole() !== 'admin') {
|
||||
// return (ctx.body = yapi.commons.resReturn(null, 401, '没有权限'));
|
||||
// }
|
||||
|
||||
let owners = [];
|
||||
|
||||
if(params.owner_uids.length === 0){
|
||||
params.owner_uids.push(
|
||||
this.getUid()
|
||||
)
|
||||
}
|
||||
|
||||
if (params.owner_uids) {
|
||||
for (let i = 0, len = params.owner_uids.length; i < len; i++) {
|
||||
let id = params.owner_uids[i];
|
||||
|
@ -105,7 +105,8 @@ class interfaceController extends baseController {
|
||||
method: minLengthStringField,
|
||||
catid: 'number',
|
||||
switch_notice: 'boolean',
|
||||
message: minLengthStringField
|
||||
message: minLengthStringField,
|
||||
tag: 'array'
|
||||
},
|
||||
addAndUpCommonField
|
||||
),
|
||||
@ -192,13 +193,13 @@ class interfaceController extends baseController {
|
||||
});
|
||||
});
|
||||
|
||||
let checkRepeat = await this.Model.checkRepeat(params.project_id, params.path, params.method);
|
||||
let checkRepeat = await this.Model.checkRepeat(params.project_id, http_path.pathname, params.method);
|
||||
|
||||
if (checkRepeat > 0) {
|
||||
return (ctx.body = yapi.commons.resReturn(
|
||||
null,
|
||||
40022,
|
||||
'已存在的接口:' + params.path + '[' + params.method + ']'
|
||||
'已存在的接口:' + http_path.pathname + '[' + params.method + ']'
|
||||
));
|
||||
}
|
||||
|
||||
@ -375,7 +376,6 @@ class interfaceController extends baseController {
|
||||
if (userinfo) {
|
||||
result.username = userinfo.username;
|
||||
}
|
||||
|
||||
ctx.body = yapi.commons.resReturn(result);
|
||||
} catch (e) {
|
||||
ctx.body = yapi.commons.resReturn(null, 402, e.message);
|
||||
@ -567,9 +567,9 @@ class interfaceController extends baseController {
|
||||
},
|
||||
params
|
||||
);
|
||||
|
||||
let http_path;
|
||||
if (params.path) {
|
||||
let http_path = url.parse(params.path, true);
|
||||
http_path = url.parse(params.path, true);
|
||||
|
||||
if (!yapi.commons.verifyPath(http_path.pathname)) {
|
||||
return (ctx.body = yapi.commons.resReturn(
|
||||
@ -596,14 +596,14 @@ class interfaceController extends baseController {
|
||||
) {
|
||||
let checkRepeat = await this.Model.checkRepeat(
|
||||
interfaceData.project_id,
|
||||
params.path,
|
||||
http_path.pathname,
|
||||
params.method
|
||||
);
|
||||
if (checkRepeat > 0) {
|
||||
return (ctx.body = yapi.commons.resReturn(
|
||||
null,
|
||||
401,
|
||||
'已存在的接口:' + params.path + '[' + params.method + ']'
|
||||
'已存在的接口:' + http_path.pathname + '[' + params.method + ']'
|
||||
));
|
||||
}
|
||||
}
|
||||
@ -616,7 +616,6 @@ class interfaceController extends baseController {
|
||||
data.req_params = [];
|
||||
}
|
||||
}
|
||||
|
||||
let result = await this.Model.up(id, data);
|
||||
let username = this.getUsername();
|
||||
let CurrentInterfaceData = await this.Model.get(id);
|
||||
@ -645,7 +644,6 @@ class interfaceController extends baseController {
|
||||
});
|
||||
|
||||
this.projectModel.up(interfaceData.project_id, { up_time: new Date().getTime() }).then();
|
||||
|
||||
if (params.switch_notice === true) {
|
||||
let diffView = showDiffMsg(jsondiffpatch, formattersHtml, logData);
|
||||
let annotatedCss = fs.readFileSync(
|
||||
@ -661,6 +659,7 @@ class interfaceController extends baseController {
|
||||
);
|
||||
|
||||
let project = await this.projectModel.getBaseInfo(interfaceData.project_id);
|
||||
|
||||
let interfaceUrl = `http://${ctx.request.host}/project/${
|
||||
interfaceData.project_id
|
||||
}/interface/api/${id}`;
|
||||
@ -733,7 +732,7 @@ class interfaceController extends baseController {
|
||||
|
||||
// let inter = await this.Model.get(id);
|
||||
let result = await this.Model.del(id);
|
||||
yapi.emitHook('interface_del', data).then();
|
||||
yapi.emitHook('interface_del', id).then();
|
||||
await this.caseModel.delByInterfaceId(id);
|
||||
let username = this.getUsername();
|
||||
this.catModel.get(data.catid).then(cate => {
|
||||
@ -902,7 +901,7 @@ class interfaceController extends baseController {
|
||||
|
||||
interfaceData.forEach(async item => {
|
||||
try {
|
||||
yapi.emitHook('interface_del', item).then();
|
||||
yapi.emitHook('interface_del', item._id).then();
|
||||
await this.caseModel.delByInterfaceId(item._id);
|
||||
} catch (e) {
|
||||
yapi.commons.log(e.message, 'error');
|
||||
|
@ -64,8 +64,8 @@ class openController extends baseController {
|
||||
json: 'string',
|
||||
project_id: 'string',
|
||||
merge: {
|
||||
type: 'boolean',
|
||||
default: false
|
||||
type: 'string',
|
||||
default: 'normal'
|
||||
}
|
||||
}
|
||||
};
|
||||
@ -97,7 +97,7 @@ class openController extends baseController {
|
||||
|
||||
let successMessage;
|
||||
let errorMessage = [];
|
||||
let data = await HanldeImportData(
|
||||
await HanldeImportData(
|
||||
res,
|
||||
project_id,
|
||||
selectCatid,
|
||||
|
@ -12,6 +12,7 @@ const userModel = require('../models/user.js');
|
||||
const logModel = require('../models/log.js');
|
||||
const followModel = require('../models/follow.js');
|
||||
const tokenModel = require('../models/token.js');
|
||||
const url = require('url');
|
||||
|
||||
const sha = require('sha.js');
|
||||
|
||||
@ -257,6 +258,7 @@ class projectController extends baseController {
|
||||
username: username,
|
||||
typeid: result._id
|
||||
});
|
||||
yapi.emitHook('project_add', result).then();
|
||||
ctx.body = yapi.commons.resReturn(result);
|
||||
}
|
||||
|
||||
@ -534,7 +536,7 @@ class projectController extends baseController {
|
||||
}
|
||||
result.role = await this.getProjectRole(params.id, 'project');
|
||||
|
||||
yapi.emitHook('project_add', params.id).then();
|
||||
yapi.emitHook('project_get', result).then();
|
||||
ctx.body = yapi.commons.resReturn(result);
|
||||
}
|
||||
|
||||
@ -826,6 +828,7 @@ class projectController extends baseController {
|
||||
username: username,
|
||||
typeid: id
|
||||
});
|
||||
yapi.emitHook('project_up', result).then();
|
||||
ctx.body = yapi.commons.resReturn(result);
|
||||
} catch (e) {
|
||||
ctx.body = yapi.commons.resReturn(null, 402, e.message);
|
||||
@ -889,6 +892,58 @@ class projectController extends baseController {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 编辑项目
|
||||
* @interface /project/up_tag
|
||||
* @method POST
|
||||
* @category project
|
||||
* @foldnumber 10
|
||||
* @param {Number} id 项目id,不能为空
|
||||
* @param {Array} [tag] 项目tag配置
|
||||
* @param {String} [tag[].name] tag名称
|
||||
* @param {String} [tag[].desc] tag描述
|
||||
* @returns {Object}
|
||||
* @example
|
||||
*/
|
||||
async upTag(ctx) {
|
||||
try {
|
||||
let id = ctx.request.body.id;
|
||||
let params = ctx.request.body;
|
||||
if (!id) {
|
||||
return (ctx.body = yapi.commons.resReturn(null, 405, '项目id不能为空'));
|
||||
}
|
||||
|
||||
if ((await this.checkAuth(id, 'project', 'edit')) !== true) {
|
||||
return (ctx.body = yapi.commons.resReturn(null, 405, '没有权限'));
|
||||
}
|
||||
|
||||
if (!params.tag || !Array.isArray(params.tag)) {
|
||||
return (ctx.body = yapi.commons.resReturn(null, 405, 'tag参数格式有误'));
|
||||
}
|
||||
|
||||
let projectData = await this.Model.get(id);
|
||||
let data = {
|
||||
up_time: yapi.commons.time()
|
||||
};
|
||||
data.tag = params.tag;
|
||||
|
||||
let result = await this.Model.up(id, data);
|
||||
let username = this.getUsername();
|
||||
yapi.commons.saveLog({
|
||||
content: `<a href="/user/profile/${this.getUid()}">${username}</a> 更新了项目 <a href="/project/${id}/interface/api">${
|
||||
projectData.name
|
||||
}</a> 的tag`,
|
||||
type: 'project',
|
||||
uid: this.getUid(),
|
||||
username: username,
|
||||
typeid: id
|
||||
});
|
||||
ctx.body = yapi.commons.resReturn(result);
|
||||
} catch (e) {
|
||||
ctx.body = yapi.commons.resReturn(null, 402, e.message);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取项目的环境变量值
|
||||
* @interface /project/get_env
|
||||
@ -1057,6 +1112,18 @@ class projectController extends baseController {
|
||||
|
||||
return (ctx.body = yapi.commons.resReturn(queryList, 0, 'ok'));
|
||||
}
|
||||
|
||||
// 输入 swagger url 的时候node端请求数据
|
||||
async swaggerUrl(ctx) {
|
||||
try {
|
||||
let ops = url.parse(ctx.request.query.url);
|
||||
let result = await yapi.commons.createWebAPIRequest(ops);
|
||||
|
||||
ctx.body = yapi.commons.resReturn(result);
|
||||
} catch (err) {
|
||||
ctx.body = yapi.commons.resReturn(null, 402, err.message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = projectController;
|
||||
|
@ -147,8 +147,8 @@ class interfaceColController extends baseController {
|
||||
*/
|
||||
async testDelete(ctx) {
|
||||
try {
|
||||
let params = ctx.request.query;
|
||||
ctx.body = yapi.commons.resReturn(params);
|
||||
let body = ctx.request.body;
|
||||
ctx.body = yapi.commons.resReturn(body);
|
||||
} catch (e) {
|
||||
ctx.body = yapi.commons.resReturn(null, 402, e.message);
|
||||
}
|
||||
@ -226,9 +226,12 @@ class interfaceColController extends baseController {
|
||||
*/
|
||||
async testResponse(ctx) {
|
||||
try {
|
||||
let result = `<div><h2>12222222</h2></div>`;
|
||||
// let result = `<div><h2>12222222</h2></div>`;
|
||||
// let result = `wieieieieiieieie`
|
||||
// let result = { b: '12', c: '23' };
|
||||
let result = { b: '12', c: '23' };
|
||||
ctx.set('Access-Control-Allow-Origin', '*');
|
||||
ctx.set('Content-Type', 'text');
|
||||
console.log(ctx.response);
|
||||
ctx.body = result;
|
||||
} catch (e) {
|
||||
ctx.body = yapi.commons.resReturn(null, 402, e.message);
|
||||
|
@ -139,6 +139,7 @@ module.exports = async (ctx, next) => {
|
||||
// let hostname = ctx.hostname;
|
||||
// let config = yapi.WEBCONFIG;
|
||||
let path = ctx.path;
|
||||
let header = ctx.request.header;
|
||||
|
||||
if (path.indexOf('/mock/') !== 0) {
|
||||
if (next) await next();
|
||||
@ -150,7 +151,10 @@ module.exports = async (ctx, next) => {
|
||||
paths.splice(0, 3);
|
||||
path = '/' + paths.join('/');
|
||||
|
||||
ctx.set('Access-Control-Allow-Origin', '*');
|
||||
ctx.set('Access-Control-Allow-Origin', header.origin);
|
||||
ctx.set('Access-Control-Allow-Credentials', true);
|
||||
|
||||
// ctx.set('Access-Control-Allow-Origin', '*');
|
||||
|
||||
if (!projectId) {
|
||||
return (ctx.body = yapi.commons.resReturn(null, 400, 'projectId不能为空'));
|
||||
@ -227,7 +231,7 @@ module.exports = async (ctx, next) => {
|
||||
if (ctx.method === 'OPTIONS' && ctx.request.header['access-control-request-method']) {
|
||||
return handleCorsRequest(ctx);
|
||||
}
|
||||
|
||||
|
||||
return (ctx.body = yapi.commons.resReturn(
|
||||
null,
|
||||
404,
|
||||
@ -245,7 +249,6 @@ module.exports = async (ctx, next) => {
|
||||
interfaceData = interfaceData[0];
|
||||
}
|
||||
|
||||
|
||||
// 必填字段是否填写好
|
||||
if (project.strice) {
|
||||
const validResult = mockValidator(interfaceData, ctx);
|
||||
@ -258,7 +261,6 @@ module.exports = async (ctx, next) => {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
let res;
|
||||
// mock 返回值处理
|
||||
res = interfaceData.res_body;
|
||||
@ -345,12 +347,15 @@ module.exports = async (ctx, next) => {
|
||||
}
|
||||
});
|
||||
}
|
||||
} else ctx.set(i, context.resHeader[i]);
|
||||
} else {
|
||||
ctx.set(i, context.resHeader[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ctx.status = context.httpCode;
|
||||
return (ctx.body = context.mockJson);
|
||||
ctx.body = context.mockJson;
|
||||
return;
|
||||
} catch (e) {
|
||||
yapi.commons.log(e, 'error');
|
||||
return (ctx.body = {
|
||||
|
@ -1,6 +1,6 @@
|
||||
const yapi = require('../yapi.js');
|
||||
const mongoose = require('mongoose');
|
||||
const autoIncrement = require('mongoose-auto-increment');
|
||||
const autoIncrement = require('../utils/mongoose-auto-increment');
|
||||
|
||||
/**
|
||||
* 所有的model都需要继承baseModel, 且需要 getSchema和getName方法,不然会报错
|
||||
|
@ -57,7 +57,7 @@ class followModel extends baseModel {
|
||||
}
|
||||
|
||||
checkProjectRepeat(uid, projectid) {
|
||||
return this.model.count({
|
||||
return this.model.countDocuments({
|
||||
uid: uid,
|
||||
projectid: projectid
|
||||
});
|
||||
|
@ -86,13 +86,13 @@ class groupModel extends baseModel {
|
||||
}
|
||||
|
||||
checkRepeat(name) {
|
||||
return this.model.count({
|
||||
return this.model.countDocuments({
|
||||
group_name: name
|
||||
});
|
||||
}
|
||||
// 分组数量统计
|
||||
getGroupListCount() {
|
||||
return this.model.count({ type: 'public' });
|
||||
return this.model.countDocuments({ type: 'public' });
|
||||
}
|
||||
|
||||
addMember(id, data) {
|
||||
@ -131,7 +131,7 @@ class groupModel extends baseModel {
|
||||
}
|
||||
|
||||
checkMemberRepeat(id, uid) {
|
||||
return this.model.count({
|
||||
return this.model.countDocuments({
|
||||
_id: id,
|
||||
'members.uid': uid
|
||||
});
|
||||
|
@ -93,7 +93,8 @@ class interfaceModel extends baseModel {
|
||||
field2: String,
|
||||
field3: String,
|
||||
api_opened: { type: Boolean, default: false },
|
||||
index: { type: Number, default: 0 }
|
||||
index: { type: Number, default: 0 },
|
||||
tag: Array
|
||||
};
|
||||
}
|
||||
|
||||
@ -155,15 +156,15 @@ class interfaceModel extends baseModel {
|
||||
}
|
||||
|
||||
checkRepeat(id, path, method) {
|
||||
return this.model.count({
|
||||
return this.model.countDocuments({
|
||||
project_id: id,
|
||||
path: path,
|
||||
'query_path.path': path,
|
||||
method: method
|
||||
});
|
||||
}
|
||||
|
||||
countByProjectId(id) {
|
||||
return this.model.count({
|
||||
return this.model.countDocuments({
|
||||
project_id: id
|
||||
});
|
||||
}
|
||||
@ -191,7 +192,7 @@ class interfaceModel extends baseModel {
|
||||
.skip((page - 1) * limit)
|
||||
.limit(limit)
|
||||
.select(
|
||||
'_id title uid path method project_id catid api_opened edit_uid status add_time up_time'
|
||||
'_id title uid path method project_id catid api_opened edit_uid status add_time up_time tag'
|
||||
)
|
||||
.exec();
|
||||
}
|
||||
@ -207,12 +208,12 @@ class interfaceModel extends baseModel {
|
||||
|
||||
//获取全部接口信息
|
||||
getInterfaceListCount() {
|
||||
return this.model.count({});
|
||||
return this.model.countDocuments({});
|
||||
}
|
||||
|
||||
listByCatid(catid, select) {
|
||||
select =
|
||||
select || '_id title uid path method project_id catid edit_uid status add_time up_time index';
|
||||
select || '_id title uid path method project_id catid edit_uid status add_time up_time index tag';
|
||||
return this.model
|
||||
.find({
|
||||
catid: catid
|
||||
@ -233,7 +234,7 @@ class interfaceModel extends baseModel {
|
||||
.skip((page - 1) * limit)
|
||||
.limit(limit)
|
||||
.select(
|
||||
'_id title uid path method project_id catid edit_uid api_opened status add_time up_time, index'
|
||||
'_id title uid path method project_id catid edit_uid api_opened status add_time up_time, index, tag'
|
||||
)
|
||||
.exec();
|
||||
}
|
||||
@ -308,7 +309,7 @@ class interfaceModel extends baseModel {
|
||||
}
|
||||
|
||||
listCount(option) {
|
||||
return this.model.count(option);
|
||||
return this.model.countDocuments(option);
|
||||
}
|
||||
|
||||
upIndex(id, index) {
|
||||
@ -331,4 +332,4 @@ class interfaceModel extends baseModel {
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = interfaceModel;
|
||||
module.exports = interfaceModel;
|
@ -63,7 +63,7 @@ class interfaceCase extends baseModel {
|
||||
|
||||
//获取全部测试接口信息
|
||||
getInterfaceCaseListCount() {
|
||||
return this.model.count({});
|
||||
return this.model.countDocuments({});
|
||||
}
|
||||
|
||||
get(id) {
|
||||
|
@ -35,7 +35,7 @@ class interfaceCat extends baseModel {
|
||||
}
|
||||
|
||||
checkRepeat(name) {
|
||||
return this.model.count({
|
||||
return this.model.countDocuments({
|
||||
name: name
|
||||
});
|
||||
}
|
||||
|
@ -33,7 +33,7 @@ class interfaceCol extends baseModel {
|
||||
}
|
||||
|
||||
checkRepeat(name) {
|
||||
return this.model.count({
|
||||
return this.model.countDocuments({
|
||||
name: name
|
||||
});
|
||||
}
|
||||
|
@ -106,7 +106,7 @@ class logModel extends baseModel {
|
||||
.exec();
|
||||
}
|
||||
listCountByGroup(typeid, pidList) {
|
||||
return this.model.count({
|
||||
return this.model.countDocuments({
|
||||
$or: [
|
||||
{
|
||||
type: 'project',
|
||||
@ -132,7 +132,7 @@ class logModel extends baseModel {
|
||||
if (selectValue && !isNaN(selectValue)) {
|
||||
params['data.interface_id'] = +selectValue;
|
||||
}
|
||||
return this.model.count(params);
|
||||
return this.model.countDocuments(params);
|
||||
}
|
||||
|
||||
listWithCatid(typeid, type, interfaceId) {
|
||||
|
@ -34,7 +34,8 @@ class projectModel extends baseModel {
|
||||
project_mock_script: String,
|
||||
is_mock_open: { type: Boolean, default: false },
|
||||
strice: { type: Boolean, default: false },
|
||||
is_json5: { type: Boolean, default: true }
|
||||
is_json5: { type: Boolean, default: true },
|
||||
tag: [{name: String, desc: String}]
|
||||
};
|
||||
}
|
||||
|
||||
@ -75,7 +76,7 @@ class projectModel extends baseModel {
|
||||
}
|
||||
|
||||
getProjectWithAuth(group_id, uid) {
|
||||
return this.model.count({
|
||||
return this.model.countDocuments({
|
||||
group_id: group_id,
|
||||
'members.uid': uid
|
||||
});
|
||||
@ -84,7 +85,7 @@ class projectModel extends baseModel {
|
||||
getBaseInfo(id, select) {
|
||||
select =
|
||||
select ||
|
||||
'_id uid name basepath switch_notice desc group_id project_type env icon color add_time up_time pre_script after_script project_mock_script is_mock_open strice is_json5';
|
||||
'_id uid name basepath switch_notice desc group_id project_type env icon color add_time up_time pre_script after_script project_mock_script is_mock_open strice is_json5 tag';
|
||||
return this.model
|
||||
.findOne({
|
||||
_id: id
|
||||
@ -102,14 +103,14 @@ class projectModel extends baseModel {
|
||||
}
|
||||
|
||||
checkNameRepeat(name, groupid) {
|
||||
return this.model.count({
|
||||
return this.model.countDocuments({
|
||||
name: name,
|
||||
group_id: groupid
|
||||
});
|
||||
}
|
||||
|
||||
checkDomainRepeat(domain, basepath) {
|
||||
return this.model.count({
|
||||
return this.model.countDocuments({
|
||||
prd_host: domain,
|
||||
basepath: basepath
|
||||
});
|
||||
@ -128,12 +129,12 @@ class projectModel extends baseModel {
|
||||
|
||||
// 获取项目数量统计
|
||||
getProjectListCount() {
|
||||
return this.model.count();
|
||||
return this.model.countDocuments();
|
||||
}
|
||||
|
||||
countWithPublic(group_id) {
|
||||
let params = { group_id: group_id, project_type: 'public' };
|
||||
return this.model.count(params);
|
||||
return this.model.countDocuments(params);
|
||||
}
|
||||
|
||||
listWithPaging(group_id, page, limit) {
|
||||
@ -150,13 +151,13 @@ class projectModel extends baseModel {
|
||||
}
|
||||
|
||||
listCount(group_id) {
|
||||
return this.model.count({
|
||||
return this.model.countDocuments({
|
||||
group_id: group_id
|
||||
});
|
||||
}
|
||||
|
||||
countByGroupId(group_id) {
|
||||
return this.model.count({
|
||||
return this.model.countDocuments({
|
||||
group_id: group_id
|
||||
});
|
||||
}
|
||||
@ -208,7 +209,7 @@ class projectModel extends baseModel {
|
||||
}
|
||||
|
||||
checkMemberRepeat(id, uid) {
|
||||
return this.model.count({
|
||||
return this.model.countDocuments({
|
||||
_id: id,
|
||||
'members.uid': uid
|
||||
});
|
||||
|
@ -34,7 +34,7 @@ class userModel extends baseModel {
|
||||
}
|
||||
|
||||
checkRepeat(email) {
|
||||
return this.model.count({
|
||||
return this.model.countDocuments({
|
||||
email: email
|
||||
});
|
||||
}
|
||||
@ -68,7 +68,7 @@ class userModel extends baseModel {
|
||||
}
|
||||
|
||||
listCount() {
|
||||
return this.model.count();
|
||||
return this.model.countDocuments();
|
||||
}
|
||||
|
||||
findByEmail(email) {
|
||||
|
@ -27,7 +27,7 @@ var hooks = {
|
||||
},
|
||||
/**
|
||||
* 客户端删除接口成功后触发
|
||||
* @param data 删除接口的详细信息
|
||||
* @param data 接口id
|
||||
*/
|
||||
interface_del: {
|
||||
type: 'multi',
|
||||
@ -65,6 +65,22 @@ var hooks = {
|
||||
type: 'multi',
|
||||
listener: []
|
||||
},
|
||||
/**
|
||||
* 客户端更新一个新项目
|
||||
* @param id 项目id
|
||||
*/
|
||||
project_up: {
|
||||
type: 'multi',
|
||||
listener: []
|
||||
},
|
||||
/**
|
||||
* 客户端获取一个项目
|
||||
* @param id 项目id
|
||||
*/
|
||||
project_get: {
|
||||
type: 'multi',
|
||||
listener: []
|
||||
},
|
||||
/**
|
||||
* 客户端删除删除一个项目
|
||||
* @param id 项目id
|
||||
|
@ -254,6 +254,11 @@ let routerConfig = {
|
||||
path: 'up_env',
|
||||
method: 'post'
|
||||
},
|
||||
{
|
||||
action: 'upTag',
|
||||
path: 'up_tag',
|
||||
method: 'post'
|
||||
},
|
||||
{
|
||||
action: 'token',
|
||||
path: 'token',
|
||||
@ -273,6 +278,11 @@ let routerConfig = {
|
||||
action: 'copy',
|
||||
path: 'copy',
|
||||
method: 'post'
|
||||
},
|
||||
{
|
||||
action: 'swaggerUrl',
|
||||
path: 'swagger_url',
|
||||
method: 'get'
|
||||
}
|
||||
],
|
||||
interface: [
|
||||
|
@ -18,6 +18,7 @@ const ejs = require('easy-json-schema');
|
||||
|
||||
const jsf = require('json-schema-faker');
|
||||
const formats = require('../../common/formats');
|
||||
const http = require('http');
|
||||
|
||||
jsf.extend ('mock', function () {
|
||||
return {
|
||||
@ -626,3 +627,39 @@ exports.handleMockScript = function(script, context) {
|
||||
context.delay = sandbox.delay;
|
||||
};
|
||||
|
||||
|
||||
|
||||
exports.createWebAPIRequest = function(ops) {
|
||||
return new Promise(function(resolve, reject) {
|
||||
let req = '';
|
||||
let http_client = http.request(
|
||||
{
|
||||
host: ops.hostname,
|
||||
method: 'GET',
|
||||
port: ops.port,
|
||||
path: ops.path
|
||||
},
|
||||
function(res) {
|
||||
res.on('error', function(err) {
|
||||
reject(err);
|
||||
});
|
||||
res.setEncoding('utf8');
|
||||
if (res.statusCode != 200) {
|
||||
reject({message: 'statusCode != 200'});
|
||||
} else {
|
||||
res.on('data', function(chunk) {
|
||||
req += chunk;
|
||||
});
|
||||
res.on('end', function() {
|
||||
resolve(req);
|
||||
});
|
||||
}
|
||||
}
|
||||
);
|
||||
http_client.on('error', (e) => {
|
||||
reject({message: 'request error'});
|
||||
});
|
||||
http_client.end();
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
const mongoose = require('mongoose');
|
||||
const yapi = require('../yapi.js');
|
||||
const autoIncrement = require('mongoose-auto-increment');
|
||||
const autoIncrement = require('./mongoose-auto-increment');
|
||||
|
||||
function model(model, schema) {
|
||||
if (schema instanceof mongoose.Schema === false) {
|
||||
@ -9,26 +9,32 @@ function model(model, schema) {
|
||||
|
||||
schema.set('autoIndex', false);
|
||||
|
||||
return yapi.connect.model(model, schema, model);
|
||||
return mongoose.model(model, schema, model);
|
||||
}
|
||||
|
||||
function connect(callback) {
|
||||
mongoose.Promise = global.Promise;
|
||||
|
||||
let config = yapi.WEBCONFIG;
|
||||
let options = { useMongoClient: true };
|
||||
let options = {useNewUrlParser: true };
|
||||
|
||||
if (config.db.user) {
|
||||
options.user = config.db.user;
|
||||
options.pass = config.db.pass;
|
||||
}
|
||||
|
||||
var connectString = `mongodb://${config.db.servername}:${config.db.port}/${config.db.DATABASE}`;
|
||||
if (config.db.authSource) {
|
||||
connectString = connectString + `?authSource=${config.db.authSource}`;
|
||||
}
|
||||
options = Object.assign({}, options, config.db.options)
|
||||
|
||||
//yapi.commons.log(connectString);
|
||||
var connectString = '';
|
||||
|
||||
if(config.db.connectString){
|
||||
connectString = config.db.connectString;
|
||||
}else{
|
||||
connectString = `mongodb://${config.db.servername}:${config.db.port}/${config.db.DATABASE}`;
|
||||
if (config.db.authSource) {
|
||||
connectString = connectString + `?authSource=${config.db.authSource}`;
|
||||
}
|
||||
}
|
||||
|
||||
let db = mongoose.connect(
|
||||
connectString,
|
||||
|
178
server/utils/mongoose-auto-increment.js
Normal file
@ -0,0 +1,178 @@
|
||||
// Module Scope
|
||||
var mongoose = require('mongoose'),
|
||||
extend = require('extend'),
|
||||
counterSchema,
|
||||
IdentityCounter;
|
||||
|
||||
// Initialize plugin by creating counter collection in database.
|
||||
exports.initialize = function (connection) {
|
||||
try {
|
||||
IdentityCounter = mongoose.model('IdentityCounter');
|
||||
} catch (ex) {
|
||||
if (ex.name === 'MissingSchemaError') {
|
||||
// Create new counter schema.
|
||||
counterSchema = new mongoose.Schema({
|
||||
model: { type: String, require: true },
|
||||
field: { type: String, require: true },
|
||||
count: { type: Number, default: 0 }
|
||||
});
|
||||
|
||||
// Create a unique index using the "field" and "model" fields.
|
||||
counterSchema.index({ field: 1, model: 1 }, { unique: true, required: true, index: -1 });
|
||||
|
||||
// Create model using new schema.
|
||||
IdentityCounter = mongoose.model('IdentityCounter', counterSchema);
|
||||
}
|
||||
else
|
||||
throw ex;
|
||||
}
|
||||
};
|
||||
|
||||
// The function to use when invoking the plugin on a custom schema.
|
||||
exports.plugin = function (schema, options) {
|
||||
|
||||
// If we don't have reference to the counterSchema or the IdentityCounter model then the plugin was most likely not
|
||||
// initialized properly so throw an error.
|
||||
if (!counterSchema || !IdentityCounter) throw new Error("mongoose-auto-increment has not been initialized");
|
||||
|
||||
// Default settings and plugin scope variables.
|
||||
var settings = {
|
||||
model: null, // The model to configure the plugin for.
|
||||
field: '_id', // The field the plugin should track.
|
||||
startAt: 0, // The number the count should start at.
|
||||
incrementBy: 1, // The number by which to increment the count each time.
|
||||
unique: true // Should we create a unique index for the field
|
||||
},
|
||||
fields = {}, // A hash of fields to add properties to in Mongoose.
|
||||
ready = false; // True if the counter collection has been updated and the document is ready to be saved.
|
||||
|
||||
switch (typeof(options)) {
|
||||
// If string, the user chose to pass in just the model name.
|
||||
case 'string':
|
||||
settings.model = options;
|
||||
break;
|
||||
// If object, the user passed in a hash of options.
|
||||
case 'object':
|
||||
extend(settings, options);
|
||||
break;
|
||||
}
|
||||
|
||||
if (settings.model == null)
|
||||
throw new Error("model must be set");
|
||||
|
||||
// Add properties for field in schema.
|
||||
fields[settings.field] = {
|
||||
type: Number,
|
||||
require: true
|
||||
};
|
||||
if (settings.field !== '_id')
|
||||
fields[settings.field].unique = settings.unique
|
||||
schema.add(fields);
|
||||
|
||||
// Find the counter for this model and the relevant field.
|
||||
IdentityCounter.findOne(
|
||||
{ model: settings.model, field: settings.field },
|
||||
function (err, counter) {
|
||||
if (!counter) {
|
||||
// If no counter exists then create one and save it.
|
||||
counter = new IdentityCounter({ model: settings.model, field: settings.field, count: settings.startAt - settings.incrementBy });
|
||||
counter.save(function () {
|
||||
ready = true;
|
||||
});
|
||||
}
|
||||
else {
|
||||
ready = true;
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
// Declare a function to get the next counter for the model/schema.
|
||||
var nextCount = function (callback) {
|
||||
IdentityCounter.findOne({
|
||||
model: settings.model,
|
||||
field: settings.field
|
||||
}, function (err, counter) {
|
||||
if (err) return callback(err);
|
||||
callback(null, counter === null ? settings.startAt : counter.count + settings.incrementBy);
|
||||
});
|
||||
};
|
||||
// Add nextCount as both a method on documents and a static on the schema for convenience.
|
||||
schema.method('nextCount', nextCount);
|
||||
schema.static('nextCount', nextCount);
|
||||
|
||||
// Declare a function to reset counter at the start value - increment value.
|
||||
var resetCount = function (callback) {
|
||||
IdentityCounter.findOneAndUpdate(
|
||||
{ model: settings.model, field: settings.field },
|
||||
{ count: settings.startAt - settings.incrementBy },
|
||||
{ new: true }, // new: true specifies that the callback should get the updated counter.
|
||||
function (err) {
|
||||
if (err) return callback(err);
|
||||
callback(null, settings.startAt);
|
||||
}
|
||||
);
|
||||
};
|
||||
// Add resetCount as both a method on documents and a static on the schema for convenience.
|
||||
schema.method('resetCount', resetCount);
|
||||
schema.static('resetCount', resetCount);
|
||||
|
||||
// Every time documents in this schema are saved, run this logic.
|
||||
schema.pre('save', function (next) {
|
||||
// Get reference to the document being saved.
|
||||
var doc = this;
|
||||
|
||||
// Only do this if it is a new document (see http://mongoosejs.com/docs/api.html#document_Document-isNew)
|
||||
if (doc.isNew) {
|
||||
// Declare self-invoking save function.
|
||||
(function save() {
|
||||
// If ready, run increment logic.
|
||||
// Note: ready is true when an existing counter collection is found or after it is created for the
|
||||
// first time.
|
||||
if (ready) {
|
||||
// check that a number has already been provided, and update the counter to that number if it is
|
||||
// greater than the current count
|
||||
if (typeof doc[settings.field] === 'number') {
|
||||
IdentityCounter.findOneAndUpdate(
|
||||
// IdentityCounter documents are identified by the model and field that the plugin was invoked for.
|
||||
// Check also that count is less than field value.
|
||||
{ model: settings.model, field: settings.field, count: { $lt: doc[settings.field] } },
|
||||
// Change the count of the value found to the new field value.
|
||||
{ count: doc[settings.field] },
|
||||
function (err) {
|
||||
if (err) return next(err);
|
||||
// Continue with default document save functionality.
|
||||
next();
|
||||
}
|
||||
);
|
||||
} else {
|
||||
// Find the counter collection entry for this model and field and update it.
|
||||
IdentityCounter.findOneAndUpdate(
|
||||
// IdentityCounter documents are identified by the model and field that the plugin was invoked for.
|
||||
{ model: settings.model, field: settings.field },
|
||||
// Increment the count by `incrementBy`.
|
||||
{ $inc: { count: settings.incrementBy } },
|
||||
// new:true specifies that the callback should get the counter AFTER it is updated (incremented).
|
||||
{ new: true },
|
||||
// Receive the updated counter.
|
||||
function (err, updatedIdentityCounter) {
|
||||
if (err) return next(err);
|
||||
// If there are no errors then go ahead and set the document's field to the current count.
|
||||
doc[settings.field] = updatedIdentityCounter.count;
|
||||
// Continue with default document save functionality.
|
||||
next();
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
// If not ready then set a 5 millisecond timer and try to save again. It will keep doing this until
|
||||
// the counter collection is ready.
|
||||
else
|
||||
setTimeout(save, 5);
|
||||
})();
|
||||
}
|
||||
// If the document does not have the field we're interested in or that field isn't a number AND the user did
|
||||
// not specify that we should increment on updates, then just continue the save without any increment logic.
|
||||
else
|
||||
next();
|
||||
});
|
||||
};
|
@ -119,8 +119,8 @@ yapi update -v v1.1.0 //升级到指定版本
|
||||
<ul>
|
||||
<li><code>enable</code> 表示是否配置 LDAP 登录,true(支持 LDAP登录 )/false(不支持LDAP登录);</li>
|
||||
<li><code>server</code> LDAP 服务器地址,前面需要加上 ldap:// 前缀,也可以是 ldaps:// 表示是通过 SSL 连接;</li>
|
||||
<li><code>baseDn</code> LDAP 服务器的登录用户名,必须是从根结点到用户节点的全路径;</li>
|
||||
<li><code>bindPassword</code> 登录该 LDAP 服务器的密码;</li>
|
||||
<li><code>baseDn</code> LDAP 服务器的登录用户名,必须是从根结点到用户节点的全路径(非必须);</li>
|
||||
<li><code>bindPassword</code> 登录该 LDAP 服务器的密码(非必须);</li>
|
||||
<li><code>searchDn</code> 查询用户数据的路径,类似数据库中的一张表的地址,注意这里也必须是全路径;</li>
|
||||
<li><code>searchStandard</code> 查询条件,这里是 mail 表示查询用户信息是通过邮箱信息来查询的。注意,该字段信息与LDAP数据库存储数据的字段相对应,如果如果存储用户邮箱信息的字段是 email, 这里就需要修改成 email.(1.3.18+支持)自定义filter表达式,基本形式为:&(objectClass=user)(cn=%s), 其中%s会被username替换</li>
|
||||
<li><code>emailPostfix</code> 登陆邮箱后缀(非必须)</li>
|
||||
|
Before ![]() (image error) Size: 35 KiB After ![]() (image error) Size: 46 KiB ![]() ![]() |
BIN
static/doc/documents/images/schema-mock-1.png
Normal file
After ![]() (image error) Size: 11 KiB |
BIN
static/doc/documents/images/schema-mock-2.png
Normal file
After ![]() (image error) Size: 22 KiB |
BIN
static/doc/documents/images/usage/adv-mock-case4-new.png
Normal file
After ![]() (image error) Size: 42 KiB |
BIN
static/doc/documents/images/usage/adv-mock-case5-new.png
Normal file
After ![]() (image error) Size: 102 KiB |
BIN
static/doc/documents/images/usage/project-message.png
Normal file
After ![]() (image error) Size: 46 KiB |
@ -177,6 +177,11 @@ window.ydoc_plugin_search_json = {
|
||||
"content": "",
|
||||
"url": "/documents/project.html",
|
||||
"children": [
|
||||
{
|
||||
"title": "基本设置",
|
||||
"url": "/documents/project.html#基本设置",
|
||||
"content": "基本设置tag 信息:可自定义tag名称和tag描述,tag信息可用在接口tag标识中;\nmock 严格模式:开启后 mock 请求会对 query,body form 的必须字段和 json schema 进行校验;\n开启json5:开启后允许接口请求body 和返回值中写 json 字段。yapi建议用户关闭 json5, 因为json-schema 格式可以进行接口格式校验。\n"
|
||||
},
|
||||
{
|
||||
"title": "新建项目",
|
||||
"url": "/documents/project.html#新建项目",
|
||||
@ -259,6 +264,11 @@ window.ydoc_plugin_search_json = {
|
||||
"content": "",
|
||||
"url": "/documents/project.html",
|
||||
"children": [
|
||||
{
|
||||
"title": "基本设置",
|
||||
"url": "/documents/project.html#基本设置",
|
||||
"content": "基本设置tag 信息:可自定义tag名称和tag描述,tag信息可用在接口tag标识中;\nmock 严格模式:开启后 mock 请求会对 query,body form 的必须字段和 json schema 进行校验;\n开启json5:开启后允许接口请求body 和返回值中写 json 字段。yapi建议用户关闭 json5, 因为json-schema 格式可以进行接口格式校验。\n"
|
||||
},
|
||||
{
|
||||
"title": "新建项目",
|
||||
"url": "/documents/project.html#新建项目",
|
||||
@ -349,7 +359,7 @@ window.ydoc_plugin_search_json = {
|
||||
{
|
||||
"title": "基本设置",
|
||||
"url": "/documents/api.html#接口配置-基本设置",
|
||||
"content": "基本设置接口路径:可以更改 HTTP 请求方式,并且支持 restful 动态路由,例如 /api/{id}/{name}, id和name是动态参数\n选择分类:可以更改接口所在分类\n状态:用于标识接口是否开发完成。\n"
|
||||
"content": "基本设置接口路径:可以更改 HTTP 请求方式,并且支持 restful 动态路由,例如 /api/{id}/{name}, id和name是动态参数\n选择分类:可以更改接口所在分类\n状态:用于标识接口是否开发完成。\nTag:用于标识接口tag信息(v1.3.23+),在接口list页可以根据tag过滤接口\n"
|
||||
},
|
||||
{
|
||||
"title": "请求参数设置",
|
||||
@ -391,7 +401,7 @@ window.ydoc_plugin_search_json = {
|
||||
{
|
||||
"title": "基本设置",
|
||||
"url": "/documents/api.html#接口配置-基本设置",
|
||||
"content": "基本设置接口路径:可以更改 HTTP 请求方式,并且支持 restful 动态路由,例如 /api/{id}/{name}, id和name是动态参数\n选择分类:可以更改接口所在分类\n状态:用于标识接口是否开发完成。\n"
|
||||
"content": "基本设置接口路径:可以更改 HTTP 请求方式,并且支持 restful 动态路由,例如 /api/{id}/{name}, id和name是动态参数\n选择分类:可以更改接口所在分类\n状态:用于标识接口是否开发完成。\nTag:用于标识接口tag信息(v1.3.23+),在接口list页可以根据tag过滤接口\n"
|
||||
},
|
||||
{
|
||||
"title": "请求参数设置",
|
||||
@ -443,12 +453,17 @@ window.ydoc_plugin_search_json = {
|
||||
{
|
||||
"title": "方式2. json-schema",
|
||||
"url": "/documents/mock.html#方式2.-json-schema",
|
||||
"content": "方式2. json-schema开启 json-schema 功能后,将不再使用 mockjs 解析定义的返回数据,而是根据 json-schema 定义的数据结构,生成随机数据。"
|
||||
"content": "方式2. json-schema开启 json-schema 功能后,根据 json-schema 定义的数据结构,生成随机数据。"
|
||||
},
|
||||
{
|
||||
"title": "如何生成随机的邮箱或 ip?",
|
||||
"url": "/documents/mock.html#方式2.-json-schema-如何生成随机的邮箱或-ip?",
|
||||
"content": "如何生成随机的邮箱或 ip?点击高级设置,选择 format 选项,比如选择 email 则该字段生成随机邮箱字符串。"
|
||||
"title": "如何生成随机的邮箱或 ip(该方法在v1.3.22之后不再适用)?",
|
||||
"url": "/documents/mock.html#方式2.-json-schema-如何生成随机的邮箱或-ip该方法在v1.3.22之后不再适用?",
|
||||
"content": "如何生成随机的邮箱或 ip(该方法在v1.3.22之后不再适用)?点击高级设置,选择 format 选项,比如选择 email 则该字段生成随机邮箱字符串。"
|
||||
},
|
||||
{
|
||||
"title": "集成 mockjs",
|
||||
"url": "/documents/mock.html#方式2.-json-schema-集成-mockjs",
|
||||
"content": "集成 mockjs基本书写方式为 mock 的数据占位符@xxx, 具体字段详见Mockjs 官网\n如果不是以@字符开头的话或者匹配不到Mockjs中的占位符就会直接生成输入的值\n"
|
||||
},
|
||||
{
|
||||
"title": "如何使用 Mock",
|
||||
@ -495,12 +510,17 @@ window.ydoc_plugin_search_json = {
|
||||
{
|
||||
"title": "方式2. json-schema",
|
||||
"url": "/documents/mock.html#方式2.-json-schema",
|
||||
"content": "方式2. json-schema开启 json-schema 功能后,将不再使用 mockjs 解析定义的返回数据,而是根据 json-schema 定义的数据结构,生成随机数据。"
|
||||
"content": "方式2. json-schema开启 json-schema 功能后,根据 json-schema 定义的数据结构,生成随机数据。"
|
||||
},
|
||||
{
|
||||
"title": "如何生成随机的邮箱或 ip?",
|
||||
"url": "/documents/mock.html#方式2.-json-schema-如何生成随机的邮箱或-ip?",
|
||||
"content": "如何生成随机的邮箱或 ip?点击高级设置,选择 format 选项,比如选择 email 则该字段生成随机邮箱字符串。"
|
||||
"title": "如何生成随机的邮箱或 ip(该方法在v1.3.22之后不再适用)?",
|
||||
"url": "/documents/mock.html#方式2.-json-schema-如何生成随机的邮箱或-ip该方法在v1.3.22之后不再适用?",
|
||||
"content": "如何生成随机的邮箱或 ip(该方法在v1.3.22之后不再适用)?点击高级设置,选择 format 选项,比如选择 email 则该字段生成随机邮箱字符串。"
|
||||
},
|
||||
{
|
||||
"title": "集成 mockjs",
|
||||
"url": "/documents/mock.html#方式2.-json-schema-集成-mockjs",
|
||||
"content": "集成 mockjs基本书写方式为 mock 的数据占位符@xxx, 具体字段详见Mockjs 官网\n如果不是以@字符开头的话或者匹配不到Mockjs中的占位符就会直接生成输入的值\n"
|
||||
},
|
||||
{
|
||||
"title": "如何使用 Mock",
|
||||
@ -744,28 +764,28 @@ window.ydoc_plugin_search_json = {
|
||||
},
|
||||
{
|
||||
"title": "数据导入",
|
||||
"content": "在数据管理可快速导入其他格式的接口数据,方便快速添加接口。YApi 目前支持 postman, swagger, har 数据导入。",
|
||||
"content": "在数据管理可快速导入其他格式的接口数据,方便快速添加接口。YApi 目前支持 postman, swagger, har 数据导入。v1.3.23+ 增加数据导入的3种同步方式 normal, good, mergin普通模式(normal):不导入已存在的接口;\n智能合并(good):已存在的接口,将合并返回数据的 response,适用于导入了 swagger 数据,保留对数据结构的改动;例如,用户对字段code 添加了mock信息, 当再次数据导入的时候 mock 字段将不会被覆盖\n完全覆盖(mergin):不保留旧数据,完全使用新数据,适用于接口定义完全交给后端定义, 默认为 normal\n",
|
||||
"url": "/documents/data.html",
|
||||
"children": [
|
||||
{
|
||||
"title": "Postman 数据导入",
|
||||
"url": "/documents/data.html#postman-数据导入",
|
||||
"content": "Postman 数据导入1.首先在postman导出接口2.选择collection_v1,点击export导出接口到文件xxx3.打开yapi平台,进入到项目页面,点击数据管理,选择相应的分组和postman导入\b方式,\b选择刚才保存的文件路径,开始导入数据"
|
||||
"content": "Postman 数据导入1.首先在 postman 导出接口2.选择 collection_v1,点击 export 导出接口到文件 xxx3.打开 yapi 平台,进入到项目页面,点击数据管理,选择相应的分组和 postman 导入 \b 方式,\b 选择刚才保存的文件路径,开始导入数据"
|
||||
},
|
||||
{
|
||||
"title": "HAR\b\b 数据导入",
|
||||
"url": "/documents/data.html#har\b\b-数据导入",
|
||||
"content": "HAR\b\b 数据导入可用 chrome 实现录制接口数据的功能,方便开发者快速导入项目接口1.打开 Chrome 浏览器开发者工具,点击network,首次使用请先clear所有请求信息,确保录制功能开启(红色为开启状态)2.操作页面实际功能,完成后点击save as HAR with content,将数据保存到文件xxx3.打开yapi平台,进入到项目页面,点击数据管理,选择相应的分组和har导入\b方式,\b选择刚才保存的文件路径,开始导入数据"
|
||||
"content": "HAR\b\b 数据导入可用 chrome 实现录制接口数据的功能,方便开发者快速导入项目接口1.打开 Chrome 浏览器开发者工具,点击 network,首次使用请先 clear 所有请求信息,确保录制功能开启(红色为开启状态)2.操作页面实际功能,完成后点击 save as HAR with content,将数据保存到文件 xxx3.打开 yapi 平台,进入到项目页面,点击数据管理,选择相应的分组和 har 导入 \b 方式,\b 选择刚才保存的文件路径,开始导入数据Tips: har 数据导入只支持 response.content.mimeType 为 application/json 类型的数据\n"
|
||||
},
|
||||
{
|
||||
"title": "Swagger 数据导入",
|
||||
"url": "/documents/data.html#swagger-数据导入",
|
||||
"content": "Swagger 数据导入什么是 Swagger ?[Swagger从入门到精通](https://www.gitbook.com/book/huangwenchao/swagger/details)1.生成 JSON 语言编写的 Swagger API 文档文件 例如这样的数据 (http://petstore.swagger.io/v2/swagger.json),可以将其内容复制到 JSON 文件中。Tips: v1.3.19 版本开始支持swagger url 导入功能\n2.打开yapi平台,进入到项目页面,点击数据管理,选择相应的分组和swagger导入\b方式,\b选择刚才的文件,开始导入数据"
|
||||
"content": "Swagger 数据导入什么是 Swagger ?[Swagger从入门到精通](https://www.gitbook.com/book/huangwenchao/swagger/details)1.生成 JSON 语言编写的 Swagger API 文档文件 例如这样的数据 (http://petstore.swagger.io/v2/swagger.json),可以将其内容复制到 JSON 文件中。Tips: v1.3.19 版本开始支持 swagger url 导入功能\n2.打开 yapi 平台,进入到项目页面,点击数据管理,选择相应的分组和 swagger 导入 \b 方式,\b 选择刚才的文件,开始导入数据"
|
||||
},
|
||||
{
|
||||
"title": "YApi接口JSON数据导入",
|
||||
"url": "/documents/data.html#yapi接口json数据导入",
|
||||
"content": "YApi接口JSON数据导入该功能在 v1.3.12 版本上线,可导入在 yapi 平台导出的 json 接口数据。"
|
||||
"title": "YApi 接口 JSON 数据导入",
|
||||
"url": "/documents/data.html#yapi-接口-json-数据导入",
|
||||
"content": "YApi 接口 JSON 数据导入该功能在 v1.3.12 版本上线,可导入在 yapi 平台导出的 json 接口数据。"
|
||||
},
|
||||
{
|
||||
"title": "通过命令行导入接口数据",
|
||||
@ -775,34 +795,34 @@ window.ydoc_plugin_search_json = {
|
||||
{
|
||||
"title": "使用方法",
|
||||
"url": "/documents/data.html#通过命令行导入接口数据-使用方法",
|
||||
"content": "使用方法第一步,确保 yapi-cli >= 1.2.7 版本,如果低于此版本请升级 yapi-cli 工具npm install -g yapi-cli第二步,在任意一个目录下新建配置文件 yapi-import.json,内容如下:{ \"type\": \"swagger\",\n \"token\": \"17fba0027f300248b804\",\n \"file\": \"swagger.json\",\n \"merge\": false,\n \"server\": \"http://yapi.local.qunar.com:3000\"\n}\ntype 是数据数据方式,目前官方只支持 swaggertoken 是项目token,在 项目设置 -> token 设置获取file 是 swagger 接口文档文件,可使用绝对路径或 urlmerge 是否覆盖旧的接口,默认不开启,配置 true 开启server 是yapi服务器地址第三步,在新建配置文件的当前目录,执行下面指令yapi import"
|
||||
"content": "使用方法第一步,确保 yapi-cli >= 1.2.7 版本,如果低于此版本请升级 yapi-cli 工具npm install -g yapi-cli第二步,在任意一个目录下新建配置文件 yapi-import.json,内容如下:{ \"type\": \"swagger\",\n \"token\": \"17fba0027f300248b804\",\n \"file\": \"swagger.json\",\n \"merge\": \"normal\",\n \"server\": \"http://yapi.local.qunar.com:3000\"\n}\ntype 是数据数据方式,目前官方只支持 swaggertoken 是项目 token,在 项目设置 -> token 设置获取file 是 swagger 接口文档文件,可使用绝对路径或 urlmerge 有三种导入方式(v1.3.23+支持) normal, good, mergin普通模式(normal):不导入已存在的接口;\n智能合并(good):已存在的接口,将合并返回数据的 response,适用于导入了 swagger 数据,保留对数据结构的改动;\n完全覆盖(mergin):不保留旧数据,完全使用新数据,适用于接口定义完全交给后端定义, 默认为 normal\nserver 是 yapi 服务器地址第三步,在新建配置文件的当前目录,执行下面指令yapi import"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"title": "数据导入",
|
||||
"content": "在数据管理可快速导入其他格式的接口数据,方便快速添加接口。YApi 目前支持 postman, swagger, har 数据导入。",
|
||||
"content": "在数据管理可快速导入其他格式的接口数据,方便快速添加接口。YApi 目前支持 postman, swagger, har 数据导入。v1.3.23+ 增加数据导入的3种同步方式 normal, good, mergin普通模式(normal):不导入已存在的接口;\n智能合并(good):已存在的接口,将合并返回数据的 response,适用于导入了 swagger 数据,保留对数据结构的改动;例如,用户对字段code 添加了mock信息, 当再次数据导入的时候 mock 字段将不会被覆盖\n完全覆盖(mergin):不保留旧数据,完全使用新数据,适用于接口定义完全交给后端定义, 默认为 normal\n",
|
||||
"url": "/documents/data.html",
|
||||
"children": [
|
||||
{
|
||||
"title": "Postman 数据导入",
|
||||
"url": "/documents/data.html#postman-数据导入",
|
||||
"content": "Postman 数据导入1.首先在postman导出接口2.选择collection_v1,点击export导出接口到文件xxx3.打开yapi平台,进入到项目页面,点击数据管理,选择相应的分组和postman导入\b方式,\b选择刚才保存的文件路径,开始导入数据"
|
||||
"content": "Postman 数据导入1.首先在 postman 导出接口2.选择 collection_v1,点击 export 导出接口到文件 xxx3.打开 yapi 平台,进入到项目页面,点击数据管理,选择相应的分组和 postman 导入 \b 方式,\b 选择刚才保存的文件路径,开始导入数据"
|
||||
},
|
||||
{
|
||||
"title": "HAR\b\b 数据导入",
|
||||
"url": "/documents/data.html#har\b\b-数据导入",
|
||||
"content": "HAR\b\b 数据导入可用 chrome 实现录制接口数据的功能,方便开发者快速导入项目接口1.打开 Chrome 浏览器开发者工具,点击network,首次使用请先clear所有请求信息,确保录制功能开启(红色为开启状态)2.操作页面实际功能,完成后点击save as HAR with content,将数据保存到文件xxx3.打开yapi平台,进入到项目页面,点击数据管理,选择相应的分组和har导入\b方式,\b选择刚才保存的文件路径,开始导入数据"
|
||||
"content": "HAR\b\b 数据导入可用 chrome 实现录制接口数据的功能,方便开发者快速导入项目接口1.打开 Chrome 浏览器开发者工具,点击 network,首次使用请先 clear 所有请求信息,确保录制功能开启(红色为开启状态)2.操作页面实际功能,完成后点击 save as HAR with content,将数据保存到文件 xxx3.打开 yapi 平台,进入到项目页面,点击数据管理,选择相应的分组和 har 导入 \b 方式,\b 选择刚才保存的文件路径,开始导入数据Tips: har 数据导入只支持 response.content.mimeType 为 application/json 类型的数据\n"
|
||||
},
|
||||
{
|
||||
"title": "Swagger 数据导入",
|
||||
"url": "/documents/data.html#swagger-数据导入",
|
||||
"content": "Swagger 数据导入什么是 Swagger ?[Swagger从入门到精通](https://www.gitbook.com/book/huangwenchao/swagger/details)1.生成 JSON 语言编写的 Swagger API 文档文件 例如这样的数据 (http://petstore.swagger.io/v2/swagger.json),可以将其内容复制到 JSON 文件中。Tips: v1.3.19 版本开始支持swagger url 导入功能\n2.打开yapi平台,进入到项目页面,点击数据管理,选择相应的分组和swagger导入\b方式,\b选择刚才的文件,开始导入数据"
|
||||
"content": "Swagger 数据导入什么是 Swagger ?[Swagger从入门到精通](https://www.gitbook.com/book/huangwenchao/swagger/details)1.生成 JSON 语言编写的 Swagger API 文档文件 例如这样的数据 (http://petstore.swagger.io/v2/swagger.json),可以将其内容复制到 JSON 文件中。Tips: v1.3.19 版本开始支持 swagger url 导入功能\n2.打开 yapi 平台,进入到项目页面,点击数据管理,选择相应的分组和 swagger 导入 \b 方式,\b 选择刚才的文件,开始导入数据"
|
||||
},
|
||||
{
|
||||
"title": "YApi接口JSON数据导入",
|
||||
"url": "/documents/data.html#yapi接口json数据导入",
|
||||
"content": "YApi接口JSON数据导入该功能在 v1.3.12 版本上线,可导入在 yapi 平台导出的 json 接口数据。"
|
||||
"title": "YApi 接口 JSON 数据导入",
|
||||
"url": "/documents/data.html#yapi-接口-json-数据导入",
|
||||
"content": "YApi 接口 JSON 数据导入该功能在 v1.3.12 版本上线,可导入在 yapi 平台导出的 json 接口数据。"
|
||||
},
|
||||
{
|
||||
"title": "通过命令行导入接口数据",
|
||||
@ -812,7 +832,7 @@ window.ydoc_plugin_search_json = {
|
||||
{
|
||||
"title": "使用方法",
|
||||
"url": "/documents/data.html#通过命令行导入接口数据-使用方法",
|
||||
"content": "使用方法第一步,确保 yapi-cli >= 1.2.7 版本,如果低于此版本请升级 yapi-cli 工具npm install -g yapi-cli第二步,在任意一个目录下新建配置文件 yapi-import.json,内容如下:{ \"type\": \"swagger\",\n \"token\": \"17fba0027f300248b804\",\n \"file\": \"swagger.json\",\n \"merge\": false,\n \"server\": \"http://yapi.local.qunar.com:3000\"\n}\ntype 是数据数据方式,目前官方只支持 swaggertoken 是项目token,在 项目设置 -> token 设置获取file 是 swagger 接口文档文件,可使用绝对路径或 urlmerge 是否覆盖旧的接口,默认不开启,配置 true 开启server 是yapi服务器地址第三步,在新建配置文件的当前目录,执行下面指令yapi import"
|
||||
"content": "使用方法第一步,确保 yapi-cli >= 1.2.7 版本,如果低于此版本请升级 yapi-cli 工具npm install -g yapi-cli第二步,在任意一个目录下新建配置文件 yapi-import.json,内容如下:{ \"type\": \"swagger\",\n \"token\": \"17fba0027f300248b804\",\n \"file\": \"swagger.json\",\n \"merge\": \"normal\",\n \"server\": \"http://yapi.local.qunar.com:3000\"\n}\ntype 是数据数据方式,目前官方只支持 swaggertoken 是项目 token,在 项目设置 -> token 设置获取file 是 swagger 接口文档文件,可使用绝对路径或 urlmerge 有三种导入方式(v1.3.23+支持) normal, good, mergin普通模式(normal):不导入已存在的接口;\n智能合并(good):已存在的接口,将合并返回数据的 response,适用于导入了 swagger 数据,保留对数据结构的改动;\n完全覆盖(mergin):不保留旧数据,完全使用新数据,适用于接口定义完全交给后端定义, 默认为 normal\nserver 是 yapi 服务器地址第三步,在新建配置文件的当前目录,执行下面指令yapi import"
|
||||
}
|
||||
]
|
||||
},
|
||||
@ -1030,7 +1050,7 @@ window.ydoc_plugin_search_json = {
|
||||
{
|
||||
"title": "后端 hookList",
|
||||
"url": "/documents/plugin-hooks.html#后端-hooklist",
|
||||
"content": "后端 hookList目前 hooksList 只有下面列出的部分,如果您有其他的需求,可提建议到 github 或者 qq 群/** * 钩子配置\n */\nvar hooks = {\n /**\n * 第三方sso登录钩子,暂只支持设置一个\n * @param ctx\n * @return 必需返回一个 promise 对象,resolve({username: '', email: ''})\n */\n 'third_login': {\n type: 'single',\n listener: null\n },\n /**\n * 客户端增加接口成功后触发\n * @param data 接口的详细信息\n */\n interface_add: {\n type: 'multi',\n listener: []\n },\n /**\n * 客户端删除接口成功后触发\n * @param data 删除接口的详细信息\n */\n interface_del: {\n type: 'multi',\n listener: []\n },\n /**\n * 客户端更新接口成功后触发\n * @param id 接口id\n */\n interface_update: {\n type: 'multi',\n listener: []\n },\n /**\n * 客户端获取接口数据列表\n * @param list 返回接口的数据列表\n */\n interface_list: {\n type: 'multi',\n listener: []\n },\n /**\n * 客户端获取一条接口信息触发\n * @param data 接口的详细信息\n */\n interface_get: {\n type: 'multi',\n listener: []\n },\n /**\n * 客户端增加一个新项目\n * @param id 项目id\n */\n 'project_add':{\n type: 'multi',\n listener: []\n },\n /**\n * 客户端删除删除一个项目\n * @param id 项目id\n */\n 'project_del':{\n type: 'multi',\n listener: []\n },\n /**\n * MockServer生成mock数据后触发\n * @param context Object\n * {\n * projectData: project,\n interfaceData: interfaceData,\n ctx: ctx,\n mockJson: res\n * }\n *\n */\n mock_after: {\n type: 'multi',\n listener: []\n },\n /**\n * 增加路由的钩子\n * type Sync\n * @param addPluginRouter Function\n * addPLuginPLugin(config)\n * config = {\n * path, // String\n * method, // String\n * controller // Class 继承baseController的class\n * action // String controller的Action\n * }\n */\n add_router: {\n type: 'multi',\n listener: []\n }\n};\n"
|
||||
"content": "后端 hookList目前 hooksList 只有下面列出的部分,如果您有其他的需求,可提建议到 github 或者 qq 群/** * 钩子配置\n */\nvar hooks = {\n /**\n * 第三方sso登录钩子,暂只支持设置一个\n * @param ctx\n * @return 必需返回一个 promise 对象,resolve({username: '', email: ''})\n */\n 'third_login': {\n type: 'single',\n listener: null\n },\n /**\n * 客户端增加接口成功后触发\n * @param data 接口的详细信息\n */\n interface_add: {\n type: 'multi',\n listener: []\n },\n /**\n * 客户端删除接口成功后触发\n * @param data 接口id\n */\n interface_del: {\n type: 'multi',\n listener: []\n },\n /**\n * 客户端更新接口成功后触发\n * @param id 接口id\n */\n interface_update: {\n type: 'multi',\n listener: []\n },\n /**\n * 客户端获取接口数据列表\n * @param list 返回接口的数据列表\n */\n interface_list: {\n type: 'multi',\n listener: []\n },\n /**\n * 客户端获取一条接口信息触发\n * @param data 接口的详细信息\n */\n interface_get: {\n type: 'multi',\n listener: []\n },\n /**\n * 客户端增加一个新项目\n * @param id 项目id\n */\n 'project_add':{\n type: 'multi',\n listener: []\n },\n /**\n * 客户端删除删除一个项目\n * @param id 项目id\n */\n 'project_del':{\n type: 'multi',\n listener: []\n },\n /**\n * MockServer生成mock数据后触发\n * @param context Object\n * {\n * projectData: project,\n interfaceData: interfaceData,\n ctx: ctx,\n mockJson: res\n * }\n *\n */\n mock_after: {\n type: 'multi',\n listener: []\n },\n /**\n * 增加路由的钩子\n * type Sync\n * @param addPluginRouter Function\n * addPLuginPLugin(config)\n * config = {\n * path, // String\n * method, // String\n * controller // Class 继承baseController的class\n * action // String controller的Action\n * }\n */\n add_router: {\n type: 'multi',\n listener: []\n }\n};\n"
|
||||
},
|
||||
{
|
||||
"title": "前端 hookList",
|
||||
@ -1047,7 +1067,7 @@ window.ydoc_plugin_search_json = {
|
||||
{
|
||||
"title": "后端 hookList",
|
||||
"url": "/documents/plugin-hooks.html#后端-hooklist",
|
||||
"content": "后端 hookList目前 hooksList 只有下面列出的部分,如果您有其他的需求,可提建议到 github 或者 qq 群/** * 钩子配置\n */\nvar hooks = {\n /**\n * 第三方sso登录钩子,暂只支持设置一个\n * @param ctx\n * @return 必需返回一个 promise 对象,resolve({username: '', email: ''})\n */\n 'third_login': {\n type: 'single',\n listener: null\n },\n /**\n * 客户端增加接口成功后触发\n * @param data 接口的详细信息\n */\n interface_add: {\n type: 'multi',\n listener: []\n },\n /**\n * 客户端删除接口成功后触发\n * @param data 删除接口的详细信息\n */\n interface_del: {\n type: 'multi',\n listener: []\n },\n /**\n * 客户端更新接口成功后触发\n * @param id 接口id\n */\n interface_update: {\n type: 'multi',\n listener: []\n },\n /**\n * 客户端获取接口数据列表\n * @param list 返回接口的数据列表\n */\n interface_list: {\n type: 'multi',\n listener: []\n },\n /**\n * 客户端获取一条接口信息触发\n * @param data 接口的详细信息\n */\n interface_get: {\n type: 'multi',\n listener: []\n },\n /**\n * 客户端增加一个新项目\n * @param id 项目id\n */\n 'project_add':{\n type: 'multi',\n listener: []\n },\n /**\n * 客户端删除删除一个项目\n * @param id 项目id\n */\n 'project_del':{\n type: 'multi',\n listener: []\n },\n /**\n * MockServer生成mock数据后触发\n * @param context Object\n * {\n * projectData: project,\n interfaceData: interfaceData,\n ctx: ctx,\n mockJson: res\n * }\n *\n */\n mock_after: {\n type: 'multi',\n listener: []\n },\n /**\n * 增加路由的钩子\n * type Sync\n * @param addPluginRouter Function\n * addPLuginPLugin(config)\n * config = {\n * path, // String\n * method, // String\n * controller // Class 继承baseController的class\n * action // String controller的Action\n * }\n */\n add_router: {\n type: 'multi',\n listener: []\n }\n};\n"
|
||||
"content": "后端 hookList目前 hooksList 只有下面列出的部分,如果您有其他的需求,可提建议到 github 或者 qq 群/** * 钩子配置\n */\nvar hooks = {\n /**\n * 第三方sso登录钩子,暂只支持设置一个\n * @param ctx\n * @return 必需返回一个 promise 对象,resolve({username: '', email: ''})\n */\n 'third_login': {\n type: 'single',\n listener: null\n },\n /**\n * 客户端增加接口成功后触发\n * @param data 接口的详细信息\n */\n interface_add: {\n type: 'multi',\n listener: []\n },\n /**\n * 客户端删除接口成功后触发\n * @param data 接口id\n */\n interface_del: {\n type: 'multi',\n listener: []\n },\n /**\n * 客户端更新接口成功后触发\n * @param id 接口id\n */\n interface_update: {\n type: 'multi',\n listener: []\n },\n /**\n * 客户端获取接口数据列表\n * @param list 返回接口的数据列表\n */\n interface_list: {\n type: 'multi',\n listener: []\n },\n /**\n * 客户端获取一条接口信息触发\n * @param data 接口的详细信息\n */\n interface_get: {\n type: 'multi',\n listener: []\n },\n /**\n * 客户端增加一个新项目\n * @param id 项目id\n */\n 'project_add':{\n type: 'multi',\n listener: []\n },\n /**\n * 客户端删除删除一个项目\n * @param id 项目id\n */\n 'project_del':{\n type: 'multi',\n listener: []\n },\n /**\n * MockServer生成mock数据后触发\n * @param context Object\n * {\n * projectData: project,\n interfaceData: interfaceData,\n ctx: ctx,\n mockJson: res\n * }\n *\n */\n mock_after: {\n type: 'multi',\n listener: []\n },\n /**\n * 增加路由的钩子\n * type Sync\n * @param addPluginRouter Function\n * addPLuginPLugin(config)\n * config = {\n * path, // String\n * method, // String\n * controller // Class 继承baseController的class\n * action // String controller的Action\n * }\n */\n add_router: {\n type: 'multi',\n listener: []\n }\n};\n"
|
||||
},
|
||||
{
|
||||
"title": "前端 hookList",
|
||||
@ -1190,14 +1210,14 @@ window.ydoc_plugin_search_json = {
|
||||
"url": "/documents/CHANGELOG.html",
|
||||
"children": [
|
||||
{
|
||||
"title": "v1.3.22",
|
||||
"url": "/documents/CHANGELOG.html#v1.3.22",
|
||||
"content": "v1.3.22json schema number和integer支持枚举\n服务端测试增加下载功能\n增加 mock 接口请求字段参数验证\n增加返回数据验证\n"
|
||||
"title": "v1.3.23",
|
||||
"url": "/documents/CHANGELOG.html#v1.3.23",
|
||||
"content": "v1.3.23接口tag功能\n数据导入增加 merge 功能\n增加参数的批量导入功能\nBug Fixed接口path中写入 ?name=xxx bug\n高级mock 匹配 data: [{item: XXX}] 时匹配不成功\n接口运行 query params 自动勾选\nmock get 带 cookie 时跨域\njson schema 嵌套多层 array 预览不展示 bug\nswagger URL 导入 跨域问题\n"
|
||||
},
|
||||
{
|
||||
"title": "Bug Fixed",
|
||||
"url": "/documents/CHANGELOG.html#bug-fixed",
|
||||
"content": "Bug Fixed命令行导入成员信息为 undefined\n修复form 参数为空时 接口无法保存的问题\n"
|
||||
"title": "v1.3.22",
|
||||
"url": "/documents/CHANGELOG.html#v1.3.22",
|
||||
"content": "v1.3.22json schema number和integer支持枚举\n服务端测试增加下载功能\n增加 mock 接口请求字段参数验证\n增加返回数据验证\nBug Fixed命令行导入成员信息为 undefined\n修复form 参数为空时 接口无法保存的问题\n"
|
||||
},
|
||||
{
|
||||
"title": "v1.3.21",
|
||||
@ -1372,14 +1392,14 @@ window.ydoc_plugin_search_json = {
|
||||
"url": "/documents/CHANGELOG.html",
|
||||
"children": [
|
||||
{
|
||||
"title": "v1.3.22",
|
||||
"url": "/documents/CHANGELOG.html#v1.3.22",
|
||||
"content": "v1.3.22json schema number和integer支持枚举\n服务端测试增加下载功能\n增加 mock 接口请求字段参数验证\n增加返回数据验证\n"
|
||||
"title": "v1.3.23",
|
||||
"url": "/documents/CHANGELOG.html#v1.3.23",
|
||||
"content": "v1.3.23接口tag功能\n数据导入增加 merge 功能\n增加参数的批量导入功能\nBug Fixed接口path中写入 ?name=xxx bug\n高级mock 匹配 data: [{item: XXX}] 时匹配不成功\n接口运行 query params 自动勾选\nmock get 带 cookie 时跨域\njson schema 嵌套多层 array 预览不展示 bug\nswagger URL 导入 跨域问题\n"
|
||||
},
|
||||
{
|
||||
"title": "Bug Fixed",
|
||||
"url": "/documents/CHANGELOG.html#bug-fixed",
|
||||
"content": "Bug Fixed命令行导入成员信息为 undefined\n修复form 参数为空时 接口无法保存的问题\n"
|
||||
"title": "v1.3.22",
|
||||
"url": "/documents/CHANGELOG.html#v1.3.22",
|
||||
"content": "v1.3.22json schema number和integer支持枚举\n服务端测试增加下载功能\n增加 mock 接口请求字段参数验证\n增加返回数据验证\nBug Fixed命令行导入成员信息为 undefined\n修复form 参数为空时 接口无法保存的问题\n"
|
||||
},
|
||||
{
|
||||
"title": "v1.3.21",
|
||||
@ -1593,7 +1613,7 @@ window.ydoc_plugin_search_json = {
|
||||
{
|
||||
"title": "配置LDAP登录",
|
||||
"url": "/devops/index.html#配置ldap登录",
|
||||
"content": "配置LDAP登录打开项目目录 config.json 文件,添加如下字段:{ \"port\": \"*****\",\n \"adminAccount\": \"********\",\n \"db\": {...},\n \"mail\": {...},\n \"ldapLogin\": {\n \"enable\": true,\n \"server\": \"ldap://l-ldapt1.ops.dev.cn0.qunar.com\",\n \"baseDn\": \"CN=Admin,CN=Users,DC=test,DC=com\",\n \"bindPassword\": \"password123\",\n \"searchDn\": \"OU=UserContainer,DC=test,DC=com\",\n \"searchStandard\": \"mail\", // 自定义格式: \"searchStandard\": \"&(objectClass=user)(cn=%s)\"\n \"emailPostfix\": \"@163.com\",\n \"emailKey\": \"mail\",\n \"usernameKey\": \"name\"\n }\n}\n\n这里面的配置项含义如下:enable 表示是否配置 LDAP 登录,true(支持 LDAP登录 )/false(不支持LDAP登录);\nserver LDAP 服务器地址,前面需要加上 ldap:// 前缀,也可以是 ldaps:// 表示是通过 SSL 连接;\nbaseDn LDAP 服务器的登录用户名,必须是从根结点到用户节点的全路径;\nbindPassword 登录该 LDAP 服务器的密码;\nsearchDn 查询用户数据的路径,类似数据库中的一张表的地址,注意这里也必须是全路径;\nsearchStandard 查询条件,这里是 mail 表示查询用户信息是通过邮箱信息来查询的。注意,该字段信息与LDAP数据库存储数据的字段相对应,如果如果存储用户邮箱信息的字段是 email, 这里就需要修改成 email.(1.3.18+支持)自定义filter表达式,基本形式为:&(objectClass=user)(cn=%s), 其中%s会被username替换\nemailPostfix 登陆邮箱后缀(非必须)\nemailKey: ldap数据库存放邮箱信息的字段(v1.3.21 新增 非必须)\nusernameKey: ldap数据库存放用户名信息的字段(v1.3.21 新增 非必须)\n重启服务器后,可以在登录页看到如下画面,说明 ladp 配置成功"
|
||||
"content": "配置LDAP登录打开项目目录 config.json 文件,添加如下字段:{ \"port\": \"*****\",\n \"adminAccount\": \"********\",\n \"db\": {...},\n \"mail\": {...},\n \"ldapLogin\": {\n \"enable\": true,\n \"server\": \"ldap://l-ldapt1.ops.dev.cn0.qunar.com\",\n \"baseDn\": \"CN=Admin,CN=Users,DC=test,DC=com\",\n \"bindPassword\": \"password123\",\n \"searchDn\": \"OU=UserContainer,DC=test,DC=com\",\n \"searchStandard\": \"mail\", // 自定义格式: \"searchStandard\": \"&(objectClass=user)(cn=%s)\"\n \"emailPostfix\": \"@163.com\",\n \"emailKey\": \"mail\",\n \"usernameKey\": \"name\"\n }\n}\n\n这里面的配置项含义如下:enable 表示是否配置 LDAP 登录,true(支持 LDAP登录 )/false(不支持LDAP登录);\nserver LDAP 服务器地址,前面需要加上 ldap:// 前缀,也可以是 ldaps:// 表示是通过 SSL 连接;\nbaseDn LDAP 服务器的登录用户名,必须是从根结点到用户节点的全路径(非必须);\nbindPassword 登录该 LDAP 服务器的密码(非必须);\nsearchDn 查询用户数据的路径,类似数据库中的一张表的地址,注意这里也必须是全路径;\nsearchStandard 查询条件,这里是 mail 表示查询用户信息是通过邮箱信息来查询的。注意,该字段信息与LDAP数据库存储数据的字段相对应,如果如果存储用户邮箱信息的字段是 email, 这里就需要修改成 email.(1.3.18+支持)自定义filter表达式,基本形式为:&(objectClass=user)(cn=%s), 其中%s会被username替换\nemailPostfix 登陆邮箱后缀(非必须)\nemailKey: ldap数据库存放邮箱信息的字段(v1.3.21 新增 非必须)\nusernameKey: ldap数据库存放用户名信息的字段(v1.3.21 新增 非必须)\n重启服务器后,可以在登录页看到如下画面,说明 ladp 配置成功"
|
||||
},
|
||||
{
|
||||
"title": "禁止注册",
|
||||
@ -1650,7 +1670,7 @@ window.ydoc_plugin_search_json = {
|
||||
{
|
||||
"title": "配置LDAP登录",
|
||||
"url": "/devops/index.html#配置ldap登录",
|
||||
"content": "配置LDAP登录打开项目目录 config.json 文件,添加如下字段:{ \"port\": \"*****\",\n \"adminAccount\": \"********\",\n \"db\": {...},\n \"mail\": {...},\n \"ldapLogin\": {\n \"enable\": true,\n \"server\": \"ldap://l-ldapt1.ops.dev.cn0.qunar.com\",\n \"baseDn\": \"CN=Admin,CN=Users,DC=test,DC=com\",\n \"bindPassword\": \"password123\",\n \"searchDn\": \"OU=UserContainer,DC=test,DC=com\",\n \"searchStandard\": \"mail\", // 自定义格式: \"searchStandard\": \"&(objectClass=user)(cn=%s)\"\n \"emailPostfix\": \"@163.com\",\n \"emailKey\": \"mail\",\n \"usernameKey\": \"name\"\n }\n}\n\n这里面的配置项含义如下:enable 表示是否配置 LDAP 登录,true(支持 LDAP登录 )/false(不支持LDAP登录);\nserver LDAP 服务器地址,前面需要加上 ldap:// 前缀,也可以是 ldaps:// 表示是通过 SSL 连接;\nbaseDn LDAP 服务器的登录用户名,必须是从根结点到用户节点的全路径;\nbindPassword 登录该 LDAP 服务器的密码;\nsearchDn 查询用户数据的路径,类似数据库中的一张表的地址,注意这里也必须是全路径;\nsearchStandard 查询条件,这里是 mail 表示查询用户信息是通过邮箱信息来查询的。注意,该字段信息与LDAP数据库存储数据的字段相对应,如果如果存储用户邮箱信息的字段是 email, 这里就需要修改成 email.(1.3.18+支持)自定义filter表达式,基本形式为:&(objectClass=user)(cn=%s), 其中%s会被username替换\nemailPostfix 登陆邮箱后缀(非必须)\nemailKey: ldap数据库存放邮箱信息的字段(v1.3.21 新增 非必须)\nusernameKey: ldap数据库存放用户名信息的字段(v1.3.21 新增 非必须)\n重启服务器后,可以在登录页看到如下画面,说明 ladp 配置成功"
|
||||
"content": "配置LDAP登录打开项目目录 config.json 文件,添加如下字段:{ \"port\": \"*****\",\n \"adminAccount\": \"********\",\n \"db\": {...},\n \"mail\": {...},\n \"ldapLogin\": {\n \"enable\": true,\n \"server\": \"ldap://l-ldapt1.ops.dev.cn0.qunar.com\",\n \"baseDn\": \"CN=Admin,CN=Users,DC=test,DC=com\",\n \"bindPassword\": \"password123\",\n \"searchDn\": \"OU=UserContainer,DC=test,DC=com\",\n \"searchStandard\": \"mail\", // 自定义格式: \"searchStandard\": \"&(objectClass=user)(cn=%s)\"\n \"emailPostfix\": \"@163.com\",\n \"emailKey\": \"mail\",\n \"usernameKey\": \"name\"\n }\n}\n\n这里面的配置项含义如下:enable 表示是否配置 LDAP 登录,true(支持 LDAP登录 )/false(不支持LDAP登录);\nserver LDAP 服务器地址,前面需要加上 ldap:// 前缀,也可以是 ldaps:// 表示是通过 SSL 连接;\nbaseDn LDAP 服务器的登录用户名,必须是从根结点到用户节点的全路径(非必须);\nbindPassword 登录该 LDAP 服务器的密码(非必须);\nsearchDn 查询用户数据的路径,类似数据库中的一张表的地址,注意这里也必须是全路径;\nsearchStandard 查询条件,这里是 mail 表示查询用户信息是通过邮箱信息来查询的。注意,该字段信息与LDAP数据库存储数据的字段相对应,如果如果存储用户邮箱信息的字段是 email, 这里就需要修改成 email.(1.3.18+支持)自定义filter表达式,基本形式为:&(objectClass=user)(cn=%s), 其中%s会被username替换\nemailPostfix 登陆邮箱后缀(非必须)\nemailKey: ldap数据库存放邮箱信息的字段(v1.3.21 新增 非必须)\nusernameKey: ldap数据库存放用户名信息的字段(v1.3.21 新增 非必须)\n重启服务器后,可以在登录页看到如下画面,说明 ladp 配置成功"
|
||||
},
|
||||
{
|
||||
"title": "禁止注册",
|
||||
|
@ -1 +1 @@
|
||||
window.WEBPACK_ASSETS = {"index.js":{"js":"index@841cead422a517b69145.js","css":"index@841cead422a517b69145.css"},"lib":{"js":"lib@250ff659130357ed53a3.js"},"lib2":{"js":"lib2@dd0309d1dda071c015ba.js"},"lib3":{"js":"lib3@117a77c58de265bbb1ec.js"},"manifest":{"js":"manifest@f2f4bd774d6c221b3d5f.js"}}
|
||||
|
||||
|
1
static/prd/index@81ab5d398510e17a91f0.css
Normal file
BIN
static/prd/index@81ab5d398510e17a91f0.css.gz
Normal file
1
static/prd/index@81ab5d398510e17a91f0.js
Normal file
BIN
static/prd/index@81ab5d398510e17a91f0.js.gz
Normal file
@ -165,4 +165,16 @@ test('isDeepMatch', t=>{
|
||||
params: { a: 'x', b: 'y' },
|
||||
res_body: '111',
|
||||
code: 1 }, {t:'1'}))
|
||||
})
|
||||
})
|
||||
|
||||
test('isDeepMatch', t=>{
|
||||
t.true(lib.isDeepMatch({ t:[{a: 1}]}, { t:[{a: 1}]}))
|
||||
})
|
||||
|
||||
test('isDeepMatch', t=>{
|
||||
t.false(lib.isDeepMatch({ t:[{a: 1, b: 12}]}, { t:[{a: 1}]}))
|
||||
})
|
||||
|
||||
test('isDeepMatch', t=>{
|
||||
t.true(lib.isDeepMatch([{a: 1}], [{a: 1}]))
|
||||
})
|