Merge branch 'dev-1.3.0' into doc

This commit is contained in:
suxiaoxin 2018-03-23 18:19:53 +08:00
commit 5ae2159b7e
53 changed files with 769 additions and 415 deletions

View File

@ -1,3 +1,17 @@
### v1.3.8
#### Feature
* 新增 json 结构可视化编辑器
* pre-script 增加 method 字段数据
#### Bug Fixed
* 点击编辑 tab 可能导致运行功能异常
* 修复postman导入没有分类的问题
* 修复postman参数导入缺失
### v1.3.7
#### Feature
@ -6,15 +20,15 @@
* 增加测试集合列表的拖动功能
* 接口列表中增加“开放接口”状态
* 接口列表树形组件支持拖动
* json-schema 导出 table表单
* json-schema 导出 table 表单
* 接口列表和测试集树形组件支持拖动
* 图标从阿里cdn替换到本地
* 图标从阿里 cdn 替换到本地
#### Bug Fixed
* 修复高级 Mock 服务端报错
* 修复测试集合table拖动频繁请求的问题
* 修复swagger 数据导入部分bug
* 修复测试集合 table 拖动频繁请求的问题
* 修复 swagger 数据导入部分 bug
### v1.3.6
@ -24,28 +38,30 @@
* 增加导出公共接口功能
* 增加复制接口路径按钮
* 增加项目 token 功能,可通过 token 访问开放接口
* antd升级到v3
* antd 升级到 v3
#### Bug Fixed
* 修复接口动态提示有误
* 修复变量表达式无法反向展示的问题
### v1.3.5
#### Feature
* 增加项目成员批量导入
* 数据导入同步数据导入支持swagger 3.0
* swagger 数据导入支持 2xx 的httpcode
* 数据导入同步,数据导入支持 swagger 3.0
* swagger 数据导入支持 2xx 的 httpcode
* 新增系统信息页面
#### Bug Fixed
* 修复离开接口编辑页面的 confirm 框有时候会触发两次 & confirm 的 X 按钮无效
* 修复添加集合后测试集合list不更新问题
* 修复添加集合后测试集合 list 不更新问题
* 测试集合点击对应接口侧边栏不切换
* 测试集合处,点击删除不成功
* 修改编辑接口后,再回到测试集合处数据不更新问题
* 修复mongodb帐号密码配置错误时引发的错误
* 修复 mongodb 帐号密码配置错误时引发的错误
* 修复删除分组后侧边数据没有更新问题
### v1.3.4
@ -53,159 +69,168 @@
#### Feature
* 帮助文档首页增加部署公司
* 进入project页面加入loading
* 接口list页table中加入分页
* 进入 project 页面加入 loading
* 接口 list table 中加入分页
* 项目添加者自动变成项目组长
#### Bug Fixed
* 修复无权限进入项目bug
* 修复复制接口query 等参数无法复制bug
* 修复导出html markdown参数丢失问题
* 修复项目设置 pre-script 无法显示问题
* 修复无权限进入项目 bug
* 修复复制接口query 等参数无法复制 bug
* 修复导出 html markdown 参数丢失问题
* 修复项目设置 pre-script 无法显示问题
### v1.3.3
#### Feature
* 邮件功能中: 1接口信息改动增加通知对应项目所有的成员2默认开启接口改动邮件提醒3) 增加邮件内容的jsondiff信息
* 邮件功能中: 1接口信息改动增加通知对应项目所有的成员2默认开启接口改动邮件提醒3) 增加邮件内容的 jsondiff 信息
#### Bug Fixed
* 优化接口运行页面插件提醒
* 完善 log 记录不到的问题
* 修复接口内容改动不发送邮件问题
* 修复部分swagger数据导入丢失问题
* 修复部分 swagger 数据导入丢失问题
### v1.3.2
#### Feature
* 分组中新增接口自定义字段,便于用户在项目中添加额外字段数据
* 导入数据时新增导入loading显示
* 分组中新增接口自定义字段,便于用户在项目中添加额外字段数据
* 导入数据时新增导入 loading 显示
### v1.3.1
#### Bug Fixed
1. 修复接口状态和req_params参数无法更新问题
2. 修复搜索测试集合不展开问题
3. 修复测试过程中全局header不存在的问题
1. 修复接口状态和 req_params 参数无法更新问题
2. 修复搜索测试集合不展开问题
3. 修复测试过程中全局 header 不存在的问题
### v1.3.0
#### Feature
* yapi 默认集成 ldap 登录方式
* yapi 做一个 sso 登录插件,基于现有的 qsso 改造成大多数公司可用的
* 环境设置支持全局 header
* 接口运行页面选择环境增加管理环境的弹层
* 接口运行支持加工运行前后的 request 和 response ,主要是处理加密的接口或各种 token 参数问题
* 自动化测试除提供自定义脚本外,还提供可视化表单形式验证一些数据,例如 statusCode、bodyContent
* 自动化测试除提供自定义脚本外,还提供可视化表单形式验证一些数据,例如 statusCode、bodyContent
* 增加查看接口详细改动
* 支持接口运行页面 body 全屏编辑
* 数据导出到 html 支持了分类
#### Bug Fixed
* 修复了高级 Mock 无法获取到真实客户端 ip
* 修复了高级 Mock 无法获取到真实客户端 ip
### v1.2.9
#### Bug Fixed
1. Api 路径兼容 postman {varible}
2. View Response Height 问题
1. Api 路径兼容 postman {varible}
2. View Response Height 问题
#### Feature
1. 新增克隆测试集功能
2. 高级 Mock 期望支持 mockjs
3. pathname 允许只有一个 /
1. 新增克隆测试集功能
2. 高级 Mock 期望支持 mockjs
3. pathname 允许只有一个 /
### v1.2.8
#### Bug Fixed
1. 修复接口运行 json 格式问题
2. 修复测试报告显示问题
3. 增加了接口数量统计
4. 多参数表达式改用双大括号{{}}
5. 修复了环境变量设置样式问题
1. 修复接口运行 json 格式问题
2. 修复测试报告显示问题
3. 增加了接口数量统计
4. 多参数表达式改用双大括号{{}}
5. 修复了环境变量设置样式问题
#### Feature
1. 测试用例增加自定义测试脚本功能
1. 测试用例增加自定义测试脚本功能
### v1.2.7
#### Bug Fixed
1. 修复接口运行功能,当 httpCode 不是200时导致无法获取 response body 问题
2. 修复路径参数无法删除优化测试集 table 页面,当文字超出一定长度会换行的问题
3. 优化测试集断言错误提示
4. 优化接口编辑 save 按钮样式
1. 修复接口运行功能,当 httpCode 不是 200 时,导致无法获取 response body 问题
2. 修复路径参数无法删除优化测试集 table 页面,当文字超出一定长度会换行的问题
3. 优化测试集断言错误提示
4. 优化接口编辑 save 按钮样式
#### Feature
1. 测试集断言增加 log 方法,用于输出调试日志
2. 可视化动态参数表达式生成器,生成类似表达式 {@email | concat: pass | md5 | substr: 1,10}
1. 测试集断言增加 log 方法,用于输出调试日志
2. 可视化动态参数表达式生成器,生成类似表达式 {@email | concat: pass | md5 | substr: 1,10}
### v1.2.6
#### Bug Fixed
1. 修复路径参数无法删除
1. 修复路径参数无法删除
#### Feature
1. 参数值支持多个动态参数,类似 str{@email}str{$.55.body.id}
2. 参数值支持管道表达式,例如 {@email | concat: pass | md5 | substr: 1,10}
3. 接口编辑参数**可拖动排序**
4. 修复路径参数无法删除问题
1. 参数值支持多个动态参数,类似 str{@email}str{$.55.body.id}
2. 参数值支持管道表达式,例如 {@email | concat: pass | md5 | substr: 1,10}
3. 接口编辑参数**可拖动排序**
4. 修复路径参数无法删除问题
### v1.2.5
#### Bug Fixed
1. 成员如果第一次添加成员时选择组长接着再添加下一个成员如果select是默认的开发者这时候会出现与上次select相同的值
2. 如果添加了一个不存在的成员还是会提示添加成功,并且发送的数据是原来发送成功的数据,这里需要重置初始值并在未找到对应用户名时对未找到的人名应该提示用户不存在
3. Fix 接口集自动化测试 header 没有解析 mock 和 变量参数
4. 在接口开发阶段,多个人并行改接口,如果最后一个人改之前没刷新页面,会把之前的人修改过的都冲掉了
5. 修复cross-requestresponse header字段重复bug
#### Bug Fixed
1. 成员如果第一次添加成员时选择组长,接着再添加下一个成员,如果 select 是默认的开发者,这时候会出现与上次 select 相同的值
2. 如果添加了一个不存在的成员还是会提示添加成功,并且发送的数据是原来发送成功的数据,这里需要重置初始值并在未找到对应用户名时对未找到的人名应该提示用户不存在
3. Fix 接口集自动化测试 header 没有解析 mock 和 变量参数
4. 在接口开发阶段,多个人并行改接口,如果最后一个人改之前没刷新页面,会把之前的人修改过的都冲掉了
5. 修复 cross-requestresponse header 字段重复 bug
#### Feature
1. 优化了分组添加,编辑交互
2. cross-request 计算了接口请求时间
3. 新增接口文档导出 html, markdown 功能
1. 优化了分组添加,编辑交互
2. cross-request 计算了接口请求时间
3. 新增接口文档导出 html, markdown 功能
### v1.2.4
#### Bug Fixed
1. 期望值输入时候换成字符串导致diff时因类型不一致匹配不上
2. swagger 导入数据时出现的 id 未定义bug
3. fix: kerberos dependencies 导致安装依赖需要编译的问题
4. 修复了高级 mock 期望过滤参数为空时匹配不到的 bug
5. 将接口编辑页的保存按钮变成一直在窗口底部
6. 修改需求文档中项目操作处修改项目中的接口测试a链接指向的网页错误问题
7. 添加接口时重名,现在提示“已存在”,并在提示信息中告知用户删改接口的位置
8. 已添加的成员,再次添加会提示“添加成功”,优化提示为已成功添加人数,和已存在人数
9. 添加分组和修改分组时有个权限问题没有更新,切换列表才更新,该问题已解决
1. 期望值输入时候换成字符串,导致 diff 时因类型不一致匹配不上
2. swagger 导入数据时出现的 id 未定义 bug
3. fix: kerberos dependencies 导致安装依赖需要编译的问题
4. 修复了高级 mock 期望过滤参数为空时匹配不到的 bug
5. 将接口编辑页的保存按钮变成一直在窗口底部
6. 修改需求文档中项目操作处修改项目中的接口测试 a 链接指向的网页错误问题
7. 添加接口时重名,现在提示“已存在”,并在提示信息中告知用户删改接口的位置
8. 已添加的成员,再次添加会提示“添加成功”,优化提示为已成功添加人数,和已存在人数
9. 添加分组和修改分组时有个权限问题没有更新,切换列表才更新,该问题已解决
10. 解决修改和删除公共分类名称处,在添加接口时,选择接口分类名称没有修改的问题
#### Feature
1. 接口 path 支持了后面带 /
2. cross-request支持了不安全的 header如 cookie, referer...
3. 支持了 path 带特殊符号"!"
4. 请求参数可改变顺序,目前只是对必需和非必需进行自动排序
5. 用户头像上传问题txt改成jpg格式上传用户头像显示空白然后无法再次上传头像。无法再次上传的问题已经解决
6. 解决用户头像改变但是header处图片不变的问题。问题描述用户上传头像成功但是Header处的头像没有改变并且点击其他页面后再回到个人中心里面的头像又变成没有重新上传时的图片必须重新刷新才可以将Header处的图片更新
7. 解决导入 postman 接口动态路由无法导入的 Bug
1. 接口 path 支持了后面带 /
2. cross-request 支持了不安全的 header如 cookie, referer...
3. 支持了 path 带特殊符号"!"
4. 请求参数可改变顺序,目前只是对必需和非必需进行自动排序
5. 用户头像上传问题txt 改成 jpg 格式上传,用户头像显示空白,然后无法再次上传头像。无法再次上传的问题已经解决
6. 解决用户头像改变但是 header 处图片不变的问题。问题描述:用户上传头像成功但是 Header 处的头像没有改变,并且点击其他页面后再回到个人中心里面的头像又变成没有重新上传时的图片,必须重新刷新才可以将 Header 处的图片更新
7. 解决导入 postman 接口动态路由无法导入的 Bug
### v1.2.0
#### Features
* 增加高级 Mock 期望功能,根据设置的请求过滤规则,返回期望数据。
* 增加统计功能
* 增加自动化测试功能,写断言脚本,实现精准测试
#### Bug Fixed
* 修复了切换集合环境的 Bug
* 修复了 mockServer 拿不到 Post 请求 Body
* 修复了接口调试 pathParams 无法使用 mock 参数和变量参数
@ -213,12 +238,14 @@
### v1.1.2
#### Features
* 接口运行增加了 query 和 body 的 enable 选项,可选择是否请求该字段
* Mock 支持了时间戳占位符 @timestamp
* 接口集运行页面可选择环境
* 接口集动态参数格式由原来的 $.{key}.{jsonPath} 改为 $.{key}.{body|params}.{jsonPath}
#### Bug Fixed
* 修复了接口集运行功能会忽略环境配置的 domain 路径
* 修复了动态路由 mock 返回结果不是该接口定义返回数据
* 修复了日志链接错误问题
@ -230,34 +257,39 @@
* 修复了接口集页面导入接口会导致 reqBody 清空 bug
### v1.1.1
#### Features
* 添加插件开发文档
* 接口和测试用例可拖动
* 优化动态提示
#### Bug Fixed
* 修复接口状态将接口方法重置为 get
* 环境配置域名带 path 无效
* 修复Swagger数据导入分类 bug
* 修复 Swagger 数据导入分类 bug
* MockServer 支持 CORS 跨域
* 优化JSON-SCHEMA转化为JSON的逻辑由原来随机转换不被required字段改为转换全部字段
* 修复了项目成员无法看到该项目的Bug
* 修复了无法查看公共项目的Bug
* 优化 JSON-SCHEMA 转化为 JSON 的逻辑,由原来随机转换不被 required 字段改为转换全部字段
* 修复了项目成员无法看到该项目的 Bug
* 修复了无法查看公共项目的 Bug
* 优化了部分样式和交互
### v1.1.0
#### Features
* 新增个人空间功能,拥有这个分组的全部权限,可以在这个分组里探索 YApi 的功能
* 新增分组动态功能
* 优化接口运行页面交互
* CrossRequest 扩展支持 https
* 增加了 Swagger 数据导入功能
### v1.0.2
#### Features
* 网站改为100%布局
* 网站改为 100%布局
* 优化搜索的提示
* 支持了 queryPath
* 接口浏览页面和编辑页面交互
@ -270,16 +302,16 @@
#### Fix Bug
* 修改接口名字后,需要刷新页面左边的侧边栏才会显示正确的名字
* mockJson 出现 nullmock 出现格式不对问题
* 没有权限的分组不可选
* 项目列表图标设计大小优化下
* 关注的项目不显眼
* 添加接口之后,再次选择添加接口,会保留上次填写的信息
* 用例名称太长,导致无法使用删除功能
* 别人知道项目id虽然没有权限但能看到里面所有内容
* 修改接口名字后,需要刷新页面左边的侧边栏才会显示正确的名字
* mockJson 出现 nullmock 出现格式不对问题
* 没有权限的分组不可选
* 项目列表图标设计大小优化下
* 关注的项目不显眼
* 添加接口之后,再次选择添加接口,会保留上次填写的信息
* 用例名称太长,导致无法使用删除功能
* 别人知道项目 id 虽然没有权限,但能看到里面所有内容
#### Features
* 接口备注集成了富文本编辑
* 支持 har 协议的接口数据导入
* 接口备注集成了富文本编辑
* 支持 har 协议的接口数据导入

