Merge branch 'dev' into dev-1.3.0

This commit is contained in:
suxiaoxin 2018-04-28 14:21:25 +08:00
commit 6a2b8e599c
54 changed files with 1312 additions and 1073 deletions

View File

@ -447,10 +447,10 @@ export default class InterfaceColMenu extends Component {
let interfaceFilter = false;
if (item.name.indexOf(this.state.filterValue) === -1) {
item.caseList = item.caseList.filter(inter => {
if (inter.casename.indexOf(this.state.filterValue) === -1) {
return false;
}
}
//arr.push('cat_' + inter.catid)
interfaceFilter = true;
return true;

View File

@ -136,7 +136,7 @@ class View extends Component {
req_body(req_body_type, req_body_other, req_body_is_json_schema) {
if (req_body_other) {
if (req_body_is_json_schema) {
if (req_body_is_json_schema && req_body_type === 'json') {
return <SchemaTable dataSource={req_body_other} />;
} else {
return (
@ -329,20 +329,23 @@ class View extends Component {
done: '已完成'
};
let bodyShow =
this.props.curData.req_body_other ||
(this.props.curData.req_body_type === 'form' &&
this.props.curData.req_body_form &&
this.props.curData.req_body_form.length);
let requestShow =
(dataSource && dataSource.length) ||
(req_dataSource && req_dataSource.length) ||
(this.props.curData.req_query && this.props.curData.req_query.length) ||
this.props.curData.req_body_other ||
(this.props.curData.req_body_form && this.props.curData.req_body_form.length);
(this.props.curData.req_query && this.props.curData.req_query.length) || bodyShow
let methodColor =
variable.METHOD_COLOR[
this.props.curData.method ? this.props.curData.method.toLowerCase() : 'get'
];
let bodyShow =
this.props.curData.req_body_other ||
(this.props.curData.req_body_form && this.props.curData.req_body_form.length);
// statusColor = statusColor[this.props.curData.status?this.props.curData.status.toLowerCase():"undone"];
// const aceEditor = <div style={{ display: this.props.curData.req_body_other && (this.props.curData.req_body_type !== "form") ? "block" : "none" }} className="colBody">
@ -350,8 +353,6 @@ class View extends Component {
// </div>
if (!methodColor) methodColor = 'get';
let res = (
<div className="caseContainer">
<h2 className="interface-title" style={{ marginTop: 0 }}>
@ -533,8 +534,6 @@ class View extends Component {
this.props.curData.res_body,
this.props.curData.res_body_is_json_schema
)}
</div>
);

View File

@ -4,7 +4,7 @@ import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import './ProjectData.scss';
import axios from 'axios';
import _ from 'underscore';
import URL from 'url'
const Dragger = Upload.Dragger;
@ -16,6 +16,7 @@ const plugin = require('client/plugin.js');
const RadioGroup = Radio.Group;
const importDataModule = {};
const exportDataModule = {};
const HandleImportData = require('common/HandleImportData')
function handleExportRouteParams (url, value) {
if(!url) {
@ -104,98 +105,18 @@ class ProjectData extends Component {
}
}
async handleAddCat(cats) {
let menuList = this.state.menuList;
let catsObj = {};
if (cats && Array.isArray(cats)) {
for (let i = 0; i < cats.length; i++) {
let cat = cats[i];
let findCat = _.find(menuList, menu => menu.name === cat.name)
catsObj[cat.name] = cat;
if (findCat) {
cat.id = findCat._id;
} else {
let result = await axios.post('/api/interface/add_cat', {
name: cat.name,
project_id: this.props.match.params.id,
desc: cat.desc
})
if (result.data.errcode) {
message.error(result.data.errmsg);
this.setState({ showLoading: false });
return false;
}
cat.id = result.data.data._id;
}
}
}
return catsObj;
}
handleAddInterface = async (res) => {
const cats = await this.handleAddCat(res.cats);
if (cats === false) {
return;
}
res = res.apis;
let len = res.length;
let count = 0;
let successNum = len;
let existNum = 0;
if(len === 0){
message.error(`解析数据为空`);
return;
}
for (let index = 0; index < res.length; index++) {
let item = res[index];
let data = {
...item,
project_id: this.props.match.params.id,
catid: this.state.selectCatid
}
if (this.props.basePath) {
data.path = data.path.indexOf(this.props.basePath) === 0 ? data.path.substr(this.props.basePath.length) : data.path;
}
if (data.catname && cats[data.catname] && typeof cats[data.catname] === 'object' && cats[data.catname].id) {
data.catid = cats[data.catname].id;
}
if (this.state.dataSync) {
// 开启同步功能
count++;
let result = await axios.post('/api/interface/save', data)
if (result.data.errcode) {
successNum--;
this.setState({ showLoading: false });
message.error(result.data.errmsg)
} else {
existNum = existNum + result.data.data.length;
}
} else {
// 未开启同步功能
count++;
let result = await axios.post('/api/interface/add', data);
if (result.data.errcode) {
successNum--;
if (result.data.errcode == 40022) {
existNum++;
}
if (result.data.errcode == 40033) {
this.setState({ showLoading: false });
message.error('没有权限')
break;
}
}
}
if (count === len) {
this.setState({ showLoading: false });
message.success(`成功导入接口 ${successNum} 个, 已存在的接口 ${existNum}`);
}
}
handleAddInterface = async (res)=>{
return await HandleImportData(
res,
this.props.match.params.id,
this.state.selectCatid,
this.state.menuList,
this.props.basePath,
this.state.dataSync,
message.error,
message.success,
()=> this.setState({ showLoading: false })
)
}
@ -359,7 +280,9 @@ class ProjectData extends Component {
<Icon type="inbox" />
</p>
<p className="ant-upload-text">点击或者拖拽文件到上传区域</p>
<p className="ant-upload-hint">{this.state.curImportType ? importDataModule[this.state.curImportType].desc : null}</p>
<p className="ant-upload-hint" onClick={(e)=>{
e.stopPropagation();
}} dangerouslySetInnerHTML={{__html: this.state.curImportType ? importDataModule[this.state.curImportType].desc : null}} ></p>
</Dragger>
</Spin>
</div>

138
common/HandleImportData.js Normal file
View File

@ -0,0 +1,138 @@
const _ = require("underscore");
const axios =require ('axios')
const isNode = typeof global == 'object' && global.global === global;
async function handle(
res,
projectId,
selectCatid,
menuList,
basePath,
dataSync,
messageError,
messageSuccess,
callback,
token,
port
) {
const handleAddCat = async cats => {
let catsObj = {};
if (cats && Array.isArray(cats)) {
for (let i = 0; i < cats.length; i++) {
let cat = cats[i];
let findCat = _.find(menuList, menu => menu.name === cat.name);
catsObj[cat.name] = cat;
if (findCat) {
cat.id = findCat._id;
} else {
let apipath = "/api/interface/add_cat"
if(isNode){
apipath = 'http://127.0.0.1:' + port + apipath
}
let data = {
name: cat.name,
project_id: projectId,
desc: cat.desc,
token
}
let result = await axios.post(apipath, data);
if (result.data.errcode) {
messageError(result.data.errmsg);
callback({ showLoading: false });
return false;
}
cat.id = result.data.data._id;
}
}
}
return catsObj;
};
const handleAddInterface = async res => {
const cats = await handleAddCat(res.cats);
if (cats === false) {
return;
}
res = res.apis;
let len = res.length;
let count = 0;
let successNum = len;
let existNum = 0;
if (len === 0) {
messageError(`解析数据为空`);
return;
}
for (let index = 0; index < res.length; index++) {
let item = res[index];
let data = Object.assign(item,{
project_id: projectId,
catid: selectCatid
});
if (basePath) {
data.path =
data.path.indexOf(basePath) === 0
? data.path.substr(basePath.length)
: data.path;
}
if (
data.catname &&
cats[data.catname] &&
typeof cats[data.catname] === "object" &&
cats[data.catname].id
) {
data.catid = cats[data.catname].id;
}
data.token = token;
if (dataSync) {
// 开启同步功能
count++;
let apipath = "/api/interface/save"
if(isNode){
apipath = 'http://127.0.0.1:' + port + apipath
}
let result = await axios.post(apipath, data);
if (result.data.errcode) {
successNum--;
callback({ showLoading: false });
messageError(result.data.errmsg);
} else {
existNum = existNum + result.data.data.length;
}
} else {
// 未开启同步功能
count++;
let apipath = "/api/interface/add"
if(isNode){
apipath = 'http://127.0.0.1:' + port + apipath
}
let result = await axios.post(apipath, data);
if (result.data.errcode) {
successNum--;
if (result.data.errcode == 40022) {
existNum++;
}
if (result.data.errcode == 40033) {
callback({ showLoading: false });
messageError("没有权限");
break;
}
}
}
if (count === len) {
callback({ showLoading: false });
messageSuccess(
`成功导入接口 ${successNum} 个, 已存在的接口 ${existNum}`
);
}
}
};
return await handleAddInterface(res);
}
module.exports = handle;

View File

@ -15,11 +15,31 @@
* [配置环境](project.md#配置环境)
* [请求配置](project.md#请求配置)
* [token配置](project.md#token配置)
* [项目复制](project.md#项目复制)
* [接口操作](api.md)
* [接口设置](api.md#接口配置)
* [接口运行](api.md#接口运行)
* [数据Mock](mock.md)
* [方式1. mockjs](mock.md#方式1. mockjs)
* [方式2. json-schema](mock.md#方式2. json-schema)
* [如何使用](mock.md#如何使用 Mock)
* [高级Mock](adv_mock.md)
* [Mock 期望](adv_mock.md#Mock 期望)
* [自定义脚本](adv_mock.md#自定义 Mock 脚本)
* [自动化测试](case.md)
* [第一步,测试集合](case.md#第一步,测试集合)
* [第二步,编辑测试用例](case.md#第二步,编辑测试用例)
* [第三步,运行自动化测试](case.md#第三步,运行自动化测试)
* [断言脚本公共变量](case.md#断言脚本公共变量)
* [服务端自动化测试](case.md#服务端自动化测试)
* [数据导入](data.md)
* [Postman 数据导入](data.md#Postman 数据导入)
* [HAR 数据导入](data.md#HAR 数据导入)
* [Swagger 数据导入](data.md#Swagger 数据导入)
* [JSON 数据导入](data.md#YApi接口JSON数据导入)
* [通过命令行导入接口数据](data.md#通过命令行导入接口数据)
* [数据导出](export-data.md)
### 自定义
* [插件](plugin-index.md)

BIN
docs/documents/case-col.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

View File

@ -1,28 +1,31 @@
# 自动化测试
<p style='text-indent:2em;line-height:1.8em'>Web 应用通常是前后端分离开发的,后端提供调用的接口,前端使用接口返回 json 数据渲染到 UI接口测试就是保证后端接口的数据正确性
传统的接口自动化测试成本高,大量的项目没有使用自动化测试保证接口的质量,仅仅依靠手动测试,是非常不可靠和容易出错的
对于很多团队,接口测试就是手动运行接口,肉眼比对接口返回的数据,这样的操作流程效率低下,容易出错。使用 YApi 只需要在可视化 GUI 下,配置下每个接口的入参和对 RESPONSE 断言,即可实现对接口的自动化测试,大大提升了接口测试的效率。<a target="_blank" href="https://blog.ymfe.org/api-autotest/#more">自动化测试实践</a></p>
YApi 为了解决这个问题,开发了可视化接口自动化测试功能,只需要配置每个接口的入参和对 RESPONSE 断言,即可实现对接口的自动化测试,大大提升了接口测试的效率。
## 测试列表
## 第一步,测试集合
使用 YApi 自动化测试,第一步需要做得是创建测试集合和导入接口,点击添加集合创建,创建完成后导入接口。
<img class="doc-img" style="width: 618px;" src="./images/usage/case-list.gif" />
![](case-col.png)
在测试列表可以看到每个测试用例的 key,还有 开始测试、报告等功能
![](import-case.png)
点击开始测试会按照 case 定义的参数从上往下一个一个进行测试,如果顺序有问题,可以拖动调整
测试完成之后,点击报告查看该次请求的结果
## 第二步,编辑测试用例
编写测试用例主要涉及两个方面,一个是请求参数,另外一个是断言脚本。
## 编辑测试用例
### 编辑请求参数
### Mock 参数
请求参数可以填写期望的字符串YApi 还提供了 Mock 参数和 变量参数。Mock参数用来生成随机字符串变量参数是为了解决请求参数依赖其他接口的返回数据或参数。
#### Mock 参数
Mock 参数每次请求都会生成随机字符串
<img class="doc-img" style="width:100%" src="./images/usage/case-edit.jpg" />
#### 变量参数
##### 变量参数
YApi 提供了强大的变量参数功能,你可以在测试的时候使用前面接口的 `参数``返回值` 作为 `后面接口的参数`,即使接口之间存在依赖,也可以轻松 **一键测试~**
@ -68,29 +71,29 @@ $.{key}.{params|body}.{path}
<img class="doc-img" style="width: 800px;" src="./images/usage/modal-postman-tips.png" />
## 自动化测试
### 编写断言脚本
点击自动化测试,出现如下弹窗,用户访问该 url 就可以获取当前测试用例的所有测试结果
编写完请求参数,可通过 js 脚本写断言,实现精准测试,在接口用例页面点击 Test 编辑。
<img src="./images/autoTest.png" />
<img src="./images/autoTestResult.png" />
![](test-case.png)
## 断言
## 第三步,运行自动化测试
可通过 js 脚本写断言,实现精准测试,在接口用例页面点击 Test 编辑。
<img class="doc-img" style="width: 618px;" src="./images/usage/case-list.gif" />
<!-- <video style="width:800px" controls="controls" autoplay="autoplay">
<source src="http://yapi.demo.qunar.com/publicapi/auto-test.mp4" type="video/mp4" />
Your browser does not support the video tag.
</video> -->
在测试列表可以看到每个测试用例的 key,还有 开始测试、报告等功能
### 公共变量
点击开始测试会按照 case 定义的参数从上往下一个一个进行测试,如果顺序有问题,可以拖动调整
#### 1.assert
测试完成之后,点击报告查看该次请求的结果
## 断言脚本公共变量
### 1.assert
断言函数,详细 api 可查看 <a target="_blank" href="https://nodejs.org/dist/latest-v8.x/docs/api/assert.html">document</a>
##### 常用 api:
#### 常用 api
* assert(value)
@ -143,3 +146,15 @@ assert.equal(body.errcode, 0)
assert.equal(body.data.group_name, 'testGroup')
assert.equal(status, 200)
```
## 服务端自动化测试
开始测试功能是在浏览器跑自动化测试他依赖于浏览器的使用环境。服务端自动化测试功能是在YApi服务端跑自动化测试不需要依赖浏览器环境只需要访问 YApi 提供的 url 链接就能跑自动化测试,非常的简单易用,而且可以集成到 jenkins。
### 详细使用方法
点击服务端测试,出现如下弹窗,用户访问该 url 就可以获取当前测试用例的所有测试结果。
<img src="./images/autoTest.png" />
<img src="./images/autoTestResult.png" />

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 135 KiB

View File

@ -43,4 +43,43 @@
<div><img class="doc-img" style="width:90%" src="./images/usage/chrome-5.jpg" /></div>
<div><img class="doc-img" style="width:90%" src="./images/usage/chrome-6.jpg" /></div>
<div><img class="doc-img" style="width:90%" src="./images/usage/chrome-6.jpg" /></div>
## YApi接口JSON数据导入
该功能在 v1.3.12 版本上线,可导入在 yapi 平台导出的 json 接口数据。
![](import-json-data.png)
## 通过命令行导入接口数据
YApi 支持通过命令行导入接口数据,他的应用场景是做自动化集成,比如配合 swagger ,接口文档前端不用维护,交由后端生成。
### 使用方法
第一步,确保 `yapi-cli >= 1.2.6` 版本,如果低于此版本请升级 `yapi-cli` 工具
```
npm install -g yapi-cli
```
第二步,新建配置文件 `yapi-import.json`,如下所示:
```json
{
"type": "swagger",
"token": "17fba0027f300248b804",
"file": "swagger.json",
"server": "http://yapi.local.qunar.com:3000"
}
```
`token` 是项目token`项目设置 -> token` 设置获取
`file` 是 swagger 接口文档文件,可使用绝对路径或 url
`server` 是yapi服务器地址
第三步,在新建配置文件的当前目录,执行下面指令
```
yapi import
```

View File

@ -0,0 +1,10 @@
# 数据导出
为了方便开发者将接口数据分析给第三方或其他使用者YApi 内置了方便易用的接口数据导出功能。
## 使用教程
在 项目 -> 数据管理选择需要导出的数据方式一共有三种导出方式html,markdown,json。然后点击导出按钮将会下载数据文件。
![](export-data.png)

Binary file not shown.

After

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 57 KiB

View File

@ -14,10 +14,59 @@
项目 -> 接口编辑 -> 返回数据设置
返回数据设置有两种方式,最新版本默认是基于 `json-schema` 定义数据结构,另外一种是基于 `json+注释` 的方式,请根据实际情况灵活选择使用。
返回数据设置有两种方式,最新版本默认是基于 `json+注释` 的方式,另外一种是基于 `json-schema` 定义数据结构,请根据实际情况灵活选择使用。
## 方式1. json-schema
## 方式1. mockjs
<img src="./images/usage/mock-demo.jpg" />
### 原理
基于 [mockjs](http://mockjs.com),跟 Mockjs 区别是 yapi 基于 json + 注释 定义 mock 数据,无法使用 mockjs 原有的函数功能。
1. 正则表达式需要基于 rule 书写,示例如下:
```
{
"name|regexp": "[a-z0-9_]+?",
"type|regexp": "json|text|xml"
}
```
2. 支持替换请求的 query, body 参数
```
{
"name": "${query.name}", //请求的url是/path?name=xiaoming, 返回的name字段是xiaoming
"type": "${body.type}" //请求的requestBody type=1,返回的type字段是1
}
```
3. 示例
```
/**
* 这是一个接口返回数据示例
*/
{
"errcode": 0,
"errmsg": "@word",
"data": {
"id": "@id", //@id 随机生成 id
"name": "@name" //@name 随机生成用户名
}
}
```
详细使用文档请查看:<a href="http://mockjs.com/examples.html">Mockjs 官网</a>
## 方式2. json-schema
<img src="./images/usage/json-schema-demo.jpg" />
开启 json-schema 功能后,将不再使用 mockjs 解析定义的返回数据,而是根据 json-schema 定义的数据结构,生成随机数据。
@ -28,38 +77,11 @@
点击高级设置,选择 `format` 选项,比如选择 `email` 则该字段生成随机邮箱字符串。
## 方式2. json5+注释
<img src="./images/usage/mock-demo.jpg" />
## 如何使用 Mock
### 原理
YApi Mock 功能基于 node 和 [mockjs](http://mockjs.com),跟 Mockjs 区别是 yapi 基于 json 定义 mock ,无法使用 mockjs 原有的函数功能,正则表达式需要基于 rule 书写,示例如下:
```
{
"name|regexp": "[a-z0-9_]+?",
"type|regexp": "json|text|xml" //枚举数据类型可这样实现
}
```
2 支持替换请求的 query, body 参数
```
{
"name": "${query.name}", //请求的url是/path?name=xiaoming, 返回的name字段是xiaoming
"type": "${body.type}" //请求的requestBody type=1,返回的type字段是1
}
```
其他基本用法请查看:<a href="http://mockjs.com/examples.html">Mockjs 官网</a>
### 如何使用 Mock
#### 1 在 js 代码直接请求yapi提供的 mock 地址(不用担心跨域问题)
### 1 在 js 代码直接请求yapi提供的 mock 地址(不用担心跨域问题)
在代码直接请求 yapi 提供的 mock 地址,以 jQuery 为例:
@ -70,11 +92,11 @@ $.post(prefix+'/baseapi/path', {username: 'xxx'}, function(res){
})
````
#### 2 基于本地服务器反向代理
### 2 基于本地服务器反向代理
优点:不用修改项目代码
##### 2.1 基于 nginx 反向代理
#### 2.1 基于 nginx 反向代理
```` nginx
location /baseapi
@ -83,7 +105,7 @@ proxy_pass http://yapi.xxx.com/mock/2817/baseapi; #baseapi后面没有"/"
}
````
##### 2.2 基于 ykit mock功能
#### 2.2 基于 ykit mock功能
```javascript
{
@ -98,7 +120,7 @@ proxy_pass http://yapi.xxx.com/mock/2817/baseapi; #baseapi后面没有"/"
##### 2.3 基于 ykit Jerry 代理
#### 2.3 基于 ykit Jerry 代理
假设您本地服务器访问地址是: http://xxx.com
@ -106,150 +128,9 @@ proxy_pass http://yapi.xxx.com/mock/2817/baseapi; #baseapi后面没有"/"
<span id="mock"></span>
##### 2.4 基于 Charles 代理
#### 2.4 基于 Charles 代理
点击 Charles 工具栏下的 tools >> Rewrite Settings 填写如下信息:
<img src="./images/charles.png" width="60%" />
### Mock 语法规范
>了解更多Mock详情[Mock.js 官方文档](http://mockjs.com/examples.html)
Mock.js 的语法规范包括两部分:
[1. 数据模板定义规范Data Template DefinitionDTD](#DTD)
[2. 数据占位符定义规范Data Placeholder DefinitionDPD](#DPD)
<span id = "DTD"></span>
### 数据模板定义规范Data Template DefinitionDTD
数据模板中的每个属性由 3 部分构成:属性名、生成规则、属性值:
```
// 属性名 name (与生成规则之间用 "|" 隔开)
// 生成规则 rule生成规则有7种详见下面的生成规则
// 属性值 value可以含有 "@占位符" 同时也指定了最终值的初始值和类型)
'name|rule': value
生成规则:
'name|min-max': value
'name|count': value
'name|min-max.dmin-dmax': value
'name|min-max.dcount': value
'name|count.dmin-dmax': value
'name|count.dcount': value
'name|+step': value
```
下面提供了6种生成规则以及示例包括 String、Number、Boolean、Object、Array
#### 1. 属性值是字符串 String
```
1. 'name|min-max': string
通过重复 string 生成一个字符串,重复次数大于等于 min小于等于 max。
2. 'name|count': string
通过重复 string 生成一个字符串,重复次数等于 count。
```
#### 2. 属性值是数字 Number
```
1. 'name|+1': number
属性值自动加 1初始值为 number。
2. 'name|min-max': number
生成一个大于等于 min、小于等于 max 的整数,属性值 number 只是用来确定类型。
3. 'name|min-max.dmin-dmax': number
生成一个浮点数,整数部分大于等于 min、小于等于 max小数部分保留 dmin 到 dmax 位。
例如:
Mock.mock({
'number1|1-100.1-10': 1,
'number2|123.1-10': 1,
'number3|123.3': 1,
'number4|123.10': 1.123
})
// =>
{
"number1": 12.92,
"number2": 123.51,
"number3": 123.777,
"number4": 123.1231091814
}
```
#### 3. 属性值是布尔型 Boolean
```
1. 'name|1': boolean
随机生成一个布尔值,值为 true 的概率是 1/2值为 false 的概率同样是 1/2。
2. 'name|min-max': value
随机生成一个布尔值,值为 value 的概率是 min / (min + max),值为 !value 的概率是 max / (min + max)。
```
#### 4. 属性值是对象 Object
```
1. 'name|count': object
从属性值 object 中随机选取 count 个属性。
2. 'name|min-max': object
从属性值 object 中随机选取 min 到 max 个属性。
```
#### 5. 属性值是数组 Array
```
1. 'name|1': array
从属性值 array 中随机选取 1 个元素,作为最终值。
2. 'name|+1': array
从属性值 array 中顺序选取 1 个元素,作为最终值。
3. 'name|min-max': array
通过重复属性值 array 生成一个新数组,重复次数大于等于 min小于等于 max。
4. 'name|count': array
通过重复属性值 array 生成一个新数组,重复次数为 count。
```
<span id = "DPD"></span>
#### 数据占位符定义规范Data Placeholder DefinitionDPD
```
占位符 只是在属性值字符串中占个位置,并不出现在最终的属性值中。
占位符 的格式为:
@占位符
说明:
1. 用 @ 来标识其后的字符串是 占位符在YApi提供的Mock输入框在输入“@”后会自动提示占位符。
例如:
name: {
first: '@FIRST',
middle: '@FIRST',
last: '@LAST',
full: '@first @middle @last'
}
// 上面的示例可以得到如下结果:
"name": {
"first": "Charles",
"middle": "Brenda",
"last": "Lopez",
"full": "Charles Brenda Lopez"
}
```

View File

@ -153,4 +153,9 @@ context.utils = {
## token配置
每个项目都有唯一的标识token用户可以使用这个token值来请求项目的所有资源数据。目前用到的地方是接口的<a href="./case.md">自动化测试</a>,用户不需要登录就可以访问接口测试结果信息。
每个项目都有唯一的标识token用户可以使用这个token值来请求项目的所有资源数据。目前用到的地方是接口的<a href="./case.md">自动化测试</a>,用户不需要登录就可以访问接口测试结果信息。
## 项目克隆
该功能在 v1.3.12 版本上线,项目克隆功能可复制项目全部接口到一个新项目,如下图所示,点击红色框里面的 icon 使用。
![](clone-project.png.png)

Binary file not shown.

After

Width:  |  Height:  |  Size: 69 KiB

View File

@ -1,292 +1,29 @@
import { message } from 'antd';
import _ from 'underscore';
const swagger = require('swagger-client');
function improtData(importDataModule) {
var SwaggerData, isOAS3;
function handlePath(path) {
if (path === '/') return path;
if (path.charAt(0) != '/') {
path = '/' + path;
}
if (path.charAt(path.length - 1) === '/') {
path = path.substr(0, path.length - 1);
}
return path;
}
function openapi2swagger(data) {
data.swagger = '2.0';
_.each(data.paths, apis => {
_.each(apis, api => {
_.each(api.responses, res => {
if (
res.content &&
res.content['application/json'] &&
typeof res.content['application/json'] === 'object'
) {
Object.assign(res, res.content['application/json']);
delete res.content;
}
});
if (api.requestBody) {
if (!api.parameters) api.parameters = [];
let body = {
type: 'object',
name: 'body',
in: 'body'
};
try {
body.schema = api.requestBody.content['application/json'].schema;
} catch (e) {
body.schema = {};
}
api.parameters.push(body);
}
});
});
return data;
}
async function handleSwaggerData(res) {
return await new Promise(resolve => {
let data = swagger({
spec: res
});
data.then(res => {
resolve(res.spec);
});
});
}
async function run(res) {
try {
let interfaceData = { apis: [], cats: [] };
res = JSON.parse(res);
isOAS3 = res.openapi && res.openapi === '3.0.0';
if (isOAS3) {
res = openapi2swagger(res);
}
res = await handleSwaggerData(res);
SwaggerData = res;
if (res.tags && Array.isArray(res.tags)) {
res.tags.forEach(tag => {
interfaceData.cats.push({
name: tag.name,
desc: tag.description
});
});
}
_.each(res.paths, (apis, path) => {
_.each(apis, (api, method) => {
api.path = path;
api.method = method;
let data = null;
try {
data = handleSwagger(api);
if (data.catname) {
if (!_.find(interfaceData.cats, item => item.name === data.catname)) {
interfaceData.cats.push({
name: data.catname,
desc: data.catname
});
}
}
} catch (err) {
data = null;
}
if (data) {
interfaceData.apis.push(data);
}
});
});
return interfaceData;
} catch (e) {
console.error(e);
message.error('数据格式有误');
}
}
function handleSwagger(data) {
let api = {};
//处理基本信息
api.method = data.method.toUpperCase();
api.title = data.summary || data.path;
api.desc = data.description;
api.catname = data.tags && Array.isArray(data.tags) ? data.tags[0] : null;
api.path = handlePath(data.path);
api.req_params = [];
api.req_body_form = [];
api.req_headers = [];
api.req_query = [];
api.req_body_type = 'raw';
api.res_body_type = 'raw';
if (data.produces && data.produces.indexOf('application/json') > -1) {
api.res_body_type = 'json';
api.res_body_is_json_schema = true;
}
if (data.consumes && Array.isArray(data.consumes)) {
if (
data.consumes.indexOf('application/x-www-form-urlencoded') > -1 ||
data.consumes.indexOf('multipart/form-data') > -1
) {
api.req_body_type = 'form';
} else if (data.consumes.indexOf('application/json') > -1) {
api.req_body_type = 'json';
api.req_body_is_json_schema = true;
}
}
//处理response
api.res_body = handleResponse(data.responses);
try {
JSON.parse(api.res_body);
api.res_body_type = 'json';
api.res_body_is_json_schema = true;
} catch (e) {
api.res_body_type = 'raw';
}
//处理参数
function simpleJsonPathParse(key, json) {
if (!key || typeof key !== 'string' || key.indexOf('#/') !== 0 || key.length <= 2) {
return null;
}
let keys = key.substr(2).split('/');
keys = keys.filter(item => {
return item;
});
for (let i = 0, l = keys.length; i < l; i++) {
try {
json = json[keys[i]];
} catch (e) {
json = '';
break;
}
}
return json;
}
if (data.parameters && Array.isArray(data.parameters)) {
data.parameters.forEach(param => {
if (param && typeof param === 'object' && param.$ref) {
param = simpleJsonPathParse(param.$ref, { parameters: SwaggerData.parameters });
}
let defaultParam = {
name: param.name,
desc: param.description,
required: param.required ? '1' : '0'
};
switch (param.in) {
case 'path':
api.req_params.push(defaultParam);
break;
case 'query':
api.req_query.push(defaultParam);
break;
case 'body':
handleBodyPamras(param.schema, api);
break;
case 'formData':
defaultParam.type = param.type === 'file' ? 'file' : 'text';
api.req_body_form.push(defaultParam);
break;
case 'header':
api.req_headers.push(defaultParam);
break;
}
});
}
return api;
}
function isJson(json) {
try {
return JSON.parse(json);
} catch (e) {
return false;
}
}
function handleBodyPamras(data, api) {
api.req_body_other = JSON.stringify(data, null, 2);
if (isJson(api.req_body_other)) {
api.req_body_type = 'json';
api.req_body_is_json_schema = true;
}
}
function handleResponse(api) {
let res_body = '';
if (!api || typeof api !== 'object') {
return res_body;
}
let codes = Object.keys(api);
let curCode;
if (codes.length > 0) {
if (codes.indexOf(200) > -1) {
curCode = 200;
} else curCode = codes[0];
let res = api[curCode];
if (res && typeof res === 'object') {
if (res.schema) {
res_body = JSON.stringify(res.schema, null, 2);
} else if (res.description) {
res_body = res.description;
}
} else if (typeof res === 'string') {
res_body = res;
} else {
res_body = '';
}
} else {
res_body = '';
}
return res_body;
}
// function handleSchema(data) {
// // if (!data) return data;
// // if (typeof data !== 'object') {
// // return data;
// // }
// // try {
// // // data.definitions = SwaggerData.definitions;
// // isOAS3 ? data.components = SwaggerData.components : data.definitions = SwaggerData.definitions
// // let schema = deref(data, true);
// // let jsfData = JSON.stringify(schema, null, 2);
// // return jsfData;
// // } catch (e) {
// // return '';
// // }
// return data;
// }
if (!importDataModule || typeof importDataModule !== 'object') {
console.error('importDataModule 参数Must be Object Type');
return null;
}
importDataModule.swagger = {
name: 'Swagger',
run: run,
desc: 'Swagger数据导入 支持 v2.0+ '
};
}
import run from './run'
module.exports = function() {
this.bindHook('import_data', improtData);
this.bindHook('import_data', function(importDataModule){
if (!importDataModule || typeof importDataModule !== 'object') {
console.error('importDataModule 参数Must be Object Type');
return null;
}
importDataModule.swagger = {
name: 'Swagger',
run: async function(res){
try{
return await run(res)
}catch(err){
console.error(err)
message.error('解析失败')
}
},
desc: `<p>Swagger数据导入 支持 v2.0+ </p>
<p>
<a target="_blank" href="https://yapi.ymfe.org/documents/data.html#通过命令行导入接口数据">通过命令行导入接口数据</a>
</p>
`
};
});
};

View File

@ -1,4 +1,4 @@
module.exports = {
server: false,
server: true,
client: true
}

View File

@ -0,0 +1,259 @@
const _ = require('underscore')
const swagger = require('swagger-client');
var SwaggerData, isOAS3;
function handlePath(path) {
if (path === '/') return path;
if (path.charAt(0) != '/') {
path = '/' + path;
}
if (path.charAt(path.length - 1) === '/') {
path = path.substr(0, path.length - 1);
}
return path;
}
function openapi2swagger(data) {
data.swagger = '2.0';
_.each(data.paths, apis => {
_.each(apis, api => {
_.each(api.responses, res => {
if (
res.content &&
res.content['application/json'] &&
typeof res.content['application/json'] === 'object'
) {
Object.assign(res, res.content['application/json']);
delete res.content;
}
});
if (api.requestBody) {
if (!api.parameters) api.parameters = [];
let body = {
type: 'object',
name: 'body',
in: 'body'
};
try {
body.schema = api.requestBody.content['application/json'].schema;
} catch (e) {
body.schema = {};
}
api.parameters.push(body);
}
});
});
return data;
}
async function handleSwaggerData(res) {
return await new Promise(resolve => {
let data = swagger({
spec: res
});
data.then(res => {
resolve(res.spec);
});
});
}
async function run(res) {
let interfaceData = { apis: [], cats: [] };
if(typeof res === 'string' && res){
res = JSON.parse(res);
}
isOAS3 = res.openapi && res.openapi === '3.0.0';
if (isOAS3) {
res = openapi2swagger(res);
}
res = await handleSwaggerData(res);
SwaggerData = res;
if (res.tags && Array.isArray(res.tags)) {
res.tags.forEach(tag => {
interfaceData.cats.push({
name: tag.name,
desc: tag.description
});
});
}
_.each(res.paths, (apis, path) => {
_.each(apis, (api, method) => {
api.path = path;
api.method = method;
let data = null;
try {
data = handleSwagger(api);
if (data.catname) {
if (!_.find(interfaceData.cats, item => item.name === data.catname)) {
interfaceData.cats.push({
name: data.catname,
desc: data.catname
});
}
}
} catch (err) {
data = null;
}
if (data) {
interfaceData.apis.push(data);
}
});
});
return interfaceData;
}
function handleSwagger(data) {
let api = {};
//处理基本信息
api.method = data.method.toUpperCase();
api.title = data.summary || data.path;
api.desc = data.description;
api.catname = data.tags && Array.isArray(data.tags) ? data.tags[0] : null;
api.path = handlePath(data.path);
api.req_params = [];
api.req_body_form = [];
api.req_headers = [];
api.req_query = [];
api.req_body_type = 'raw';
api.res_body_type = 'raw';
if (data.produces && data.produces.indexOf('application/json') > -1) {
api.res_body_type = 'json';
api.res_body_is_json_schema = true;
}
if (data.consumes && Array.isArray(data.consumes)) {
if (
data.consumes.indexOf('application/x-www-form-urlencoded') > -1 ||
data.consumes.indexOf('multipart/form-data') > -1
) {
api.req_body_type = 'form';
} else if (data.consumes.indexOf('application/json') > -1) {
api.req_body_type = 'json';
api.req_body_is_json_schema = true;
}
}
//处理response
api.res_body = handleResponse(data.responses);
try {
JSON.parse(api.res_body);
api.res_body_type = 'json';
api.res_body_is_json_schema = true;
} catch (e) {
api.res_body_type = 'raw';
}
//处理参数
function simpleJsonPathParse(key, json) {
if (!key || typeof key !== 'string' || key.indexOf('#/') !== 0 || key.length <= 2) {
return null;
}
let keys = key.substr(2).split('/');
keys = keys.filter(item => {
return item;
});
for (let i = 0, l = keys.length; i < l; i++) {
try {
json = json[keys[i]];
} catch (e) {
json = '';
break;
}
}
return json;
}
if (data.parameters && Array.isArray(data.parameters)) {
data.parameters.forEach(param => {
if (param && typeof param === 'object' && param.$ref) {
param = simpleJsonPathParse(param.$ref, { parameters: SwaggerData.parameters });
}
let defaultParam = {
name: param.name,
desc: param.description,
required: param.required ? '1' : '0'
};
switch (param.in) {
case 'path':
api.req_params.push(defaultParam);
break;
case 'query':
api.req_query.push(defaultParam);
break;
case 'body':
handleBodyPamras(param.schema, api);
break;
case 'formData':
defaultParam.type = param.type === 'file' ? 'file' : 'text';
api.req_body_form.push(defaultParam);
break;
case 'header':
api.req_headers.push(defaultParam);
break;
}
});
}
return api;
}
function isJson(json) {
try {
return JSON.parse(json);
} catch (e) {
return false;
}
}
function handleBodyPamras(data, api) {
api.req_body_other = JSON.stringify(data, null, 2);
if (isJson(api.req_body_other)) {
api.req_body_type = 'json';
api.req_body_is_json_schema = true;
}
}
function handleResponse(api) {
let res_body = '';
if (!api || typeof api !== 'object') {
return res_body;
}
let codes = Object.keys(api);
let curCode;
if (codes.length > 0) {
if (codes.indexOf(200) > -1) {
curCode = 200;
} else curCode = codes[0];
let res = api[curCode];
if (res && typeof res === 'object') {
if (res.schema) {
res_body = JSON.stringify(res.schema, null, 2);
} else if (res.description) {
res_body = res.description;
}
} else if (typeof res === 'string') {
res_body = res;
} else {
res_body = '';
}
} else {
res_body = '';
}
return res_body;
}
module.exports = run;

View File

@ -0,0 +1,12 @@
module.exports = function(){
this.bindHook('import_data', function(importDataModule){
importDataModule.swagger = async (res)=>{
try{
return await require('./run.js')(res)
}catch(err){
this.commons.log(err, 'error')
return false;
}
}
})
}

View File

@ -3,7 +3,7 @@ const projectModel = require('../models/project.js');
const userModel = require('../models/user.js');
const interfaceModel = require('../models/interface.js');
const groupModel = require('../models/group.js');
const tokenModel = require('../models/token.js');
const _ = require('underscore');
const jwt = require('jsonwebtoken');
@ -19,6 +19,8 @@ class baseController {
async init(ctx) {
this.$user = null;
this.tokenModel = yapi.getInst(tokenModel);
this.projectModel = yapi.getInst(projectModel);
let ignoreRouter = [
'/api/user/login_by_token',
'/api/user/login',
@ -28,15 +30,56 @@ class baseController {
'/api/user/avatar',
'/api/user/login_by_ldap'
];
let openApiRouter = /^\/api\/open\/.*/
if (ignoreRouter.indexOf(ctx.path) > -1) {
//let openApiRouter = /^\/api\/open\/.*/
if (ignoreRouter.indexOf(ctx.path) === 0) {
this.$auth = true;
} else if(openApiRouter.test(ctx.path)){
this.$auth = true;
} else {
}
// else if(openApiRouter.test(ctx.path)){
// this.$auth = true;
// }
else {
await this.checkLogin(ctx);
}
let openApiRouter = [
'/api/open/run_auto_test',
'/api/open/import_data',
'/api/interface/add',
'/api/interface/save',
'/api/interface/up',
'/api/interface/add_cat'
]
let token = ctx.query.token || ctx.request.body.token;
if(token && openApiRouter.indexOf(ctx.path) > 0){
if(this.$auth){
ctx.params.project_id = await this.getProjectIdByToken(token)
this.$tokenAuth = true;
}
if(!token){
return
}
let checkId = await this.getProjectIdByToken(token);
let projectData = await this.projectModel.get(checkId);
if(projectData) {
ctx.params.project_id = checkId;
this.$tokenAuth = true;
this.$uid = '999999'
this.$user = {
_id: this.$uid,
role: 'member',
username: 'system'
}
this.$auth = true
};
}
}
async getProjectIdByToken(token){
let projectId = await this.tokenModel.findId(token);
if(projectId) {
return projectId.toObject().project_id
}
}
getUid() {
@ -62,6 +105,7 @@ class baseController {
return false;
} catch (e) {
yapi.commons.log(e, 'error')
return false;
}
@ -179,7 +223,7 @@ class baseController {
return 'member';
}
catch (e) {
yapi.commons.log(e.message, 'error')
yapi.commons.log(e, 'error')
return false;
}
}

View File

@ -138,9 +138,12 @@ class interfaceController extends baseController {
async add(ctx) {
let params = ctx.params;
let auth = await this.checkAuth(params.project_id, 'project', 'edit')
if (!auth) {
return ctx.body = yapi.commons.resReturn(null, 40033, '没有权限');
if(!this.$tokenAuth){
let auth = await this.checkAuth(params.project_id, 'project', 'edit')
if (!auth) {
return ctx.body = yapi.commons.resReturn(null, 40033, '没有权限');
}
}
params.method = params.method || 'GET';
params.res_body_is_json_schema = _.isUndefined (params.res_body_is_json_schema) ? false : params.res_body_is_json_schema;
@ -238,9 +241,11 @@ class interfaceController extends baseController {
async save(ctx) {
let params = ctx.params;
let auth = await this.checkAuth(params.project_id, 'project', 'edit')
if (!auth) {
return ctx.body = yapi.commons.resReturn(null, 40033, '没有权限');
if(!this.$tokenAuth){
let auth = await this.checkAuth(params.project_id, 'project', 'edit')
if (!auth ) {
return ctx.body = yapi.commons.resReturn(null, 40033, '没有权限');
}
}
params.method = params.method || 'GET';
params.method = params.method.toUpperCase();
@ -495,10 +500,13 @@ class interfaceController extends baseController {
if (!interfaceData) {
return ctx.body = yapi.commons.resReturn(null, 400, '不存在的接口');
}
let auth = await this.checkAuth(interfaceData.project_id, 'project', 'edit')
if (!auth) {
return ctx.body = yapi.commons.resReturn(null, 400, '没有权限');
if(!this.$tokenAuth){
let auth = await this.checkAuth(interfaceData.project_id, 'project', 'edit')
if (!auth) {
return ctx.body = yapi.commons.resReturn(null, 400, '没有权限');
}
}
let data = Object.assign({
up_time: yapi.commons.time()
@ -697,20 +705,22 @@ class interfaceController extends baseController {
project_id: 'number',
desc: 'string'
});
if (!params.project_id) {
return ctx.body = yapi.commons.resReturn(null, 400, '项目id不能为空');
}
let auth = await this.checkAuth(params.project_id, 'project', 'edit')
if (!auth) {
return ctx.body = yapi.commons.resReturn(null, 400, '没有权限');
if(!this.$tokenAuth){
let auth = await this.checkAuth(params.project_id, 'project', 'edit')
if (!auth) {
return ctx.body = yapi.commons.resReturn(null, 400, '没有权限');
}
}
if (!params.name) {
return ctx.body = yapi.commons.resReturn(null, 400, '名称不能为空');
}
let result = await this.catModel.save({
name: params.name,
project_id: params.project_id,

View File

@ -2,7 +2,7 @@ const projectModel = require('../models/project.js');
const interfaceColModel = require('../models/interfaceCol.js');
const interfaceCaseModel = require('../models/interfaceCase.js');
const interfaceModel = require('../models/interface.js');
const tokenModel = require('../models/token.js');
const interfaceCatModel = require('../models/interfaceCat.js')
const followModel = require('../models/follow.js');
const userModel = require('../models/user.js')
const yapi = require('../yapi.js');
@ -10,6 +10,17 @@ const baseController = require('./base.js');
const { handleParams, crossRequest, handleCurrDomain, checkNameIsExistInArray } = require('../../common/postmanLib')
const {handleParamsValue} = require('../../common/utils.js')
const renderToHtml = require('../utils/reportHtml')
const axios = require('axios')
const HanldeImportData = require('../../common/HandleImportData')
/**
* {
* postman: require('./m')
* }
*/
const importDataModule = {}
yapi.emitHook('import_data', importDataModule)
class openController extends baseController{
constructor(ctx){
@ -18,7 +29,7 @@ class openController extends baseController{
this.interfaceColModel = yapi.getInst(interfaceColModel)
this.interfaceCaseModel = yapi.getInst(interfaceCaseModel)
this.interfaceModel = yapi.getInst(interfaceModel)
this.tokenModel = yapi.getInst(tokenModel);
this.interfaceCatModel = yapi.getInst(interfaceCatModel)
this.followModel = yapi.getInst(followModel);
this.userModel = yapi.getInst(userModel)
this.handleValue = this.handleValue.bind(this)
@ -35,17 +46,75 @@ class openController extends baseController{
type: 'boolean',
default: false
}
},
importData: {
'*type': 'string',
'url': 'string',
'*token': 'string',
'json': 'string',
'project_id': "string",
"dataSync": {
type: 'boolean',
default: false
}
}
}
}
async getProjectIdByToken(token){
let projectId = await this.tokenModel.findId(token);
if(projectId) {
return projectId.toObject().project_id
}
async importData(ctx){
let type = ctx.params.type;
let url = ctx.params.url;
let content = ctx.params.json;
let project_id = ctx.params.project_id;
let dataSync = ctx.params.dataSync;
let token = ctx.params.token;
if(!type || !importDataModule[type]){
return ctx.body = yapi.commons.resReturn(null, 40022, '不存在的导入方式');
}
if(!content){
return ctx.body = yapi.commons.resReturn(null, 40022, 'json 不能为空');
}
try{
content = JSON.parse(content)
}catch(e){
return ctx.body = yapi.commons.resReturn(null, 40022, 'json 格式有误');
}
let menuList = await this.interfaceCatModel.list(project_id)
let selectCatid = menuList[0]._id;
let projectData = await this.projectModel.get(project_id)
let res = await importDataModule[type](content)
let successMessage;
let errorMessage = []
let data = await HanldeImportData(
res,
project_id,
selectCatid,
menuList,
projectData.basePath,
dataSync,
(err)=>{
errorMessage.push(err)
},
(msg)=>{
successMessage = msg
},
()=> {},
token,
yapi.WEBCONFIG.port
)
if(errorMessage.length > 0){
return ctx.body = yapi.commons.resReturn(null, 404, errorMessage.join("\n"))
}
ctx.body = yapi.commons.resReturn(null, 0, successMessage)
}
async projectInterfaceData(ctx){
ctx.body = 'projectInterfaceData'
}
@ -60,16 +129,11 @@ class openController extends baseController{
const reports = this.reports = {};
const testList = []
let id = ctx.params.id;
let token = ctx.params.token;
let curEnv = ctx.params.env_name;
let colData = await this.interfaceColModel.get(id);
if(!colData){
return ctx.body = yapi.commons.resReturn(null, 40022, 'id值不存在');
}
if(!token){
return ctx.body = yapi.commons.resReturn(null, 40033, '没有权限');
}
}
let checkId = await this.getProjectIdByToken(token);
@ -86,6 +150,7 @@ class openController extends baseController{
caseList = caseList.data;
for(let i=0, l= caseList.length; i< l; i++){
let item = caseList[i];
item.id = item._id;
item.case_env = curEnv || item.case_env;
item.req_headers = this.handleReqHeader(item.req_headers, projectData.env, curEnv)
@ -98,7 +163,7 @@ class openController extends baseController{
}catch(err){
result = err;
}
reports[item.id] = result;
records[item.id] = {
params: result.params,
@ -170,7 +235,7 @@ class openController extends baseController{
options = handleParams(interfaceData, this.handleValue, requestParams)
let result = {
id: interfaceData.id,
name: interfaceData.title,
name: interfaceData.casename,
path: interfaceData.path,
code: 400,
validRes: []
@ -311,4 +376,5 @@ class openController extends baseController{
}
module.exports = openController;

View File

@ -1,170 +1,187 @@
const yapi = require('../yapi.js');
const baseController = require('./base.js');
const fs=require('fs') //引入文件模块
const path = require('path');
class interfaceColController extends baseController{
constructor(ctx) {
super(ctx);
class interfaceColController extends baseController {
constructor(ctx) {
super(ctx);
}
/**
* 测试 get
* @interface /test/get
* @method GET
* @returns {Object}
* @example
*/
async testGet(ctx) {
try {
let query = ctx.query;
ctx.body = yapi.commons.resReturn(query);
} catch (e) {
ctx.body = yapi.commons.resReturn(null, 402, e.message);
}
}
/**
* 测试 get
* @interface /test/get
* @method GET
* @returns {Object}
* @example
*/
async testGet(ctx){
try {
let query = ctx.query;
ctx.body = yapi.commons.resReturn(query);
} catch (e) {
ctx.body = yapi.commons.resReturn(null, 402, e.message);
async testHttpCode(ctx) {
let params = ctx.request.body;
ctx.status = +ctx.query.code || 200;
ctx.body = yapi.commons.resReturn(params);
}
/**
* 测试 post
* @interface /test/post
* @method POST
* @returns {Object}
* @example
*/
async testPost(ctx) {
try {
let params = ctx.request.body;
ctx.body = yapi.commons.resReturn(params);
} catch (e) {
ctx.body = yapi.commons.resReturn(null, 402, e.message);
}
}
/**
* 测试 单文件上传
* @interface /test/single/upload
* @method POST
* @returns {Object}
* @example
*/
async testSingleUpload(ctx) {
try {
// let params = ctx.request.body;
let req = ctx.req;
let chunks = [],
size = 0;
req.on('data', function(chunk) {
chunks.push(chunk);
size += chunk.length;
});
req.on('finish', function() {
console.log(34343);
});
req.on('end', function() {
let data = new Buffer(size);
for (let i = 0, pos = 0, l = chunks.length; i < l; i++) {
let chunk = chunks[i];
chunk.copy(data, pos);
pos += chunk.length;
}
fs.writeFileSync(path.join(yapi.WEBROOT_RUNTIME, 'test.text'), data, function(err){
return ctx.body = yapi.commons.resReturn(null, 402, '写入失败');
});
});
ctx.body = yapi.commons.resReturn({ res: '上传成功' });
} catch (e) {
ctx.body = yapi.commons.resReturn(null, 402, e.message);
}
}
async testHttpCode(ctx){
let params = ctx.request.body;
ctx.status = +ctx.query.code || 200;
ctx.body = yapi.commons.resReturn(params);
/**
* 测试 文件上传
* @interface /test/files/upload
* @method POST
* @returns {Object}
* @example
*/
async testFilesUpload(ctx) {
try {
let params = ctx.request.body;
ctx.body = yapi.commons.resReturn({ res: '上传成功' });
} catch (e) {
ctx.body = yapi.commons.resReturn(null, 402, e.message);
}
}
/**
* 测试 post
* @interface /test/post
* @method POST
* @returns {Object}
* @example
*/
async testPost(ctx){
try{
let params = ctx.request.body;
ctx.body = yapi.commons.resReturn(params);
}catch(e){
ctx.body = yapi.commons.resReturn(null, 402, e.message);
}
/**
* 测试 put
* @interface /test/put
* @method PUT
* @returns {Object}
* @example
*/
async testPut(ctx) {
try {
let params = ctx.request.body;
ctx.body = yapi.commons.resReturn(params);
} catch (e) {
ctx.body = yapi.commons.resReturn(null, 402, e.message);
}
}
/**
* 测试 单文件上传
* @interface /test/single/upload
* @method POST
* @returns {Object}
* @example
*/
async testSingleUpload(ctx){
try{
let params = ctx.request.body;
ctx.body = yapi.commons.resReturn({res: '上传成功'});
}catch(e){
ctx.body = yapi.commons.resReturn(null, 402, e.message);
}
/**
* 测试 delete
* @interface /test/delete
* @method DELETE
* @returns {Object}
* @example
*/
async testDelete(ctx) {
try {
let params = ctx.request.body;
ctx.body = yapi.commons.resReturn(params);
} catch (e) {
ctx.body = yapi.commons.resReturn(null, 402, e.message);
}
}
/**
* 测试 文件上传
* @interface /test/files/upload
* @method POST
* @returns {Object}
* @example
*/
async testFilesUpload(ctx){
try{
let params = ctx.request.body;
ctx.body = yapi.commons.resReturn({res: '上传成功'});
}catch(e){
ctx.body = yapi.commons.resReturn(null, 402, e.message);
}
/**
* 测试 head
* @interface /test/head
* @method HEAD
* @returns {Object}
* @example
*/
async testHead(ctx) {
try {
let query = ctx.query;
ctx.body = yapi.commons.resReturn(query);
} catch (e) {
ctx.body = yapi.commons.resReturn(null, 402, e.message);
}
}
/**
* 测试 put
* @interface /test/put
* @method PUT
* @returns {Object}
* @example
*/
async testPut(ctx){
try{
let params = ctx.request.body;
ctx.body = yapi.commons.resReturn(params);
}catch(e){
ctx.body = yapi.commons.resReturn(null, 402, e.message);
}
/**
* 测试 options
* @interface /test/options
* @method OPTIONS
* @returns {Object}
* @example
*/
async testOptions(ctx) {
try {
let query = ctx.query;
ctx.body = yapi.commons.resReturn(query);
} catch (e) {
ctx.body = yapi.commons.resReturn(null, 402, e.message);
}
}
/**
* 测试 delete
* @interface /test/delete
* @method DELETE
* @returns {Object}
* @example
*/
async testDelete(ctx){
try{
let params = ctx.request.body;
ctx.body = yapi.commons.resReturn(params);
}catch(e){
ctx.body = yapi.commons.resReturn(null, 402, e.message);
}
/**
* 测试 patch
* @interface /test/patch
* @method PATCH
* @returns {Object}
* @example
*/
async testPatch(ctx) {
try {
let params = ctx.request.body;
ctx.body = yapi.commons.resReturn(params);
} catch (e) {
ctx.body = yapi.commons.resReturn(null, 402, e.message);
}
/**
* 测试 head
* @interface /test/head
* @method HEAD
* @returns {Object}
* @example
*/
async testHead(ctx){
try{
let query = ctx.query;
ctx.body = yapi.commons.resReturn(query);
}catch(e){
ctx.body = yapi.commons.resReturn(null, 402, e.message);
}
}
/**
* 测试 options
* @interface /test/options
* @method OPTIONS
* @returns {Object}
* @example
*/
async testOptions(ctx){
try{
let query = ctx.query;
ctx.body = yapi.commons.resReturn(query);
}catch(e){
ctx.body = yapi.commons.resReturn(null, 402, e.message);
}
}
/**
* 测试 patch
* @interface /test/patch
* @method PATCH
* @returns {Object}
* @example
*/
async testPatch(ctx){
try{
let params = ctx.request.body;
ctx.body = yapi.commons.resReturn(params);
}catch(e){
ctx.body = yapi.commons.resReturn(null, 402, e.message);
}
}
}
}
module.exports = interfaceColController
module.exports = interfaceColController;

View File

@ -154,6 +154,11 @@ var hooks = {
add_ws_router: {
type: 'multi',
listener: []
},
import_data: {
type: 'multi',
listener: []
}
};

View File

@ -494,6 +494,10 @@ let routerConfig = {
action: "runAutoTest",
path: "run_auto_test",
method: "get"
},{
action: "importData",
path: "import_data",
method: "post"
}]
}

View File

@ -425,18 +425,18 @@ exports.createAction = (
ctx.request.body,
ctx.params
);
let validResult = yapi.commons.validateParams(
inst.schemaMap[action],
ctx.params
);
// let validResult = yapi.commons.validateParams(
// inst.schemaMap[action],
// ctx.params
// );
if (!validResult.valid) {
return (ctx.body = yapi.commons.resReturn(
null,
400,
validResult.message
));
}
// if (!validResult.valid) {
// return (ctx.body = yapi.commons.resReturn(
// null,
// 400,
// validResult.message
// ));
// }
}
if (inst.$auth === true) {
await inst[action].call(inst, ctx);

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

File diff suppressed because one or more lines are too long

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 135 KiB

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

Binary file not shown.

After

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 57 KiB

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

Binary file not shown.

After

Width:  |  Height:  |  Size: 69 KiB

View File

@ -7,7 +7,7 @@
<div class="icon">&#xf0fd;</div>
<input type="text" class="input js-input" placeholder="搜索" />
<div class="m-search-result js-search-result"></div>
</div></div><nav class="m-header-nav js-nav"><ul class="m-header-items"><li class="item "><a class="href" href="documents/index.html">教程</a></li><li class="item "><a class="href" href="devops/index.html">内网部署</a></li></ul></nav><div id="js-nav-btn" class="m-header-btn ui-font-ydoc"></div></header><div class="m-content" id="js-content"><div><div class="g-home"><section class="m-section home"><div class="m-section-container"><div class="m-section-title"><h4 class="name">YApi</h4><p class="desc">旨在为开发、产品、测试人员提供更优雅的接口管理服务。可以帮助开发者轻松创建、发布、维护 API</p><div class="m-section-btngroup"><a href="./documents/index.html"><div class="btn ">开始</div></a><a href="https://github.com/ymfe/yapi"><div class="btn btn-ghost">Github &gt;</div></a></div><p class="caption">当前版本: v1.3.9</p></div><div class="m-section-banner"><img src="./ydoc/images/dogbg@1x.png" alt="bg" srcSet="./ydoc/images/dogbg@2x.png 2x"/></div></div></section><section class="m-section feature"><div class="m-section-container"><div class="m-section-box"><div class="item"><h6 class="title">权限管理</h6><p class="desc">YApi 成熟的团队管理扁平化项目权限配置满足各类企业的需求</p></div><div class="item"><h6 class="title">可视化接口管理</h6><p class="desc">基于 websocket 的多人协作接口编辑功能和类 postman 测试工具,让多人协作成倍提升开发效率</p></div><div class="item"><h6 class="title">Mock Server</h6><p class="desc">易用的 Mock Server再也不用担心 mock 数据的生成了</p></div><div class="item"><h6 class="title">自动化测试</h6><p class="desc">完善的接口自动化测试,保证数据的正确性</p></div><div class="item"><h6 class="title">数据导入</h6><p class="desc">支持导入 swagger, postman, har 数据格式,方便迁移旧项目</p></div><div class="item"><h6 class="title">插件机制</h6><p class="desc">强大的插件机制,满足各类业务需求</p></div></div></div></section></div><footer class="m-footer"><div class="m-footer-container"><div class="m-footer-links"><div class="group"><p class="title">团队网址</p><ul><li><a class="href" href="https://ymfe.org/">YMFE</a></li><li><a class="href" href="https://blog.ymfe.org/">YMFE Blog</a></li></ul></div><div class="group"><p class="title">Git仓库</p><ul><li><a class="href" href="https://github.com/YMFE/yapi">Github</a></li><li><a class="href" href="https://github.com/YMFE/yapi/issues">Github Issue</a></li></ul></div></div><div class="m-footer-title"><p class="copyright">© 2018 <a class="href" href="https://ymfe.org/">YMFE Team</a></p><p>Build by <a class="href" href="https://ydoc.ymfe.org/">ydoc</a></p></div></div></footer></div></div></div></div><div><div class="m-mask js-mask">
</div></div><nav class="m-header-nav js-nav"><ul class="m-header-items"><li class="item "><a class="href" href="documents/index.html">教程</a></li><li class="item "><a class="href" href="devops/index.html">内网部署</a></li></ul></nav><div id="js-nav-btn" class="m-header-btn ui-font-ydoc"></div></header><div class="m-content" id="js-content"><div><div class="g-home"><section class="m-section home"><div class="m-section-container"><div class="m-section-title"><h4 class="name">YApi</h4><p class="desc">旨在为开发、产品、测试人员提供更优雅的接口管理服务。可以帮助开发者轻松创建、发布、维护 API</p><div class="m-section-btngroup"><a href="./documents/index.html"><div class="btn ">开始</div></a><a href="https://github.com/ymfe/yapi"><div class="btn btn-ghost">Github &gt;</div></a></div><p class="caption">当前版本: v1.3.12</p></div><div class="m-section-banner"><img src="./ydoc/images/dogbg@1x.png" alt="bg" srcSet="./ydoc/images/dogbg@2x.png 2x"/></div></div></section><section class="m-section feature"><div class="m-section-container"><div class="m-section-box"><div class="item"><h6 class="title">权限管理</h6><p class="desc">YApi 成熟的团队管理扁平化项目权限配置满足各类企业的需求</p></div><div class="item"><h6 class="title">可视化接口管理</h6><p class="desc">基于 websocket 的多人协作接口编辑功能和类 postman 测试工具,让多人协作成倍提升开发效率</p></div><div class="item"><h6 class="title">Mock Server</h6><p class="desc">易用的 Mock Server再也不用担心 mock 数据的生成了</p></div><div class="item"><h6 class="title">自动化测试</h6><p class="desc">完善的接口自动化测试,保证数据的正确性</p></div><div class="item"><h6 class="title">数据导入</h6><p class="desc">支持导入 swagger, postman, har 数据格式,方便迁移旧项目</p></div><div class="item"><h6 class="title">插件机制</h6><p class="desc">强大的插件机制,满足各类业务需求</p></div></div></div></section></div><footer class="m-footer"><div class="m-footer-container"><div class="m-footer-links"><div class="group"><p class="title">团队网址</p><ul><li><a class="href" href="https://ymfe.org/">YMFE</a></li><li><a class="href" href="https://blog.ymfe.org/">YMFE Blog</a></li></ul></div><div class="group"><p class="title">Git仓库</p><ul><li><a class="href" href="https://github.com/YMFE/yapi">Github</a></li><li><a class="href" href="https://github.com/YMFE/yapi/issues">Github Issue</a></li></ul></div></div><div class="m-footer-title"><p class="copyright">© 2018 <a class="href" href="https://ymfe.org/">YMFE Team</a></p><p>Build by <a class="href" href="https://ydoc.ymfe.org/">ydoc</a></p></div></div></footer></div></div></div></div><div><div class="m-mask js-mask">
<div class="container">
<img src="" alt="" class="img js-mask-img" />
</div>

View File

@ -236,6 +236,11 @@ window.ydoc_plugin_search_json = {
"title": "token配置",
"url": "/documents/project.html#token配置",
"content": "token配置每个项目都有唯一的标识token用户可以使用这个token值来请求项目的所有资源数据。目前用到的地方是接口的自动化测试用户不需要登录就可以访问接口测试结果信息。"
},
{
"title": "项目克隆",
"url": "/documents/project.html#项目克隆",
"content": "项目克隆该功能在 v1.3.12 版本上线,项目克隆功能可复制项目全部接口到一个新项目,如下图所示,点击红色框里面的 icon 使用。"
}
]
},
@ -303,6 +308,11 @@ window.ydoc_plugin_search_json = {
"title": "token配置",
"url": "/documents/project.html#token配置",
"content": "token配置每个项目都有唯一的标识token用户可以使用这个token值来请求项目的所有资源数据。目前用到的地方是接口的自动化测试用户不需要登录就可以访问接口测试结果信息。"
},
{
"title": "项目克隆",
"url": "/documents/project.html#项目克隆",
"content": "项目克隆该功能在 v1.3.12 版本上线,项目克隆功能可复制项目全部接口到一个新项目,如下图所示,点击红色框里面的 icon 使用。"
}
]
},
@ -388,42 +398,42 @@ window.ydoc_plugin_search_json = {
{
"title": "定义 mock 数据示例",
"url": "/documents/mock.html#定义-mock-数据示例",
"content": "定义 mock 数据示例项目 -> 接口编辑 -> 返回数据设置返回数据设置有两种方式,最新版本默认是基于 json-schema 定义数据结构,另外一种是基于 json+注释 的方式,请根据实际情况灵活选择使用。"
"content": "定义 mock 数据示例项目 -> 接口编辑 -> 返回数据设置返回数据设置有两种方式,最新版本默认是基于 json+注释 的方式,另外一种是基于 json-schema 定义数据结构,请根据实际情况灵活选择使用。"
},
{
"title": "方式1. json-schema",
"url": "/documents/mock.html#方式1.-json-schema",
"content": "方式1. json-schema开启 json-schema 功能后,将不再使用 mockjs 解析定义的返回数据,而是根据 json-schema 定义的数据结构,生成随机数据。"
},
{
"title": "如何生成随机的邮箱或 ip",
"url": "/documents/mock.html#方式1.-json-schema-如何生成随机的邮箱或-ip",
"content": "如何生成随机的邮箱或 ip点击高级设置选择 format 选项,比如选择 email 则该字段生成随机邮箱字符串。"
},
{
"title": "方式2. json5+注释",
"url": "/documents/mock.html#方式2.-json5+注释",
"content": "方式2. json5+注释"
"title": "方式1. mockjs",
"url": "/documents/mock.html#方式1.-mockjs",
"content": "方式1. mockjs"
},
{
"title": "原理",
"url": "/documents/mock.html#方式2.-json5+注释-原理",
"content": "原理YApi Mock 功能基于 node 和 mockjs跟 Mockjs 区别是 yapi 基于 json 定义 mock ,无法使用 mockjs 原有的函数功能,正则表达式需要基于 rule 书写,示例如下:{ \"name|regexp\": \"[a-z0-9_]+?\",\n \"type|regexp\": \"json|text|xml\" //\b\b枚举数据类型可这样实现\n}\n\n2 支持替换请求的 query, body 参数{ \"name\": \"${query.name}\", //请求的url是/path?name=xiaoming, 返回的name字段是xiaoming\n \"type\": \"${body.type}\" //请求的requestBody type=1,返回的type字段是1\n}\n\n其他基本用法请查看Mockjs 官网"
"url": "/documents/mock.html#方式1.-mockjs-原理",
"content": "原理基于 mockjs跟 Mockjs 区别是 yapi 基于 json + 注释 定义 mock 数据,无法使用 mockjs 原有的函数功能。正则表达式需要基于 rule 书写,示例如下:\n{ \"name|regexp\": \"[a-z0-9_]+?\",\n \"type|regexp\": \"json|text|xml\"\n}\n\n支持替换请求的 query, body 参数\n{ \"name\": \"${query.name}\", //请求的url是/path?name=xiaoming, 返回的name字段是xiaoming\n \"type\": \"${body.type}\" //请求的requestBody type=1,返回的type字段是1\n}\n\n示例\n/** * 这是一个接口返回数据示例\n */\n\n{\n \"errcode\": 0,\n \"errmsg\": \"@word\",\n \"data\": {\n \"id\": \"@id\", //@id 随机生成 id\n \"name\": \"@name\" //@name 随机生成用户名\n }\n}\n\n详细使用文档请查看Mockjs 官网"
},
{
"title": "方式2. json-schema",
"url": "/documents/mock.html#方式2.-json-schema",
"content": "方式2. json-schema开启 json-schema 功能后,将不再使用 mockjs 解析定义的返回数据,而是根据 json-schema 定义的数据结构,生成随机数据。"
},
{
"title": "如何生成随机的邮箱或 ip",
"url": "/documents/mock.html#方式2.-json-schema-如何生成随机的邮箱或-ip",
"content": "如何生成随机的邮箱或 ip点击高级设置选择 format 选项,比如选择 email 则该字段生成随机邮箱字符串。"
},
{
"title": "如何使用 Mock",
"url": "/documents/mock.html#方式2.-json5+注释-如何使用-mock",
"content": "如何使用 Mock1 在 js 代码直接请求yapi提供的 mock 地址(不用担心跨域问题)在代码直接请求 yapi 提供的 mock 地址,以 jQuery 为例let prefix = 'http://yapi.xxx.com/mock/2817'$.post(prefix+'/baseapi/path', {username: 'xxx'}, function(res){\n console.log(res) //返回上图预览部分的数据\n})\n2 \b基于本地服务器反向代理优点:不用修改项目代码2.1 基于 nginx 反向代理location /baseapi{\nproxy_pass http://yapi.xxx.com/mock/2817/baseapi; #baseapi后面没有\"/\"\n}\n2.2 基于 ykit mock功能{ pattern: /\\/api\\/(.*)/,\n responder: 'http://yapi.xxx.com/mock/58/api/$1'\n}\n上面通过正则匹配将所有接口转到 http://yapi.xxx.com 上,比如 http://localhost/api/user/status 会成为 http://yapi.xxx.com/mock/58/api/user/status详细使用指南: ykit-config-mock2.3 基于 ykit Jerry 代理假设您本地服务器访问地址是: http://xxx.com2.4 基于 Charles 代理点击 Charles 工具栏下的 tools >> Rewrite Settings 填写如下信息:"
"url": "/documents/mock.html#如何使用-mock",
"content": "如何使用 Mock"
},
{
"title": "Mock 语法规范",
"url": "/documents/mock.html#方式2.-json5+注释-mock-语法规范",
"content": "Mock 语法规范了解更多Mock详情Mock.js 官方文档\nMock.js 的语法规范包括两部分1. 数据模板定义规范Data Template DefinitionDTD2. 数据占位符定义规范Data Placeholder DefinitionDPD"
"title": "1 在 js 代码直接请求yapi提供的 mock 地址(不用担心跨域问题)",
"url": "/documents/mock.html#如何使用-mock-1-在-js-代码直接请求yapi提供的-mock-地址(不用担心跨域问题)",
"content": "1 在 js 代码直接请求yapi提供的 mock 地址(不用担心跨域问题)在代码直接请求 yapi 提供的 mock 地址,以 jQuery 为例let prefix = 'http://yapi.xxx.com/mock/2817'$.post(prefix+'/baseapi/path', {username: 'xxx'}, function(res){\n console.log(res) //返回上图预览部分的数据\n})\n"
},
{
"title": "数据模板定义规范Data Template DefinitionDTD",
"url": "/documents/mock.html#方式2.-json5+注释-数据模板定义规范data-template-definitiondtd",
"content": "数据模板定义规范Data Template DefinitionDTD数据模板中的每个属性由 3 部分构成:属性名、生成规则、属性值:// 属性名 name (与生成规则之间用 \"|\" 隔开)// 生成规则 rule生成规则有7种详见下面的生成规则\n// 属性值 value可以含有 \"@占位符\" 同时也指定了最终值的初始值和类型)\n\n'name|rule': value\n\n生成规则\n'name|min-max': value\n'name|count': value\n'name|min-max.dmin-dmax': value\n'name|min-max.dcount': value\n'name|count.dmin-dmax': value\n'name|count.dcount': value\n'name|+step': value\n下面提供了6种生成规则以及示例包括 String、Number、Boolean、Object、Array1. 属性值是字符串 String1. 'name|min-max': string\n通过重复 string 生成一个字符串,重复次数大于等于 min小于等于 max。\n\n2. 'name|count': string\n\n通过重复 string 生成一个字符串,重复次数等于 count。\n2. 属性值是数字 Number1. 'name|+1': number\n属性值自动加 1初始值为 number。\n\n2. 'name|min-max': number\n\n生成一个大于等于 min、小于等于 max 的整数,属性值 number 只是用来确定类型。\n\n3. 'name|min-max.dmin-dmax': number\n\n生成一个浮点数整数部分大于等于 min、小于等于 max小数部分保留 dmin 到 dmax 位。\n\n例如\nMock.mock({\n 'number1|1-100.1-10': 1,\n 'number2|123.1-10': 1,\n 'number3|123.3': 1,\n 'number4|123.10': 1.123\n})\n// =>\n{\n \"number1\": 12.92,\n \"number2\": 123.51,\n \"number3\": 123.777,\n \"number4\": 123.1231091814\n}\n3. 属性值是布尔型 Boolean1. 'name|1': boolean\n随机生成一个布尔值值为 true 的概率是 1/2值为 false 的概率同样是 1/2。\n\n2. 'name|min-max': value\n\n随机生成一个布尔值值为 value 的概率是 min / (min + max),值为 !value 的概率是 max / (min + max)。\n4. 属性值是对象 Object1. 'name|count': object\n从属性值 object 中随机选取 count 个属性。\n\n2. 'name|min-max': object\n\n从属性值 object 中随机选取 min 到 max 个属性。\n5. 属性值是数组 Array1. 'name|1': array\n从属性值 array 中随机选取 1 个元素,作为最终值。\n\n2. 'name|+1': array\n\n从属性值 array 中顺序选取 1 个元素,作为最终值。\n\n3. 'name|min-max': array\n\n通过重复属性值 array 生成一个新数组,重复次数大于等于 min小于等于 max。\n\n4. 'name|count': array\n\n通过重复属性值 array 生成一个新数组,重复次数为 count。\n数据占位符定义规范Data Placeholder DefinitionDPD占位符 只是在属性值字符串中占个位置,并不出现在最终的属性值中。\n占位符 的格式为:\n\n@占位符\n\n说明\n1. 用 @ 来标识其后的字符串是 占位符在YApi提供的Mock输入框在输入“@”后会自动提示占位符。\n\n例如\nname: {\n first: '@FIRST',\n middle: '@FIRST',\n last: '@LAST',\n full: '@first @middle @last'\n}\n// 上面的示例可以得到如下结果:\n\"name\": {\n \"first\": \"Charles\",\n \"middle\": \"Brenda\",\n \"last\": \"Lopez\",\n \"full\": \"Charles Brenda Lopez\"\n}\n"
"title": "2 \b基于本地服务器反向代理",
"url": "/documents/mock.html#如何使用-mock-2-\b基于本地服务器反向代理",
"content": "2 \b基于本地服务器反向代理优点:不用修改项目代码2.1 基于 nginx 反向代理location /baseapi{\nproxy_pass http://yapi.xxx.com/mock/2817/baseapi; #baseapi后面没有\"/\"\n}\n2.2 基于 ykit mock功能{ pattern: /\\/api\\/(.*)/,\n responder: 'http://yapi.xxx.com/mock/58/api/$1'\n}\n上面通过正则匹配将所有接口转到 http://yapi.xxx.com 上,比如 http://localhost/api/user/status 会成为 http://yapi.xxx.com/mock/58/api/user/status详细使用指南: ykit-config-mock2.3 基于 ykit Jerry 代理假设您本地服务器访问地址是: http://xxx.com2.4 基于 Charles 代理点击 Charles 工具栏下的 tools >> Rewrite Settings 填写如下信息:"
}
]
},
@ -435,42 +445,42 @@ window.ydoc_plugin_search_json = {
{
"title": "定义 mock 数据示例",
"url": "/documents/mock.html#定义-mock-数据示例",
"content": "定义 mock 数据示例项目 -> 接口编辑 -> 返回数据设置返回数据设置有两种方式,最新版本默认是基于 json-schema 定义数据结构,另外一种是基于 json+注释 的方式,请根据实际情况灵活选择使用。"
"content": "定义 mock 数据示例项目 -> 接口编辑 -> 返回数据设置返回数据设置有两种方式,最新版本默认是基于 json+注释 的方式,另外一种是基于 json-schema 定义数据结构,请根据实际情况灵活选择使用。"
},
{
"title": "方式1. json-schema",
"url": "/documents/mock.html#方式1.-json-schema",
"content": "方式1. json-schema开启 json-schema 功能后,将不再使用 mockjs 解析定义的返回数据,而是根据 json-schema 定义的数据结构,生成随机数据。"
},
{
"title": "如何生成随机的邮箱或 ip",
"url": "/documents/mock.html#方式1.-json-schema-如何生成随机的邮箱或-ip",
"content": "如何生成随机的邮箱或 ip点击高级设置选择 format 选项,比如选择 email 则该字段生成随机邮箱字符串。"
},
{
"title": "方式2. json5+注释",
"url": "/documents/mock.html#方式2.-json5+注释",
"content": "方式2. json5+注释"
"title": "方式1. mockjs",
"url": "/documents/mock.html#方式1.-mockjs",
"content": "方式1. mockjs"
},
{
"title": "原理",
"url": "/documents/mock.html#方式2.-json5+注释-原理",
"content": "原理YApi Mock 功能基于 node 和 mockjs跟 Mockjs 区别是 yapi 基于 json 定义 mock ,无法使用 mockjs 原有的函数功能,正则表达式需要基于 rule 书写,示例如下:{ \"name|regexp\": \"[a-z0-9_]+?\",\n \"type|regexp\": \"json|text|xml\" //\b\b枚举数据类型可这样实现\n}\n\n2 支持替换请求的 query, body 参数{ \"name\": \"${query.name}\", //请求的url是/path?name=xiaoming, 返回的name字段是xiaoming\n \"type\": \"${body.type}\" //请求的requestBody type=1,返回的type字段是1\n}\n\n其他基本用法请查看Mockjs 官网"
"url": "/documents/mock.html#方式1.-mockjs-原理",
"content": "原理基于 mockjs跟 Mockjs 区别是 yapi 基于 json + 注释 定义 mock 数据,无法使用 mockjs 原有的函数功能。正则表达式需要基于 rule 书写,示例如下:\n{ \"name|regexp\": \"[a-z0-9_]+?\",\n \"type|regexp\": \"json|text|xml\"\n}\n\n支持替换请求的 query, body 参数\n{ \"name\": \"${query.name}\", //请求的url是/path?name=xiaoming, 返回的name字段是xiaoming\n \"type\": \"${body.type}\" //请求的requestBody type=1,返回的type字段是1\n}\n\n示例\n/** * 这是一个接口返回数据示例\n */\n\n{\n \"errcode\": 0,\n \"errmsg\": \"@word\",\n \"data\": {\n \"id\": \"@id\", //@id 随机生成 id\n \"name\": \"@name\" //@name 随机生成用户名\n }\n}\n\n详细使用文档请查看Mockjs 官网"
},
{
"title": "方式2. json-schema",
"url": "/documents/mock.html#方式2.-json-schema",
"content": "方式2. json-schema开启 json-schema 功能后,将不再使用 mockjs 解析定义的返回数据,而是根据 json-schema 定义的数据结构,生成随机数据。"
},
{
"title": "如何生成随机的邮箱或 ip",
"url": "/documents/mock.html#方式2.-json-schema-如何生成随机的邮箱或-ip",
"content": "如何生成随机的邮箱或 ip点击高级设置选择 format 选项,比如选择 email 则该字段生成随机邮箱字符串。"
},
{
"title": "如何使用 Mock",
"url": "/documents/mock.html#方式2.-json5+注释-如何使用-mock",
"content": "如何使用 Mock1 在 js 代码直接请求yapi提供的 mock 地址(不用担心跨域问题)在代码直接请求 yapi 提供的 mock 地址,以 jQuery 为例let prefix = 'http://yapi.xxx.com/mock/2817'$.post(prefix+'/baseapi/path', {username: 'xxx'}, function(res){\n console.log(res) //返回上图预览部分的数据\n})\n2 \b基于本地服务器反向代理优点:不用修改项目代码2.1 基于 nginx 反向代理location /baseapi{\nproxy_pass http://yapi.xxx.com/mock/2817/baseapi; #baseapi后面没有\"/\"\n}\n2.2 基于 ykit mock功能{ pattern: /\\/api\\/(.*)/,\n responder: 'http://yapi.xxx.com/mock/58/api/$1'\n}\n上面通过正则匹配将所有接口转到 http://yapi.xxx.com 上,比如 http://localhost/api/user/status 会成为 http://yapi.xxx.com/mock/58/api/user/status详细使用指南: ykit-config-mock2.3 基于 ykit Jerry 代理假设您本地服务器访问地址是: http://xxx.com2.4 基于 Charles 代理点击 Charles 工具栏下的 tools >> Rewrite Settings 填写如下信息:"
"url": "/documents/mock.html#如何使用-mock",
"content": "如何使用 Mock"
},
{
"title": "Mock 语法规范",
"url": "/documents/mock.html#方式2.-json5+注释-mock-语法规范",
"content": "Mock 语法规范了解更多Mock详情Mock.js 官方文档\nMock.js 的语法规范包括两部分1. 数据模板定义规范Data Template DefinitionDTD2. 数据占位符定义规范Data Placeholder DefinitionDPD"
"title": "1 在 js 代码直接请求yapi提供的 mock 地址(不用担心跨域问题)",
"url": "/documents/mock.html#如何使用-mock-1-在-js-代码直接请求yapi提供的-mock-地址(不用担心跨域问题)",
"content": "1 在 js 代码直接请求yapi提供的 mock 地址(不用担心跨域问题)在代码直接请求 yapi 提供的 mock 地址,以 jQuery 为例let prefix = 'http://yapi.xxx.com/mock/2817'$.post(prefix+'/baseapi/path', {username: 'xxx'}, function(res){\n console.log(res) //返回上图预览部分的数据\n})\n"
},
{
"title": "数据模板定义规范Data Template DefinitionDTD",
"url": "/documents/mock.html#方式2.-json5+注释-数据模板定义规范data-template-definitiondtd",
"content": "数据模板定义规范Data Template DefinitionDTD数据模板中的每个属性由 3 部分构成:属性名、生成规则、属性值:// 属性名 name (与生成规则之间用 \"|\" 隔开)// 生成规则 rule生成规则有7种详见下面的生成规则\n// 属性值 value可以含有 \"@占位符\" 同时也指定了最终值的初始值和类型)\n\n'name|rule': value\n\n生成规则\n'name|min-max': value\n'name|count': value\n'name|min-max.dmin-dmax': value\n'name|min-max.dcount': value\n'name|count.dmin-dmax': value\n'name|count.dcount': value\n'name|+step': value\n下面提供了6种生成规则以及示例包括 String、Number、Boolean、Object、Array1. 属性值是字符串 String1. 'name|min-max': string\n通过重复 string 生成一个字符串,重复次数大于等于 min小于等于 max。\n\n2. 'name|count': string\n\n通过重复 string 生成一个字符串,重复次数等于 count。\n2. 属性值是数字 Number1. 'name|+1': number\n属性值自动加 1初始值为 number。\n\n2. 'name|min-max': number\n\n生成一个大于等于 min、小于等于 max 的整数,属性值 number 只是用来确定类型。\n\n3. 'name|min-max.dmin-dmax': number\n\n生成一个浮点数整数部分大于等于 min、小于等于 max小数部分保留 dmin 到 dmax 位。\n\n例如\nMock.mock({\n 'number1|1-100.1-10': 1,\n 'number2|123.1-10': 1,\n 'number3|123.3': 1,\n 'number4|123.10': 1.123\n})\n// =>\n{\n \"number1\": 12.92,\n \"number2\": 123.51,\n \"number3\": 123.777,\n \"number4\": 123.1231091814\n}\n3. 属性值是布尔型 Boolean1. 'name|1': boolean\n随机生成一个布尔值值为 true 的概率是 1/2值为 false 的概率同样是 1/2。\n\n2. 'name|min-max': value\n\n随机生成一个布尔值值为 value 的概率是 min / (min + max),值为 !value 的概率是 max / (min + max)。\n4. 属性值是对象 Object1. 'name|count': object\n从属性值 object 中随机选取 count 个属性。\n\n2. 'name|min-max': object\n\n从属性值 object 中随机选取 min 到 max 个属性。\n5. 属性值是数组 Array1. 'name|1': array\n从属性值 array 中随机选取 1 个元素,作为最终值。\n\n2. 'name|+1': array\n\n从属性值 array 中顺序选取 1 个元素,作为最终值。\n\n3. 'name|min-max': array\n\n通过重复属性值 array 生成一个新数组,重复次数大于等于 min小于等于 max。\n\n4. 'name|count': array\n\n通过重复属性值 array 生成一个新数组,重复次数为 count。\n数据占位符定义规范Data Placeholder DefinitionDPD占位符 只是在属性值字符串中占个位置,并不出现在最终的属性值中。\n占位符 的格式为:\n\n@占位符\n\n说明\n1. 用 @ 来标识其后的字符串是 占位符在YApi提供的Mock输入框在输入“@”后会自动提示占位符。\n\n例如\nname: {\n first: '@FIRST',\n middle: '@FIRST',\n last: '@LAST',\n full: '@first @middle @last'\n}\n// 上面的示例可以得到如下结果:\n\"name\": {\n \"first\": \"Charles\",\n \"middle\": \"Brenda\",\n \"last\": \"Lopez\",\n \"full\": \"Charles Brenda Lopez\"\n}\n"
"title": "2 \b基于本地服务器反向代理",
"url": "/documents/mock.html#如何使用-mock-2-\b基于本地服务器反向代理",
"content": "2 \b基于本地服务器反向代理优点:不用修改项目代码2.1 基于 nginx 反向代理location /baseapi{\nproxy_pass http://yapi.xxx.com/mock/2817/baseapi; #baseapi后面没有\"/\"\n}\n2.2 基于 ykit mock功能{ pattern: /\\/api\\/(.*)/,\n responder: 'http://yapi.xxx.com/mock/58/api/$1'\n}\n上面通过正则匹配将所有接口转到 http://yapi.xxx.com 上,比如 http://localhost/api/user/status 会成为 http://yapi.xxx.com/mock/58/api/user/status详细使用指南: ykit-config-mock2.3 基于 ykit Jerry 代理假设您本地服务器访问地址是: http://xxx.com2.4 基于 Charles 代理点击 Charles 工具栏下的 tools >> Rewrite Settings 填写如下信息:"
}
]
},
@ -580,85 +590,115 @@ window.ydoc_plugin_search_json = {
},
{
"title": "自动化测试",
"content": "Web 应用通常是前后端分离开发的,后端提供调用的接口,前端使用接口返回 json 数据渲染到 UI接口测试就是保证后端接口的数据正确性。对于很多团队接口测试就是手动运行接口肉眼比对接口返回的数据这样的操作流程效率低下容易出错。使用 YApi 只需要在可视化 GUI 下,配置下每个接口的入参和对 RESPONSE 断言,即可实现对接口的自动化测试,大大提升了接口测试的效率。自动化测试实践",
"content": "传统的接口自动化测试成本高大量的项目没有使用自动化测试保证接口的质量仅仅依靠手动测试是非常不可靠和容易出错的。YApi 为了解决这个问题,开发了可视化接口自动化测试功能,只需要配置每个接口的入参和对 RESPONSE 断言,即可实现对接口的自动化测试,大大提升了接口测试的效率。",
"url": "/documents/case.html",
"children": [
{
"title": "测试列表",
"url": "/documents/case.html#测试列表",
"content": "测试列表在测试列表可以看到每个测试用例的 key,还有 开始测试、报告等功能点击开始测试会按照 case 定义的参数从上往下一个一个进行测试,如果顺序有问题,可以拖动调整测试完成之后,点击报告查看该次请求的结果"
"title": "第一步,测试集合",
"url": "/documents/case.html#第一步,测试集合",
"content": "第一步,测试集合使用 YApi 自动化测试,第一步需要做得是创建测试集合和导入接口,点击添加集合创建,创建完成后导入接口。"
},
{
"title": "编辑测试用例",
"url": "/documents/case.html#编辑测试用例",
"content": "编辑测试用例"
"title": "第二步,编辑测试用例",
"url": "/documents/case.html#第二步,编辑测试用例",
"content": "第二步,编辑测试用例编写测试用例主要涉及两个方面,一个是请求参数,另外一个是断言脚本。"
},
{
"title": "Mock 参数",
"url": "/documents/case.html#编辑测试用例-mock-参数",
"content": "Mock 参数Mock 参数每次请求都会生成随机字符串变量参数YApi 提供了强大的变量参数功能,你可以在测试的时候使用前面接口的 参数 或 返回值 作为 后面接口的参数,即使接口之间存在依赖,也可以轻松 一键测试~Tips: 参数只能是测试过程中排在前面的接口中的变量参数\n格式$.{key}.{params|body}.{path}例如:现有两个接口,分别是“导航标题”和“文章列表”文章列表接口需要传参数: 当前标题(id),而这个 id 需要通过 导航标题 的返回值获取,这时应在 文章列表 的参数输入框中根据前者的 key 找到对应 id。导航标题 的参数和返回值有如下结构: 参数:\n \n 返回值:\n \n则 文章列表 的参数可以如下配置:其中 $. 是使用 动态变量 的标志,$.269.params 即表示 key 值为 269 用例的请求参数,$.269.body 即表示 key 值为 269 用例的返回值。如果 requestBody 是 json 格式也可以在 json 中写变量参数如下图Tips: 上下拖动测试集合的列表项可以调整测试的顺序。\n目前 yapi 中的querybody,header和pathParam的输入参数已经支持点击选择功能。无需自己填写表达式只需在弹窗中选择需要展示的表达式即可。 输入选项包括常量mock数据在测试集合中也支持变量选择。具体用法单击编辑按钮打开表达式生成器点击需要的数据创建表达式这里也可以实时查看表达式结果。Tips: 在测试集合中插入变量参数可以会出现下图的提示信息,这是正常现象。因为该参数只能在各个接口顺序执行的时候才能拉到变量参数中的值\n"
"title": "编辑请求参数",
"url": "/documents/case.html#第二步,编辑测试用例-编辑请求参数",
"content": "编辑请求参数请求参数可以填写期望的字符串YApi 还提供了 Mock 参数和 变量参数。Mock参数用来生成随机字符串变量参数是为了解决请求参数依赖其他接口的返回数据或参数。Mock 参数Mock 参数每次请求都会生成随机字符串变量参数YApi 提供了强大的变量参数功能,你可以在测试的时候使用前面接口的 参数 或 返回值 作为 后面接口的参数,即使接口之间存在依赖,也可以轻松 一键测试~Tips: 参数只能是测试过程中排在前面的接口中的变量参数\n格式$.{key}.{params|body}.{path}例如:现有两个接口,分别是“导航标题”和“文章列表”文章列表接口需要传参数: 当前标题(id),而这个 id 需要通过 导航标题 的返回值获取,这时应在 文章列表 的参数输入框中根据前者的 key 找到对应 id。导航标题 的参数和返回值有如下结构: 参数:\n \n 返回值:\n \n则 文章列表 的参数可以如下配置:其中 $. 是使用 动态变量 的标志,$.269.params 即表示 key 值为 269 用例的请求参数,$.269.body 即表示 key 值为 269 用例的返回值。如果 requestBody 是 json 格式也可以在 json 中写变量参数如下图Tips: 上下拖动测试集合的列表项可以调整测试的顺序。\n目前 yapi 中的querybody,header和pathParam的输入参数已经支持点击选择功能。无需自己填写表达式只需在弹窗中选择需要展示的表达式即可。 输入选项包括常量mock数据在测试集合中也支持变量选择。具体用法单击编辑按钮打开表达式生成器点击需要的数据创建表达式这里也可以实时查看表达式结果。Tips: 在测试集合中插入变量参数可以会出现下图的提示信息,这是正常现象。因为该参数只能在各个接口顺序执行的时候才能拉到变量参数中的值\n"
},
{
"title": "自动化测试",
"url": "/documents/case.html#自动化测试",
"content": "自动化测试点击自动化测试,出现如下弹窗,用户访问该 url 就可以获取当前测试用例的所有测试结果"
"title": "编写断言脚本",
"url": "/documents/case.html#第二步,编辑测试用例-编写断言脚本",
"content": "编写断言脚本编写完请求参数,可通过 js 脚本写断言,实现精准测试,在接口用例页面点击 Test 编辑。"
},
{
"title": "断言",
"url": "/documents/case.html#断言",
"content": "断言可通过 js 脚本写断言,实现精准测试,在接口用例页面点击 Test 编辑。"
"title": "第三步,运行自动化测试",
"url": "/documents/case.html#第三步,运行自动化测试",
"content": "第三步,运行自动化测试在测试列表可以看到每个测试用例的 key,还有 开始测试、报告等功能点击开始测试会按照 case 定义的参数从上往下一个一个进行测试,如果顺序有问题,可以拖动调整测试完成之后,点击报告查看该次请求的结果"
},
{
"title": "公共变量",
"url": "/documents/case.html#断言-公共变量",
"content": "公共变量1.assert断言函数详细 api 可查看 document常用 api:\nassert(value)\n判断 value 是否为 truth, 例如 assert(1) 通过, assert(0) 不通过,只要 value 不是 null, 0, false 等值验证通过\n\n\nassert.equal(actual, expected)\n判断 actual 是否等于 expected例如 assert(1, 1)通过\n\n\nassert.notEqual(actual, expected)\n判断 actual 是否不等于 expected\n\n\nassert.deepEqual(actual, expected)\n假设 actual = {a:1} 是一个对象,即便 expected = {a:1},如果使用 assert.equal 可能也是不相等的,因为在 js 引用的只是对象的一个指针,需要使用 assert.deepEqual 比较两个对象是否相等\n\n\nassert.notDeepEaual(actual, expected)\n深度比较两个对象是否不相等\n\n2.statushttp 状态码3.paramshttp request params, 合并了 query 和 body4.body返回 response body5.header返回 response header6.records记录的 http 请求信息,假设需要获取 key 为 555 的接口参数或者响应数据,可通过 records[555].params 或 records[555].body 获取7.loglogmessage 函数,调试时使用log 信息仅仅在断言失败后打印"
"title": "断言脚本公共变量",
"url": "/documents/case.html#断言脚本公共变量",
"content": "断言脚本公共变量"
},
{
"title": "1.assert",
"url": "/documents/case.html#断言脚本公共变量-1.assert",
"content": "1.assert断言函数详细 api 可查看 document常用 api\nassert(value)\n判断 value 是否为 truth, 例如 assert(1) 通过, assert(0) 不通过,只要 value 不是 null, 0, false 等值验证通过\n\n\nassert.equal(actual, expected)\n判断 actual 是否等于 expected例如 assert(1, 1)通过\n\n\nassert.notEqual(actual, expected)\n判断 actual 是否不等于 expected\n\n\nassert.deepEqual(actual, expected)\n假设 actual = {a:1} 是一个对象,即便 expected = {a:1},如果使用 assert.equal 可能也是不相等的,因为在 js 引用的只是对象的一个指针,需要使用 assert.deepEqual 比较两个对象是否相等\n\n\nassert.notDeepEaual(actual, expected)\n深度比较两个对象是否不相等\n\n2.statushttp 状态码3.paramshttp request params, 合并了 query 和 body4.body返回 response body5.header返回 response header6.records记录的 http 请求信息,假设需要获取 key 为 555 的接口参数或者响应数据,可通过 records[555].params 或 records[555].body 获取7.loglogmessage 函数,调试时使用log 信息仅仅在断言失败后打印"
},
{
"title": "示例",
"url": "/documents/case.html#断言-示例",
"url": "/documents/case.html#断言脚本公共变量-示例",
"content": "示例assert.equal(body.errcode, 0)assert.equal(body.data.group_name, 'testGroup')\nassert.equal(status, 200)\n"
},
{
"title": "服务端自动化测试",
"url": "/documents/case.html#服务端自动化测试",
"content": "服务端自动化测试开始测试功能是在浏览器跑自动化测试他依赖于浏览器的使用环境。服务端自动化测试功能是在YApi服务端跑自动化测试不需要依赖浏览器环境只需要访问 YApi 提供的 url 链接就能跑自动化测试,\b非常的简单易用\b而且可以集成到 jenkins。"
},
{
"title": "详细使用方法",
"url": "/documents/case.html#服务端自动化测试-详细使用方法",
"content": "详细使用方法点击服务端测试,出现如下弹窗,用户访问该 url 就可以获取当前测试用例的所有测试结果。"
}
]
},
{
"title": "自动化测试",
"content": "Web 应用通常是前后端分离开发的,后端提供调用的接口,前端使用接口返回 json 数据渲染到 UI接口测试就是保证后端接口的数据正确性。对于很多团队接口测试就是手动运行接口肉眼比对接口返回的数据这样的操作流程效率低下容易出错。使用 YApi 只需要在可视化 GUI 下,配置下每个接口的入参和对 RESPONSE 断言,即可实现对接口的自动化测试,大大提升了接口测试的效率。自动化测试实践",
"content": "传统的接口自动化测试成本高大量的项目没有使用自动化测试保证接口的质量仅仅依靠手动测试是非常不可靠和容易出错的。YApi 为了解决这个问题,开发了可视化接口自动化测试功能,只需要配置每个接口的入参和对 RESPONSE 断言,即可实现对接口的自动化测试,大大提升了接口测试的效率。",
"url": "/documents/case.html",
"children": [
{
"title": "测试列表",
"url": "/documents/case.html#测试列表",
"content": "测试列表在测试列表可以看到每个测试用例的 key,还有 开始测试、报告等功能点击开始测试会按照 case 定义的参数从上往下一个一个进行测试,如果顺序有问题,可以拖动调整测试完成之后,点击报告查看该次请求的结果"
"title": "第一步,测试集合",
"url": "/documents/case.html#第一步,测试集合",
"content": "第一步,测试集合使用 YApi 自动化测试,第一步需要做得是创建测试集合和导入接口,点击添加集合创建,创建完成后导入接口。"
},
{
"title": "编辑测试用例",
"url": "/documents/case.html#编辑测试用例",
"content": "编辑测试用例"
"title": "第二步,编辑测试用例",
"url": "/documents/case.html#第二步,编辑测试用例",
"content": "第二步,编辑测试用例编写测试用例主要涉及两个方面,一个是请求参数,另外一个是断言脚本。"
},
{
"title": "Mock 参数",
"url": "/documents/case.html#编辑测试用例-mock-参数",
"content": "Mock 参数Mock 参数每次请求都会生成随机字符串变量参数YApi 提供了强大的变量参数功能,你可以在测试的时候使用前面接口的 参数 或 返回值 作为 后面接口的参数,即使接口之间存在依赖,也可以轻松 一键测试~Tips: 参数只能是测试过程中排在前面的接口中的变量参数\n格式$.{key}.{params|body}.{path}例如:现有两个接口,分别是“导航标题”和“文章列表”文章列表接口需要传参数: 当前标题(id),而这个 id 需要通过 导航标题 的返回值获取,这时应在 文章列表 的参数输入框中根据前者的 key 找到对应 id。导航标题 的参数和返回值有如下结构: 参数:\n \n 返回值:\n \n则 文章列表 的参数可以如下配置:其中 $. 是使用 动态变量 的标志,$.269.params 即表示 key 值为 269 用例的请求参数,$.269.body 即表示 key 值为 269 用例的返回值。如果 requestBody 是 json 格式也可以在 json 中写变量参数如下图Tips: 上下拖动测试集合的列表项可以调整测试的顺序。\n目前 yapi 中的querybody,header和pathParam的输入参数已经支持点击选择功能。无需自己填写表达式只需在弹窗中选择需要展示的表达式即可。 输入选项包括常量mock数据在测试集合中也支持变量选择。具体用法单击编辑按钮打开表达式生成器点击需要的数据创建表达式这里也可以实时查看表达式结果。Tips: 在测试集合中插入变量参数可以会出现下图的提示信息,这是正常现象。因为该参数只能在各个接口顺序执行的时候才能拉到变量参数中的值\n"
"title": "编辑请求参数",
"url": "/documents/case.html#第二步,编辑测试用例-编辑请求参数",
"content": "编辑请求参数请求参数可以填写期望的字符串YApi 还提供了 Mock 参数和 变量参数。Mock参数用来生成随机字符串变量参数是为了解决请求参数依赖其他接口的返回数据或参数。Mock 参数Mock 参数每次请求都会生成随机字符串变量参数YApi 提供了强大的变量参数功能,你可以在测试的时候使用前面接口的 参数 或 返回值 作为 后面接口的参数,即使接口之间存在依赖,也可以轻松 一键测试~Tips: 参数只能是测试过程中排在前面的接口中的变量参数\n格式$.{key}.{params|body}.{path}例如:现有两个接口,分别是“导航标题”和“文章列表”文章列表接口需要传参数: 当前标题(id),而这个 id 需要通过 导航标题 的返回值获取,这时应在 文章列表 的参数输入框中根据前者的 key 找到对应 id。导航标题 的参数和返回值有如下结构: 参数:\n \n 返回值:\n \n则 文章列表 的参数可以如下配置:其中 $. 是使用 动态变量 的标志,$.269.params 即表示 key 值为 269 用例的请求参数,$.269.body 即表示 key 值为 269 用例的返回值。如果 requestBody 是 json 格式也可以在 json 中写变量参数如下图Tips: 上下拖动测试集合的列表项可以调整测试的顺序。\n目前 yapi 中的querybody,header和pathParam的输入参数已经支持点击选择功能。无需自己填写表达式只需在弹窗中选择需要展示的表达式即可。 输入选项包括常量mock数据在测试集合中也支持变量选择。具体用法单击编辑按钮打开表达式生成器点击需要的数据创建表达式这里也可以实时查看表达式结果。Tips: 在测试集合中插入变量参数可以会出现下图的提示信息,这是正常现象。因为该参数只能在各个接口顺序执行的时候才能拉到变量参数中的值\n"
},
{
"title": "自动化测试",
"url": "/documents/case.html#自动化测试",
"content": "自动化测试点击自动化测试,出现如下弹窗,用户访问该 url 就可以获取当前测试用例的所有测试结果"
"title": "编写断言脚本",
"url": "/documents/case.html#第二步,编辑测试用例-编写断言脚本",
"content": "编写断言脚本编写完请求参数,可通过 js 脚本写断言,实现精准测试,在接口用例页面点击 Test 编辑。"
},
{
"title": "断言",
"url": "/documents/case.html#断言",
"content": "断言可通过 js 脚本写断言,实现精准测试,在接口用例页面点击 Test 编辑。"
"title": "第三步,运行自动化测试",
"url": "/documents/case.html#第三步,运行自动化测试",
"content": "第三步,运行自动化测试在测试列表可以看到每个测试用例的 key,还有 开始测试、报告等功能点击开始测试会按照 case 定义的参数从上往下一个一个进行测试,如果顺序有问题,可以拖动调整测试完成之后,点击报告查看该次请求的结果"
},
{
"title": "公共变量",
"url": "/documents/case.html#断言-公共变量",
"content": "公共变量1.assert断言函数详细 api 可查看 document常用 api:\nassert(value)\n判断 value 是否为 truth, 例如 assert(1) 通过, assert(0) 不通过,只要 value 不是 null, 0, false 等值验证通过\n\n\nassert.equal(actual, expected)\n判断 actual 是否等于 expected例如 assert(1, 1)通过\n\n\nassert.notEqual(actual, expected)\n判断 actual 是否不等于 expected\n\n\nassert.deepEqual(actual, expected)\n假设 actual = {a:1} 是一个对象,即便 expected = {a:1},如果使用 assert.equal 可能也是不相等的,因为在 js 引用的只是对象的一个指针,需要使用 assert.deepEqual 比较两个对象是否相等\n\n\nassert.notDeepEaual(actual, expected)\n深度比较两个对象是否不相等\n\n2.statushttp 状态码3.paramshttp request params, 合并了 query 和 body4.body返回 response body5.header返回 response header6.records记录的 http 请求信息,假设需要获取 key 为 555 的接口参数或者响应数据,可通过 records[555].params 或 records[555].body 获取7.loglogmessage 函数,调试时使用log 信息仅仅在断言失败后打印"
"title": "断言脚本公共变量",
"url": "/documents/case.html#断言脚本公共变量",
"content": "断言脚本公共变量"
},
{
"title": "1.assert",
"url": "/documents/case.html#断言脚本公共变量-1.assert",
"content": "1.assert断言函数详细 api 可查看 document常用 api\nassert(value)\n判断 value 是否为 truth, 例如 assert(1) 通过, assert(0) 不通过,只要 value 不是 null, 0, false 等值验证通过\n\n\nassert.equal(actual, expected)\n判断 actual 是否等于 expected例如 assert(1, 1)通过\n\n\nassert.notEqual(actual, expected)\n判断 actual 是否不等于 expected\n\n\nassert.deepEqual(actual, expected)\n假设 actual = {a:1} 是一个对象,即便 expected = {a:1},如果使用 assert.equal 可能也是不相等的,因为在 js 引用的只是对象的一个指针,需要使用 assert.deepEqual 比较两个对象是否相等\n\n\nassert.notDeepEaual(actual, expected)\n深度比较两个对象是否不相等\n\n2.statushttp 状态码3.paramshttp request params, 合并了 query 和 body4.body返回 response body5.header返回 response header6.records记录的 http 请求信息,假设需要获取 key 为 555 的接口参数或者响应数据,可通过 records[555].params 或 records[555].body 获取7.loglogmessage 函数,调试时使用log 信息仅仅在断言失败后打印"
},
{
"title": "示例",
"url": "/documents/case.html#断言-示例",
"url": "/documents/case.html#断言脚本公共变量-示例",
"content": "示例assert.equal(body.errcode, 0)assert.equal(body.data.group_name, 'testGroup')\nassert.equal(status, 200)\n"
},
{
"title": "服务端自动化测试",
"url": "/documents/case.html#服务端自动化测试",
"content": "服务端自动化测试开始测试功能是在浏览器跑自动化测试他依赖于浏览器的使用环境。服务端自动化测试功能是在YApi服务端跑自动化测试不需要依赖浏览器环境只需要访问 YApi 提供的 url 链接就能跑自动化测试,\b非常的简单易用\b而且可以集成到 jenkins。"
},
{
"title": "详细使用方法",
"url": "/documents/case.html#服务端自动化测试-详细使用方法",
"content": "详细使用方法点击服务端测试,出现如下弹窗,用户访问该 url 就可以获取当前测试用例的所有测试结果。"
}
]
},
@ -681,6 +721,21 @@ window.ydoc_plugin_search_json = {
"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 文件中。2.打开yapi平台进入到项目页面点击数据管理选择相应的分组和swagger导入\b方式\b选择刚才的文件开始导入数据"
},
{
"title": "YApi接口JSON数据导入",
"url": "/documents/data.html#yapi接口json数据导入",
"content": "YApi接口JSON数据导入该功能在 v1.3.12 版本上线,可导入在 yapi 平台导出的 json 接口数据。"
},
{
"title": "通过命令行导入接口数据",
"url": "/documents/data.html#通过命令行导入接口数据",
"content": "通过命令行导入接口数据YApi 支持通过命令行导入接口数据,他的应用场景是做自动化集成,比如配合 swagger ,接口文档前端不用维护,交由后端生成。"
},
{
"title": "使用方法",
"url": "/documents/data.html#通过命令行导入接口数据-使用方法",
"content": "使用方法第一步,确保 yapi-cli >= 1.2.6 版本,如果低于此版本请升级 yapi-cli 工具npm install -g yapi-cli第二步新建配置文件 yapi-import.json如下所示{ \"type\": \"swagger\",\n \"token\": \"17fba0027f300248b804\",\n \"file\": \"swagger.json\",\n \"server\": \"http://yapi.local.qunar.com:3000\"\n}\ntoken 是项目token在 项目设置 -> token 设置获取file 是 swagger 接口文档文件,可使用绝对路径或 urlserver 是yapi服务器地址第三步在新建配置文件的当前目录执行下面指令yapi import"
}
]
},
@ -703,6 +758,45 @@ window.ydoc_plugin_search_json = {
"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 文件中。2.打开yapi平台进入到项目页面点击数据管理选择相应的分组和swagger导入\b方式\b选择刚才的文件开始导入数据"
},
{
"title": "YApi接口JSON数据导入",
"url": "/documents/data.html#yapi接口json数据导入",
"content": "YApi接口JSON数据导入该功能在 v1.3.12 版本上线,可导入在 yapi 平台导出的 json 接口数据。"
},
{
"title": "通过命令行导入接口数据",
"url": "/documents/data.html#通过命令行导入接口数据",
"content": "通过命令行导入接口数据YApi 支持通过命令行导入接口数据,他的应用场景是做自动化集成,比如配合 swagger ,接口文档前端不用维护,交由后端生成。"
},
{
"title": "使用方法",
"url": "/documents/data.html#通过命令行导入接口数据-使用方法",
"content": "使用方法第一步,确保 yapi-cli >= 1.2.6 版本,如果低于此版本请升级 yapi-cli 工具npm install -g yapi-cli第二步新建配置文件 yapi-import.json如下所示{ \"type\": \"swagger\",\n \"token\": \"17fba0027f300248b804\",\n \"file\": \"swagger.json\",\n \"server\": \"http://yapi.local.qunar.com:3000\"\n}\ntoken 是项目token在 项目设置 -> token 设置获取file 是 swagger 接口文档文件,可使用绝对路径或 urlserver 是yapi服务器地址第三步在新建配置文件的当前目录执行下面指令yapi import"
}
]
},
{
"title": "数据导出",
"content": "为了方便开发者将接口数据分析给第三方或其他使用者YApi 内置了方便易用的接口数据导出功能。",
"url": "/documents/export-data.html",
"children": [
{
"title": "使用教程",
"url": "/documents/export-data.html#使用教程",
"content": "使用教程在 项目 -> 数据管理选择需要导出的数据方式一共有三种导出方式html,markdown,json。然后点击导出按钮将会下载数据文件。"
}
]
},
{
"title": "数据导出",
"content": "为了方便开发者将接口数据分析给第三方或其他使用者YApi 内置了方便易用的接口数据导出功能。",
"url": "/documents/export-data.html",
"children": [
{
"title": "使用教程",
"url": "/documents/export-data.html#使用教程",
"content": "使用教程在 项目 -> 数据管理选择需要导出的数据方式一共有三种导出方式html,markdown,json。然后点击导出按钮将会下载数据文件。"
}
]
},
@ -1058,7 +1152,7 @@ window.ydoc_plugin_search_json = {
{
"title": "v1.3.12",
"url": "/documents/CHANGELOG.html#v1.3.12",
"content": "v1.3.12Feature接口列表支持路径查询\n项目复制\n预览页面交互优化\n优化服务端自动化测试文案\nBug Fixed项目中访客权限的账号可以 增、删、改接口中高级mock的设置\n高级Mock 中的响应时间值无法保存(实际提示为:保存成功)\n分类为空时添加接口\n"
"content": "v1.3.12Feature接口列表支持路径查询\n项目复制\n预览页面交互优化\n优化服务端自动化测试文案\n增加项目接口数据导入导出功能\nBug Fixed项目中访客权限的账号可以 增、删、改接口中高级mock的设置\n高级Mock 中的响应时间值无法保存(实际提示为:保存成功)\n分类为空时添加接口\n"
},
{
"title": "v1.3.11",
@ -1185,7 +1279,7 @@ window.ydoc_plugin_search_json = {
{
"title": "v1.3.12",
"url": "/documents/CHANGELOG.html#v1.3.12",
"content": "v1.3.12Feature接口列表支持路径查询\n项目复制\n预览页面交互优化\n优化服务端自动化测试文案\nBug Fixed项目中访客权限的账号可以 增、删、改接口中高级mock的设置\n高级Mock 中的响应时间值无法保存(实际提示为:保存成功)\n分类为空时添加接口\n"
"content": "v1.3.12Feature接口列表支持路径查询\n项目复制\n预览页面交互优化\n优化服务端自动化测试文案\n增加项目接口数据导入导出功能\nBug Fixed项目中访客权限的账号可以 增、删、改接口中高级mock的设置\n高级Mock 中的响应时间值无法保存(实际提示为:保存成功)\n分类为空时添加接口\n"
},
{
"title": "v1.3.11",