View File

@ -38,8 +38,9 @@ YApi 是<strong>高效</strong>、<strong>易用</strong>、<strong>功能强大
### YApi 资源
* [yapi sso 登录插件](https://github.com/YMFE/yapi-plugin-qsso)
* [yapi docker 部署](https://github.com/branchzero/yapi-docker) By branchzero
* [yapi docker 容器](https://hub.docker.com/r/silsuer/yapi/) By silsuer
* [yapi docker ](https://github.com/branchzero/yapi-docker) By branchzero
* [yapi docker ](https://hub.docker.com/r/silsuer/yapi/) By silsuer
* [yapi docker ](https://github.com/fiochen/docker-yapi-env) By fiochen
### YApi 教程
* [Centos-安装环境配置](https://github.com/suxiaoxin/yapi_user_guide/blob/master/centos%20%E5%AE%89%E8%A3%85%E7%8E%AF%E5%A2%83%E9%85%8D%E7%BD%AE.md)

View File

@ -11,53 +11,53 @@ const MockExtra = require('common/mock-extra.js')
var langTools = ace.acequire("ace/ext/language_tools"),
wordList = [
{ name: '字符串', mock: '@string' },
{ name: '自然数', mock: '@natural' },
{ name: '浮点数', mock: '@float' },
{ name: '字符', mock: '@character' },
{ name: '布尔', mock: '@boolean' },
{ name: 'url', mock: '@url' },
{ name: '域名', mock: '@domain' },
{ name: 'ip地址', mock: '@ip' },
{ name: 'id', mock: '@id' },
{ name: 'guid', mock: '@guid' },
{ name: '当前时间', mock: '@now' },
{ name: '时间戳', mock: '@timestamp'},
{ name: '日期', mock: '@date' },
{ name: '时间', mock: '@time' },
{ name: '日期时间', mock: '@datetime' },
{ name: '图片连接', mock: '@image' },
{ name: '图片data', mock: "@imageData" },
{ name: '颜色', mock: '@color' },
{ name: '颜色hex', mock: '@hex' },
{ name: '颜色rgba', mock: '@rgba' },
{ name: '颜色rgb', mock: '@rgb' },
{ name: '颜色hsl', mock: '@hsl' },
{ name: '整数', mock: '@integer' },
{ name: 'email', mock: '@email' },
{ name: '大段文本', mock: '@paragraph' },
{ name: '句子', mock: '@sentence' },
{ name: '单词', mock: '@word' },
{ name: '大段中文文本', mock: '@cparagraph' },
{ name: '中文标题', mock: '@ctitle' },
{ name: '标题', mock: '@title' },
{ name: '姓名', mock: '@name' },
{ name: '中文姓名', mock: '@cname' },
{ name: '中文姓', mock: '@cfirst' },
{ name: '中文名', mock: '@clast' },
{ name: '英文姓', mock: '@first' },
{ name: '英文名', mock: '@last' },
{ name: '中文句子', mock: '@csentence' },
{ name: '中文词组', mock: '@cword' },
{ name: '地址', mock: '@region' },
{ name: '省份', mock: '@province' },
{ name: '城市', mock: '@city' },
{ name: '地区', mock: '@county' },
{ name: '转换为大写', mock: '@upper' },
{ name: '转换为小写', mock: '@lower' },
{ name: '挑选(枚举)', mock: '@pick' },
{ name: '打乱数组', mock: '@shuffle' },
{ name: '协议', mock: '@protocol' }
// { name: '字符串', mock: '@string' },
// { name: '自然数', mock: '@natural' },
// { name: '浮点数', mock: '@float' },
// { name: '字符', mock: '@character' },
// { name: '布尔', mock: '@boolean' },
// { name: 'url', mock: '@url' },
// { name: '域名', mock: '@domain' },
// { name: 'ip地址', mock: '@ip' },
// { name: 'id', mock: '@id' },
// { name: 'guid', mock: '@guid' },
// { name: '当前时间', mock: '@now' },
// { name: '时间戳', mock: '@timestamp'},
// { name: '日期', mock: '@date' },
// { name: '时间', mock: '@time' },
// { name: '日期时间', mock: '@datetime' },
// { name: '图片连接', mock: '@image' },
// { name: '图片data', mock: "@imageData" },
// { name: '颜色', mock: '@color' },
// { name: '颜色hex', mock: '@hex' },
// { name: '颜色rgba', mock: '@rgba' },
// { name: '颜色rgb', mock: '@rgb' },
// { name: '颜色hsl', mock: '@hsl' },
// { name: '整数', mock: '@integer' },
// { name: 'email', mock: '@email' },
// { name: '大段文本', mock: '@paragraph' },
// { name: '句子', mock: '@sentence' },
// { name: '单词', mock: '@word' },
// { name: '大段中文文本', mock: '@cparagraph' },
// { name: '中文标题', mock: '@ctitle' },
// { name: '标题', mock: '@title' },
// { name: '姓名', mock: '@name' },
// { name: '中文姓名', mock: '@cname' },
// { name: '中文姓', mock: '@cfirst' },
// { name: '中文名', mock: '@clast' },
// { name: '英文姓', mock: '@first' },
// { name: '英文名', mock: '@last' },
// { name: '中文句子', mock: '@csentence' },
// { name: '中文词组', mock: '@cword' },
// { name: '地址', mock: '@region' },
// { name: '省份', mock: '@province' },
// { name: '城市', mock: '@city' },
// { name: '地区', mock: '@county' },
// { name: '转换为大写', mock: '@upper' },
// { name: '转换为小写', mock: '@lower' },
// { name: '挑选(枚举)', mock: '@pick' },
// { name: '打乱数组', mock: '@shuffle' },
// { name: '协议', mock: '@protocol' }
];
let dom = ace.acequire("ace/lib/dom");

View File

@ -65,7 +65,7 @@ Footer.defaultProps = {
linkList: [
{
itemTitle: 'YMFE',
itemLink: 'http://ued.qunar.com/ymfe/about'
itemLink: 'https://ymfe.org'
}
]

View File

@ -89,6 +89,7 @@ export default class Run extends Component {
return item.name === value;
}) : 0;
index = index === -1 ? 0 : index
let req_header = [].concat(this.props.data.req_headers);
let header = [].concat(env[index].header);
header.forEach(item => {
@ -100,6 +101,9 @@ export default class Run extends Component {
req_header.push(item)
}
})
req_header = req_header.filter(item=>{
return item && typeof item === 'object'
})
return req_header
}
@ -352,7 +356,6 @@ export default class Run extends Component {
case_env,
inputValue,
hasPlugin } = this.state;
return (

View File

@ -1,5 +1,6 @@
import React, { Component } from 'react';
import { Table } from 'antd';
import json5 from 'json5'
import PropTypes from 'prop-types'
import { schemaTransformToTable } from '../../../common/shema-transformTo-table.js';
import _ from 'underscore';
@ -16,7 +17,8 @@ const messageMap = {
enum: '枚举',
uniqueItems: '元素是否都不同',
itemType: 'item 类型',
format: 'format'
format: 'format',
itemFormat: 'format'
};
const columns = [
@ -96,8 +98,16 @@ class SchemaTable extends Component {
}
render() {
let product = JSON.parse(this.props.dataSource)
render() {
let product
try{
product = json5.parse(this.props.dataSource)
}catch(e){
product = null
}
if(!product){
return null;
}
let data = schemaTransformToTable(product)
data = _.isArray(data) ? data : []
return <Table bordered size="small" pagination={false} dataSource={data} columns={columns} />;

View File

@ -51,7 +51,7 @@ export default class Group extends Component {
</Sider>
<Layout>
<Content style={{ height: '100%', margin: '0 24px 0 16px', overflow: 'initial', backgroundColor: '#fff' }}>
<Tabs type="card" size="large" className="m-tab" style={{ height: '100%' }}>
<Tabs type="card" className="m-tab tabs-large" style={{ height: '100%' }}>
<TabPane tab="项目列表" key="1"><ProjectList /></TabPane>
{this.props.currGroup.type === 'public' ? <TabPane tab="成员列表" key="2"><MemberList /></TabPane> : null}
{["admin", "owner", "guest", "dev"].indexOf(this.props.curUserRoleInGroup) > -1 || this.props.curUserRole === "admin" ? <TabPane tab="分组动态" key="3"><GroupLog /></TabPane> : ""}

View File

@ -82,7 +82,7 @@ class Interface extends Component {
this.props.setColData({
isShowCol: true
})
// this.props.getProject(this.props.match.params.id)
this.props.getProject(this.props.match.params.id)
}
render() {
const { action } = this.props.match.params;
@ -94,7 +94,7 @@ class Interface extends Component {
<Layout style={{minHeight: 'calc(100vh - 156px)', marginLeft: '24px', marginTop: '24px'}}>
<Sider style={{ height: '100%' }} width={300}>
<div className="left-menu">
<Tabs type="card" size= "large" activeKey={activeKey} onChange={this.onChange}>
<Tabs type="card" className="tabs-large" activeKey={activeKey} onChange={this.onChange}>
<Tabs.TabPane tab="接口列表" key="api">
</Tabs.TabPane>

View File

@ -66,12 +66,11 @@ class InterfaceEdit extends Component {
}
}
componentWillMount() {
componentDidMount() {
let domain = location.hostname + (location.port !== "" ? ":" + location.port : "");
let s;
//因后端 node 仅支持 ws 暂不支持 wss
let wsProtocol = location.protocol === 'https' ? 'ws' : 'ws';
try {
s = new WebSocket(wsProtocol + '://' + domain + '/api/interface/solve_conflict?id=' + this.props.match.params.actionId);
s.onopen = () => {
@ -99,14 +98,15 @@ class InterfaceEdit extends Component {
curdata: this.props.curdata,
status: 1
})
console.error('websocket connect failed.')
console.warn('websocket 连接失败,将导致多人编辑同一个接口冲突。')
}
} catch (e) {
this.setState({
curdata: this.props.curdata,
status: 1
})
console.error(e);
console.error('websocket 连接失败,将导致多人编辑同一个接口冲突。');
}
}

View File

@ -127,7 +127,7 @@ class Content extends Component {
plugin.emitHook('interface_tab', InterfaceTabs);
const tabs = <Tabs size="large" onChange={this.onChange} activeKey={this.state.curtab} defaultActiveKey="view" >
const tabs = <Tabs className="tabs-large" onChange={this.onChange} activeKey={this.state.curtab} defaultActiveKey="view" >
{Object.keys(InterfaceTabs).map(key=>{
let item = InterfaceTabs[key];
return <TabPane tab={item.name} key={key}></TabPane>
@ -156,7 +156,7 @@ class Content extends Component {
onCancel={this.handleCancel}
footer={[
<Button key="back" onClick={this.handleCancel}> </Button>,
<Button key="submit" onClick={this.handleOk}> 1</Button>
<Button key="submit" onClick={this.handleOk}> </Button>
]}
>
<p>离开页面会丢失当前编辑的内容确定要离开吗</p>

View File

@ -6,26 +6,30 @@ import constants from "../../../../constants/variable.js";
import { handlePath, nameLengthLimit } from "../../../../common.js";
import { changeEditStatus } from "../../../../reducer/modules/interface.js";
import json5 from "json5";
import { message, Tabs, Affix } from "antd";
import { message, Affix } from "antd";
import EasyDragSort from "../../../../components/EasyDragSort/EasyDragSort.js";
import mockEditor from "client/components/AceEditor/mockEditor";
import axios from 'axios'
import axios from "axios";
import formats from 'common/formats'
const jSchema = require("json-schema-editor-visual");
const ResBodySchema = jSchema({lang: 'zh_CN', format: formats});
const ReqBodySchema = jSchema({lang: 'zh_CN'});
require('codemirror/lib/codemirror.css') // codemirror
require('tui-editor/dist/tui-editor.css'); // editor ui
require('tui-editor/dist/tui-editor-contents.css'); // editor content
require('highlight.js/styles/github.css'); // code block highlight
require('./editor.css')
var Editor = require('tui-editor');
require("codemirror/lib/codemirror.css"); // codemirror
require("tui-editor/dist/tui-editor.css"); // editor ui
require("tui-editor/dist/tui-editor-contents.css"); // editor content
require("highlight.js/styles/github.css"); // code block highlight
require("./editor.css");
var Editor = require("tui-editor");
function checkIsJsonSchema(json) {
try {
json = json5.parse(json);
if(json.properties && typeof json.properties === 'object'){
if(!json.type)json.type = 'object';
json = json5.parse(json);
if (json.properties && typeof json.properties === "object") {
if (!json.type) json.type = "object";
}
if(json.items && typeof json.items === 'object'){
if(!json.type)json.type = 'array'
if (json.items && typeof json.items === "object") {
if (!json.type) json.type = "array";
}
if (!json.type) return false;
json.type = json.type.toLowerCase();
@ -37,7 +41,6 @@ function checkIsJsonSchema(json) {
}
}
const TabPane = Tabs.TabPane;
let EditFormContext;
const validJson = json => {
try {
@ -121,6 +124,7 @@ class InterfaceEditForm extends Component {
};
initState(curdata) {
this.startTime = new Date().getTime()
if (curdata.req_query && curdata.req_query.length === 0)
delete curdata.req_query;
if (curdata.req_headers && curdata.req_headers.length === 0)
@ -282,11 +286,11 @@ class InterfaceEditForm extends Component {
} else if (values.req_body_type === "json") {
values.req_headers
? values.req_headers.map(item => {
if (item.name === "Content-Type") {
item.value = "application/json";
isHavaContentType = true;
}
})
if (item.name === "Content-Type") {
item.value = "application/json";
isHavaContentType = true;
}
})
: [];
if (isHavaContentType === false) {
values.req_headers = values.req_headers || [];
@ -314,15 +318,15 @@ class InterfaceEditForm extends Component {
}
if (values.req_body_is_json_schema && values.req_body_other) {
values.req_body_other = checkIsJsonSchema(values.req_body_other)
if(!values.req_body_other){
return message.error('请求参数 json-schema 格式有误')
values.req_body_other = checkIsJsonSchema(values.req_body_other);
if (!values.req_body_other) {
return message.error("请求参数 json-schema 格式有误");
}
}
if (values.res_body_is_json_schema && values.res_body) {
values.res_body = checkIsJsonSchema(values.res_body)
if(!values.res_body){
return message.error('返回数据 json-schema 格式有误')
if (values.res_body_is_json_schema && values.res_body && values.res_body_type === 'json') {
values.res_body = checkIsJsonSchema(values.res_body);
if (!values.res_body) {
return message.error("返回数据 json-schema 格式有误");
}
}
@ -366,7 +370,7 @@ class InterfaceEditForm extends Component {
mockEditor({
container: "req_body_json",
data: that.state.req_body_other,
onChange: function(d) {
onChange: function (d) {
that.setState({
req_body_other: d.text
});
@ -378,7 +382,7 @@ class InterfaceEditForm extends Component {
this.resBodyEditor = mockEditor({
container: "res_body_json",
data: that.state.res_body,
onChange: function(d) {
onChange: function (d) {
that.setState({
res_body: d.text
});
@ -387,16 +391,16 @@ class InterfaceEditForm extends Component {
fullScreen: true
});
this.mockPreview = mockEditor({
container: "mock-preview",
data: "",
readOnly: true
});
// this.mockPreview = mockEditor({
// container: "mock-preview",
// data: "",
// readOnly: true
// });
this.editor = new Editor({
el: document.querySelector('#desc'),
initialEditType: 'wysiwyg',
height: '500px',
el: document.querySelector("#desc"),
initialEditType: "wysiwyg",
height: "500px",
initialValue: this.state.markdown || this.state.desc
});
}
@ -424,13 +428,13 @@ class InterfaceEditForm extends Component {
handleMockPreview = async () => {
let str = "";
try {
if(this.props.form.getFieldValue('res_body_is_json_schema')){
let schema = json5.parse(this.props.form.getFieldValue('res_body'))
let result = await axios.post('/api/interface/schema2json', {
if (this.props.form.getFieldValue("res_body_is_json_schema")) {
let schema = json5.parse(this.props.form.getFieldValue("res_body"));
let result = await axios.post("/api/interface/schema2json", {
schema: schema
})
});
return this.mockPreview.setValue(JSON.stringify(result.data));
}
if (this.resBodyEditor.curData.format === true) {
@ -483,7 +487,7 @@ class InterfaceEditForm extends Component {
}
if (val && val.length > 3) {
val.replace(/\{(.+?)\}/g, function(str, match) {
val.replace(/\{(.+?)\}/g, function (str, match) {
insertParams(match);
});
}
@ -538,16 +542,45 @@ class InterfaceEditForm extends Component {
wrapperCol: { span: 18 }
};
let res_body = '';
let req_body_other = '';
try {
res_body = this.state.res_body ? JSON.stringify(json5.parse(this.state.res_body), null, 2) : ''
} catch (e) {
res_body = '';
}
try {
req_body_other = this.state.req_body_other ? JSON.stringify(json5.parse(this.state.req_body_other), null, 2) : '';
} catch (e) {
req_body_other = ''
}
const queryTpl = (data, index) => {
return (
<Row key={index} className="interface-edit-item-content">
<Col span="1" easy_drag_sort_child="true" className="interface-edit-item-content-col interface-edit-item-content-col-drag" >
<Col
span="1"
easy_drag_sort_child="true"
className="interface-edit-item-content-col interface-edit-item-content-col-drag"
>
<Icon type="bars" />
</Col>
<Col span="4" draggable="false" className="interface-edit-item-content-col">
<Col
span="4"
draggable="false"
className="interface-edit-item-content-col"
>
{getFieldDecorator("req_query[" + index + "].name", {
initialValue: data.name
})(<TextArea autosize={{minRows:1, maxRows: 1}} placeholder="参数名称" />)}
})(
<TextArea
autosize={{ minRows: 1, maxRows: 1 }}
placeholder="参数名称"
/>
)}
</Col>
<Col span="3" className="interface-edit-item-content-col">
{getFieldDecorator("req_query[" + index + "].required", {
@ -583,7 +616,11 @@ class InterfaceEditForm extends Component {
const headerTpl = (data, index) => {
return (
<Row key={index} className="interface-edit-item-content">
<Col span="1" easy_drag_sort_child="true" className="interface-edit-item-content-col interface-edit-item-content-col-drag" >
<Col
span="1"
easy_drag_sort_child="true"
className="interface-edit-item-content-col interface-edit-item-content-col-drag"
>
<Icon type="bars" />
</Col>
<Col span="4" className="interface-edit-item-content-col">
@ -604,7 +641,12 @@ class InterfaceEditForm extends Component {
<Col span="5" className="interface-edit-item-content-col">
{getFieldDecorator("req_headers[" + index + "].value", {
initialValue: data.value
})(<TextArea autosize={{minRows:1, maxRows: 1}} placeholder="参数值" />)}
})(
<TextArea
autosize={{ minRows: 1, maxRows: 1 }}
placeholder="参数值"
/>
)}
</Col>
<Col span="5" className="interface-edit-item-content-col">
{getFieldDecorator("req_headers[" + index + "].example", {
@ -630,13 +672,22 @@ class InterfaceEditForm extends Component {
const requestBodyTpl = (data, index) => {
return (
<Row key={index} className="interface-edit-item-content">
<Col span="1" easy_drag_sort_child="true" className="interface-edit-item-content-col interface-edit-item-content-col-drag" >
<Col
span="1"
easy_drag_sort_child="true"
className="interface-edit-item-content-col interface-edit-item-content-col-drag"
>
<Icon type="bars" />
</Col>
<Col span="4" className="interface-edit-item-content-col">
{getFieldDecorator("req_body_form[" + index + "].name", {
initialValue: data.name
})(<TextArea autosize={{minRows:1, maxRows: 1}} placeholder="name" />)}
})(
<TextArea
autosize={{ minRows: 1, maxRows: 1 }}
placeholder="name"
/>
)}
</Col>
<Col span="3" className="interface-edit-item-content-col">
{getFieldDecorator("req_body_form[" + index + "].type", {
@ -711,8 +762,8 @@ class InterfaceEditForm extends Component {
const headerList = this.state.req_headers
? this.state.req_headers.map((item, index) => {
return headerTpl(item, index);
})
return headerTpl(item, index);
})
: [];
const requestBodyList = this.state.req_body_form.map((item, index) => {
@ -807,7 +858,7 @@ class InterfaceEditForm extends Component {
disabled
value={this.props.basepath}
readOnly
onChange={() => {}}
onChange={() => { }}
style={{ width: "25%" }}
/>
</Tooltip>
@ -896,7 +947,7 @@ class InterfaceEditForm extends Component {
<EasyDragSort
data={() => this.props.form.getFieldValue("req_query")}
onChange={this.handleDragMove("req_query")}
onlyChild='easy_drag_sort_child'
onlyChild="easy_drag_sort_child"
>
{QueryList}
</EasyDragSort>
@ -970,7 +1021,7 @@ class InterfaceEditForm extends Component {
<EasyDragSort
data={() => this.props.form.getFieldValue("req_body_form")}
onChange={this.handleDragMove("req_body_form")}
onlyChild='easy_drag_sort_child'
onlyChild="easy_drag_sort_child"
>
{requestBodyList}
</EasyDragSort>
@ -993,8 +1044,11 @@ class InterfaceEditForm extends Component {
initialValue: this.state.req_body_is_json_schema
})(<Switch checkedChildren="开" unCheckedChildren="关" />)}
<Col style={{marginTop: '5px'}} className="interface-edit-json-info">
{!this.props.form.getFieldValue("req_body_is_json_schema") ? (
<Col
style={{ marginTop: "5px" }}
className="interface-edit-json-info"
>
{!this.props.form.getFieldValue("req_body_is_json_schema") ? (
<span>
基于 Json5, 参数描述信息用注释的方式实现{" "}
<Tooltip title={<pre>{Json5Example}</pre>}>
@ -1003,42 +1057,54 @@ class InterfaceEditForm extends Component {
style={{ color: "#086dbf" }}
/>
</Tooltip>
</span>
) : (
<span>基于 json-schema 描述数据结构</span>
)}
全局编辑 退出全屏 请按{" "}
<span style={{ fontWeight: "500" }}>F9</span>
全局编辑 退出全屏 请按{" "}F9</span>
) : <ReqBodySchema onChange={(text)=>{
this.setState({
req_body_other: text
})
if(new Date().getTime()-this.startTime > 1000) {
EditFormContext.props.changeEditStatus(true);
}
}} data={req_body_other} />}
</Col>
<Col id="req_body_json" style={{ minHeight: "300px" }} />
<Col id="req_body_json" style={{ minHeight: "300px", display: !this.props.form.getFieldValue("req_body_is_json_schema") ? 'block' : 'none' }} />
</Row>
{this.props.form.getFieldValue("req_body_type") === "file" &&
this.state.hideTabs.req.body !== "hide" ? (
<Row className="interface-edit-item">
<Col className="interface-edit-item-other-body">
{getFieldDecorator("req_body_other", {
initialValue: this.state.req_body_other
})(<TextArea placeholder="" autosize={true} />)}
</Col>
</Row>
) : null}
this.state.hideTabs.req.body !== "hide" ? (
<Row className="interface-edit-item">
<Col className="interface-edit-item-other-body">
{getFieldDecorator("req_body_other", {
initialValue: this.state.req_body_other
})(<TextArea placeholder="" autosize={true} />)}
</Col>
</Row>
) : null}
{this.props.form.getFieldValue("req_body_type") === "raw" &&
this.state.hideTabs.req.body !== "hide" ? (
<Row>
<Col>
{getFieldDecorator("req_body_other", {
initialValue: this.state.req_body_other
})(<TextArea placeholder="" autosize={{ minRows: 8 }} />)}
</Col>
</Row>
) : null}
this.state.hideTabs.req.body !== "hide" ? (
<Row>
<Col>
{getFieldDecorator("req_body_other", {
initialValue: this.state.req_body_other
})(<TextArea placeholder="" autosize={{ minRows: 8 }} />)}
</Col>
</Row>
) : null}
</div>
{/* ----------- Response ------------- */}
<h2 className="interface-title">返回数据设置</h2>
<h2 className="interface-title">返回数据设置&nbsp;
{getFieldDecorator("res_body_is_json_schema", {
valuePropName: "checked",
initialValue: this.state.res_body_is_json_schema
})(<Switch checkedChildren="json-schema" unCheckedChildren="json" />)}
</h2>
<div className="container-radiogroup">
{getFieldDecorator("res_body_type", {
initialValue: this.state.res_body_type
@ -1060,25 +1126,12 @@ class InterfaceEditForm extends Component {
}}
>
<Col>
<Tabs
size="large"
defaultActiveKey="tpl"
onChange={this.handleJsonType}
>
<TabPane tab="模板" key="tpl" />
<TabPane tab="预览" key="preview" />
</Tabs>
<div style={{marginTop: '10px'}}>
<span>JSON-SCHEMA:&nbsp;</span>
{getFieldDecorator("res_body_is_json_schema", {
valuePropName: "checked",
initialValue: this.state.res_body_is_json_schema
})(<Switch checkedChildren="开" unCheckedChildren="关" />)}
<div style={{ padding: "10px 0", fontSize: "15px" }}>
{!this.props.form.getFieldValue("res_body_is_json_schema") ?
<span>基于 mockjs json5,使用注释方式写参数说明{" "}
<div style={{ marginTop: "10px" }}>
{!this.props.form.getFieldValue("res_body_is_json_schema") ? (
<div style={{ padding: "10px 0", fontSize: "15px" }}>
<span>
基于 mockjs json5,使用注释方式写参数说明{" "}
<Tooltip title={<pre>{Json5Example}</pre>}>
<Icon
type="question-circle-o"
@ -1089,33 +1142,34 @@ class InterfaceEditForm extends Component {
<span
className="href"
onClick={() =>
window.open("https://yapi.ymfe.org/mock.html", "_blank")
window.open(
"https://yapi.ymfe.org/mock.html",
"_blank"
)
}
>
查看文档
</span>
</span>
:
<span >基于 json-schema 描述数据结构</span>}
全局编辑 退出全屏 请按{" "}
<span style={{ fontWeight: "500" }}>F9</span>
</div>
) : <ResBodySchema onChange={(text) => {
this.setState({
res_body: text
})
if(new Date().getTime()-this.startTime > 1000){
EditFormContext.props.changeEditStatus(true);
}
}} data={res_body} />}
全局编辑 退出全屏 请按{" "}
<span style={{ fontWeight: "500" }}>F9</span>
</div>
<div
id="res_body_json"
style={{
minHeight: "300px",
display: this.state.jsonType === "tpl" ? "block" : "none"
}}
/>
<div
id="mock-preview"
style={{
backgroundColor: "#eee",
lineHeight: "20px",
minHeight: "300px",
display:
this.state.jsonType === "preview" ? "block" : "none"
display: !this.props.form.getFieldValue("res_body_is_json_schema") && this.state.jsonType === "tpl" ? "block" : "none"
}}
/>
</div>
@ -1145,7 +1199,11 @@ class InterfaceEditForm extends Component {
<div className="panel-sub">
<FormItem className={"interface-edit-item"}>
<div>
<div id="desc" style={{lineHeight: '20px'}} className="remark-editor" />
<div
id="desc"
style={{ lineHeight: "20px" }}
className="remark-editor"
/>
</div>
</FormItem>
</div>

View File

@ -12,6 +12,7 @@ import constants from '../../../../constants/variable.js'
import copy from 'copy-to-clipboard';
import SchemaTable from '../../../../components/SchemaTable/SchemaTable.js'
const HTTP_METHOD = constants.HTTP_METHOD;
@ -391,5 +392,4 @@ class View extends Component {
}
}
export default View

View File

@ -205,7 +205,7 @@
}
.panel-sub {
background: rgba(236, 238, 241, 0.67);
padding: .16rem;
padding: .10rem;
}
.ant-radio-button-wrapper-checked {
color: #fff;

View File

@ -324,6 +324,7 @@ class ProjectData extends Component {
<div className="dataImportTile">
<Select placeholder="请选择导入数据的方式" onChange={this.handleImportType}>
{Object.keys(importDataModule).map((name) => {
return <Option key={name} value={name}>{importDataModule[name].name}</Option>
})}
</Select>

View File

@ -28,8 +28,8 @@ class Setting extends Component {
return (
<div className="g-row">
<Tabs size="large" type="card" className="has-affix-footer">
<TabPane tab="项目配置" key="1">
<Tabs type="card" className="has-affix-footer tabs-large">
<TabPane tab="项目配置" key="1" >
<ProjectMessage projectId={+id}/>
</TabPane>
<TabPane tab="环境配置" key="2">

View File

@ -11,6 +11,7 @@ import follow from './follow.js'
import { emitHook } from 'client/plugin.js'
const reducerModules = {
group,
user,

View File

@ -172,6 +172,10 @@ em {
margin-top: -1px;
}
.tabs-large .ant-tabs-nav-container{
font-size: 16px;
}
.ant-tree li .ant-tree-node-content-wrapper{
padding: 3px 5px;
height: unset;

89
common/formats.js Normal file
View File

@ -0,0 +1,89 @@
const formats = [
{
name: "url",
title: 'url'
},
{
name: "domain",
title: "域名"
},
{
name: "ip",
title: "ipv4 地址"
},
{
name: "id"
},
{
name: "guid"
},
{
name: "now"
},
{
name: "timestamp"
},
{
name: "date"
},
{
name: "time"
},
{
name: "datetime"
},
{
name: "image",
title: "图片链接"
},
{
name: "imageData",
title: "图片"
},
{
name: "email",
title: "邮箱"
},
{
name: "paragraph",
title: "段落"
},
{
name: "sentence",
title: "句子"
},
{
name: "word",
title: "单词"
},
{
name: "title",
title: "标题"
},
{
name: "name",
title: "姓名"
},
{
name: "region",
title: "地区"
},
{
name: "province",
title: "省份"
},
{
name: "city",
title: "城市名"
},
{
name: "county",
title: "国家"
},
{
name: "mobile",
title: '手机号'
}
];
module.exports = formats;

View File

@ -166,6 +166,7 @@ async function crossRequest(defaultOptions, preScript, afterScript) {
let urlObj = URL.parse(options.url, true), query = {};
query = Object.assign(query, urlObj.query);
let context = {
method: options.method,
pathname: urlObj.pathname,
query: query,
requestHeader: options.headers || {},

View File

@ -96,7 +96,7 @@ const SchemaObject = (data, key) => {
if(value.type === 'object'||_.isUndefined(value.type)&&_.isArray(optionForm)){
item = Object.assign({},item,{ children: optionForm})
item = Object.assign({},item,{ type: 'object',children: optionForm})
delete item.sub
} else {
item = Object.assign({}, item, optionForm)
@ -137,7 +137,9 @@ const SchemaArray =(data, index) => {
maxItems: data.maxItems,
itemType: items.type,
children: optionForm
}
if(items.type === 'string'){
item = Object.assign({},item, {itemFormat: items.format})
}
return item
}

View File

@ -152,4 +152,12 @@ exports.json_parse = function(json){
}catch(err){
return json;
}
}
exports.json_format= function(json){
try{
return JSON.stringify(JSON.parse(json), null, ' ');
}catch(e){
return json;
}
}

View File

@ -1,13 +1,20 @@
import {message} from 'antd'
import URL from 'url';
const GenerateSchema = require('generate-schema/src/schemas/json.js');
import { json_parse } from '../../common/utils.js'
function json_format(json){
try{
return JSON.stringify(JSON.parse(json), null, ' ');
}catch(e){
return json;
}
const transformJsonToSchema = (json) => {
let jsonData = json_parse(json)
jsonData = GenerateSchema(jsonData);
let schemaData = JSON.stringify(jsonData)
return schemaData
}
function postman(importDataModule){
@ -100,6 +107,7 @@ function postman(importDataModule){
interfaceData.apis.push(data);
}
}
console.log(interfaceData)
return interfaceData;
}catch(e){
@ -150,7 +158,9 @@ function postman(importDataModule){
}
}else if(item === 'req_body_other' && reqType === 'json' && data.request.postData){
res[item] = json_format(data.request.postData.text);
res[item] = transformJsonToSchema(data.request.postData.text);
}else if(item === "req_headers"){
res[item] = [{
name: 'Content-Type',
@ -173,7 +183,7 @@ function postman(importDataModule){
}else if(item === 'res_body_type'){
res[item] = 'json';
}else if(item === 'res_body'){
res[item] = json_format(data.response.content.text);
res[item] = transformJsonToSchema(data.response.content.text);
}
else{
res[item] = data.request[reflect[item]];

View File

@ -1,9 +1,15 @@
import {message} from 'antd'
import URL from 'url';
import _ from 'underscore';
const GenerateSchema = require('generate-schema/src/schemas/json.js');
import { json_parse } from '../../common/utils.js'
function postman(importDataModule){
var folders = [];
function parseUrl(url){
return URL.parse(url)
}
@ -28,7 +34,8 @@ function postman(importDataModule){
res.push({
name: query[item].key,
desc: query[item].description,
required: query[item].enable
example: query[item].value,
required: query[item].enabled ? '1' : '0'
});
}
}
@ -42,7 +49,7 @@ function postman(importDataModule){
name: headers[item].key,
desc: headers[item].description,
value: headers[item].value,
required: headers[item].enable
required: headers[item].enabled ? '1' : '0'
});
}
}
@ -55,8 +62,10 @@ function postman(importDataModule){
for(let item in body_form){
res.push({
name: body_form[item].key,
value: body_form[item].value,
type: body_form[item].type
example: body_form[item].value,
type: body_form[item].type,
required: body_form[item].enabled ? '1': '0',
desc: body_form[item].description
});
}
}
@ -68,7 +77,7 @@ function postman(importDataModule){
path = decodeURIComponent(path);
if(!path) return '';
path = path.replace(/{{\w*}}/g, '');
path = path.replace(/\{\{.*\}\}/g, '');
if(path[0] != "/"){
path = "/" + path;
@ -80,14 +89,30 @@ function postman(importDataModule){
try{
res = JSON.parse(res);
let interData = res.requests;
let interfaceData = {apis: []};
interData = checkInterRepeat.bind(this)(interData);
let interfaceData = {apis: [], cats: []};
interData = checkInterRepeat.bind(this)(interData);
if (res.folders && Array.isArray(res.folders)) {
res.folders.forEach(tag => {
interfaceData.cats.push({
name: tag.name,
desc: tag.description
});
});
}
if(_.find(res.folders,item => item.collectionId === res.id)){
folders = res.folders
}
if(interData && interData.length){
for(let item in interData){
let data = importPostman.bind(this)(interData[item]);
interfaceData.apis.push(data);
}
}
console.log(interfaceData)
return interfaceData;
}catch(e){
@ -96,7 +121,8 @@ function postman(importDataModule){
}
function importPostman(data,key){
function importPostman(data, key){
let reflect = {//数据字段映射关系
title: "name",
path: "url",
@ -107,9 +133,11 @@ function postman(importDataModule){
req_params: "",
req_body_type: "dataMode",
req_body_form: "data",
req_body_other: "rawModeData"
req_body_other: "rawModeData",
res_body: "text",
res_body_type: "language"
};
let allKey = ["title","path","method","desc","req_query","req_headers","req_body_type","req_body_form","req_body_other"];
let allKey = ["title","path","catname","method","desc","req_query","req_headers","req_body_type","req_body_form","req_body_other","res"];
key = key || allKey;
let res = {};
for(let item in key){
@ -131,7 +159,16 @@ function postman(importDataModule){
}
}
}else if(item === "path"){
} else if(item === 'req_body_other') {
if(data.headers.indexOf('application/json')>-1){
res[item] = transformJsonToSchema(data[reflect[item]])
} else {
res[item] = data[reflect[item]];
}
}
else if(item === "path"){
res[item] = handlePath.bind(this)(data[reflect[item]]);
if(res[item] && res[item].indexOf("/:") > -1){
let params = res[item].substr(res[item].indexOf("/:")+2).split("/:");
@ -155,12 +192,49 @@ function postman(importDataModule){
}else{
res[item] = data[reflect[item]];
}
}else{
} else if(item === 'catname'){
let found = folders.filter(item => {
return item.id === data.folder
})
res[item] = found && Array.isArray(found) && found.length>0 ? found[0].name : null;
}
else if(item === 'res') {
let response = handleResponses(data['responses'])
if(response) {
res['res_body'] = response['res_body'],
res['res_body_type'] = response['res_body_type']
}
}
else{
res[item] = data[reflect[item]];
}
}
return res;
}
const handleResponses = (data) => {
if(data&&data.length){
let res = data[0];
let response = {};
response['res_body_type'] = res.language === 'json' ? 'json' : 'raw'
response['res_body'] = res.language === 'json' ? transformJsonToSchema(res.text): res.text;
return response;
}
return null
}
const transformJsonToSchema = (json) => {
let jsonData = json_parse(json)
jsonData = GenerateSchema(jsonData);
let schemaData = JSON.stringify(jsonData)
return schemaData
}
if(!importDataModule || typeof importDataModule !== 'object'){
console.error('obj参数必需是一个对象');

View File

@ -1,83 +1,86 @@
import { message } from 'antd'
import _ from 'underscore'
const swagger = require('swagger-client')
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 === '/') return path;
if (path.charAt(0) != '/') {
path = '/' + path;
}
if (path.charAt(path.length - 1) === "/") {
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'])
_.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 = {};
}
try{
body.schema = api.requestBody.content['application/json'].schema
}catch(e){
body.schema = {}
}
api.parameters.push(body);
}
})
})
});
});
return data;
}
async function handleSwaggerData(res){
async function handleSwaggerData(res) {
return await new Promise(resolve=>{
return await new Promise(resolve => {
let data = swagger({
spec: res
})
data.then(res=>{
console.log(res.spec)
resolve(res.spec)
})
})
});
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'
isOAS3 = res.openapi && res.openapi === '3.0.0';
if (isOAS3) {
res = openapi2swagger(res)
res = openapi2swagger(res);
}
res = await handleSwaggerData(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) => {
@ -86,13 +89,13 @@ function improtData(importDataModule) {
api.method = method;
let data = null;
try {
data = handleSwagger(api)
if(data.catname){
if(!_.find(interfaceData.cats, (item)=> item.name === data.catname)){
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) {
@ -100,22 +103,19 @@ function improtData(importDataModule) {
}
if (data) {
interfaceData.apis.push(data);
}
})
})
console.log(interfaceData)
});
});
return interfaceData;
} catch (e) {
console.error(e);
message.error("数据格式有误");
message.error('数据格式有误');
}
}
function handleSwagger(data) {
let api = {};
//处理基本信息
api.method = data.method.toUpperCase();
@ -137,7 +137,10 @@ function improtData(importDataModule) {
}
if (data.consumes && Array.isArray(data.consumes)) {
if (data.consumes.indexOf('application/x-www-form-urlencoded') > -1 || data.consumes.indexOf('multipart/form-data') > -1) {
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';
@ -155,15 +158,14 @@ function improtData(importDataModule) {
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("/");
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]];
@ -178,25 +180,35 @@ function improtData(importDataModule) {
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 })
param = simpleJsonPathParse(param.$ref, { parameters: SwaggerData.parameters });
}
let defaultParam = {
name: param.name,
desc: param.description,
required: param.required ? "1" : "0"
}
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;
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;
}
@ -209,7 +221,7 @@ function improtData(importDataModule) {
}
function handleBodyPamras(data, api) {
api.req_body_other = JSON.stringify(data,null,2);
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;
@ -223,15 +235,14 @@ function improtData(importDataModule) {
}
let codes = Object.keys(api);
let curCode;
if(codes.length > 0){
if(codes.indexOf(200) > -1){
if (codes.length > 0) {
if (codes.indexOf(200) > -1) {
curCode = 200;
}else curCode = codes[0]
} else curCode = codes[0];
let res = api[curCode];
if (res && typeof res === 'object') {
if (res.schema) {
res_body = JSON.stringify(res.schema,null,2);
res_body = JSON.stringify(res.schema, null, 2);
} else if (res.description) {
res_body = res.description;
}
@ -240,8 +251,8 @@ function improtData(importDataModule) {
} else {
res_body = '';
}
}else{
res_body = ''
} else {
res_body = '';
}
return res_body;
}
@ -251,7 +262,6 @@ function improtData(importDataModule) {
// // if (typeof data !== 'object') {
// // return data;
// // }
// // try {
// // // data.definitions = SwaggerData.definitions;
@ -274,12 +284,9 @@ function improtData(importDataModule) {
name: 'Swagger',
run: run,
desc: 'Swagger数据导入 支持 v2.0+ '
}
};
}
module.exports = function () {
this.bindHook('import_data', improtData)
}
module.exports = function() {
this.bindHook('import_data', improtData);
};

61
package-lock.json generated
View File

@ -1,6 +1,6 @@
{
"name": "yapi",
"version": "1.3.7",
"version": "1.3.8",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
@ -7703,6 +7703,15 @@
"is-property": "1.0.2"
}
},
"generate-schema": {
"version": "2.6.0",
"resolved": "http://registry.npm.taobao.org/generate-schema/download/generate-schema-2.6.0.tgz",
"integrity": "sha1-msA3VQ/UJDeDqfdoHTm+6IcLzsI=",
"requires": {
"commander": "2.14.1",
"type-of-is": "3.5.1"
}
},
"get-caller-file": {
"version": "1.0.2",
"resolved": "http://registry.npm.taobao.org/get-caller-file/download/get-caller-file-1.0.2.tgz",
@ -10365,8 +10374,7 @@
"js-tokens": {
"version": "3.0.2",
"resolved": "http://registry.npm.taobao.org/js-tokens/download/js-tokens-3.0.2.tgz",
"integrity": "sha1-mGbfOVECEw449/mWvOtlRDIJwls=",
"dev": true
"integrity": "sha1-mGbfOVECEw449/mWvOtlRDIJwls="
},
"js-yaml": {
"version": "3.10.0",
@ -10441,6 +10449,20 @@
"resolved": "http://registry.npm.taobao.org/json-schema/download/json-schema-0.2.3.tgz",
"integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM="
},
"json-schema-editor-visual": {
"version": "1.0.5",
"resolved": "http://registry.npm.taobao.org/json-schema-editor-visual/download/json-schema-editor-visual-1.0.5.tgz",
"integrity": "sha1-HZDynHSHIoPG8rAqMd30cySSsP4=",
"dev": true,
"requires": {
"antd": "3.2.2",
"brace": "0.10.0",
"generate-schema": "2.6.0",
"moox": "1.0.2",
"react-redux": "5.0.7",
"underscore": "1.8.3"
}
},
"json-schema-faker": {
"version": "0.5.0-rc13",
"resolved": "http://registry.npm.taobao.org/json-schema-faker/download/json-schema-faker-0.5.0-rc13.tgz",
@ -11087,8 +11109,7 @@
"lodash-es": {
"version": "4.17.5",
"resolved": "http://registry.npm.taobao.org/lodash-es/download/lodash-es-4.17.5.tgz",
"integrity": "sha1-n8bnN7HE0VHY+criJHMF1VLOdI8=",
"dev": true
"integrity": "sha1-n8bnN7HE0VHY+criJHMF1VLOdI8="
},
"lodash._arraycopy": {
"version": "3.0.0",
@ -11407,7 +11428,6 @@
"version": "1.3.1",
"resolved": "http://registry.npm.taobao.org/loose-envify/download/loose-envify-1.3.1.tgz",
"integrity": "sha1-0aitM/qc4OcT1l/dCsi3SNR4yEg=",
"dev": true,
"requires": {
"js-tokens": "3.0.2"
}
@ -12005,6 +12025,26 @@
"extend": "3.0.1"
}
},
"moox": {
"version": "1.0.2",
"resolved": "http://registry.npm.taobao.org/moox/download/moox-1.0.2.tgz",
"integrity": "sha1-lDxR8Mgbx8lR8rhKbC7zj8tF/1k=",
"requires": {
"flux-standard-action": "2.0.1",
"immer": "1.1.1",
"redux": "3.7.2"
},
"dependencies": {
"flux-standard-action": {
"version": "2.0.1",
"resolved": "http://registry.npm.taobao.org/flux-standard-action/download/flux-standard-action-2.0.1.tgz",
"integrity": "sha1-nilH9ZBO3Uj5+rdRbduN6m9hIHU=",
"requires": {
"lodash": "4.17.5"
}
}
}
},
"move-concurrently": {
"version": "1.0.1",
"resolved": "http://registry.npm.taobao.org/move-concurrently/download/move-concurrently-1.0.1.tgz",
@ -18146,7 +18186,6 @@
"version": "3.7.2",
"resolved": "http://registry.npm.taobao.org/redux/download/redux-3.7.2.tgz",
"integrity": "sha1-BrcxIyFZAdJdBlvjQusCa8HIU3s=",
"dev": true,
"requires": {
"lodash": "4.17.5",
"lodash-es": "4.17.5",
@ -18157,8 +18196,7 @@
"symbol-observable": {
"version": "1.2.0",
"resolved": "http://registry.npm.taobao.org/symbol-observable/download/symbol-observable-1.2.0.tgz",
"integrity": "sha1-wiaIrtTqs83C3+rLtWFmBWCgCAQ=",
"dev": true
"integrity": "sha1-wiaIrtTqs83C3+rLtWFmBWCgCAQ="
}
}
},
@ -20781,6 +20819,11 @@
"mime-types": "2.1.18"
}
},
"type-of-is": {
"version": "3.5.1",
"resolved": "http://registry.npm.taobao.org/type-of-is/download/type-of-is-3.5.1.tgz",
"integrity": "sha1-7sL8ibgo2/mQDrZBbu4w9P4PzTE="
},
"typedarray": {
"version": "0.0.6",
"resolved": "http://registry.npm.taobao.org/typedarray/download/typedarray-0.0.6.tgz",

View File

@ -1,6 +1,6 @@
{
"name": "yapi",
"version": "1.3.7",
"version": "1.3.8",
"description": "YAPI",
"main": "index.js",
"scripts": {
@ -37,6 +37,7 @@
"deref": "^0.7.3",
"easy-json-schema": "0.0.2-beta",
"fs-extra": "^3.0.1",
"generate-schema": "^2.6.0",
"immer": "^1.1.1",
"js-base64": "^2.3.2",
"json-schema-faker": "^0.5.0-rc13",
@ -62,6 +63,7 @@
"moment": "2.18.1",
"mongoose": "4.7.0",
"mongoose-auto-increment": "5.0.1",
"moox": "^1.0.2",
"nodemailer": "4.0.1",
"os": "0.1.1",
"request": "2.81.0",
@ -106,6 +108,7 @@
"fast-sass-loader-china": "1.2.5",
"ghooks": "^2.0.0",
"happypack": "^4.0.0-beta.5",
"json-schema-editor-visual": "^1.0.5",
"less": "^2.7.2",
"less-loader": "^4.0.5",
"node-sass-china": "4.5.0",

View File

@ -16,18 +16,6 @@ const fs = require('fs-extra')
const path = require('path');
const RES_BODY_TPL= `
/**
* 这是一个 response 事例
*/
{
"errcode": 0, //错误编码
"data": {
"id": "uuid-xxx", //产品id
"name": "iphone" //产品名称
}
}
`
// const annotatedCss = require("jsondiffpatch/public/formatters-styles/annotated.css");
// const htmlCss = require("jsondiffpatch/public/formatters-styles/html.css");
@ -155,10 +143,11 @@ class interfaceController extends baseController {
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) ? true : params.res_body_is_json_schema;
params.req_body_is_json_schema = _.isUndefined(params.req_body_is_json_schema) ? true : params.req_body_is_json_schema;
params.method = params.method.toUpperCase();
params.req_params = params.req_params || [];
params.res_body_type = params.res_body_type ? params.res_body_type.toLowerCase() : 'json';
params.res_body = _.isUndefined(params.res_body) ? RES_BODY_TPL : params.res_body;
let http_path = url.parse(params.path, true);
if (!yapi.commons.verifyPath(http_path.pathname)) {
@ -255,6 +244,7 @@ class interfaceController extends baseController {
}
params.method = params.method || 'GET';
params.method = params.method.toUpperCase();
let http_path = url.parse(params.path, true);
@ -498,7 +488,8 @@ class interfaceController extends baseController {
let id = params.id;
params.message = params.message || '';
params.message = params.message.replace(/\n/g, "<br>")
params.res_body_is_json_schema = _.isUndefined (params.res_body_is_json_schema) ? true : params.res_body_is_json_schema;
params.req_body_is_json_schema = _.isUndefined(params.req_body_is_json_schema) ? true : params.req_body_is_json_schema;
let interfaceData = await this.Model.get(id);
if (!interfaceData) {

View File

@ -303,6 +303,9 @@ class openController extends baseController{
req_header.push(item)
}
})
req_header = req_header.filter(item=> {
return item && typeof item === 'object'
})
return req_header
}

View File

@ -29,7 +29,11 @@ class interfaceModel extends baseModel {
}]
},
req_query: [{
name: String, value: String, example: String, desc: String, required: {
name: String,
value: String,
example: String,
desc: String,
required: {
type: String,
enum: ["1", "0"],
default: "1"
@ -53,7 +57,10 @@ class interfaceModel extends baseModel {
},
req_body_is_json_schema: { type: Boolean, default: false },
req_body_form: [{
name: String, type: { type: String, enum: ['text', 'file'] }, example: String, desc: String, required: {
name: String, type: { type: String, enum: ['text', 'file'] },
example: String,
desc: String,
required: {
type: String,
enum: ["1", "0"],
default: "1"

View File

@ -10,6 +10,7 @@ const interfaceModel = require("../models/interface.js");
const json5 = require("json5");
const _ = require("underscore");
const Ajv = require("ajv");
const Mock = require('mockjs')
const ajv = new Ajv({
allErrors: true,
coerceTypes: true,
@ -20,11 +21,22 @@ var localize = require("ajv-i18n");
const ejs = require("easy-json-schema");
const jsf = require('json-schema-faker');
const formats = require('../../common/formats')
const defaultOptions = {
failOnInvalidTypes: false,
failOnInvalidFormat: false
}
formats.forEach(item=>{
item = item.name;
jsf.format(item, ()=>{
if(item === 'mobile'){
return jsf.random.randexp('^[1][34578][0-9]{9}$')
}
return Mock.mock('@'+ item)
})
})
exports.schemaToJson = function(schema, options={}){
Object.assign(options, defaultOptions)
jsf.option(options);

View File

@ -1 +1 @@
window.WEBPACK_ASSETS = {"index.js":{"js":"index@4072296c3ab1ff869ae6.js","css":"index@4072296c3ab1ff869ae6.css"},"lib":{"js":"lib@2c127a21f39ec3099038.js"},"lib2":{"js":"lib2@7cfb0c59a27c8e001cf9.js"},"lib3":{"js":"lib3@a78cbd2ace894d39018d.js"},"manifest":{"js":"manifest@f2f4bd774d6c221b3d5f.js"}}
window.WEBPACK_ASSETS = {"index.js":{"js":"index@ad4abec8f739058748b5.js","css":"index@ad4abec8f739058748b5.css"},"lib":{"js":"lib@187de6d2e73924ebfee4.js"},"lib2":{"js":"lib2@f97b0358ee9bfe96c7d6.js"},"lib3":{"js":"lib3@5a7eecc9c13567fc07b1.js"},"manifest":{"js":"manifest@f2f4bd774d6c221b3d5f.js"}}

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.

File diff suppressed because one or more lines are too long

Binary file not shown.

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.

File diff suppressed because one or more lines are too long

Binary file not shown.

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.

File diff suppressed because one or more lines are too long

View File

@ -1 +1 @@
!function(e){function t(n){if(r[n])return r[n].exports;var i=r[n]={exports:{},id:n,loaded:!1};return e[n].call(i.exports,i,i.exports,t),i.loaded=!0,i.exports}var n=window.webpackJsonp;window.webpackJsonp=function(s,o){for(var u,f,l=0,c=[];l<s.length;l++)f=s[l],i[f]&&c.push.apply(c,i[f]),i[f]=0;for(u in o)e[u]=o[u];for(n&&n(s,o);c.length;)c.shift().call(null,t);if(o[0])return r[0]=0,t(0)};var r={},i={4:0};t.e=function(e,n){if(0===i[e])return n.call(null,t);if(void 0!==i[e])i[e].push(n);else{i[e]=[n];var r=document.getElementsByTagName("head")[0],s=document.createElement("script");s.type="text/javascript",s.charset="utf-8",s.async=!0,s.src=t.p+""+e+".chunk.min.js",r.appendChild(s)}},t.m=e,t.c=r,t.p=""}([])
(function(e){function i(t){if(n[t])return n[t].exports;var r=n[t]={exports:{},id:t,loaded:false};e[t].call(r.exports,r,r.exports,i);r.loaded=true;return r.exports}var t=window["webpackJsonp"];window["webpackJsonp"]=function(o,u){var a,f,l=0,c=[];for(;l<o.length;l++){f=o[l];if(r[f])c.push.apply(c,r[f]);r[f]=0}for(a in u){e[a]=u[a]}if(t)t(o,u);while(c.length)c.shift().call(null,i);if(u[0]){n[0]=0;return i(0)}};var n={};var r={4:0};i.e=function(t,n){if(r[t]===0)return n.call(null,i);if(r[t]!==undefined){r[t].push(n)}else{r[t]=[n];var s=document.getElementsByTagName("head")[0];var o=document.createElement("script");o.type="text/javascript";o.charset="utf-8";o.async=true;o.src=i.p+""+t+".chunk.min.js";s.appendChild(o)}};i.m=e;i.c=n;i.p=""})([])

View File

@ -71,7 +71,7 @@ module.exports = {
defaultQuery.plugins.push(["import", { libraryName: "antd"}])
return defaultQuery;
},
exclude: /node_modules\/(?!_?(yapi-plugin|randexp))/
exclude: /node_modules\/(?!_?(yapi-plugin|json-schema-editor-visual))/
}
}],
devtool: 'cheap-source-map',
@ -188,14 +188,8 @@ module.exports = {
})
if (this.env == 'prd') {
baseConfig.plugins.push(new this.webpack.optimize.UglifyJsPlugin({
compress: {
warnings: false
}
}))
baseConfig.plugins.push(assetsPluginInstance)
baseConfig.plugins.push(compressPlugin)
}
return baseConfig;
}