diff --git a/LICENSE b/LICENSE index 06c2da8..c809635 100644 --- a/LICENSE +++ b/LICENSE @@ -186,7 +186,7 @@ same "printed page" as the copyright notice for easier identification within third-party archives. - Copyright ©2017 TommyLemon(https://github.com/TommyLemon/APIJSONAuto) + Copyright ©2017 TommyLemon(https://github.com/TommyLemon/APIAuto) Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/README.md b/README.md index 02c6bbc..eb30768 100755 --- a/README.md +++ b/README.md @@ -1,67 +1,341 @@ -# APIJSON Auto -自动化接口管理工具,支持自动生成文档与注释、自动生成代码、自动化回归测试、自动静态检查等。 +

+ APIAuto +

+ +

☔ HTTP 接口 机器学习零代码测试 和 一站式智能开发管理 工具

+ +

+ 快速上手 + 视频教程 + 在线体验 +

+ +

+ +

+ +--- +敏捷开发最强大易用的 HTTP 接口工具,机器学习零代码测试、生成代码与静态检查、生成文档与光标悬浮注释。
+集合 文档、测试、Mock、调试、管理 的一站式体验,还有 **AI 问答** 和一键 格式化、注释/取消注释 等高效快捷键。
+在常用功能上远超 Postman, Swagger, YApi 等各种 开源、商业 的 API 文档/测试 工具,并能一键导入用例和文档。
+支持 GET, POST, PUT, PATCH, DELETE, HEAD 等各种 HTTP Method 及 Content-Type, URL /{Path}/{Variable}。
+不仅适用于 RESTful、类 RESTful、GRPC 的 API,还是腾讯 [APIJSON](https://github.com/Tencent/APIJSON) 官方建议的文档与测试工具。
+腾讯内部用户包括 IEG 互动娱乐事业群、TEG 技术工程事业群、CSIG 云与智慧事业群 的多个部门及团队,
+外部用户包含 华为、工商银行某地分行、500 强上市公司传音、跨境电商巨头 SHEIN、行业领头羊社保科技 等。 + +![](https://github.com/user-attachments/assets/24460af3-0001-46e7-aa2a-df28b711a8cf) +![](https://github.com/user-attachments/assets/8bc839b1-cce1-4cda-a35d-77ade1387035) +![](https://user-images.githubusercontent.com/5738175/145665502-94231804-5ea8-4784-b30d-d5558aad0f8d.jpeg) + +

+腾讯 AI 测试圈子演讲(部分) +

+ +![](https://raw.githubusercontent.com/TommyLemon/StaticResources/master/APIAuto/APIAuto_Tencent_TEG_AITestCircle_quick_and_slow.gif) + + +#### 腾讯内先后被 TEG 工具开发组、微信支付部门、IEG 工具开发组、CDG 金融支付组、IEG PC 游戏平台部 邀请分享了 +* APIAuto-机器学习 HTTP 接口工具 +* 零代码测试工具与实践(API•单元•UI) +* 零代码开发和测试(API测试•单元测试•API开发) +* 零代码开发和测试(API测试•单元测试•API开发) +* 零代码开发和测试(接口测试•单元测试•接口开发) + +#### 质效无双线上技术访谈-零代码智能测试工具实践介绍-第11期 +https://testwo.cn1.quickconnect.cn/vs/sharing/iiP8VK1C#!aG9tZV92aWRlby0xMQ== +![https://testwo.cn1.quickconnect.cn/vs/sharing/iiP8VK1C#!aG9tZV92aWRlby0xMQ==](https://user-images.githubusercontent.com/5738175/179575169-de9cc578-6d90-4aec-bbf4-f28147277ed2.png) + +#### 和华为云副总裁并列,QECon-全球软件质量&效能 大会分享总结 +零代码开发和测试 成为大会主会场回看预约海报唯一分会场演讲范例

+wecom-temp-377bbd0daf5aed716baf7ebcb003d94c + + + +现场录播回放视频:QECon大会-零代码开发和测试(APIJSON和APIAuto)
+https://www.bilibili.com/video/BV1yv411p7Y4 +
+ +![](https://raw.githubusercontent.com/TommyLemon/StaticResources/master/APIAuto/APIAuto_mltesting.gif) +![](https://raw.githubusercontent.com/TommyLemon/StaticResources/master/APIAuto/APIAuto_mltest_check_result.gif) + + +![image](https://user-images.githubusercontent.com/5738175/145665614-f9208e35-9dc2-4a02-a8c9-0d7c9f4a87bc.png) +![APIJSON 和 APIAuto - 零代码开发和测试-QECon 大会-图像 008](https://user-images.githubusercontent.com/5738175/145665488-42b04a4b-1f74-4fb8-8a62-db3535f4256c.jpeg) +![APIJSON 和 APIAuto - 零代码开发和测试-QECon 大会-图像 009](https://user-images.githubusercontent.com/5738175/145665490-d90dda3f-2439-44d2-b7ae-88fb6f7e6c92.jpeg) +![APIJSON 和 APIAuto - 零代码开发和测试-QECon 大会-图像 010](https://user-images.githubusercontent.com/5738175/145665492-65409dd7-10ef-4c70-928e-48affe020df4.jpeg) +![APIJSON 和 APIAuto - 零代码开发和测试-QECon 大会-图像 012](https://user-images.githubusercontent.com/5738175/145665496-628f7cc4-f6b9-4329-a759-8d0185de4f87.jpeg) +![APIJSON 和 APIAuto - 零代码开发和测试-QECon 大会-图像 013](https://user-images.githubusercontent.com/5738175/145665502-94231804-5ea8-4784-b30d-d5558aad0f8d.jpeg) +![APIJSON 和 APIAuto - 零代码开发和测试-QECon 大会-图像 014](https://user-images.githubusercontent.com/5738175/145665504-a6d6b251-0284-4026-9d62-0cc7937082b1.jpeg) +![APIJSON 和 APIAuto - 零代码开发和测试-QECon 大会-图像 016](https://user-images.githubusercontent.com/5738175/145665508-0c02afe0-0e13-4f5f-8fd8-f0a017ed6e26.jpeg) +![APIJSON 和 APIAuto - 零代码开发和测试-QECon 大会-图像 017](https://user-images.githubusercontent.com/5738175/145665509-cf1d841a-a7a6-441c-8b68-eaf65b452bfc.jpeg) +![APIJSON 和 APIAuto - 零代码开发和测试-QECon 大会-图像 018](https://user-images.githubusercontent.com/5738175/145665512-86b5f067-c490-4de6-afa7-78c0f328c9cd.jpeg) +![APIJSON 和 APIAuto - 零代码开发和测试-QECon 大会-图像 019](https://user-images.githubusercontent.com/5738175/145665514-b22e7180-237a-4f13-acc1-8eb21c9b5b37.jpeg) +![APIJSON 和 APIAuto - 零代码开发和测试-QECon 大会-图像 020](https://user-images.githubusercontent.com/5738175/145665516-61e2693b-ec34-4775-9a71-52b5af5d3ede.jpeg) +![APIJSON 和 APIAuto - 零代码开发和测试-QECon 大会-图像 021](https://user-images.githubusercontent.com/5738175/145665518-a35bc996-9cc2-478a-a1de-7ba731dbe557.jpeg) +![APIJSON 和 APIAuto - 零代码开发和测试-QECon 大会-图像 022](https://user-images.githubusercontent.com/5738175/145665522-8f0ff509-5510-4d94-96e2-d088aecf6fce.jpeg) +![APIJSON 和 APIAuto - 零代码开发和测试-QECon 大会-图像 023](https://user-images.githubusercontent.com/5738175/145665525-a624f521-4a6f-4315-9aa3-a7309348d083.jpeg) +![APIJSON 和 APIAuto - 零代码开发和测试-QECon 大会-图像 024](https://user-images.githubusercontent.com/5738175/145665527-4a5da35a-da8c-4abd-8fb7-71059fbb4520.jpeg) +![APIJSON 和 APIAuto - 零代码开发和测试-QECon 大会-图像 025](https://user-images.githubusercontent.com/5738175/145665528-dabbcaa0-617a-41e7-b3d3-cc66251934b1.jpeg) +![APIJSON 和 APIAuto - 零代码开发和测试-QECon 大会-图像 026](https://user-images.githubusercontent.com/5738175/145665530-5b3b1cbd-0962-49fa-ab59-b307672b7c62.jpeg) +![APIJSON 和 APIAuto - 零代码开发和测试-QECon 大会-图像 027](https://user-images.githubusercontent.com/5738175/145665531-2449009f-7fea-435c-ad0e-f7f2f525d4e5.jpeg) +![APIJSON 和 APIAuto - 零代码开发和测试-QECon 大会-图像 028](https://user-images.githubusercontent.com/5738175/145665535-b86d4e49-cfa5-4aa5-8f71-d8e68a85828a.jpeg) +![APIJSON 和 APIAuto - 零代码开发和测试-QECon 大会-图像 030](https://user-images.githubusercontent.com/5738175/145665537-de24b9b6-b47f-45cd-82bf-b7d06d156ce6.jpeg) +![APIJSON 和 APIAuto - 零代码开发和测试-QECon 大会-图像 031](https://user-images.githubusercontent.com/5738175/145665538-983ffe6f-f293-466a-ab4c-d5de12e20fae.jpeg) +![APIJSON 和 APIAuto - 零代码开发和测试-QECon 大会-图像 033](https://user-images.githubusercontent.com/5738175/145665542-2e1b0a43-ed06-4305-8e7d-b7c475dad0f9.jpeg) +![APIJSON 和 APIAuto - 零代码开发和测试-QECon 大会-图像 034](https://user-images.githubusercontent.com/5738175/145665545-00229bae-726a-4426-ae76-d43ed45df65d.jpeg) +![APIJSON 和 APIAuto - 零代码开发和测试-QECon 大会-图像 036](https://user-images.githubusercontent.com/5738175/145665552-05259dac-9546-4819-9db3-bb23e332f742.jpeg) +![APIJSON 和 APIAuto - 零代码开发和测试-QECon 大会-图像 037](https://user-images.githubusercontent.com/5738175/145665553-68403dcb-4cdd-42d4-9ffc-e32657e16b2b.jpeg) + + +

### 特点功能 -* 自动生成接口文档,清晰可读永远最新 -* 自动生成请求代码,支持Android和iOS -* 自动生成JavaBean文件,一键下载 -* 自动管理与测试接口用例,一键共享 +* 自动生成接口文档,光标悬浮注释 +* 自动校验与格式化参数,支持高亮和收展 +* 自动生成各种语言前后端代码,一键下载 +* 自动机器学习零代码测试接口,一键运行 +* 自动管理接口测试用例,一键共享 * 自动给请求JSON加注释,一键切换 * 自动保存历史请求记录,一键恢复 -* 自动校验与格式化JSON,支持高亮和收展 + +以上是简略图,机器学习测试、自动生成代码、自动静态检查、自动生成注释 等详细的功能介绍见
+ +[https://github.com/TommyLemon/APIAuto/blob/master/apijson/README.md](https://github.com/TommyLemon/APIAuto/blob/master/apijson/README.md) + +### 演讲稿件 +[APIAuto-机器学习 HTTP 接口工具](https://github.com/TommyLemon/StaticResources/tree/master/APIAuto/Share)
+[QECon 大会-腾讯 Tommy-零代码开发和测试](https://github.com/TommyLemon/StaticResources/tree/master/APIAuto/Share) + +### 视频教程 +Bilibili:https://search.bilibili.com/all?keyword=APIAuto +image + +
+优酷:https://i.youku.com/i/UNTg1NzI1MjQ4MA== +image + + +### 相关推荐 +[别再生成测试代码了!](https://mp.weixin.qq.com/s/G1GVNhhFbSX5GoyRU6GURg)
+[APIAuto: 最先进的HTTP接口工具](https://blog.csdn.net/Nifc666/article/details/141966487) + + +### 百度、搜狗、抖音公网接口调用演示
+因为这些接口不支持 CORS 跨域,所以需要开启托管服务代理。
+可以复制 Chrome 等浏览器、Charles 等抓包工具的请求文本,
+粘贴到 APIAuto 的 URL 输入框,会自动填充 URL, JSON, Header 等。
+https://github.com/TommyLemon/APIAuto/issues/16 + +#### 百度 +![APIAuto_request_thirdparty_api_baidu](https://user-images.githubusercontent.com/5738175/154853951-558b9ce0-b8a5-4f35-a811-3c3fbee1235a.gif) + +#### 搜狗 +![APIAuto_request_sogou_api](https://user-images.githubusercontent.com/5738175/154854769-dbb0da94-ce59-41a9-8e79-f500c61e17b3.gif) + +#### 抖音 +![APIAuto_request_douyin_api](https://user-images.githubusercontent.com/5738175/154854538-d21f22cc-d9f1-4f84-ae2f-8e63bfd02f8f.gif) + +
+ +**还可以参考视频:APIAuto 测试请求第三方 HTTP API**
+https://www.bilibili.com/video/BV1JZ4y1d7c8 +![image](https://user-images.githubusercontent.com/5738175/160234764-a8e02ca4-1d0e-407b-8ac7-2f85c9f200d6.png) + + +
+ +### 快速上手 + +本项目是纯静态 SPA 网页,下载源码解压后:
+可以用浏览器打开 index.html,建议用 [Chrome](https://www.google.com/intl/zh-CN/chrome) 或 [Firefox](https://www.mozilla.org/zh-CN/firefox) (Safari、Edge、IE 等可能有兼容问题),注意此方法不显示 svg 图标。
+也可以用 [IntelliJ Webstorm](https://www.jetbrains.com/webstorm/), [IntelliJ IDEA](https://www.jetbrains.com/idea/), [Eclipse](https://www.eclipse.org/) 等 IDE 来打开。
+也可以部署到服务器并用 [Nginx](https://www.jianshu.com/p/11fa3a1a6d65) 或 [Node](https://segmentfault.com/a/1190000039744899) 反向代理,或者 [把源码放到 SpringBoot 项目的 resources/static 目录](https://github.com/APIJSON/APIJSON-Demo/tree/master/APIJSON-Java-Server)。
+还可以直接访问官方网站 http://apijson.cn/api 或 http://apijson.cn:8080
+
+把左侧 URL 输入框内基地址改为你主机的地址(例如 http://localhost:8080 ),
+然后在右上角 设置 下拉菜单内修改 数据库类型Database、数据库模式Schema。
+
+右上角登录的默认管理员账号为 13000082001 密码为 123456,
+右侧上方中间 3 个标签是默认的测试用户账号,点击登录/退出,左侧 - 删除,右侧 + 新增。
+
+**自动生成文档、自动管理测试用例 这两个功能 需要部署 APIJSON 后端,建议用 APIJSONBoot 系列之一 Demo,见**
+https://github.com/APIJSON/APIJSON-Demo/tree/master/APIJSON-Java-Server + +**建议使用已 [内置 APIAuto](https://github.com/APIJSON/APIJSON-Demo/tree/master/APIJSON-Java-Server/APIJSONBoot-MultiDataSource/src/main/resources/static) 的 [APIJSONBoot-MultiDataSource](https://github.com/APIJSON/APIJSON-Demo/tree/master/APIJSON-Java-Server/APIJSONBoot-MultiDataSource),可以避免以下常见问题 1, 3, 4**
-![](https://raw.githubusercontent.com/TommyLemon/StaticResources/master/APIJSON_Auto_get.jpg) -![](https://raw.githubusercontent.com/TommyLemon/StaticResources/master/APIJSON_Auto_code.jpg) -![](https://raw.githubusercontent.com/TommyLemon/StaticResources/master/APIJSON_Auto_doc.jpg) -![](https://raw.githubusercontent.com/TommyLemon/StaticResources/master/APIJSON_Auto_test.jpg) +### 新增测试用例 + +可以使用以下几种方式: + +#### 1.从 Postman/Swagger/YApi/Rap 等其它接口工具/平台一键导入 +点右上角登录 > 点右上角设置 > 导入第三方文档(平台 URL) > 如果默认设置不符你的需求,可以在弹窗内修改 > 点上传按钮 + +#### 2.从浏览器 Network 接口信息界面或 Charles 等抓包工具复制后粘贴到 URL 输入框 +https://github.com/TommyLemon/APIAuto#%E7%99%BE%E5%BA%A6%E6%90%9C%E7%8B%97%E6%8A%96%E9%9F%B3%E5%85%AC%E7%BD%91%E6%8E%A5%E5%8F%A3%E8%B0%83%E7%94%A8%E6%BC%94%E7%A4%BA + +#### 3.调用 /delegate 代理接口来录制请求的方法、参数、Header、响应等信息 +https://github.com/APIJSON/APIJSON-Demo/tree/master/APIJSON-Java-Server/APIJSONBoot-MultiDataSource#%E4%BB%A3%E7%90%86%E6%8E%A5%E5%8F%A3%E5%8F%8A%E5%BD%95%E5%88%B6%E6%B5%81%E9%87%8F + +#### 4.打开分享链接来自动填充 URL、参数 JSON、请求头、参数注入配置、设置项 等 +例如: +http://apijson.cn/api/?send=true&type=JSON&url=http%3A%2F%2Fapijson.cn%3A8080%2Fget&json={%22[]%22:{%22Comment%22:{},%22User%22:{%22id@%22:%22%2FComment%2FuserId%22}}} + +#### 5.在界面手动填写 URL、参数 JSON、请求头 等再点击上传/分享按钮 +可点击分享按钮生成分享链接,用浏览器打开即可自动填充。
+退出登录后可设置 使用的请求类型,全部类型为 PARAM,JSON,FORM,DATA,GRPC + +
+ +### 后台 Headless 无 UI 模式回归测试 +Jenkins、蓝盾 等 CI/CD 等流水线不支持带 UI 测试,所以提供了这个模式,
+通过调用 HTTP API 即可执行用例和查看进度,方便集成到 CI/CD 流水线。 +![image](https://user-images.githubusercontent.com/5738175/199452068-dee4cbcb-ca4c-484a-8953-84b6bd238982.png) +![image](https://user-images.githubusercontent.com/5738175/199453742-b1e897f5-6950-40e2-8bfc-80b826966c6b.png) + +#### 1.配置 Node 环境及 NPM 包管理工具 +https://nodejs.org + +#### 2.安装相关依赖 +https://koajs.com +```sh +nvm install 7 +npm i koa +``` + +#### 3.使用后台 HTTP 服务 +先启动 HTTP 服务 +```sh +cd js +node server.js +``` +如果运行报错 missing package xxx,说明缺少相关依赖,参考步骤 2 来执行 +```sh +npm i xxx +``` +然后再启动 HTTP 服务。
+ +启动成功后会有提示,点击链接或者复制到浏览器输入框打开即可。

+如果托管服务是用 [APIJSONBoot-MultiDataSource](https://github.com/APIJSON/APIJSON-Demo/tree/master/APIJSON-Java-Server/APIJSONBoot-MultiDataSource) 部署的,
+链接 host 后可以加上 /api,例如 http://localhost:3000/api/test/start,
+通过这个接口来放宽前端执行时查询测试用例、参数配置等列表的条数,一次可批量执行更多用例。

-### 视频教程 +### 常见问题 -[http://i.youku.com/apijson](http://i.youku.com/apijson) +**本网页工具基本每个按钮/输入框等 UI 组件都有注释或悬浮文档等形式的操作提示,
+很多问题都不需要看文档/视频,可以直接通过把光标放上去等简单尝试来得到解答** +#### 1.无法访问接口 +如果是 APIAuto 本身调用的后端接口,则一般是 Chrome 90+ 对 CORS 请求禁止携带 Cookie
+或 Chrome 80-89 强制 same-site Cookie 的策略导致,打开以下链接查看解决方法
+https://github.com/TommyLemon/APIAuto/issues/9 -### 部署方法 +如果是其它接口,则一般是以上原因或者被接口不支持 CORS 跨域,可以改为支持,
+或者在 APIAuto 右上角设置开启托管服务器代理,通过后端代理访问接口,
+注意默认是官网的托管服务器 http://apijson.cn:9090 ,仅支持公网,
+如果是贵公司内网,请按以上 [部署方法](https://github.com/TommyLemon/APIAuto#%E9%83%A8%E7%BD%B2%E6%96%B9%E6%B3%95) 文档来部署 APIJSON 后端到内网,并修改托管服务器地址。 -可以直接下载源码解压后访问index.html,
-也可以直接用 http://apijson.org ,把基地址改为你主机的地址(例如 http://localhost:8080 )即可。 +#### 2.没有生成文档 +右上角设置项与数据库实际配置不一致 等
+https://github.com/Tencent/APIJSON/issues/85 -自动生成文档、自动管理测试用例 这两个功能 需要部署APIJSON后端,见 -[https://github.com/TommyLemon/APIJSON/tree/master/APIJSON-Java-Server](https://github.com/TommyLemon/APIJSON/tree/master/APIJSON-Java-Server) +#### 3.托管服务器访问不了 +不能代理接口、不能展示文档、不能对断言结果纠错 等
+https://github.com/TommyLemon/APIAuto/issues/12 +#### 4.apijson.cn 访问不了 +托管服务地址改为 http://47.98.196.224:8080
+https://github.com/TommyLemon/APIAuto/issues/13 + +更多问题及解答
+https://github.com/TommyLemon/APIAuto/issues + +
+ +### Roadmap 路线图 +1.Translate document to English/Italian/Franch/Spanish...
+ +2.新增功能
+1) 断言结果 新增按钮 变-\{原因},点击后右侧展示 JSON diff view;
+2) 右下角列表展示具体每个断言有问题的字段,点击后 JSON view 只显示该字段对应值
+其他待补充...
+
+3.完善自动断言,支持更多格式的匹配
+
+4.解决 bug
+
+5.提升性能
+
+6.其他待补充...
+ +
### 感谢开源 * jsonon * editor.md * vue.js +### 技术交流 +##### 关于作者 +[https://github.com/TommyLemon](https://github.com/TommyLemon)
+![image](https://github.com/user-attachments/assets/c4aa8573-f8b3-4973-8c37-29677c06ac3b) -### 关于作者 -TommyLemon:[https://github.com/TommyLemon](https://github.com/TommyLemon)
-QQ技术交流群:607020115 +如果有什么问题或建议可以 [提 issue](https://github.com/TommyLemon/APIAuto/issues),交流技术,分享经验。
+如果你解决了某些 bug,或者新增了一些功能,欢迎 [提 PR 贡献代码](https://github.com/Tencent/APIJSON/blob/master/CONTRIBUTING.md),感激不尽。 +
+
-如果有什么问题或建议可以 [提ISSUE](https://github.com/TommyLemon/APIJSONAuto/issues) 或 加群,交流技术,分享经验。
-如果你解决了某些bug,或者新增了一些功能,欢迎 [贡献代码](https://github.com/TommyLemon/APIJSONAuto/pulls),感激不尽。 +### 其它项目 +[APIJSON](https://github.com/Tencent/APIJSON) 🚀 腾讯零代码、全功能、强安全 ORM 库 🏆 后端接口和文档零代码,前端(客户端) 定制返回 JSON 的数据和结构 +[UnitAuto](https://github.com/TommyLemon/UnitAuto) 机器学习单元测试平台,零代码、全方位、自动化 测试 方法/函数 的正确性和可用性 -### 其它项目 -[APIJSON](https://github.com/TommyLemon/APIJSON) 后端接口和文档自动化,前端(客户端) 定制返回JSON的数据和结构 +[SQLAuto](https://github.com/TommyLemon/SQLAuto) 智能零代码自动化测试 SQL 语句执行结果的数据库工具,任意增删改查、任意 SQL 模板变量、一键批量生成参数组合、快速构造大量测试数据 + +[UIGO](https://github.com/TommyLemon/UIGO) 📱 零代码快准稳 UI 智能录制回放平台 🚀 自动兼容任意宽高比分辨率屏幕,自动精准等待网络请求,录制回放快、准、稳! + +[apijson-doc](https://github.com/vincentCheng/apijson-doc) APIJSON 官方文档,提供排版清晰、搜索方便的文档内容展示,包括设计规范、图文教程等 + +[APIJSONdocs](https://github.com/ruoranw/APIJSONdocs) APIJSON 英文文档,提供排版清晰的文档内容展示,包括详细介绍、设计规范、使用方式等 + +[apijson.org](https://github.com/APIJSON/apijson.org) APIJSON 官方网站,提供 APIJSON 的 功能简介、登记用户、作者与贡献者、相关链接 等 -[APIJSON.NET](https://github.com/liaozb/APIJSON.NET) C# 版 APIJSON 服务端库,支持 MySQL, PostgreSQL, MS SQL Server, Oracle, SQLite +[APIJSON.NET](https://github.com/liaozb/APIJSON.NET) C# 版 APIJSON ,支持 MySQL, PostgreSQL, SQL Server, Oracle, SQLite -[apijson](https://github.com/orchie/apijson) PHP 版 APIJSON,支持 MySQL, PostgreSQL, MS SQL Server, Oracle, SQLite 等 +[apijson-go](https://github.com/glennliao/apijson-go) Go 版 APIJSON , 基于Go(>=1.18) + GoFrame2, 支持查询、单表增删改、权限管理等 + +[apijson-go](https://gitee.com/tiangao/apijson-go) Go 版 APIJSON ,支持单表查询、数组查询、多表一对一关联查询、多表一对多关联查询 等 -[apijson](https://github.com/TEsTsLA/apijson) Node.ts 版 APIJSON 服务端库,支持 MySQL, PostgreSQL, MS SQL Server, Oracle, SQLite, MariaDB, WebSQL +[apijson-hyperf](https://github.com/kvnZero/hyperf-APIJSON.git) PHP 版 APIJSON,基于 Hyperf 支持 MySQL + +[APIJSON-php](https://github.com/xianglong111/APIJSON-php) PHP 版 APIJSON,基于 ThinkPHP,支持 MySQL, PostgreSQL, SQL Server, Oracle 等 + +[apijson-php](https://github.com/qq547057827/apijson-php) PHP 版 APIJSON,基于 ThinkPHP,支持 MySQL, PostgreSQL, SQL Server, Oracle 等 + +[apijson-node](https://github.com/kevinaskin/apijson-node) 字节跳动工程师开源的 Node.ts 版 APIJSON,提供 nestjs 和 typeorm 的 Demo 及后台管理 + +[uliweb-apijson](https://github.com/zhangchunlin/uliweb-apijson) Python 版 APIJSON,支持 MySQL, PostgreSQL, SQL Server, Oracle, SQLite 等 + +[apijson-practice](https://github.com/vcoolwind/apijson-practice) BAT 技术专家开源的 APIJSON 参数校验注解 Library 及相关 Demo + +[Android-ZBLibrary](https://github.com/TommyLemon/Android-ZBLibrary) Android MVP 快速开发框架,Demo 全面,注释详细,使用简单,代码严谨 ### 持续更新 -[https://github.com/TommyLemon/APIJSONAuto/commits/master](https://github.com/TommyLemon/APIJSONAuto/commits/master) +https://github.com/TommyLemon/APIAuto/commits/master + ### 我要赞赏 -创作不易,右上角点 ⭐Star 支持下吧,谢谢 ^_^
-[https://github.com/TommyLemon/APIJSONAuto](https://github.com/TommyLemon/APIJSONAuto) +腾讯、中国邮政、字节跳动、阿里巴巴、美团、网易、百度、京东、滴滴、平安、SHEIN、快手、携程、Bilibili、微众银行、VIVO、
+58 集团、中兴 等 和国外 NVIDIA, Amazon, SAP, ThoughtWorks, Red Hat 等各大知名大厂员工点了 Star,感谢大家的支持~
+![image](https://github.com/TommyLemon/APIAuto/assets/5738175/723e1c9c-7cf7-431a-b29a-b878e99c7e39) + +**创作不易、坚持更难,右上角点亮 ⭐Star 支持/收藏下本项目吧,谢谢 ^_^**
+https://github.com/TommyLemon/APIAuto + + diff --git a/apijson/CodeUtil.js b/apijson/CodeUtil.js index 6776f14..3190bab 100644 --- a/apijson/CodeUtil.js +++ b/apijson/CodeUtil.js @@ -1,4 +1,4 @@ -/*Copyright ©2017 TommyLemon(https://github.com/TommyLemon/APIJSONAuto) +/*Copyright ©2017 TommyLemon(https://github.com/TommyLemon/APIAuto) Licensed under the Apache License, Version 2.0 (the "License"); you may not use CodeUtil file except in compliance with the License. @@ -12,480 +12,5166 @@ See the License for the specific language governing permissions and limitations under the License.*/ +if (typeof window == 'undefined') { + try { + eval(` + var StringUtil = require("./StringUtil"); + var JSONObject = require("./JSONObject"); + var JSON5 = require('json5'); + `) + } catch (e) { + console.log(e) + } +} /**util for generate code * @author Lemon */ var CodeUtil = { TAG: 'CodeUtil', + APP_NAME: 'APIAuto', + DIVIDER: '/', + + LANGUAGE_KOTLIN: 'Kotlin', + LANGUAGE_JAVA: 'Java', + LANGUAGE_C_SHARP: 'C#', + LANGUAGE_SWIFT: 'Swift', +// LANGUAGE_OBJECTIVE_C: 'Objective-C', + LANGUAGE_GO: 'Go', + LANGUAGE_C_PLUS_PLUS: 'C++', + LANGUAGE_TYPE_SCRIPT: 'TypeScript', + LANGUAGE_JAVA_SCRIPT: 'JavaScript', + LANGUAGE_PHP: 'PHP', + LANGUAGE_PYTHON: 'Python', + + DATABASE_MYSQL: 'MYSQL', + DATABASE_POSTGRESQL: 'POSTGRESQL', + DATABASE_SQLITE: 'SQLITE', + DATABASE_ORACLE: 'ORACLE', + DATABASE_SQLSERVER: 'SQLSERVER', + DATABASE_DB2: 'DB2', + DATABASE_DAMENG: 'DAMENG', + DATABASE_KINGBASE: 'KINGBASE', + DATABASE_TIDB: 'TIDB', + DATABASE_TDENGINE: 'TDENGINE', + DATABASE_SURREALDB: 'SURREALDB', + DATABASE_PRESTO: 'PRESTO', + DATABASE_TRINO: 'TRINO', + DATABASE_INFLUXDB: 'INFLUXDB', + DATABASE_CLICKHOUSE: 'CLICKHOUSE', + DATABASE_ELASTICSEARCH: 'ELASTICSEARCH', + DATABASE_REDIS: 'REDIS', + DATABASE_KAFKA: 'KAFKA', + DATABASE_MARIADB: 'MARIADB', + DATABASE_HIVE: 'HIVE', + DATABASE_SNOWFLAKE: 'SNOWFLAKE', + DATABASE_DATABRICKS: 'DATABRICKS', + DATABASE_MILVUS: 'MILVUS', + DATABASE_IOTDB: 'IOTDB', + DATABASE_DUCKDB: 'DUCKDB', + DATABASE_CASSANDRA: 'CASSANDRA', + DATABASE_MONGODB: 'MONGODB', + + type: 'JSON', + database: 'MYSQL', + schema: 'sys', + language: 'Kotlin', + functionList: [], + requestList: [], + tableList: [], + thirdParty: 'YAPI', + thirdPartyApiMap: null, // {} + /**生成JSON的注释 * @param reqStr //已格式化的JSON String * @param tableList + * @param method + * @param database + * @param language * @return parseComment */ - parseComment: function (reqStr, tableList, method) { //怎么都获取不到真正的长度,cols不行,默认20不变,maxLineLength不行,默认undefined不变 , maxLineLength) { + parseComment: function (reqStr, tableList, method, database, language, isReq, standardObj, isExtract, isWarning, isAPIJSONRouter) { //怎么都获取不到真正的长度,cols不行,默认20不变,maxLineLength不行,默认undefined不变 , maxLineLength) { if (StringUtil.isEmpty(reqStr)) { return ''; } - method = method == null ? 'GET' : method.toUpperCase(); + var reqObj = JSON5.parse(reqStr); + + var methodInfo = JSONObject.parseUri(method, isReq) || {}; + method = methodInfo.method; + var isRestful = methodInfo.isRestful; + var tag = methodInfo.tag; + var startName = methodInfo.table; + + if (isRestful != true) { + method = method.toUpperCase(); + } var lines = reqStr.split('\n'); - var line; - var depth = 0; - var names = []; + var depth = startName == null ? 0 : 1; + var names = startName == null ? [] : [startName]; + var isInSubquery = false; - var index; - var key; - var value; + var curObj = { + parent: null, + name: null, + value: { + [startName == null ? '' : startName]: reqObj + } + }; + +// var cc = isRestful == true ? '//' : ' //'; // 对 APIJSON API 要求严格些,因为本来就有字段注释 +// var ccLen = cc.length; - var comment; for (var i = 0; i < lines.length; i ++) { - line = lines[i].trim(); + var line = lines[i].trim() || ''; //每一种都要提取:左边的key - index = line == null ? -1 : line.indexOf(': '); //可能是 ' 或 ",所以不好用 ': , ": 判断 - key = index < 0 ? '' : line.substring(1, index - 1); + var index = line.indexOf(':'); //可能是 ' 或 ",所以不好用 ': , ": 判断 + var key = index < 0 ? (depth <= 1 && startName != null ? startName : '') : line.substring(1, index - 1); + var cIndex = line.lastIndexOf(' //'); + var ccLen = cIndex < 0 ? 2 : 3; + if (cIndex < 0) { + cIndex = line.lastIndexOf('//'); + } + + var comment = ''; + if (cIndex >= 0) { + if (isExtract && standardObj != null && (isReq || depth != 1 + || [JSONResponse.KEY_CODE, JSONResponse.KEY_MSG, JSONResponse.KEY_THROW].indexOf(key) < 0)) { + comment = line.substring(cIndex + ccLen).trim(); + // standardObj = CodeUtil.updateStandardByPath(standardObj, names, key, value, comment) + } + + line = line.substring(0, cIndex).trim(); + } if (line.endsWith(',')) { line = line.substring(0, line.length - 1); } line = line.trim(); + var value = (curObj.value || {})[key]; + + var hintComment; + if (line.endsWith('{')) { //对象,判断是不是Table,再加对应的注释 - depth ++; + if (value == null) { + value = {} + } + + if (depth > 0 && comment.length > 0) { + standardObj = JSONResponse.updateStandardByPath(standardObj, names, key, value, comment) + } + + isInSubquery = key.endsWith('@'); + + hintComment = CodeUtil.getComment4Request(tableList, names[depth - 1], key, value, method, false, database, language, isReq, names, isRestful, standardObj, isWarning, isAPIJSONRouter); + names[depth] = key; - comment = CodeUtil.getComment4Request(tableList, null, key, null, method); + depth ++; + + curObj = { + parent: curObj, + name: key, + value: value + }; } else { if (line.endsWith('}')) { + if (value == null) { + value = {} + } + + if (depth > 0 && comment.length > 0) { + standardObj = JSONResponse.updateStandardByPath(standardObj, names, key, value, comment) + } + + isInSubquery = false; + if (line.endsWith('{}')) { //对象,判断是不是Table,再加对应的注释 - comment = CodeUtil.getComment4Request(tableList, null, key, null, method); + hintComment = CodeUtil.getComment4Request(tableList, names[depth - 1], key, value, method, false, database, language, isReq, names, isRestful, standardObj, isWarning, isAPIJSONRouter); } else { depth --; + names = names.slice(0, depth); + + if (isWarning && i > 0 && i < lines.length - 1) { + lines[i] = ''; // 节约性能,收尾不能为空,否则外面 trim 一下格式就变了对不上原文本。奇怪的是右大括号 } 总是不走这里 + } + + curObj = curObj.parent || {}; continue; } } - else if (key == '') { //[ 1, \n 2, \n 3] 跳过 - continue; + // else if (key == '') { //[ 1, \n 2, \n 3] 跳过 + // if (depth > 0 && comment.length > 0) { + // standardObj = JSONResponse.updateStandardByPath(standardObj, names, 0, '', comment) + // } + // + // continue; + // } + else { + if (line.endsWith('[')) { // [] 不影响 + if (value == null) { + value = [] + } + + if (depth > 0 && comment.length > 0) { + standardObj = JSONResponse.updateStandardByPath(standardObj, names, key, value, comment) + } + + hintComment = CodeUtil.getComment4Request(tableList, names[depth - 1], key, value, method, false, database, language, isReq, names, isRestful, standardObj, isWarning, isAPIJSONRouter); + + names[depth] = key; + depth ++; + + curObj = { + parent: curObj, + name: key, + value: value + }; + } + else { + if (line.endsWith(']')) { + if (value == null) { + value = [] + } + + if (depth > 0 && comment.length > 0) { + standardObj = JSONResponse.updateStandardByPath(standardObj, names, key, value, comment) + } + + if (line.endsWith('[]')) { //对象,判断是不是Table,再加对应的注释 + hintComment = CodeUtil.getComment4Request(tableList, names[depth - 1], key, value, method, false, database, language, isReq, names, isRestful, standardObj, isWarning, isAPIJSONRouter); + } + else { + depth --; + names = names.slice(0, depth); + + if (isWarning && i > 0 && i < lines.length - 1) { + lines[i] = ''; // 节约性能,收尾不能为空,否则外面 trim 一下格式就变了对不上原文本。奇怪的是右大括号 } 总是不走这里 + } + + curObj = curObj.parent || {}; + continue; + } + } + else if (value == null) { //其它,直接在后面加上注释 + value = line.substring(index + 2).trim() + if (value.startsWith('"')) { + value = value.substring(1, value.lastIndexOf('"')) + } + else { + try { + value = parseJSON(value) + } + catch (e) { + console.log(e) + } + } + } + // alert('depth = ' + depth + '; line = ' + line + '; isArray = ' + isArray); + hintComment = CodeUtil.getComment4Request(tableList, names[depth - 1], key, value, method, isInSubquery, database, language, isReq, names, isRestful, standardObj, isWarning, isAPIJSONRouter); + } } - else { //其它,直接在后面加上注释 - var isArray = line.endsWith('['); // [] 不影响 - // alert('depth = ' + depth + '; line = ' + line + '; isArray = ' + isArray); - comment = value == 'null' ? ' ! null无效' : CodeUtil.getComment4Request(tableList, names[depth], key - , isArray ? '' : line.substring(index + 2).trim(), method); + + if (depth > 0 && comment.length > 0) { + standardObj = JSONResponse.updateStandardByPath(standardObj, names, key, value, comment) } } - lines[i] += comment; + // 普通注释需要完整保留原 JSON,以防预览请求不显示部分 JSON 内容 + if (isWarning && i > 0 && i < lines.length - 1 && StringUtil.isEmpty(hintComment, true)) { + lines[i] = ''; // 节约性能,收尾不能为空,否则外面 trim 一下格式就变了对不上原文本。奇怪的是右大括号 } 总是不走这里 + } + else { + lines[i] += hintComment; + } } - return lines.join('\n'); + var apiMap = isRestful ? CodeUtil.thirdPartyApiMap : null; + var api = apiMap == null ? null : apiMap['/' + method]; + var detail = api == null ? null : api.detail; + + return lines.join('\n') + (StringUtil.isEmpty(detail, true) ? '' : '\n\n/*\n\n' + detail + '\n\n*/'); + }, + + + getOperation: function (method, json) { + var ind = method == null ? -1 : method.indexOf('?'); + method = StringUtil.toLowerCase(ind < 0 ? method : method.substring(0, ind)); + if (method.startsWith('insert') || method.startsWith('post') || method.startsWith('add') + || method.startsWith('pub') || method.startsWith('write')) { + return 'INSERT' + } + if (method.startsWith('update') || method.startsWith('edit') || method.startsWith('put') + || method.startsWith('patch') || method.startsWith('mutate') || method.startsWith('mod')) { // modify + return 'UPDATE' + } + if (method.startsWith('del') || method.startsWith('remove') || method.startsWith('rmv') + || method.startsWith('clear') || method.startsWith('clean') || method.startsWith('release')) { + return 'DELETE' + } + if (method.startsWith('get') || method.startsWith('find') || method.startsWith('query') || method.startsWith('list') + || method.startsWith('search') || method.startsWith('select') || method.startsWith('read') + || method.startsWith('retrieve') || method.startsWith('fetch')) { + return 'SELECT' + } + if (JSONResponse.getType(json) == 'object') { + for (var key in json) { + var k = key == null ? '' : key.replaceAll('_', '').toLowerCase() + if (k.startsWith('page') || json.size != null || json.orderby != null) { + return 'SELECT' + } + } + } + return null // 'SELECT' }, - /**解析出 生成iOS-Swift请求JSON 的代码 + /**生成封装 Unity3D-C# 请求 JSON 的代码 * 只需要把所有 对象标识{} 改为数组标识 [] * @param name * @param reqObj * @param depth * @return parseCode */ - parseSwift: function(name, reqObj, depth) { - name = name || ''; + parseCSharpRequest: function(name, reqObj, depth) { if (depth == null || depth < 0) { depth = 0; } - var hasContent = false; + + var isEmpty = true; + if (reqObj instanceof Array) { + isEmpty = reqObj.length <= 0; + } + else if (reqObj instanceof Object) { + isEmpty = Object.keys(reqObj).length <= 0; + } + + var padding = CodeUtil.getBlank(depth); + var nextPadding = CodeUtil.getBlank(depth + 1); return CodeUtil.parseCode(name, reqObj, { onParseParentStart: function () { - return '[\n'; + return isEmpty ? 'new JObject{' : 'new JObject{\n'; }, onParseParentEnd: function () { - return (hasContent ? '\n' : CodeUtil.getBlank(depth + 1) + ':\n') + CodeUtil.getBlank(depth) + ']'; + return isEmpty ? '}' : '\n' + padding + '}'; }, onParseChildArray: function (key, value, index) { - hasContent = true; - return (index > 0 ? ',\n' : '') + CodeUtil.getBlank(depth + 1) + '"' + key + '": ' + CodeUtil.parseSwift(key, value, depth + 1); + return (index > 0 ? ',\n' : '') + nextPadding + '{"' + key + '", ' + CodeUtil.parseCSharpRequest(key, value, depth + 1) + '}'; }, onParseChildObject: function (key, value, index) { - hasContent = true; - return (index > 0 ? ',\n' : '') + CodeUtil.getBlank(depth + 1) + '"' + key + '": ' + CodeUtil.parseSwift(key, value, depth + 1); + return (index > 0 ? ',\n' : '') + nextPadding + '{"' + key + '", ' + CodeUtil.parseCSharpRequest(key, value, depth + 1) + '}'; }, - onParseChildOther: function (key, value, index) { - hasContent = true; + onParseArray: function (key, value, index, isOuter) { + var isEmpty = value.length <= 0; + var s = 'new JArray{' + (isEmpty ? '' : '\n'); - var v; //避免改变原来的value - if (typeof value == 'string') { - log(CodeUtil.TAG, 'parseJava for typeof value === "string" >> ' ); + var inner = ''; + var innerPadding = isOuter ? nextPadding : CodeUtil.getBlank(depth + 2); + for (var i = 0; i < value.length; i ++) { + inner += (i > 0 ? ',\n' : '') + innerPadding + CodeUtil.parseCSharpRequest(null, value[i], depth + (isOuter ? 1 : 2)); + } + s += inner; - v = '"' + value + '"'; + s += isEmpty ? '}' : '\n' + (isOuter ? padding : nextPadding) + '}'; + return s; + }, + + onParseChildOther: function (key, value, index, isOuter) { + var v; //避免改变原来的value + if (value == null) { + v = 'null'; } else if (value instanceof Array) { - log(CodeUtil.TAG, 'parseJava for typeof value === "array" >> ' ); - - v = '[' + CodeUtil.getArrayString(value, '...' + name + '/' + key) + ']'; + v = this.onParseArray(key, value, index, isOuter); + } + else if (typeof value == 'string') { + v = '"' + value + '"'; } else { v = value } - return (index > 0 ? ',\n' : '') + CodeUtil.getBlank(depth + 1) + '"' + key + '": ' + v; + return (index > 0 ? ',\n' : '') + (key == null ? v : (isOuter ? padding : nextPadding) + '{"' + key + '", ' + v + '}'); } }) }, - - - /**解析出 生成Android-Java请求JSON 的代码 + /**生成封装 Web-PHP 请求JSON 的代码 + * 只需要把所有 对象标识{} 改为数组标识 [] * @param name * @param reqObj * @param depth + * @param isSmart * @return parseCode - * @return isSmart 是否智能 */ - parseJava: function(name, reqObj, depth, isSmart) { - name = name || ''; + parsePHPRequest: function(name, reqObj, depth, isSmart) { if (depth == null || depth < 0) { depth = 0; } + var isEmpty = true; + if (reqObj instanceof Array) { + isEmpty = reqObj.length <= 0; + } + else if (reqObj instanceof Object) { + isEmpty = Object.keys(reqObj).length <= 0; + } - const parentKey = JSONObject.isArrayKey(name) ? JSONResponse.getSimpleName(CodeUtil.getItemKey(name)) : CodeUtil.getTableKey(JSONResponse.getSimpleName(name)); - + var padding = CodeUtil.getBlank(depth); + var nextPadding = CodeUtil.getBlank(depth + 1); + var quote = isSmart ? "'" : '"'; return CodeUtil.parseCode(name, reqObj, { onParseParentStart: function () { - return '\n' + (isSmart ? 'JSONRequest' : 'Map') + ' ' + parentKey + ' = new ' + (isSmart ? 'JSONRequest' : 'LinkedHashMap<>') + '();'; + if (isSmart) { + return isEmpty ? '(object) [' : '[\n'; + } + return isEmpty ? '(object) array(' : 'array(\n'; }, onParseParentEnd: function () { - return ''; + if (isSmart) { + return isEmpty ? ']' : '\n' + padding + ']'; + } + return isEmpty ? ')' : '\n' + padding + ')'; }, onParseChildArray: function (key, value, index) { + return this.onParseChildObject(key, value, index); + }, - var s = '\n\n//' + key + '<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<'; - - const count = isSmart ? (value.count || 0) : 0; - const page = isSmart ? (value.page || 0) : 0; - const query = isSmart ? value.query : null; - const join = isSmart ? value.join : null; + onParseChildObject: function (key, value, index) { + return this.onParseChildOther(key, value, index); + }, - log(CodeUtil.TAG, 'parseJava for count = ' + count + '; page = ' + page); + onParseArray: function (key, value, index, isOuter) { + var s = (isSmart ? '[' : 'array(') + (isEmpty ? '' : '\n'); - if (isSmart) { - delete value.count; - delete value.page; - delete value.query; - delete value.join; + var inner = ''; + var innerPadding = isOuter ? nextPadding : CodeUtil.getBlank(depth + 2); + for (var i = 0; i < value.length; i ++) { + inner += (i > 0 ? ',\n' : '') + innerPadding + CodeUtil.parsePHPRequest(null, value[i], depth + (isOuter ? 1 : 2), isSmart); } + s += inner; - s += CodeUtil.parseJava(key, value, depth + 1, isSmart); - - log(CodeUtil.TAG, 'parseJava for delete >> count = ' + count + '; page = ' + page); - - var name = JSONResponse.getSimpleName(CodeUtil.getItemKey(key)); - - if (isSmart) { - var prefix = key.substring(0, key.length - 2); + s += isEmpty ? (isSmart ? ']' : ')') : '\n' + (isOuter ? padding : nextPadding) + (isSmart ? ']' : ')'); + return s; + }, - s += '\n\n'; - if (query != null) { - s += name + '.setQuery(' + (CodeUtil.QUERY_TYPE_CONSTS[query] || CodeUtil.QUERY_TYPE_CONSTS[0]) + ');\n'; - } - if (StringUtil.isEmpty(join, true) == false) { - s += name + '.setJoin("' + join + '");\n'; - } - s += parentKey + '.putAll(' + name + '.toArray(' - + count + ', ' + page + (prefix.length <= 0 ? '' : ', "' + prefix + '"') + '));'; + onParseChildOther: function (key, value, index, isOuter) { + var v; //避免改变原来的value + if (value == null) { + v = 'null'; + } + else if (value instanceof Array) { + v = this.onParseArray(key, value, index, isOuter); + } + else if (value instanceof Object) { + v = CodeUtil.parsePHPRequest(key, value, depth + 1, isSmart); + } + else if (typeof value == 'string') { + log(CodeUtil.TAG, 'parsePHPRequest for typeof value === "string" >> ' ); + v = quote + value + quote; } else { - s += '\n\n' + parentKey + '.put("' + key + '", ' + name + ');'; + v = value } + return (index > 0 ? ',\n' : '') + (key == null ? '' : (isOuter ? padding : nextPadding) + quote + key + quote + ' => ') + v; + } + }) - s += '\n//' + key + '>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>\n'; + }, + /**生成封装 Web-Python 请求JSON 的代码 + * 转换注释符号和关键词 + * @param reqStr + * @return + */ + parsePythonRequest: function(name, reqObj, depth, isSmart, reqStr) { + if (isSmart != true) { + if (StringUtil.isEmpty(reqStr, true) && reqObj != null) { + reqStr = JSON.stringify(reqObj, null, ' ') + } + return StringUtil.trim(reqStr).replace(/\/\//g, '#').replace(/null/g, 'None').replace(/false/g, 'False').replace(/true/g, 'True').replace(/\/\*/g, isSmart ? '\'\'\'' : '"""').replace(/\*\//g, isSmart ? '\'\'\'' : '"""').replace(/'/g, '"') + } - return s; - }, + if (depth == null || depth < 0) { + depth = 0; + } - onParseChildObject: function (key, value, index) { - var s = '\n\n//' + key + '<<<<<<<<<<<<<<<<<<<<<<<<<<<<<'; + var isEmpty = true; + if (reqObj instanceof Array) { + isEmpty = reqObj.length <= 0; + } + else if (reqObj instanceof Object) { + isEmpty = Object.keys(reqObj).length <= 0; + } - const isTable = isSmart && JSONObject.isTableKey(JSONResponse.getTableName(key)); + var padding = CodeUtil.getBlank(depth); + var nextPadding = CodeUtil.getBlank(depth + 1); + var nextNextPadding = CodeUtil.getBlank(depth + 2); + var nextNextNextPadding = CodeUtil.getBlank(depth + 3); + var quote = isSmart ? "'" : '"' - const column = isTable ? value['@column'] : null; - const group = isTable ? value['@group'] : null; - const having = isTable ? value['@having'] : null; - const order = isTable ? value['@order'] : null; - const combine = isTable ? value['@combine'] : null; - const schema = isTable ? value['@schema'] : null; - const database = isTable ? value['@database'] : null; - const role = isTable ? value['@role'] : null; + return CodeUtil.parseCode(name, reqObj, { - if (isTable) { - delete value['@column']; - delete value['@group']; - delete value['@having']; - delete value['@order']; - delete value['@combine']; - delete value['@schema']; - delete value['@database']; - delete value['@role']; - } + onParseParentStart: function () { + return isEmpty ? '{' : '{\n'; + }, - s += CodeUtil.parseJava(key, value, depth + 1, isTable); + onParseParentEnd: function () { + return isEmpty ? '}' : ('\n' + CodeUtil.getBlank(depth) + '}'); + }, - const name = CodeUtil.getTableKey(JSONResponse.getSimpleName(key)); - if (isTable) { - s = column == null ? s : s + '\n' + name + '.setColumn(' + CodeUtil.getJavaValue(name, key, column) + ');'; - s = group == null ? s : s + '\n' + name + '.setGroup(' + CodeUtil.getJavaValue(name, key, group) + ');'; - s = having == null ? s : s + '\n' + name + '.setHaving(' + CodeUtil.getJavaValue(name, key, having) + ');'; - s = order == null ? s : s + '\n' + name + '.setOrder(' + CodeUtil.getJavaValue(name, key, order) + ');'; - s = combine == null ? s : s + '\n' + name + '.setCombine(' + CodeUtil.getJavaValue(name, key, combine) + ');'; - s = schema == null ? s : s + '\n' + name + '.setSchema(' + CodeUtil.getJavaValue(name, key, schema) + ');'; - s = database == null ? s : s + '\n' + name + '.setDatabase(' + CodeUtil.getJavaValue(name, key, database) + ');'; - s = role == null ? s : s + '\n' + name + '.setRole(' + CodeUtil.getJavaValue(name, key, role) + ');'; - } + onParseChildArray: function (key, value, index) { + return (index > 0 ? ',\n' : '') + nextPadding + quote + key + quote + ': ' + CodeUtil.parsePythonRequest(key, value, depth + 1, isSmart); + }, + + onParseChildObject: function (key, value, index) { + return (index > 0 ? ',\n' : '') + nextPadding + quote + key + quote + ': ' + CodeUtil.parsePythonRequest(key, value, depth + 1, isSmart); + }, - s += '\n\n' + parentKey + '.put("' + key + '", ' + name + ');'; + onParseArray: function (key, value, index, isOuter) { + var isEmpty = value.length <= 0; + var s = '[' + (isEmpty ? '' : '\n'); - s += '\n//' + key + '>>>>>>>>>>>>>>>>>>>>>>>>>>>>>\n'; + var inner = ''; + var innerPadding = isOuter ? nextNextPadding : nextNextNextPadding; + for (var i = 0; i < value.length; i ++) { + inner += (i > 0 ? ',\n' : '') + innerPadding + CodeUtil.parsePythonRequest(null, value[i], depth + (isOuter ? 1 : 2), isSmart); + } + s += inner; + s += isEmpty ? ']' : '\n' + (isOuter ? nextPadding : nextNextPadding) + ']'; return s; }, - onParseChildOther: function (key, value, index) { - if (depth <= 0 && isSmart) { - if (key == 'tag') { - return '\n' + parentKey + '.setTag(' + CodeUtil.getJavaValue(name, key, value) + ');'; - } - if (key == 'version') { - return '\n' + parentKey + '.setVersion(' + CodeUtil.getJavaValue(name, key, value) + ');'; - } - if (key == 'format') { - return '\n' + parentKey + '.setFormat(' + CodeUtil.getJavaValue(name, key, value) + ');'; - } - if (key == '@schema') { - return '\n' + parentKey + '.setSchema(' + CodeUtil.getJavaValue(name, key, value) + ');'; - } - if (key == '@database') { - return '\n' + parentKey + '.setDatabase(' + CodeUtil.getJavaValue(name, key, value) + ');'; - } - if (key == '@role') { - return '\n' + parentKey + '.setRole(' + CodeUtil.getJavaValue(name, key, value) + ');'; - } - } - return '\n' + parentKey + '.put("' + key + '", ' + CodeUtil.getJavaValue(name, key, value) + ');'; + onParseChildOther: function (key, value, index, isOuter) { + var valStr = (value instanceof Array ? this.onParseArray(key, value, index, true) :CodeUtil.getCode4Value(CodeUtil.LANGUAGE_PYTHON, value, key, depth, isSmart)); + return (index > 0 ? ',\n' : '') + (key == null ? '' : (isOuter ? padding : nextPadding) + quote + key + quote + ': ') + valStr; } }) }, - - /**TODO 为for循环生成函数 - * 解析出 生成Android-Java返回结果JSON 的代码 + /**封装 生成 iOS-Swift 请求 JSON 的代码 + * 只需要把所有 对象标识{} 改为数组标识 [] * @param name - * @param resObj + * @param reqObj * @param depth * @return parseCode */ - parseJavaResponse: function(name, resObj, depth) { + parseSwiftRequest: function(name, reqObj, depth) { if (depth == null || depth < 0) { depth = 0; } - if (name == null || name == '') { - name = 'response'; + var isEmpty = true; + if (reqObj instanceof Array) { + isEmpty = reqObj.length <= 0; + } + else if (reqObj instanceof Object) { + isEmpty = Object.keys(reqObj).length <= 0; } - return CodeUtil.parseCode(name, resObj, { + var padding = CodeUtil.getBlank(depth); + var nextPadding = CodeUtil.getBlank(depth + 1); + + return CodeUtil.parseCode(name, reqObj, { onParseParentStart: function () { - return depth > 0 ? '' : CodeUtil.getBlank(depth) + 'JSONResponse ' + name + ' = new JSONResponse(resultJson); \n'; + return isEmpty ? '[' : '[\n'; }, onParseParentEnd: function () { - return ''; + return isEmpty ? ':]' : ('\n' + CodeUtil.getBlank(depth) + ']'); }, onParseChildArray: function (key, value, index) { - return this.onParseChildObject(key, value, index); + return (index > 0 ? ',\n' : '') + nextPadding + '"' + key + '": ' + CodeUtil.parseSwiftRequest(key, value, depth + 1); }, onParseChildObject: function (key, value, index) { - return this.onParseJSONObject(key, value, index); + return (index > 0 ? ',\n' : '') + nextPadding + '"' + key + '": ' + CodeUtil.parseSwiftRequest(key, value, depth + 1); }, - onParseChildOther: function (key, value, index) { - - if (value instanceof Array) { - log(CodeUtil.TAG, 'parseJavaResponse for typeof value === "array" >> ' ); - - return this.onParseJSONArray(key, value, index); - } - if (value instanceof Object) { - log(CodeUtil.TAG, 'parseJavaResponse for typeof value === "array" >> ' ); + onParseArray: function (key, value, index, isOuter) { + var isEmpty = value.length <= 0; + var s = '[' + (isEmpty ? '' : '\n'); - return this.onParseJSONObject(key, value, index); + var inner = ''; + var innerPadding = isOuter ? nextPadding : CodeUtil.getBlank(depth + 2); + for (var i = 0; i < value.length; i ++) { + inner += (i > 0 ? ',\n' : '') + innerPadding + CodeUtil.parseSwiftRequest(null, value[i], depth + (isOuter ? 1 : 2)); } + s += inner; - var type = CodeUtil.getJavaTypeFromJS(key, value, true); - - return '\n' + CodeUtil.getBlank(depth) + type + ' ' + key + ' = ' + name + '.get' - + (/[A-Z]/.test(type.substring(0, 1)) ? type : StringUtil.firstCase(type + 'Value', true)) + '("' + key + '");'; + s += isEmpty ? ']' : '\n' + (isOuter ? padding : nextPadding) + ']'; + return s; }, - onParseJSONArray: function (key, value, index) { - var padding = '\n' + CodeUtil.getBlank(depth); - var innerPadding = padding + CodeUtil.getBlank(1); - var k = JSONResponse.replaceArray(key); - //还有其它字段冲突以及for循环的i冲突,解决不完的,只能让开发者自己抽出函数 var item = StringUtil.addSuffix(k, 'Item'); - var type = CodeUtil.getJavaTypeFromJS('item', value[0], false); + onParseChildOther: function (key, value, index, isOuter) { + var v; //避免改变原来的value + if (value == null) { + v = 'nil'; + } + else if (value instanceof Array) { + v = this.onParseArray(key, value, index, isOuter); + } + else if (typeof value == 'string') { + v = '"' + value + '"'; + } + else { + v = value + } - var s = '\n' + padding + '//' + key + '<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<'; + return (index > 0 ? ',\n' : '') + (key == null ? '' : (isOuter ? padding : nextPadding) + '"' + key + '": ') + v; + } + }) - s += padding + 'JSONArray ' + k + ' = JSON.nullToEmpty(' + name + '.getJSONArray("' + key + '"));'; + }, - s += '\n' + padding + '//TODO 把这段代码抽取一个函数,以免for循环嵌套时 i 冲突 或 id等其它字段冲突'; + /**生成封装 Web-Go 请求 JSON 的代码 + * 只需要把所有 对象标识{} 改为数组标识 [] + * @param name + * @param reqObj + * @param depth + * @return parseCode + */ + parseGoRequest: function(name, reqObj, depth) { + if (depth == null || depth < 0) { + depth = 0; + } - s += padding + type + ' item;'; + var isEmpty = true; + if (reqObj instanceof Array) { + isEmpty = reqObj.length <= 0; + } + else if (reqObj instanceof Object) { + isEmpty = Object.keys(reqObj).length <= 0; + } - s += padding + 'for (int i = 0; i < ' + k + '.size(); i++) {'; + var padding = CodeUtil.getBlank(depth); + var nextPadding = CodeUtil.getBlank(depth + 1); - s += innerPadding + 'item = ' + k + '.get' + type + '(i);'; - s += innerPadding + 'if (item == null) {'; - s += innerPadding + ' continue;'; - s += innerPadding + '}'; - //不能生成N个,以第0个为准,可能会不全,剩下的由开发者自己补充。 for (var i = 0; i < value.length; i ++) { - if (value[0] instanceof Object) { - s += CodeUtil.parseJavaResponse('item', value[0], depth + 1); - } - // } + return CodeUtil.parseCode(name, reqObj, { - s += padding + '}'; + onParseParentStart: function () { + return isEmpty ? 'map[string]interface{} {' : 'map[string]interface{} {\n'; + }, - s += padding + '//' + key + '>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>\n'; + onParseParentEnd: function () { + return isEmpty ? '}' : ',\n' + padding + '}'; + }, - return s; + onParseChildArray: function (key, value, index) { + return (index > 0 ? ',\n' : '') + nextPadding + '"' + key + '": ' + CodeUtil.parseGoRequest(key, value, depth + 1); }, - onParseJSONObject: function (key, value, index) { - var padding = '\n' + CodeUtil.getBlank(depth); - var k = StringUtil.firstCase(JSONResponse.getSimpleName(key)); + onParseChildObject: function (key, value, index) { + return (index > 0 ? ',\n' : '') + nextPadding + '"' + key + '": ' + CodeUtil.parseGoRequest(key, value, depth + 1); + }, - var s = '\n' + padding + '//' + key + '<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<'; + onParseArray: function (key, value, index, isOuter) { + var isEmpty = value.length <= 0; + var s = '[]interface{} {' + (isEmpty ? '' : '\n'); - s += padding + 'JSONObject ' + k + ' = JSON.nullToEmpty(' + name + '.getJSONObject("' + key + '"));\n' + var inner = ''; + var innerPadding = isOuter ? nextPadding : CodeUtil.getBlank(depth + 2); + for (var i = 0; i < value.length; i ++) { + inner += (i > 0 ? ',\n' : '') + innerPadding + CodeUtil.parseGoRequest(null, value[i], depth + (isOuter ? 1 : 2)); + } + s += inner; - s += CodeUtil.parseJavaResponse(k, value, depth); + s += isEmpty ? '}' : ',\n' + (isOuter ? padding : nextPadding) + '}'; + return s; + }, - s += padding + '//' + key + '>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>\n'; + onParseChildOther: function (key, value, index, isOuter) { + var v; //避免改变原来的value + if (value == null) { + v = 'nil'; + } + else if (value instanceof Array) { + v = this.onParseArray(key, value, index, isOuter); + } + else if (typeof value == 'string') { + v = '"' + value + '"'; + } + else { + v = value + } - return s; + return (index > 0 ? ',\n' : '') + (key == null ? '' : (isOuter ? padding : nextPadding) + '"' + key + '": ') + v; } }) }, +// /**解析出 生成iOS-Objective-C请求JSON 的代码 +// * 只需要把所有 对象标识{} 改为数组标识 [] +// * @param name +// * @param reqObj +// * @param depth +// * @return parseCode +// */ +// parseObjectiveCRequest: function(name, reqObj, depth) { +// return CodeUtil.parseSwiftRequest(name, reqObj, depth); +// }, - /**解析出 生成请求JSON 的代码 + /**解析出 生成Android-Kotlin 请求JSON 的代码 * @param name * @param reqObj - * @param callback Object,带以下回调函数function: - * 解析父对象Parent的onParseParentStart和onParseParentEnd, - * 解析APIJSON数组Object的onParseArray, - * 解析普通Object的onParseObject, - * 解析其它键值对的onParseOther. + * @param depth + * @return parseCode + * @return isSmart 是否智能 + */ + parseKotlinRequest: function(name, reqObj, depth, isSmart, isArrayItem, useVar4Value, type, host, url, comment, isRESTful) { + if (depth == null || depth < 0) { + depth = 0; + } + var isEmpty = true; + if (reqObj instanceof Array) { + isEmpty = reqObj.length <= 0; + } + else if (reqObj instanceof Object) { + isEmpty = Object.keys(reqObj).length <= 0; + } + + var padding = CodeUtil.getBlank(depth); + var nextPadding = CodeUtil.getBlank(depth + 1); + var nextNextPadding = CodeUtil.getBlank(depth + 2); + + if (depth <= 0) { + //RESTful 等非 APIJSON 规范的 API <<<<<<<<<<<<<<<<<<<<<<<<<< + var requestMethod = StringUtil.isEmpty(type, true) || type == 'PARAM' ? 'GET' : 'POST'; + + url = url || ''; + + var lastIndex = url.lastIndexOf(CodeUtil.DIVIDER); + var methodUri = url; // lastIndex < 0 ? url : url.substring(lastIndex); + var methodName = JSONResponse.getVariableName(lastIndex < 0 ? url : url.substring(lastIndex + 1)); + + url = url.substring(0, lastIndex); + lastIndex = url.lastIndexOf(CodeUtil.DIVIDER); + var varName = JSONResponse.getVariableName(lastIndex < 0 ? url : url.substring(lastIndex + 1)); + var modelName = StringUtil.firstCase(varName, true); + + if (StringUtil.isEmpty(modelName, true) != true) { + var useStaticClass = type == 'JSON' && !isSmart + + var nextNextNextPadding = CodeUtil.getBlank(depth + 3); + var nextNextNextNextPadding = CodeUtil.getBlank(depth + 4); + + // var controllerUri = url; // lastIndex < 0 ? '' : url.substring(0, lastIndex); + var isPost = type != 'PARAM' && (methodUri.indexOf('post') >= 0 || methodUri.indexOf('add') >= 0 || methodUri.indexOf('create') >= 0); + var isPut = type != 'PARAM' && (methodUri.indexOf('put') >= 0 || methodUri.indexOf('edit') >= 0 || methodUri.indexOf('update') >= 0); + var isDelete = type != 'PARAM' && (methodUri.indexOf('delete') >= 0 || methodUri.indexOf('remove') >= 0 || methodUri.indexOf('del') >= 0); + var isWrite = isPost || isPut || isDelete; + var isGet = !isWrite; // methodUri.indexOf('get') >= 0 || methodUri.indexOf('fetch') >= 0 || methodUri.indexOf('query') >= 0; + var isList = isGet && (methodUri.indexOf('list') >= 0 || methodUri.indexOf('List') >= 0 || typeof reqObj.pageNum == 'number'); + + var dataType = isWrite ? 'Int' : (isList ? 'List<' + modelName + '>' : modelName); + + var requestType = (type == 'JSON' ? (isSmart ? 'RequestBody' : modelName + (isList ? 'List' : '') + 'Request') : (type == 'DATA' ? 'Map' : '')); + var responseType = modelName + (isList ? 'List' : '') + 'Response'; + var fullResponseType = responseType + '<' + dataType + '>' + + var str = ''; + if (reqObj != null) { + if (useStaticClass) { + str += '\nvar request = ' + CodeUtil.parseKotlinRequest(requestType, reqObj, depth, isSmart, isArrayItem, false, type, null, null, null, true); + } + else { + for (var k in reqObj) { + var v = reqObj[k]; + + if (v instanceof Object) { + var kn = isSmart ? JSONResponse.getVariableName(k) : CodeUtil.getKotlinTypeFromJS(k, v, false, false, false, !isSmart); + str += '\nvar ' + kn + ' = ' + CodeUtil.parseKotlinRequest(kn, v, depth, isSmart, isArrayItem, false, type, null, null, null, true); + } + } + } + } + + var s = '//调用示例' + (StringUtil.isEmpty(str, true) ? '' : '\n' + StringUtil.trim(str) + '\n') + + '\n' + methodName + '(' + (useStaticClass ? 'request' : CodeUtil.getCode4KotlinArgValues(reqObj, true)) + ')' + + if (isSmart) { + s += '\n' + nextPadding + '.enqueue(object : HttpCallbackImpl<' + fullResponseType + '>() {' + + '\n' + nextNextPadding + 'override fun onHttpSucceed(data: ' + fullResponseType + ', requestCode: Int) {' + + '\n' + nextNextNextPadding + 'super.onHttpSucceed(data, requestCode)' + + '\n' + nextNextNextPadding + '//TODO 继续处理' + + '\n' + nextNextPadding + '}' + + '\n' + nextPadding + '})\n\n' + } + else { + s += '\n' + nextPadding + '.enqueue(object : Callback<' + fullResponseType + '>() {' + + '\n' + nextNextPadding + 'override fun onFailure(call: Call<' + fullResponseType + '>, t: Throwable) {' + + '\n' + nextNextNextPadding + 'Toast.makeText(context, t.message, Toast.LENGTH_SHORT).show()' + + '\n' + nextNextNextPadding + '//TODO 继续处理' + + '\n' + nextNextPadding + '}' + + '\n' + nextNextPadding + 'override fun onResponse(call: Call<' + fullResponseType + '>, response: Response<' + fullResponseType + '>) {' + + '\n' + nextNextNextPadding + 'if (! response.isSuccessful()){' + + '\n' + nextNextNextNextPadding + 'onFailure(call, Exception(response.message()))' + + '\n' + nextNextNextNextPadding + 'return' + + '\n' + nextNextNextPadding + '}' + + '\n' + nextNextNextPadding + 'var body = response.body()' + + '\n' + nextNextNextPadding + 'if (! body.isSuccess()){' + + '\n' + nextNextNextNextPadding + 'onFailure(call, Exception(body.msg))' + + '\n' + nextNextNextNextPadding + 'return' + + '\n' + nextNextNextPadding + '}\n' + + '\n' + nextNextNextPadding + 'var data = body.data' + + '\n' + nextNextNextPadding + '//TODO 继续处理' + + '\n' + nextNextPadding + '}' + + '\n' + nextPadding + '})\n\n' + } + + //这里不用 Query-QueryMap ,而是直接 toJSONString 传给 String,是因为 QueryMap 会用 Retrofit/OKHttp 内部会取出值来拼接 + s += '\n//接口定义' + + '\n@Keep' + + '\ninterface ' + modelName + 'Service { // ApiService { // 建议统一用这个,方法都放进来' + + (type == 'JSON' ? '\n' + nextPadding + '@Headers("Content-Type: application/json;charset=UTF-8")' : (type == 'FORM' ? '\n' + nextPadding + '@FormUrlEncoded' : (type == 'DATA' ? '\n' + nextPadding + '@Multipart' : ''))) + + '\n' + nextPadding + '@' + requestMethod + '("' + methodUri + '")' + + '\n' + nextPadding + 'fun ' + methodName + '(' + (type == 'JSON' ? (isSmart ? '@Body requestBody: ' + requestType : '@Body req: ' + requestType) : (type == 'DATA' ? '@PartMap requestBodyMap: ' + requestType : CodeUtil.getCode4KotlinArgs(reqObj, true, type == 'PARAM' ? 'Query' : 'Field', !isSmart, !isSmart, true))) + '): Call<' + (isSmart ? 'JSONResponse' : fullResponseType) + '>' + + '\n' +'}\n' + + '\n/**请求方法' + + '\n * ' + StringUtil.trim(comment) + + '\n */' + + '\n@JvmStatic' + + '\nfun ' + methodName + '(' + (useStaticClass ? 'request: ' + requestType : CodeUtil.getCode4KotlinArgs(reqObj, true, null, true, false, false)) + '): ' + 'Call<' + (isSmart ? 'JSONResponse' : fullResponseType) + '>' + ' {' + + '\n' + (useStaticClass ? '' : (type == 'JSON' || type == 'DATA' ? (nextPadding + 'var request = ' + CodeUtil.parseKotlinRequest(name, reqObj, depth + 1, isSmart, isArrayItem, true, type, null, null, null, true)) : '') + + '\n' + (type != 'JSON' ? '' : '\n' + nextPadding + 'var requestBody = RequestBody.create(MediaType.parse("application/json;charset=UTF-8"), JSON.toJSONString(request))') + ) + + '\n' + nextPadding + 'var service = RETROFIT.create(' + modelName + 'Service.class)' + + '\n' + nextPadding + 'return service.' + methodName + '(' + (type == 'JSON' ? (isSmart ? 'requestBody' : 'request') : (type == 'DATA' ? 'request' : CodeUtil.getCode4KotlinArgs(reqObj, false, null, !isSmart, true, true))) + ')' + + '\n}\n' + + '\n' + '//Retrofit 实例,全局存一份,可改为单例' + + '\n' + 'const val RETROFIT = Retrofit.Builder()' + + '\n' + nextPadding + '.baseUrl("' + StringUtil.trim(host) + '")' + + '\n' + nextPadding + '.addConverterFactory(GsonConverterFactory.create())' + + '\n' + nextPadding + '.build()\n' + + + if (isSmart) { + s += '\n' + '//通用 HTTP 回调 API,全局保存一份' + + '\n@Keep' + + '\ninterface HttpCallback {' + + '\n' + nextPadding + 'fun onHttpFailed(code: Int, msg: String)' + + '\n' + nextPadding + 'fun onHttpSucceed(data: D, requestCode: Int)' + + '\n' + nextPadding + 'fun showToast(msg: String)' + + '\n' + nextPadding + 'fun showLoading()' + + '\n' + nextPadding + 'fun hideLoading()' + + '\n' + '}\n' + + '\n//通用 HTTP 回调解析类,全局存一份' + + '\n@Keep' + + '\nopen class HttpCallbackImpl : Callback, HttpCallback {\n' + + '\n' + nextPadding + 'companion object {' + + '\n' + nextNextPadding + 'const val TAG = "HttpCallbackImpl"' + + '\n' + nextPadding + '}\n' + + '\n' + nextPadding + 'open var isShowToast: Boolean = true' + + '\n' + nextPadding + 'open var isShowLoading: Boolean = true' + + '\n' + nextPadding + 'open var requestCode: Int = 0' + + '\n' + nextPadding + 'open var callback: Callback? = null' + + '\n' + nextPadding + 'open var httpCallback: HttpCallback? = null\n' + + '\n' + nextPadding + 'constructor HttpCallbackImpl(): super() {}' + + '\n' + nextPadding + 'constructor HttpCallbackImpl(callback: Callback) : this() {' + + '\n' + nextNextPadding + 'this.callback = callback' + + '\n' + nextPadding + '}' + + '\n' + nextPadding + 'constructor HttpCallbackImpl(httpCallback: HttpCallback?) : this() {' + + '\n' + nextNextPadding + 'this.httpCallback = httpCallback' + + '\n' + nextPadding + '}' + + '\n' + nextPadding + 'constructor HttpCallbackImpl(requestCode: Int, httpCallback: HttpCallback?) : this(httpCallback) {' + + '\n' + nextNextPadding + 'this.requestCode = requestCode' + + '\n' + nextPadding + '}' + + '\n' + nextPadding + 'constructor HttpCallbackImpl(isShowLoading: Boolean, httpCallback: HttpCallback?) : this(httpCallback) {' + + '\n' + nextNextPadding + 'this.isShowLoading = isShowLoading' + + '\n' + nextPadding + '}' + + '\n' + nextPadding + 'constructor HttpCallbackImpl(isShowToast: Boolean, isShowLoading: Boolean, httpCallback: HttpCallback?) : this(isShowLoading, httpCallback) {' + + '\n' + nextNextPadding + 'this.isShowToast = isShowToast' + + '\n' + nextPadding + '}' + + '\n' + nextPadding + 'constructor HttpCallbackImpl(requestCode: Int, isShowToast: Boolean, isShowLoading: Boolean, httpCallback: HttpCallback?) : this(isShowToast, isShowLoading, httpCallback) {' + + '\n' + nextNextPadding + 'this.requestCode = requestCode' + + '\n' + nextPadding + '}\n\n' + + '\n' + nextPadding + 'override fun onFailure(call: Call, t: Throwable) {' + + '\n' + nextNextPadding + 'try {' + + '\n' + nextNextNextPadding + 'callback?.onFailure(call, t)' + + '\n' + nextNextNextPadding + 'onHttpFailed(0, t.message)' + + '\n' + nextNextPadding + '} catch(e: Exception) {' + + '\n' + nextNextNextPadding + 'Log.e(TAG, "onFailure catch e: Exception = " + e.message)' + + '\n' + nextNextNextPadding + 'if (BuildConfig.DEBUG) {' + + '\n' + nextNextNextNextPadding + 'throw e' + + '\n' + nextNextNextPadding + '}' + + '\n' + nextNextNextPadding + 'CrashReport.postCatchedException(e)' + + '\n' + nextNextPadding + '}' + + '\n' + nextPadding + '}\n' + + '\n' + nextPadding + 'override fun onResponse(call: Call, response: Response<' + fullResponseType + '>) {' + + '\n' + nextNextPadding + 'try {' + + '\n' + nextNextNextPadding + 'callback?.onResponse(call, response)' + + '\n' + nextNextNextPadding + 'if (! response.isSuccessful()){' + + '\n' + nextNextNextNextPadding + 'onFailure(call, Exception(response.message()))' + + '\n' + nextNextNextNextPadding + 'return' + + '\n' + nextNextNextPadding + '}' + + '\n' + nextNextNextPadding + 'var body = response.body()' + + '\n' + nextNextNextPadding + 'if (body == null || ! body.isSuccess()){' + + '\n' + nextNextNextNextPadding + 'onFailure(call, Exception(body?.msg ?: "网络异常"))' + + '\n' + nextNextNextNextPadding + 'return' + + '\n' + nextNextNextPadding + '}' + + '\n' + nextNextNextPadding + 'onHttpSucceed(body.data, requestCode)' + + '\n' + nextNextPadding + '} catch(e: Exception) {' + + '\n' + nextNextNextPadding + 'Log.e(TAG, "onResponse catch e: Exception = " + e.message)' + + '\n' + nextNextNextPadding + 'if (BuildConfig.DEBUG) {' + + '\n' + nextNextNextNextPadding + 'throw e' + + '\n' + nextNextNextPadding + '}' + + '\n' + nextNextNextPadding + 'CrashReport.postCatchedException(e)' + + '\n' + nextNextPadding + '}' + + '\n' + nextPadding + '}\n' + + '\n' + nextPadding + 'override fun onHttpFailed(code: Int, msg: String) {' + + '\n' + nextNextPadding + 'if (isShowLoading) {' + + '\n' + nextNextNextPadding + 'hideLoading()' + + '\n' + nextNextPadding + '}' + + '\n' + nextNextPadding + 'if (isShowToast) {' + + '\n' + nextNextNextPadding + 'showToast(msg)' + + '\n' + nextNextPadding + '}' + + '\n' + nextNextPadding + 'httpCallback?.onHttpFailed(code, msg)' + + '\n' + nextPadding + '}\n' + + '\n' + nextPadding + 'override fun onHttpSucceed(data: D, requestCode: Int) {' + + '\n' + nextNextPadding + 'if (isShowLoading) {' + + '\n' + nextNextNextPadding + 'hideLoading()' + + '\n' + nextNextPadding + '}' + + '\n' + nextNextPadding + 'httpCallback?.onHttpSucceed(data, requestCode)' + + '\n' + nextPadding + '}\n' + + '\n' + nextPadding + 'override fun showToast(msg: String) {' + + '\n' + nextNextPadding + 'httpCallback?.showToast(msg)' + + '\n' + nextPadding + '}\n' + + '\n' + nextPadding + 'override fun showLoading() {' + + '\n' + nextNextPadding + 'httpCallback?.showLoading()' + + '\n' + nextPadding + '}\n' + + '\n' + nextPadding + 'override fun hideLoading() {' + + '\n' + nextNextPadding + 'httpCallback?.hideLoading()' + + '\n' + nextPadding + '}\n' + + '\n' + '}\n' + } + else { + if (isList) { + modelName += 'List'; + varName += 'List'; + } + + s += '\n\n//回调实体类' + + '\n@Keep' + + '\nopen class ' + responseType + ' : BaseResponse {' + + '\n' + nextPadding + '@Transient' + + '\n' + nextPadding + 'open var ' + varName + ': ' + dataType + CodeUtil.initEmptyValue4Type(dataType, true, true) + '\n' + + '\n}\n' + + '\n//通用 HTTP 解析实体基类,全局存一份' + + '\n@Keep' + + '\nopen class BaseResponse {' + + '\n' + nextPadding + '@Transient' + + '\n' + nextPadding + 'open var code: Int' + CodeUtil.initEmptyValue4Type('Int', true, true) + '\n' + + '\n' + nextPadding + '@Transient' + + '\n' + nextPadding + 'open var msg: String' + CodeUtil.initEmptyValue4Type('String', true, true) + '\n' + + '\n' + nextPadding + '@Transient' + + '\n' + nextPadding + 'open var data: T? = null\n' + + '\n' + nextPadding + 'open fun isSuccess(): Boolean {' + + '\n' + nextNextPadding + 'return code == 200' + + '\n' + nextPadding + '}\n' + + '\n' + '}'; + + } + + return s + (isSmart || type != 'JSON' ? '' : '\n\n//请求实体类\n' + StringUtil.trim(CodeUtil.parseKotlinClasses(requestType, reqObj, 0, false, false))); + } + //RESTful 等非 APIJSON 规范的 API >>>>>>>>>>>>>>>>>>>>>>>>>> + } + + var useStaticClass = isRESTful && type == 'JSON' && isSmart != true && StringUtil.isEmpty(name, true) != true + + var parentKey = JSONObject.isArrayKey(name) + ? JSONResponse.getVariableName(CodeUtil.getItemKey(name)) + (depth <= 1 ? '' : depth) + : CodeUtil.getTableKey(JSONResponse.getVariableName(name)); + + return CodeUtil.parseCode(name, reqObj, { + + onParseParentStart: function () { + if (useStaticClass) { + return StringUtil.firstCase(JSONResponse.getVariableName(name), true) + '()' + (isEmpty ? '' : '.apply {\n') + } + return useVar4Value && type == 'DATA' ? 'mapOf(\n' : (isEmpty ? 'HashMap(' : 'mapOf(\n'); + }, + + onParseParentEnd: function () { + if (useStaticClass) { + return isEmpty ? '' : '\n' + padding + '}' + } + return isEmpty ? ')' : '\n' + padding + ')'; + }, + + onParseChildArray: function (key, value, index) { + if (useVar4Value) { + return this.onParseChildOther(key, value, index); + } + var vn = useStaticClass ? JSONResponse.getVariableName(key) : null + var kn = useStaticClass ? StringUtil.firstCase(JSONResponse.getVariableName(key), true) : null + return (index <= 0 ? '' : (useStaticClass ? '\n' : ',\n')) + nextPadding + (useStaticClass ? vn + ' = ' : '"' + key + '" to ') + CodeUtil.parseKotlinRequest(useStaticClass ? kn : key, value, depth + 1, isSmart, isArrayItem, useVar4Value, type, null, null, null, isRESTful); + }, + + onParseChildObject: function (key, value, index) { + if (useVar4Value) { + return this.onParseChildOther(key, value, index); + } + var vn = useStaticClass ? JSONResponse.getVariableName(key) : null + var kn = useStaticClass ? StringUtil.firstCase(JSONResponse.getVariableName(key), true) : null + return (index <= 0 ? '' : (useStaticClass ? '\n' : ',\n')) + nextPadding + (useStaticClass ? vn + ' = ' : '"' + key + '" to ') + CodeUtil.parseKotlinRequest(useStaticClass ? kn : key, value, depth + 1, isSmart, isArrayItem, useVar4Value, type, null, null, null, isRESTful); + }, + + onParseArray: function (key, value, index, isOuter) { + if (useVar4Value) { + return this.onParseChildOther(key, value, index, isOuter); + } + + var isEmpty = value.length <= 0; + var s = isEmpty ? 'ArrayList(' : 'listOf(\n'; + + if (isEmpty != true) { + var inner = ''; + var innerPadding = isOuter ? nextPadding : nextNextPadding; + + for (var i = 0; i < value.length; i ++) { + inner += (i > 0 ? ',\n' : '') + innerPadding + CodeUtil.parseKotlinRequest(null, value[i], depth + (isOuter ? 1 : 2), isSmart, isArrayItem, useVar4Value, type, null, null, null, isRESTful); + } + s += inner; + } + + s += isEmpty ? ')' : ('\n' + (isOuter ? padding : nextPadding) + ')'); + + return s; + }, + + onParseChildOther: function (key, value, index, isOuter) { + var valStr; + if (useVar4Value != true && value instanceof Array) { + valStr = this.onParseArray(key, value, index, isOuter); + } + else { + valStr = useVar4Value ? JSONResponse.getVariableName(key) : CodeUtil.getCode4Value(CodeUtil.LANGUAGE_KOTLIN, value); + if (useVar4Value && type == 'DATA') { + if (value instanceof Object) { + valStr = 'JSON.toJSONString(' + valStr + ')'; + } + valStr = 'RequestBody.create(MediaType.parse("multipart/form-data", ' + valStr + ')'; + } + } + + return (index <= 0 ? '' : (useStaticClass ? '\n' : ',\n')) + (key == null ? '' : (isOuter ? padding : nextPadding) + + (useStaticClass ? JSONResponse.getVariableName(key) + ' = ' : '"' + key + '" to ')) + valStr; + } + }) + + }, + + + /**解析出 生成Android-Java请求JSON 的代码 + * @param name + * @param reqObj + * @param depth + * @return parseCode + * @return isSmart 是否智能 + */ + parseJavaRequest: function(name, reqObj, depth, isSmart, isArrayItem, useVar4Value, type, url, comment) { + name = name || 'request' + if (depth == null || depth < 0) { + depth = 0; + } + + + var prefix = CodeUtil.getBlank(depth); + var nextPrefix = CodeUtil.getBlank(depth + 1); + var nextNextPrefix = CodeUtil.getBlank(depth + 2); + + if (depth <= 0) { + //RESTful 等非 APIJSON 规范的 API <<<<<<<<<<<<<<<<<<<<<<<<<< + var requestMethod = StringUtil.isEmpty(type, true) || type == 'PARAM' ? 'GET' : 'POST'; + + url = url || ''; + + var lastIndex = url.lastIndexOf(CodeUtil.DIVIDER); + var methodUri = url; // lastIndex < 0 ? url : url.substring(lastIndex); + var methodName = JSONResponse.getVariableName(lastIndex < 0 ? url : url.substring(lastIndex + 1)); + + url = url.substring(0, lastIndex); + lastIndex = url.lastIndexOf(CodeUtil.DIVIDER); + var varName = JSONResponse.getVariableName(lastIndex < 0 ? url : url.substring(lastIndex + 1)); + var modelName = StringUtil.firstCase(varName, true); + + if (StringUtil.isEmpty(modelName, true) != true) { + // var controllerUri = url; // lastIndex < 0 ? '' : url.substring(0, lastIndex); + var isPost = type != 'PARAM' && (methodUri.indexOf('post') >= 0 || methodUri.indexOf('add') >= 0 || methodUri.indexOf('create') >= 0); + var isPut = type != 'PARAM' && (methodUri.indexOf('put') >= 0|| methodUri.indexOf('edit') >= 0 || methodUri.indexOf('update') >= 0); + var isDelete = type != 'PARAM' && (methodUri.indexOf('delete') >= 0 || methodUri.indexOf('remove') >= 0 || methodUri.indexOf('del') >= 0); + var isWrite = isPost || isPut || isDelete; + var isGet = ! isWrite; // methodUri.indexOf('get') >= 0 || methodUri.indexOf('fetch') >= 0 || methodUri.indexOf('query') >= 0; + var isList = isGet && (methodUri.indexOf('list') >= 0 || methodUri.indexOf('List') >= 0 || typeof reqObj.pageNum == 'number'); + + var dataType = isWrite ? 'Integer' : (isList ? 'List<' + modelName + '>' : modelName); + + var responseType = modelName + (isList ? 'List' : '') + 'Response'; + + var str = ''; + if (reqObj != null) { + for (var k in reqObj) { + var v = reqObj[k]; + + if (v instanceof Object) { + str += '\n' + CodeUtil.parseJavaRequest(JSONResponse.getVariableName(k), v, depth, isSmart); + } + } + } + + var s = '//调用示例' + (StringUtil.isEmpty(str, true) ? '' : '\n' + StringUtil.trim(str) + '\n') + + '\n' + methodName + '(' + CodeUtil.getCode4JavaArgValues(reqObj, true) + ');\n' + + '\n/**' + + '\n * ' + StringUtil.trim(comment) + + '\n */' + + '\npublic static ' + 'Call<' + (isSmart ? 'JSONResponse' : responseType + '<' + dataType + '>') + '>' + ' ' + methodName + '(' + CodeUtil.getCode4JavaArgs(reqObj, true, null, ! isSmart) + ') {\n' + + (type == 'JSON' || type == 'DATA' ? CodeUtil.parseJavaRequest(name, reqObj, depth + 1, isSmart, isArrayItem, true, type, url) : '') + '\n' + + (type == 'JSON' ? '\n' + nextPrefix + 'RequestBody requestBody = RequestBody.create(MediaType.parse("application/json;charset=UTF-8"), JSON.toJSONString(request));' : '') + + '\n' + nextPrefix + modelName + 'Service service = retrofit.create(' + modelName + 'Service.class);' + + '\n' + nextPrefix + 'return service.' + methodName + '(' + (type == 'JSON' ? 'requestBody' : (type == 'DATA' ? 'request' : CodeUtil.getCode4JavaArgs(reqObj, false, null, ! isSmart, true, true))) + ');' + + '\n}\n'; + + //这里不用 Query-QueryMap ,而是直接 toJSONString 传给 String,是因为 QueryMap 会用 Retrofit/OKHttp 内部会取出值来拼接 + s += '\npublic interface ' + modelName + 'Service { // ApiService { //建议统一用这个,方法都放进来' + + (type == 'JSON' ? '\n' + nextPrefix + '@Headers("Content-Type: application/json;charset=UTF-8")' : (type == 'FORM' ? '\n' + nextPrefix + '@FormUrlEncoded': (type == 'DATA' ? '\n' + nextPrefix + '@Multipart': ''))) + + '\n' + nextPrefix + '@' + requestMethod + '("' + methodUri + '")' + + '\n' + nextPrefix + 'Call<' + (isSmart ? 'JSONResponse' : responseType + '<' + dataType + '>') + '>' + ' ' + methodName + '(' + (type == 'JSON' ? '@Body RequestBody requestBody' : (type == 'DATA' ? '@PartMap Map requestBodyMap' : CodeUtil.getCode4JavaArgs(reqObj, true, type == 'PARAM' ? 'Query' : 'Field', ! isSmart, true))) + ');' + + '\n' + '}'; + + if (! isSmart) { + if (isList) { + modelName += 'List'; + varName += 'List'; + } + + s += '\n\n' + + 'public class ' + responseType + ' extends Response {\n' + + nextPrefix + 'private ' + dataType + ' ' + varName + ';\n\n' + + nextPrefix + 'public '+ dataType + ' get' + modelName + '() {\n' + + nextNextPrefix + 'return ' + varName + ';\n' + + nextPrefix + '}\n' + + nextPrefix + 'public ' + responseType + ' set' + modelName + '(' + dataType + ' ' + varName + ') {\n' + + nextNextPrefix + 'this.' + varName + ' = ' + varName + ';\n' + + nextNextPrefix + 'return this;\n' + + nextPrefix + '}\n' + + '}'; + + s += '\n\n' + + 'public class Response {\n' + + nextPrefix + 'private int code;\n' + + nextPrefix + 'private String msg;\n' + + nextPrefix + 'private T data;\n\n' + + nextPrefix + 'public int getCode() {\n' + + nextNextPrefix + 'return code;\n' + + nextPrefix + '}\n' + + nextPrefix + 'public Response setCode(int code) {\n' + + nextNextPrefix + 'this.code = code;\n' + + nextNextPrefix + 'return this;\n' + + nextPrefix + '}\n\n' + + nextPrefix + 'public String getMsg() {\n' + + nextNextPrefix + 'return msg;\n' + + nextPrefix + '}\n' + + nextPrefix + 'public Response setMsg(String msg) {\n' + + nextNextPrefix + 'this.msg = msg;\n' + + nextNextPrefix + 'return this;\n' + + nextPrefix + '}\n\n' + + nextPrefix + 'public T getData() {\n' + + nextNextPrefix + 'return data;\n' + + nextPrefix + '}\n' + + nextPrefix + 'public Response setData(T data) {\n' + + nextNextPrefix + 'this.data = data;\n' + + nextNextPrefix + 'return this;\n' + + nextPrefix + '}\n' + + '}'; + + } + + return s; + } + //RESTful 等非 APIJSON 规范的 API >>>>>>>>>>>>>>>>>>>>>>>>>> + } + + + var parentKey = JSONObject.isArrayKey(name) + ? JSONResponse.getVariableName(CodeUtil.getItemKey(name)) + (depth <= 1 ? '' : depth) + : CodeUtil.getTableKey(JSONResponse.getVariableName(name)); + + return CodeUtil.parseCode(name, reqObj, { + + onParseParentStart: function () { + if (isArrayItem == true) { + isArrayItem = false; + return ''; + } + var s = '\n' + prefix + (useVar4Value && type == 'DATA' ? 'Map' : (isSmart ? 'JSONRequest' : 'Map')) + ' ' + parentKey + ' = new ' + (isSmart ? 'JSONRequest' : 'LinkedHashMap<>') + '();'; + + return s; + }, + + onParseParentEnd: function () { + return ''; + }, + + onParseChildArray: function (key, value, index) { + if (useVar4Value) { + return this.onParseChildOther(key, value, index); + } + + var s = '\n\n' + prefix + '{ ' + '// ' + key + ' <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<'; + + var count = isSmart ? (value.count || 0) : 0; + var page = isSmart ? (value.page || 0) : 0; + var query = isSmart ? value.query : null; + var join = isSmart ? value.join : null; + + log(CodeUtil.TAG, 'parseJavaRequest for count = ' + count + '; page = ' + page); + + if (isSmart) { + delete value.count; + delete value.page; + delete value.query; + delete value.join; + } + + s += CodeUtil.parseJavaRequest(key, value, depth + 1, isSmart); + + log(CodeUtil.TAG, 'parseJavaRequest for delete >> count = ' + count + '; page = ' + page); + + var name = JSONResponse.getVariableName(CodeUtil.getItemKey(key)) + (depth <= 0 ? '' : depth + 1); + + if (isSmart) { + var alias = key.substring(0, key.length - 2); + + s += '\n\n'; + if (query != null) { + s += nextPrefix + name + '.setQuery(' + (CodeUtil.QUERY_TYPE_CONSTS[query] || CodeUtil.QUERY_TYPE_CONSTS[0]) + ');\n'; + } + if (StringUtil.isEmpty(join, true) == false) { + s += nextPrefix + name + '.setJoin("' + join + '");\n'; + } + + s += nextPrefix + parentKey + '.putAll(' + name + '.toArray(' + + count + ', ' + page + (alias.length <= 0 ? '' : ', "' + alias + '"') + '));'; + } + else { + s += '\n\n' + CodeUtil.getBlank(depth + 1) + parentKey + '.put("' + key + '", ' + name + ');'; + } + + s += '\n' + prefix + '} ' + '// ' + key + ' >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>\n'; + + return s; + }, + + onParseChildObject: function (key, value, index) { + if (useVar4Value) { + return this.onParseChildOther(key, value, index); + } + + var s = '\n\n' + prefix + '{ ' + '// ' + key + ' <<<<<<<<<<<<<<<<<<<<<<<<<<<<<'; + + var isTable = isSmart && JSONObject.isTableKey(JSONResponse.getTableName(key)); + + var column = isTable ? value['@column'] : null; + var group = isTable ? value['@group'] : null; + var having = isTable ? value['@having'] : null; + var order = isTable ? value['@order'] : null; + var combine = isTable ? value['@combine'] : null; + var schema = isTable ? value['@schema'] : null; + var database = isTable ? value['@database'] : null; + var datasource = isTable ? value['@datasource'] : null; + var raw = isTable ? value['@raw'] : null; + var role = isTable ? value['@role'] : null; + var explain = isTable ? value['@explain'] : null; + var json = isTable ? value['@json'] : null; + var cache = isTable ? value['@cache'] : null; + + if (isTable) { + delete value['@column']; + delete value['@group']; + delete value['@having']; + delete value['@order']; + delete value['@combine']; + delete value['@schema']; + delete value['@database']; + delete value['@datasource']; + delete value['@raw']; + delete value['@role']; + delete value['@explain']; + delete value['@json']; + delete value['@cache']; + } + + s += CodeUtil.parseJavaRequest(key, value, depth + 1, isSmart); + + const name = CodeUtil.getTableKey(JSONResponse.getVariableName(key)); + if (isTable) { + s = column == null ? s : s + '\n' + nextPrefix + name + '.setColumn(' + CodeUtil.getCode4Value(CodeUtil.LANGUAGE_JAVA, column) + ');'; + s = group == null ? s : s + '\n' + nextPrefix + name + '.setGroup(' + CodeUtil.getCode4Value(CodeUtil.LANGUAGE_JAVA, group) + ');'; + s = having == null ? s : s + '\n' + nextPrefix + name + '.setHaving(' + CodeUtil.getCode4Value(CodeUtil.LANGUAGE_JAVA, having) + ');'; + s = order == null ? s : s + '\n' + nextPrefix + name + '.setOrder(' + CodeUtil.getCode4Value(CodeUtil.LANGUAGE_JAVA, order) + ');'; + s = combine == null ? s : s + '\n' + nextPrefix + name + '.setCombine(' + CodeUtil.getCode4Value(CodeUtil.LANGUAGE_JAVA, combine) + ');'; + s = schema == null ? s : s + '\n' + nextPrefix + name + '.setSchema(' + CodeUtil.getCode4Value(CodeUtil.LANGUAGE_JAVA, schema) + ');'; + s = database == null ? s : s + '\n' + nextPrefix + name + '.setDatabase(' + CodeUtil.getCode4Value(CodeUtil.LANGUAGE_JAVA, database) + ');'; + s = datasource == null ? s : s + '\n' + nextPrefix + name + '.setDatasource(' + CodeUtil.getCode4Value(CodeUtil.LANGUAGE_JAVA, datasource) + ');'; + s = raw == null ? s : s + '\n' + nextPrefix + name + '.setRaw(' + CodeUtil.getCode4Value(CodeUtil.LANGUAGE_JAVA, raw) + ');'; + s = role == null ? s : s + '\n' + nextPrefix + name + '.setRole(' + CodeUtil.getCode4Value(CodeUtil.LANGUAGE_JAVA, role) + ');'; + s = explain == null ? s : s + '\n' + nextPrefix + name + '.setExplain(' + CodeUtil.getCode4Value(CodeUtil.LANGUAGE_JAVA, explain) + ');'; + s = json == null ? s : s + '\n' + nextPrefix + name + '.setJson(' + CodeUtil.getCode4Value(CodeUtil.LANGUAGE_JAVA, json) + ');'; + s = cache == null ? s : s + '\n' + nextPrefix + name + '.setCache(' + CodeUtil.getCode4Value(CodeUtil.LANGUAGE_JAVA, cache) + ');'; + } + + s += '\n\n' + nextPrefix + parentKey + '.put("' + key + '", ' + name + ');'; + + s += '\n' + prefix + '} ' + '// ' + key + ' >>>>>>>>>>>>>>>>>>>>>>>>>>>>>\n'; + + return s; + }, + + onParseArray: function (key, value, index, isOuter) { + if (useVar4Value) { + return this.onParseChildOther(key, value, index, isOuter); + } + + var s = '\n\n' + prefix + '{ ' + '// ' + key + ' <<<<<<<<<<<<<<<<<<<<<<<<<<<<<'; + + var varName = JSONResponse.formatKey(key, true, false, false, true, true, true) + + if (isArrayItem != true) { + s += '\n' + nextPrefix + (isSmart ? 'JSONArray ' : 'List ') + varName + ' = new ' + (isSmart ? 'JSONArray' : 'ArrayList<>') + '();'; + } + + if (value.length > 0) { + var itemName = StringUtil.addSuffix(varName, 'Item') + (depth <= 0 ? '' : depth + 1); + + var innerPrefix = CodeUtil.getBlank(depth + 2); + var inner = ''; + + for (var i = 0; i < value.length; i++) { + if (value[i] instanceof Object == false) { + inner += '\n' + nextPrefix + varName + '.add(' + CodeUtil.getCode4Value(CodeUtil.LANGUAGE_JAVA, value[i]) + ');'; + } + else { + inner += '\n\n' + nextPrefix + '{ ' + '// ' + key + '[' + i + '] <<<<<<<<<<<<<<<<<<<<<<<<<<<<<'; + + if (value[i] instanceof Array) { + inner += '\n' + innerPrefix + (isSmart ? 'JSONArray ' : 'List ') + itemName + ' = new ' + (isSmart ? 'JSONArray' : 'ArrayList<>') + '();'; + } + else if (value[i] instanceof Object) { + inner += '\n' + innerPrefix + (isSmart ? 'JSONObject ' : 'Map ') + itemName + ' = new ' + (isSmart ? 'JSONObject' : 'LinkedHashMap<>') + '();'; + } + else { + inner += '//FIXME 这里不可能出现 value[' + i + '] 类型为 ' + (typeof value[i]) + '!'; //不可能 + } + + inner += CodeUtil.getCode4Value(CodeUtil.LANGUAGE_JAVA, value[i], itemName, depth + 1, isSmart, true, CodeUtil.parseJavaRequest); + inner += '\n' + innerPrefix + varName + '.add(' + itemName + ');'; + inner += '\n' + nextPrefix + '} ' + '// ' + key + '[' + i + '] >>>>>>>>>>>>>>>>>>>>>>>>>>>>>\n'; + } + } + + s += inner; + } + + if (isArrayItem != true) { + if (reqObj instanceof Array) { + s += '\n\n' + nextPrefix + parentKey + '.add(' + varName + ');'; + } + else if (reqObj instanceof Object) { + s += '\n\n' + nextPrefix + parentKey + '.put("' + key + '", ' + varName + ');'; + } + else { + s += '//FIXME 这里不可能出现 reqObj 类型为 ' + (typeof reqObj) + '!'; //不可能 + } + } + + s += '\n' + prefix + '} ' + '// ' + key + ' >>>>>>>>>>>>>>>>>>>>>>>>>>>>>\n'; + return s; + }, + + onParseChildOther: function (key, value, index, isOuter) { + if (useVar4Value != true && value instanceof Array) { + return this.onParseArray(key, value, index, isOuter); + } + + var valStr = useVar4Value ? JSONResponse.getVariableName(key) : CodeUtil.getCode4Value(CodeUtil.LANGUAGE_JAVA, value); + if (useVar4Value && type == 'DATA') { + if (value instanceof Object) { + valStr = 'JSON.toJSONString(' + valStr + ')'; + } + valStr = 'RequestBody.create(MediaType.parse("multipart/form-data", ' + valStr + ')'; + } + + if (depth <= 0 && isSmart) { + if (key == 'tag') { + return '\n' + parentKey + '.setTag(' + valStr + ');'; + } + if (key == 'version') { + return '\n' + parentKey + '.setVersion(' + valStr + ');'; + } + if (key == 'format') { + return '\n' + parentKey + '.setFormat(' + valStr + ');'; + } + if (key == '@schema') { + return '\n' + parentKey + '.setSchema(' + valStr + ');'; + } + if (key == '@database') { + return '\n' + parentKey + '.setDatabase(' + valStr + ');'; + } + if (key == '@datasource') { + return '\n' + parentKey + '.setDatasource(' + valStr + ');'; + } + if (key == '@role') { + return '\n' + parentKey + '.setRole(' + valStr + ');'; + } + if (key == '@explain') { + return '\n' + parentKey + '.setExplain(' + valStr + ');'; + } + } + + return '\n' + prefix + parentKey + '.put("' + key + '", ' + valStr + ');'; + } + }) + + }, + + + + /**解析出 生成Android-Java请求JSON 的代码 + * @param name + * @param reqObj + * @param depth + * @return parseCode + * @return isSmart 是否智能 + */ + parseCppRequest: function(name, reqObj, depth, isSmart, isArrayItem) { + name = name || 'request' + if (depth == null || depth < 0) { + depth = 0; + } + + var parentKey = JSONObject.isArrayKey(name) + ? JSONResponse.getVariableName(CodeUtil.getItemKey(name)) + (depth <= 1 ? '' : depth) + : CodeUtil.getTableKey(JSONResponse.getVariableName(name)); + + var prefix = CodeUtil.getBlank(depth); + var nextPrefix = CodeUtil.getBlank(depth + 1); + + return (depth > 0 ? "" : "rapidjson::Document document;" + + "\nrapidjson::Document::AllocatorType& allocator = document.GetAllocator();\n" + ) + CodeUtil.parseCode(name, reqObj, { + + onParseParentStart: function () { + if (isArrayItem == true) { + isArrayItem = false; + return ''; + } + return '\n' + prefix + 'rapidjson::Value ' + parentKey + '(rapidjson::kObjectType);'; + }, + + onParseParentEnd: function () { + return ''; + }, + + onParseChildArray: function (key, value, index) { + var name = JSONResponse.getVariableName(CodeUtil.getItemKey(key)) + (depth <= 0 ? '' : depth + 1); + + var s = '\n\n' + prefix + '{ ' + '// ' + key + ' <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<'; + s += CodeUtil.parseCppRequest(key, value, depth + 1, isSmart); + s += '\n\n' + CodeUtil.getBlank(depth + 1) + parentKey + '.AddMember("' + key + '", ' + name + ', allocator);'; + s += '\n' + prefix + '} ' + '// ' + key + ' >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>\n'; + + return s; + }, + + onParseChildObject: function (key, value, index) { + const name = CodeUtil.getTableKey(JSONResponse.getVariableName(key)); + + var s = '\n\n' + prefix + '{ ' + '// ' + key + ' <<<<<<<<<<<<<<<<<<<<<<<<<<<<<'; + s += CodeUtil.parseCppRequest(key, value, depth + 1, isSmart); + s += '\n\n' + nextPrefix + parentKey + '.AddMember("' + key + '", ' + name + ', allocator);'; + s += '\n' + prefix + '} ' + '// ' + key + ' >>>>>>>>>>>>>>>>>>>>>>>>>>>>>\n'; + + return s; + }, + + onParseArray: function (key, value, index, isOuter) { + var s = '\n\n' + prefix + '{ ' + '// ' + key + ' <<<<<<<<<<<<<<<<<<<<<<<<<<<<<'; + + var varName = JSONResponse.formatKey(key, true, false, false, true, true, true) + + if (isArrayItem != true) { + s += '\n' + nextPrefix + 'rapidjson::Value ' + varName + '(rapidjson::kArrayType);'; + } + + if (value.length > 0) { + var itemName = StringUtil.addSuffix(varName, 'Item') + (depth <= 0 ? '' : depth + 1); + + var innerPrefix = CodeUtil.getBlank(depth + 2); + var inner = ''; + + for (var i = 0; i < value.length; i++) { + if (value[i] instanceof Object == false) { + inner += '\n' + nextPrefix + varName + '.PushBack(' + CodeUtil.getCode4Value(CodeUtil.LANGUAGE_C_PLUS_PLUS, value[i]) + ', allocator);'; + } + else { + inner += '\n\n' + nextPrefix + '{ ' + '// ' + key + '[' + i + '] <<<<<<<<<<<<<<<<<<<<<<<<<<<<<'; + if (value[i] instanceof Array) { + inner += '\n' + innerPrefix + 'rapidjson::Value ' + itemName + '(rapidjson::kArrayType);'; + } + else if (value[i] instanceof Object) { + inner += '\n' + innerPrefix + 'rapidjson::Value ' + itemName + '(rapidjson::kObjectType);'; + } + else { + inner += '//FIXME 这里不可能出现 value[' + i + '] 类型为 ' + (typeof value[i]) + '!'; //不可能 + } + + inner += CodeUtil.getCode4Value(CodeUtil.LANGUAGE_C_PLUS_PLUS, value[i], itemName, depth + 1, isSmart, true, CodeUtil.parseCppRequest); + inner += '\n' + innerPrefix + varName + '.PushBack(' + itemName + ', allocator);'; + inner += '\n' + nextPrefix + '} ' + '// ' + key + '[' + i + '] >>>>>>>>>>>>>>>>>>>>>>>>>>>>>\n'; + } + } + + s += inner; + } + + if (isArrayItem != true) { + if (reqObj instanceof Array) { + s += '\n\n' + nextPrefix + parentKey + '.PushBack(' + varName + ', allocator);'; + } + else if (reqObj instanceof Object) { + s += '\n\n' + nextPrefix + parentKey + '.AddMember("' + key + '", ' + varName + ', allocator);'; + } + else { + s += '//FIXME 这里不可能出现 reqObj 类型为 ' + (typeof reqObj) + '!'; //不可能 + } + } + + s += '\n' + prefix + '} ' + '// ' + key + ' >>>>>>>>>>>>>>>>>>>>>>>>>>>>>\n'; + return s; + }, + + onParseChildOther: function (key, value, index, isOuter) { + if (value instanceof Array) { + return this.onParseArray(key, value, index, isOuter); + } + return '\n' + prefix + parentKey + '.AddMember("' + key + '", ' + CodeUtil.getCode4Value(CodeUtil.LANGUAGE_C_PLUS_PLUS, value) + ', allocator);'; + } + }) + + }, + + + + /**生成 iOS-Swift 解析 Response JSON 的代码 + * @param name_ + * @param resObj + * @param depth + * @param isSmart + * @return parseCode + */ + parseSwiftResponse: function(name_, resObj, depth, isSmart) { + if (depth == null || depth < 0) { + depth = 0; + } + + var name = name_; //解决生成多余的解析最外层的初始化代码 + if (StringUtil.isEmpty(name, true)) { + name = 'response'; + } + + return CodeUtil.parseCode(name, resObj, { + + onParseParentStart: function () { + return depth > 0 || StringUtil.isEmpty(name_, true) == false ? '' : CodeUtil.getBlank(depth) + 'let ' + name + ': NSDictionary = try! NSJSONSerialization.JSONObjectWithData(resultJson!, options: .MutableContainers) as! NSDictionary \n'; + }, + + onParseParentEnd: function () { + return ''; + }, + + onParseChildArray: function (key, value, index) { + return this.onParseChildObject(key, value, index); + }, + + onParseChildObject: function (key, value, index) { + return this.onParseJSONObject(key, value, index); + }, + + onParseChildOther: function (key, value, index) { + + if (value instanceof Array) { + log(CodeUtil.TAG, 'parseSwiftResponse for typeof value === "array" >> ' ); + + return this.onParseJSONArray(key, value, index); + } + if (value instanceof Object) { + log(CodeUtil.TAG, 'parseSwiftResponse for typeof value === "array" >> ' ); + + return this.onParseJSONObject(key, value, index); + } + + var type = CodeUtil.getSwiftTypeFromJS(key, value); + var padding = '\n' + CodeUtil.getBlank(depth); + var varName = JSONResponse.getVariableName(key); + + return padding + 'let ' + varName + ': ' + type + ' = ' + name + '["' + key + '"] as! ' + type + + padding + 'print("' + name + '.' + varName + ' = " + ' + varName + ');'; + }, + + onParseJSONArray: function (key, value, index) { + value = value || [] + + var padding = '\n' + CodeUtil.getBlank(depth); + var innerPadding = padding + CodeUtil.getBlank(1); + + var k = JSONResponse.getVariableName(key); + var itemName = StringUtil.addSuffix(k, 'Item') + (depth <= 0 ? '' : depth); + + //还有其它字段冲突以及for循环的i冲突,解决不完的,只能让开发者自己抽出函数 var item = StringUtil.addSuffix(k, 'Item'); + var type = CodeUtil.getSwiftTypeFromJS('item', value[0]); + + var s = '\n' + padding + '// ' + key + ' <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<'; + + s += padding + 'let ' + k + ': NSArray = ' + name + '["' + key + '"] as! NSArray'; + + s += '\n' + padding + '// TODO 把这段代码抽取一个函数,以免for循环嵌套时 i 冲突 或 id等其它字段冲突'; + + + var indexName = 'i' + (depth <= 0 ? '' : depth); + if (isSmart) { + s += padding + 'for (' + indexName + ', ' + itemName + ') in ' + k + ' {'; + } + else { + s += padding + 'let ' + itemName + ': ' + type; + s += padding + 'for var ' + indexName + ' = 0; ' + indexName + ' < ' + k + '.size(); ' + indexName + '++ {'; + s += innerPadding + itemName + ' = ' + k + '[' + indexName + '] as! ' + type; + } + + s += innerPadding + 'if (' + itemName + ' == nil) {'; + s += innerPadding + ' continue'; + s += innerPadding + '}'; + s += innerPadding + 'print("\\n' + itemName + ' = ' + k + '[" + ' + indexName + ' + "] = \\n" + ' + itemName + ' + "\\n\\n"' + ')'; + s += innerPadding + '// TODO 你的代码\n'; + + //不能生成N个,以第0个为准,可能会不全,剩下的由开发者自己补充。 for (var i = 0; i < value.length; i ++) { + if (value[0] instanceof Object) { + s += CodeUtil.parseSwiftResponse(itemName, value[0], depth + 1, isSmart); + } + // } + + s += padding + '}'; + + s += padding + '// ' + key + ' >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>\n'; + + return s; + }, + + onParseJSONObject: function (key, value, index) { + var padding = '\n' + CodeUtil.getBlank(depth); + var k = JSONResponse.getVariableName(key); + + var s = '\n' + padding + '// ' + key + ' <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<'; + + s += padding + 'let ' + k + ': NSDictionary = ' + name + '["' + key + '"] as! NSDictionary\n' + + s += CodeUtil.parseSwiftResponse(k, value, depth, isSmart); + + s += padding + '// ' + key + ' >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>\n'; + + return s; + } + }) + + }, + + +// /**生成 iOS-CodeUtil.LANGUAGE_OBJECTIVE_C 解析 Response JSON 的代码 +// * @param name_ +// * @param resObj +// * @param depth +// * @return parseCode +// */ +// parseObjectiveCResponse: function(name_, resObj, depth) { +// if (depth == null || depth < 0) { +// depth = 0; +// } +// +// var name = name_; //解决生成多余的解析最外层的初始化代码 +// if (StringUtil.isEmpty(name, true)) { +// name = 'response'; +// } +// +// return CodeUtil.parseCode(name, resObj, { +// +// onParseParentStart: function () { +// return depth > 0 || StringUtil.isEmpty(name_, true) == false ? '' : CodeUtil.getBlank(depth) + 'let ' + name + ': NSDictionary = try! NSJSONSerialization.JSONObjectWithData(resultJson!, options: .MutableContainers) as! NSDictionary \n'; +// }, +// +// onParseParentEnd: function () { +// return ''; +// }, +// +// onParseChildArray: function (key, value, index) { +// return this.onParseChildObject(key, value, index); +// }, +// +// onParseChildObject: function (key, value, index) { +// return this.onParseJSONObject(key, value, index); +// }, +// +// onParseChildOther: function (key, value, index) { +// +// if (value instanceof Array) { +// log(CodeUtil.TAG, 'parseSwiftResponse for typeof value === "array" >> ' ); +// +// return this.onParseJSONArray(key, value, index); +// } +// if (value instanceof Object) { +// log(CodeUtil.TAG, 'parseSwiftResponse for typeof value === "array" >> ' ); +// +// return this.onParseJSONObject(key, value, index); +// } +// +// var type = CodeUtil.getSwiftTypeFromJS(key, value); +// var padding = '\n' + CodeUtil.getBlank(depth); +// var varName = JSONResponse.getVariableName(key); +// +// return padding + 'let ' + varName + ': ' + type + ' = ' + name + '["' + key + '"] as! ' + type +// + padding + 'print("' + name + '.' + varName + ' = " + ' + varName + ');'; +// }, +// +// onParseJSONArray: function (key, value, index) { +// value = value || [] +// +// var padding = '\n' + CodeUtil.getBlank(depth); +// var innerPadding = padding + CodeUtil.getBlank(1); +// +// var k = JSONResponse.getVariableName(key); +// var itemName = StringUtil.addSuffix(k, 'Item') + (depth <= 0 ? '' : depth); +// +// //还有其它字段冲突以及for循环的i冲突,解决不完的,只能让开发者自己抽出函数 var item = StringUtil.addSuffix(k, 'Item'); +// var type = CodeUtil.getSwiftTypeFromJS('item', value[0]); +// +// var s = '\n' + padding + '// ' + key + ' <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<'; +// +// s += padding + 'let ' + k + ': NSArray = ' + name + '["' + key + '"] as! NSArray'; +// +// s += '\n' + padding + '// TODO 把这段代码抽取一个函数,以免for循环嵌套时 i 冲突 或 id等其它字段冲突'; +// +// s += padding + 'let ' + itemName + ': ' + type; +// +// var indexName = 'i' + (depth <= 0 ? '' : depth); +// s += padding + 'for (int ' + indexName + ' = 0; ' + indexName + ' < ' + k + '.size(); ' + indexName + '++) {'; +// +// s += innerPadding + itemName + ' = ' + k + '[' + indexName + '] as! ' + type; +// s += innerPadding + 'if (' + itemName + ' == nil) {'; +// s += innerPadding + ' continue'; +// s += innerPadding + '}'; +// s += innerPadding + 'print("\\n' + itemName + ' = ' + k + '[" + ' + indexName + ' + "] = \\n" + ' + itemName + ' + "\\n\\n"' + ')'; +// s += innerPadding + '// TODO 你的代码\n'; +// +// //不能生成N个,以第0个为准,可能会不全,剩下的由开发者自己补充。 for (var i = 0; i < value.length; i ++) { +// if (value[0] instanceof Object) { +// s += CodeUtil.parseSwiftResponse(itemName, value[0], depth + 1); +// } +// // } +// +// s += padding + '}'; +// +// s += padding + '// ' + key + ' >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>\n'; +// +// return s; +// }, +// +// onParseJSONObject: function (key, value, index) { +// var padding = '\n' + CodeUtil.getBlank(depth); +// var k = JSONResponse.getVariableName(key); +// +// var s = '\n' + padding + '// ' + key + ' <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<'; +// +// s += padding + 'let ' + k + ': NSDictionary = ' + name + '["' + key + '"] as! NSDictionary\n' +// +// s += CodeUtil.parseSwiftResponse(k, value, depth); +// +// s += padding + '// ' + key + ' >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>\n'; +// +// return s; +// } +// }) +// +// }, + + + /**生成 Web-JavaScript 解析 Response JSON 的代码 + * @param name_ + * @param resObj + * @param depth + * @param isSmart + * @return parseCode + */ + parseJavaScriptResponse: function(name_, resObj, depth, isSmart) { + if (depth == null || depth < 0) { + depth = 0; + } + + var name = name_; //解决生成多余的解析最外层的初始化代码 + if (StringUtil.isEmpty(name, true)) { + name = 'response'; + } + + var varKey = isSmart ? 'let' : 'var' + var quote = isSmart ? "'" : '"' + + return CodeUtil.parseCode(name, resObj, { + + onParseParentStart: function () { + return depth > 0 || StringUtil.isEmpty(name_, true) == false ? '' : CodeUtil.getBlank(depth) + varKey + ' ' + name + ' = parseJSON(resultJson) \n'; + }, + + onParseParentEnd: function () { + return ''; + }, + + onParseChildArray: function (key, value, index) { + return this.onParseChildObject(key, value, index); + }, + + onParseChildObject: function (key, value, index) { + return this.onParseJSONObject(key, value, index); + }, + + onParseChildOther: function (key, value, index) { + + if (value instanceof Array) { + log(CodeUtil.TAG, 'parseJavaScriptResponse for typeof value === "array" >> ' ); + + return this.onParseJSONArray(key, value, index); + } + if (value instanceof Object) { + log(CodeUtil.TAG, 'parseJavaScriptResponse for typeof value === "array" >> ' ); + + return this.onParseJSONObject(key, value, index); + } + + var padding = '\n' + CodeUtil.getBlank(depth); + var varName = JSONResponse.getVariableName(key); + + return padding + varKey + ' ' + varName + ' = ' + name + + (isSmart && StringUtil.isName(key) ? '.' + key : '[' + quote + key + quote + ']') + + padding + 'console.log("' + name + '.' + varName + ' = " + ' + varName + ')'; + }, + + onParseJSONArray: function (key, value, index) { + value = value || [] + + var padding = '\n' + CodeUtil.getBlank(depth); + var innerPadding = padding + CodeUtil.getBlank(1); + + var k = JSONResponse.getVariableName(key); + var itemName = StringUtil.addSuffix(k, 'Item') + (depth <= 0 ? '' : depth); + + //还有其它字段冲突以及for循环的i冲突,解决不完的,只能让开发者自己抽出函数 var item = StringUtil.addSuffix(k, 'Item'); + + var s = '\n' + padding + '// ' + key + ' <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<'; + + s += padding + varKey + ' ' + k + ' = ' + name + (isSmart && StringUtil.isName(key) ? '.' + key : '[' + quote + key + quote + ']') + ' || []'; + + s += '\n' + padding + '// TODO 把这段代码抽取一个函数,以免for循环嵌套时 i 冲突 或 id等其它字段冲突'; + + s += padding + varKey + ' ' + itemName; + + var indexName = 'i' + (depth <= 0 ? '' : depth); + s += padding + 'for (' + varKey + ' ' + indexName + ' = 0; ' + indexName + ' < ' + k + '.length; ' + indexName + '++) {'; + + s += innerPadding + itemName + ' = ' + k + '[' + indexName + ']'; + s += innerPadding + 'if (' + itemName + ' == null) {'; + s += innerPadding + ' continue'; + s += innerPadding + '}'; + s += innerPadding + 'console.log("\\n' + itemName + ' = ' + k + '[" + ' + indexName + ' + "] = \\n" + ' + itemName + ' + "\\n\\n"' + ')'; + s += innerPadding + '// TODO 你的代码\n'; + + //不能生成N个,以第0个为准,可能会不全,剩下的由开发者自己补充。 for (var i = 0; i < value.length; i ++) { + if (value[0] instanceof Object) { + s += CodeUtil.parseJavaScriptResponse(itemName, value[0], depth + 1, isSmart); + } + // } + + s += padding + '}'; + + s += padding + '// ' + key + ' >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>\n'; + + return s; + }, + + onParseJSONObject: function (key, value, index) { + var padding = '\n' + CodeUtil.getBlank(depth); + var k = JSONResponse.getVariableName(key); + + var s = '\n' + padding + '// ' + key + ' <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<'; + + s += padding + varKey + ' ' + k + ' = ' + name + (isSmart && StringUtil.isName(key) ? '.' + key : '[' + quote + key + quote + ']') + ' || {} \n' + + s += CodeUtil.parseJavaScriptResponse(k, value, depth, isSmart); + + s += padding + '// ' + key + ' >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>\n'; + + return s; + } + }) + + }, + + /**生成 Web-PHP 解析 Response JSON 的代码 + * @param name_ + * @param resObj + * @param depth + * @param isSmart + * @return parseCode + */ + parsePHPResponse: function(name_, resObj, depth, isSmart) { + if (depth == null || depth < 0) { + depth = 0; + } + + var name = name_; //解决生成多余的解析最外层的初始化代码 + if (StringUtil.isEmpty(name, true)) { + name = 'response'; + } + + var blank = CodeUtil.getBlank(1); + var quote = isSmart ? "'" : '"'; + + return CodeUtil.parseCode(name, resObj, { + + onParseParentStart: function () { + return depth > 0 || StringUtil.isEmpty(name_, true) == false ? '' : CodeUtil.getBlank(depth) + '$' + name + ' = json_decode($resultJson, true); \n'; + }, + + onParseParentEnd: function () { + return ''; + }, + + onParseChildArray: function (key, value, index) { + return this.onParseChildObject(key, value, index); + }, + + onParseChildObject: function (key, value, index) { + return this.onParseJSONObject(key, value, index); + }, + + onParseChildOther: function (key, value, index) { + + if (value instanceof Array) { + log(CodeUtil.TAG, 'parsePHPResponse for typeof value === "array" >> ' ); + + return this.onParseJSONArray(key, value, index); + } + if (value instanceof Object) { + log(CodeUtil.TAG, 'parsePHPResponse for typeof value === "array" >> ' ); + + return this.onParseJSONObject(key, value, index); + } + + var padding = '\n' + CodeUtil.getBlank(depth); + var varName = JSONResponse.getVariableName(key); + + return padding + '$' + varName + ' = $' + name + '[' + quote + key + quote + '];' + + padding + 'echo (' + quote + (isSmart ? '' : '\\') + '$' + name + '->' + (isSmart ? '' : '\\') + '$' + varName + ' = ' + quote + ' . $' + varName + ');'; + }, + + onParseJSONArray: function (key, value, index) { + value = value || [] + + var padding = '\n' + CodeUtil.getBlank(depth); + var innerPadding = padding + CodeUtil.getBlank(1); + + var k = JSONResponse.getVariableName(key); + var itemName = StringUtil.addSuffix(k, 'Item') + (depth <= 0 ? '' : depth); + + //还有其它字段冲突以及for循环的i冲突,解决不完的,只能让开发者自己抽出函数 var item = StringUtil.addSuffix(k, 'Item'); + + var s = '\n' + padding + '// ' + key + ' <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<'; + + s += padding + '$' + k + ' = $' + name + '[' + quote + key + quote + ']' + ';'; + s += padding + 'if ($' + k + ' === null) {'; + s += padding + blank + '$' + k + ' = ' + (isSmart ? '[];' : 'array();'); + s += padding + '}\n'; + + s += '\n' + padding + '// TODO 把这段代码抽取一个函数,以免for循环嵌套时 i 冲突 或 id等其它字段冲突'; + + var indexName = 'i' + (depth <= 0 ? '' : depth); + if (isSmart) { + s += padding + 'foreach ($' + k + ' as $' + indexName + ' => $' + itemName + ') {'; + } + else { + s += padding + 'for (' + '$' + indexName + ' = 0; $' + indexName + ' < count($' + k + '); $' + indexName + '++) {'; + s += innerPadding + '$' + itemName + ' = $' + k + '[$' + indexName + '];'; + } + + s += innerPadding + 'if ($' + itemName + ' === null) {'; + s += innerPadding + ' continue;'; + s += innerPadding + '}'; + s += innerPadding + 'echo (' + quote + '\\n' + (isSmart ? '' : '\\') + '$' + itemName + ' = ' + (isSmart ? '' : '\\') + '$' + k + '[' + quote + ' . ' + '$' + indexName + ' . ' + quote + '] = \\n' + quote + ' . $' + itemName + ' . ' + quote + '\\n\\n' + quote + ');'; + s += innerPadding + '// TODO 你的代码\n'; + + //不能生成N个,以第0个为准,可能会不全,剩下的由开发者自己补充。 for (var i = 0; i < value.length; i ++) { + if (value[0] instanceof Object) { + s += CodeUtil.parsePHPResponse(itemName, value[0], depth + 1, isSmart); + } + // } + + s += padding + '}'; + + s += padding + '// ' + key + ' >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>\n'; + + return s; + }, + + onParseJSONObject: function (key, value, index) { + var padding = '\n' + CodeUtil.getBlank(depth); + var k = JSONResponse.getVariableName(key); + + var s = '\n' + padding + '// ' + key + ' <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<'; + + s += padding + '$' + k + ' = $' + name + '[' + quote + key + quote + '];' + s += padding + 'if ($' + k + ' === null) {'; + s += padding + blank + '$' + k + ' = (object) ' + (isSmart ? '[];' : 'array();'); + s += padding + '}\n'; + + s += CodeUtil.parsePHPResponse(k, value, depth, isSmart); + + s += padding + '// ' + key + ' >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>\n'; + + return s; + } + }) + + }, + + /**生成 Web-Go 解析 Response JSON 的代码 + * @param name_ + * @param resObj + * @param depth + * @param isSmart + * @return parseCode + */ + parseGoResponse: function(name_, resObj, depth, isSmart) { + if (depth == null || depth < 0) { + depth = 0; + } + + var name = name_; //解决生成多余的解析最外层的初始化代码 + if (StringUtil.isEmpty(name, true)) { + name = 'response'; + } + + var quote = '"' + + return CodeUtil.parseCode(name, resObj, { + + onParseParentStart: function () { + return depth > 0 || StringUtil.isEmpty(name_, true) == false + ? '' + : CodeUtil.getBlank(depth) + name + ' := map[string]interface{} {} \n' + + CodeUtil.getBlank(depth) + 'json.NewDecoder(bytes.NewBuffer(response.Body)).Decode(&' + name + ') \n'; + }, + + onParseParentEnd: function () { + return ''; + }, + + onParseChildArray: function (key, value, index) { + return this.onParseChildObject(key, value, index); + }, + + onParseChildObject: function (key, value, index) { + return this.onParseJSONObject(key, value, index); + }, + + onParseChildOther: function (key, value, index) { + + if (value instanceof Array) { + log(CodeUtil.TAG, 'parseGoResponse for typeof value === "array" >> ' ); + + return this.onParseJSONArray(key, value, index); + } + if (value instanceof Object) { + log(CodeUtil.TAG, 'parseGoResponse for typeof value === "array" >> ' ); + + return this.onParseJSONObject(key, value, index); + } + + var type = CodeUtil.getGoTypeFromJS(key, value); + var padding = '\n' + CodeUtil.getBlank(depth); + var varName = JSONResponse.getVariableName(key); + + return padding + varName + ' := ' + name + '[' + quote + key + quote + '].(' + type + ')' + + padding + 'log.Println("' + name + '.' + varName + ' = " + string(' + varName + '))'; + }, + + onParseJSONArray: function (key, value, index) { + value = value || [] + + var padding = '\n' + CodeUtil.getBlank(depth); + var innerPadding = padding + CodeUtil.getBlank(1); + + var k = JSONResponse.getVariableName(key); + var itemName = StringUtil.addSuffix(k, 'Item') + (depth <= 0 ? '' : depth); + + //还有其它字段冲突以及for循环的i冲突,解决不完的,只能让开发者自己抽出函数 var item = StringUtil.addSuffix(k, 'Item'); + var type = value[0] == null ? 'interface{}' : CodeUtil.getGoTypeFromJS(key, value[0]); + + var s = '\n' + padding + '// ' + key + ' <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<'; + + s += padding + k + ' := ' + name + '[' + quote + key + quote + '].([]interface{})' + s += padding + 'if ' + k + ' == nil {'; + s += padding + ' ' + k + ' = []interface{} {}'; + s += padding + '}\n'; + + s += '\n' + padding + '// TODO 把这段代码抽取一个函数,以免for循环嵌套时 i 冲突 或 id等其它字段冲突'; + + var indexName = 'i' + (depth <= 0 ? '' : depth); + s += padding + 'for ' + indexName + ' := range ' + k + ' {'; // let i in arr; let item of arr + + s += innerPadding + itemName + ' := ' + k + '[' + indexName + '].(' + type + ')'; + s += innerPadding + 'if ' + itemName + ' == nil {'; + s += innerPadding + ' continue'; + s += innerPadding + '}'; + s += innerPadding + 'log.Println("\\n' + itemName + ' = ' + k + '[" + string(' + indexName + ') + "] = \\n" + string(' + itemName + ') + "\\n\\n"' + ')'; + s += innerPadding + '// TODO 你的代码\n'; + + //不能生成N个,以第0个为准,可能会不全,剩下的由开发者自己补充。 for (var i = 0; i < value.length; i ++) { + if (value[0] instanceof Object) { + s += CodeUtil.parseGoResponse(itemName, value[0], depth + 1, isSmart); + } + // } + + s += padding + '}'; + + s += padding + '// ' + key + ' >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>\n'; + + return s; + }, + + onParseJSONObject: function (key, value, index) { + var padding = '\n' + CodeUtil.getBlank(depth); + var k = JSONResponse.getVariableName(key); + + var s = '\n' + padding + '// ' + key + ' <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<'; + + s += padding + k + ' := ' + name + '[' + quote + key + quote + '].(map[string]interface{})' + s += padding + 'if ' + k + ' == nil {'; + s += padding + ' ' + k + ' = map[string]interface{} {}'; + s += padding + '}\n'; + + s += CodeUtil.parseGoResponse(k, value, depth, isSmart); + + s += padding + '// ' + key + ' >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>\n'; + + return s; + } + }) + + }, + + /**生成 Web-C++ 解析 Response JSON 的代码 + * @param name_ + * @param resObj + * @param depth + * @param isSmart + * @return parseCode + */ + parseCppResponse: function(name_, resObj, depth, isSmart) { + if (depth == null || depth < 0) { + depth = 0; + } + + var name = name_; //解决生成多余的解析最外层的初始化代码 + if (StringUtil.isEmpty(name, true)) { + name = 'response'; + } + + var tab = CodeUtil.getBlank(1); + var blockBlank = tab.substring(1); + var padding = '\n' + CodeUtil.getBlank(depth); + var nextPadding = padding + tab; + var nextNextPadding = nextPadding + tab; + + return CodeUtil.parseCode(name, resObj, { + + onParseParentStart: function () { + return depth > 0 || StringUtil.isEmpty(name_, true) == false + ? '' : padding + 'rapidjson::Document response;' + padding + 'response.Parse(resultJson);\n'; + }, + + onParseParentEnd: function () { + return ''; + }, + + onParseChildArray: function (key, value, index) { + return this.onParseChildObject(key, value, index); + }, + + onParseChildObject: function (key, value, index) { + return this.onParseJSONObject(key, value, index); + }, + + onParseChildOther: function (key, value, index) { + + if (value instanceof Array) { + log(CodeUtil.TAG, 'parseCppResponse for typeof value === "array" >> ' ); + + return this.onParseJSONArray(key, value, index); + } + if (value instanceof Object) { + log(CodeUtil.TAG, 'parseCppResponse for typeof value === "array" >> ' ); + + return this.onParseJSONObject(key, value, index); + } + + var type = CodeUtil.getCppTypeFromJS(key, value); + var getter = CodeUtil.getCppGetterFromJS(key, value); + var padding = '\n' + CodeUtil.getBlank(depth); + var varName = JSONResponse.getVariableName(key); + + return padding + 'rapidjson::Value& ' + varName + 'Value = ' + name + '["' + key + '"];' + + padding + type + ' ' + varName + ' = ' + varName + 'Value.IsNull() ? NULL : ' + varName + 'Value.' + getter + '();' + + padding + 'std::cout << "' + name + '.' + varName + + ' = " << ' + varName + (value instanceof Object ? '.GetString()' : '') + ' << std::endl;'; + }, + + onParseJSONArray: function (key, value, index) { + value = value || [] + + var k = JSONResponse.getVariableName(key); + var itemName = StringUtil.addSuffix(k, 'Item') + (depth <= 0 ? '' : depth); + + //还有其它字段冲突以及for循环的i冲突,解决不完的,只能让开发者自己抽出函数 var item = StringUtil.addSuffix(k, 'Item'); + + var s = '\n' + padding + '{' + blockBlank + '// ' + key + ' <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<'; + + s += nextPadding + 'rapidjson::Value& ' + k + 'Value = ' + name + '["' + key + '"];'; + s += nextPadding + 'if (' + k + 'Value.IsNull()) {'; + s += nextNextPadding + k + 'Value.SetArray();'; + s += nextPadding + '}'; + s += nextPadding + 'rapidjson::Value::Array ' + k + ' = ' + k + 'Value.GetArray();'; + + s += '\n' + nextPadding + '// TODO 把这段代码抽取一个函数,以免for循环嵌套时 i 冲突 或 id等其它字段冲突'; + + var indexName = 'i' + (depth <= 0 ? '' : depth); + if (isSmart) { + s += nextPadding + 'int ' + indexName + ' = -1;'; + s += nextPadding + 'for (rapidjson::Value& ' + itemName + 'Value : ' + k + ') {'; + s += nextNextPadding + indexName + ' ++;'; + } + else { + s += nextPadding + 'for (int ' + indexName + ' = 0; ' + indexName + ' < ' + k + '.Size(); ' + indexName + '++) {'; + s += nextNextPadding + 'rapidjson::Value& ' + itemName + 'Value = ' + k + '[' + indexName + '];'; + } + + s += nextNextPadding + 'if (' + itemName + 'Value.IsNull()) {'; + s += nextNextPadding + tab + 'continue;'; + s += nextNextPadding + '}'; + + var itemType = CodeUtil.getCppTypeFromJS(key, value[0], true); + var itemGetter = CodeUtil.getCppGetterFromJS(key, value[0], true); + s += nextNextPadding + itemType + ' ' + itemName + ' = ' + itemName + 'Value.' + itemGetter + '();'; + + s += nextNextPadding + '// std::cout << "\\n' + itemName + ' = ' + k + '[" << ' + indexName + ' << "] = \\n" << ' + + itemName + (value instanceof Object ? '.GetString()' : '') + ' << "\\n\\n"' + ' << std::endl;'; + s += nextNextPadding + '// TODO 你的代码\n'; + + //不能生成N个,以第0个为准,可能会不全,剩下的由开发者自己补充。 for (var i = 0; i < value.length; i ++) { + if (value[0] instanceof Object) { + s += CodeUtil.parseCppResponse(itemName, value[0], depth + 2, isSmart); + } + + s += nextPadding + '}'; + + s += padding + '}' + blockBlank + '// ' + key + ' >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>\n'; + + return s; + }, + + onParseJSONObject: function (key, value, index) { + var k = JSONResponse.getVariableName(key); + + var s = '\n' + padding + '{' + blockBlank + '// ' + key + ' <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<'; + + s += nextPadding + 'rapidjson::Value& ' + k + 'Value = ' + name + '["' + key + '"];' + s += nextPadding + 'if (' + k + 'Value.IsNull()) {'; + s += nextNextPadding + k + 'Value.SetObject();'; + s += nextPadding + '}'; + s += nextPadding + 'rapidjson::Value::Object ' + k + ' = ' + k + 'Value.GetObject();\n'; + + s += CodeUtil.parseCppResponse(k, value, depth + 1, isSmart); + + s += padding + '}' + blockBlank + '// ' + key + ' >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>\n'; + + return s; + } + }) + + }, + + PYTHON_KEY_WORDS: [ + 'bool', 'int', 'float', 'str', 'list', 'dict', 'is', 'as', 'type', 'import', 'from', 'def', 'assert', 'return', + 'None', 'False', 'True', 'null', 'false', 'true', 'print', 'for', 'in', 'range', 'yield', 'async', 'await', + 'if', 'elif', 'else', 'eval', 'exec', 'tuple', 'object', 'req', 'res', 'res_data', 'and', 'or', 'not' + ], + + + /**生成 Web-Python 解析 Response JSON 的代码 + * @param name_ + * @param resObj + * @param depth + * @param isSmart + * @return parseCode + */ + parsePythonResponse: function(name_, resObj, depth, isSmart, isML, funDefs, funNames) { + if (depth == null || depth < 0) { + depth = 0; + } + if (depth <= 0) { + return CodeUtil.parsePythonResponseByStandard('', name_, resObj, null, 1, isSmart, false, funDefs, funNames); + } + + if (funDefs == null) { + funDefs = [] + } + if (funNames == null) { + funNames = [] + } + + var name = name_; //解决生成多余的解析最外层的初始化代码 + if (StringUtil.isEmpty(name, true)) { + name = 'res_data'; + } + + var quote = "'"; + + var str = CodeUtil.parseCode(name, resObj, { + + onParseParentStart: function () { //解决生成多余的解析最外层的初始化代码 + return depth > 0 || StringUtil.isEmpty(name_, true) == false ? '' : CodeUtil.getBlank(depth) + '# ' + name + (isSmart ? '' : ': dict') + ' = json.loads(resultJson) \n'; + }, + + onParseParentEnd: function () { + return ''; + }, + + onParseChildArray: function (key, value, index) { + return this.onParseChildObject(key, value, index); + }, + + onParseChildObject: function (key, value, index) { + return this.onParseJSONObject(key, value, index); + }, + + onParseChildOther: function (key, value, index) { + if (value instanceof Array) { + log(CodeUtil.TAG, 'parsePythonResponse for typeof value === "array" >> ' ); + return this.onParseJSONArray(key, value, index); + } + + if (value instanceof Object) { + log(CodeUtil.TAG, 'parsePythonResponse for typeof value === "array" >> ' ); + return this.onParseJSONObject(key, value, index); + } + + var type = value == null ? 'any' : CodeUtil.getPythonTypeFromJS(key, value); + var padding = '\n' + CodeUtil.getBlank(depth); + var varName = JSONResponse.getVariableName(key); + if (varName.startsWith('_') != true && CodeUtil.PYTHON_KEY_WORDS.indexOf(varName) >= 0) { + varName = '_' + varName // { '1': 0, '2': true ... } '1' -> '_1' + } + + var funName = 'is_' + varName; + if (isSmart && funNames.indexOf(funName) < 0) { + var funDef = 'def ' + funName + '(' + varName + ': ' + type + ', strict: bool = False) -> bool:' + + '\n if is_' + (type == 'str' ? 'blank' : 'empty') + '(' + varName + '):' + + '\n return not strict' + + '\n return ' + varName + (type == 'bool' ? ' is ' : ' = ') + + CodeUtil.getCode4Value(CodeUtil.LANGUAGE_PYTHON, value, key); + funDefs.push(funDef); + funNames.push(funName); + } + + return padding + varName + (isSmart ? '' : ': ' + type) + ' = ' + + (isSmart ? ('get_val(' + name + ', ') : (name + '[')) + quote + key + quote + (isSmart ? ')' : ']') + + padding + 'print(\'' + name + '.' + varName + ' = \' + str(' + varName + '))' + + padding + 'assert is_' + varName + '(' + varName + ')\n'; + }, + + onParseJSONArray: function (key, value, index) { + value = value || [] + + var padding = '\n' + CodeUtil.getBlank(depth); + var innerPadding = padding + CodeUtil.getBlank(1); + + var k = JSONResponse.getVariableName(key, 'array'); + if (k.startsWith('_') != true && CodeUtil.PYTHON_KEY_WORDS.indexOf(k) >= 0) { + k = '_' + k; + } + + var itemName = StringUtil.addSuffix(k, 'Item') + (depth <= 0 ? '' : depth); + + //还有其它字段冲突以及for循环的i冲突,解决不完的,只能让开发者自己抽出函数 var item = StringUtil.addSuffix(k, 'Item'); + var type = value[0] == null ? 'any' : CodeUtil.getPythonTypeFromJS(key, value[0]); + + var s = '\n' + padding + '# ' + key + ' <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<'; + + //不支持 varname: list[int] 这种语法 s += padding + k + (isSmart ? '' : ': list[' + type + ']') + ' = ' + name + '[' + quote + key + quote + ']' + s += padding + k + (isSmart ? '' : ': list') + ' = ' + + (isSmart ? ('get_val(' + name + ', ') : (name + '[')) + quote + key + quote + (isSmart ? ')' : ']') + ' or []' + s += padding + '# assert not_none(' + k + ')'; + // s += padding + 'if ' + k + ' == None:'; + // s += padding + ' ' + k + ' = []\n'; + + s += '\n' + padding + '#TODO 把这段代码抽取一个函数,以免for循环嵌套时 i 冲突 或 id等其它字段冲突'; + + var indexName = 'i' + (depth <= 0 ? '' : depth + 1); + s += padding + 'for ' + indexName + ' in range(len(' + k + ')):'; // let i in arr; let item of arr + s += innerPadding + itemName + (isSmart ? '' : ': ' + type) + ' = ' + k + '[' + indexName + ']'; + s += innerPadding + 'assert not_none(' + itemName + ')'; + s += innerPadding + '# if ' + itemName + ' is None:'; + s += innerPadding + '# continue\n'; + s += innerPadding + 'print(\'\\n' + itemName + ' = ' + k + '[\' + str(' + indexName + ') + \'] = \\n\' + str(' + itemName + ') + \'\\n\\n\'' + ')'; + s += innerPadding + '# TODO 你的代码\n'; + + //不能生成N个,以第0个为准,可能会不全,剩下的由开发者自己补充。 for (var i = 0; i < value.length; i ++) { + if (value[0] instanceof Object) { + s += CodeUtil.parsePythonResponse(itemName, value[0], depth + 1, isSmart, isML, funDefs, funNames); + } + // } + + s += padding + '# ' + key + ' >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>\n'; + + return s; + }, + + onParseJSONObject: function (key, value, index) { + var padding = '\n' + CodeUtil.getBlank(depth); + var k = JSONResponse.getVariableName(key); + if (k.startsWith('_') != true && CodeUtil.PYTHON_KEY_WORDS.indexOf(k) >= 0) { + k = '_' + k; + } + + var s = '\n' + padding + '# ' + key + ' <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<'; + + s += padding + k + (isSmart ? '' : ': dict') + ' = ' + + (isSmart ? ('get_val(' + k + ', ') : (k + '[')) + quote + key + quote + (isSmart ? ')' : ']') + ' or {}' + s += padding + '# assert not_none(' + k + ')'; + // s += padding + 'if ' + k + ' == None:'; + // s += padding + ' ' + k + ' = {}\n'; + + s += CodeUtil.parsePythonResponse(k, value, depth, isSmart, isML, funDefs, funNames); + + s += padding + '# ' + key + ' >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>\n'; + + return s; + } + }) + + if (depth <= 0) { + str = `def asserts(res): +res_data = rep.json() + +` + str; + + if (funDefs.length > 0) { + str += '\n\n\n# TODO 把这些通用函数放到专门的一个 asserter.py 文件中 <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<\n\n' + + funDefs.join('\n\n\n') + + '\n\n# TODO 把这些通用函数放到专门的一个 asserter.py 文件中 >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>'; + } + } + + return str; + }, + + parsePythonResponseByStandard: function(name, key, target, real, depth, isSmart, ignoreDef, funDefs, funNames) { + var isRoot = depth <= 1 && StringUtil.isEmpty(name, true); + name = name == null ? 'res_data' : name; + if (target == null) { + if (real == null) { + return ''; + } + target = JSONResponse.updateStandardByPath(null, null, null, real); + } + if (target instanceof Array) { // JSONArray + throw new Error('Standard 在 ' + name + ' 语法错误,不应该有 array!'); + } + + + log('\n\n\n\n\nparsePythonResponseByStandard <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<\n' + + ' \ntarget = ' + JSON.stringify(target, null, ' ') + '\n\n\nreal = ' + JSON.stringify(real, null, ' ')); + + depth = depth == null || depth < 0 ? 0 : depth; + + funDefs = funDefs == null ? [] : funDefs; + funNames = funNames == null ? [] : funNames; + + var quote = isSmart ? "'" : '"'; + + var type_ = target.type; + log('parsePythonResponseByStandard type = target.type = ' + type_ + ' >>') + var type = type_ == null ? 'any' : CodeUtil.getPythonTypeFromJSType(key, null, type_); + + var varName = JSONResponse.getVariableName(StringUtil.isEmpty(key, true) ? 'res_data' : key, 'array'); + if (varName.startsWith('_') != true && CodeUtil.PYTHON_KEY_WORDS.indexOf(varName) >= 0) { + varName = '_' + varName; + } + + var padding = '\n' + CodeUtil.getBlank(depth); + var innerPadding = padding + CodeUtil.getBlank(1); + + var s = ignoreDef ? '' : (padding + varName + (isSmart ? '' : ': ' + type) + ' = ' + ( + StringUtil.isEmpty(key, true) + ? 'res.json()' + : (isSmart ? ('get_val(' + name + ', ') : (name + '[')) + quote + key + quote + (isSmart ? ')' : ']') + )); + + if (ignoreDef != true) { + if (type_ == 'object') { + s += ' or {}' + } + else if (type_ == 'array') { + s += ' or []' + } + } + + var notNull = target.notNull; + log('parsePythonResponseByStandard notNull = target.notNull = ' + notNull + ' >>'); + + var funName = 'is_' + (isRoot ? '' : name + '_') + varName; + var genFunDef = isSmart && funNames.indexOf(funName) < 0; + + var prefix = padding + 'assert '; + var prefix2 = genFunDef ? '\n if not (' : prefix; + + var funDef = '' + if (isSmart) { + if (genFunDef) { + funDef = 'def ' + funName + '(' + varName + ': ' + type + ', strict: bool = False) -> bool:' + + '\n if is_' + (type == 'str' ? 'blank' : 'empty') + '(' + varName + '):' + + '\n return not strict' + + '\n if not is_' + type + '(' + varName + ', strict):' + + '\n return false\n' + } + s += prefix + funName + '(' + varName + ', ' + notNull + ')'; + } else { + if (notNull) { + s += prefix + 'not_none(' + varName + ')'; + } + s += prefix + 'is_' + type + '(' + varName + ')'; + } + + var lengths = target.lengths; + if (lengths != null && lengths.length > 0) { + var lengthLevel = target.lengthLevel; + + var as = (genFunDef ? '\n ' : padding) + varName + 'Len = 0 if ' + varName + ' is None else len(' + varName + ')' + if (lengthLevel == 0 || lengths.length <= 1) { + as += prefix2 + varName + 'Len in ' + JSON.stringify(lengths); + } else { + as += prefix2 + varName + 'Len >= ' + lengths[lengths.length - 1] + ' and ' + varName + 'Len <= ' + lengths[0]; + } + + if (isSmart) { + if (genFunDef && StringUtil.isNotEmpty(as, true)) { + funDef += as + '):\n return false'; + } + } else { + s += as; + } + } + + var values = target.values; + log('parsePythonResponseByStandard values = target.values = ' + JSON.stringify(values, null, ' ') + ' >>'); + var firstVal = values == null || values.length <= 0 ? null : values[0]; + + if (values != null && values.length > 0) { + var valueLevel = target.valueLevel; + log('parsePythonResponseByStandard valueLevel = target.valueLevel = ' + valueLevel + ' >>'); + + if (type_ == 'array') { // JSONArray + log('parsePythonResponseByStandard type == array >> '); + var itemName = StringUtil.addSuffix(varName, 'Item') + (depth <= 1 ? '' : depth); + + s += '\n' + padding + '# TODO 把这段代码抽取一个函数,以免 for 循环嵌套时 i 冲突 或 id 等其它字段冲突'; + var indexName = 'i' + (depth <= 1 ? '' : depth); + s += padding + 'for ' + indexName + ' in range(len(' + varName + ')):'; // let i in arr; let item of arr + s += innerPadding + itemName + (isSmart ? '' : ': ' + type) + ' = ' + varName + '[' + indexName + ']'; + s += innerPadding + 'assert not_none(' + itemName + ')'; + s += innerPadding + '# if ' + itemName + ' is None:'; + s += innerPadding + '# continue'; + + var cs = CodeUtil.parsePythonResponseByStandard(varName, itemName, firstVal, null, depth + 1, isSmart, true, funDefs, funNames); + if (StringUtil.isNotEmpty(cs, true)) { + s += '\n' + innerPadding + cs.trim(); + } + } else if (type_ == 'object') { // JSONObject + log('parsePythonResponseByStandard type == object >> '); + + var tks = firstVal == null ? [] : Object.keys(firstVal); + var tk; + for (var i = 0; i < tks.length; i++) { //遍历并递归下一层 + tk = tks[i]; + if (tk == null) { + continue; + } + log('parsePythonResponseByStandard for tk = ' + tk + ' >> '); + var cs = CodeUtil.parsePythonResponseByStandard(varName, tk, firstVal[tk], null, depth, isSmart, false, funDefs, funNames); + if (StringUtil.isNotEmpty(cs, true)) { + s += '\n' + padding + cs.trim(); + } + } + } else { // Boolean, Number, String + log('parsePythonResponseByStandard type == boolean | number | string >> '); + + var as = ''; + if (type_ == 'number' || type_ == 'integer') { + var select = (target.trend || {}).select; + var maxVal = firstVal; + var minVal = values == null || values.length <= 0 ? null : values[values.length - 1]; + + if (select == '>') { + as = prefix2 + varName + ' > ' + maxVal; + } else if (select == '>=') { + as = prefix2 + varName + ' >= ' + maxVal; + } else if (select == '<') { + as = prefix2 + varName + ' < ' + minVal; + } else if (select == '<=') { + as = prefix2 + varName + ' <= ' + minVal; + } else if (select == '%' || (valueLevel == 1 || values.length >= 2)) { + as = prefix2 + varName + ' >= ' + minVal + ' and ' + varName + ' <= ' + maxVal; + } else { + as = prefix2 + varName + ' in ' + JSON.stringify(values); + } + } else { + as = prefix2 + varName + ' in ' + JSON.stringify(values); + } + + if (isSmart) { + if (genFunDef && StringUtil.isNotEmpty(as, true)) { + funDef += as + '):\n return false'; + } + } else { + s += as; + } + } + + } + + var fas = ''; + var format = target.format; + if (typeof format == 'string' && FORMAT_PRIORITIES[format] != null) { + var verifier = FORMAT_VERIFIERS[format]; + if (typeof verifier == 'function') { + fas = prefix2 + verifier.name + '(' + varName + ', ' + notNull + ')'; + } + } + else if (format instanceof Array == false && format instanceof Object) { + s += prefix2 + varName + '_json = json.loads(' + varName + ')' + try { + var realObj = parseJSON(real); + var cs = CodeUtil.parsePythonResponseByStandard(varName + '_json', key, format, realObj, depth, isSmart, true, funDefs, funNames); + if (StringUtil.isNotEmpty(cs, true)) { + s += '\n' + padding + cs.trim(); + } + } catch (e) { + log(e) + } + + } + + if (isSmart) { + if (genFunDef && StringUtil.isNotEmpty(fas, true)) { + funDef += fas + '):\n return false'; + } + } else { + s += fas; + } + + if (isSmart && genFunDef) { + funDef += '\n return true' + funDefs.push(funDef); + funNames.push(funName); + } + + if (isRoot) { + s = 'def asserts(res):' + + '\n' + s + + '\n return res' + + if (funDefs.length > 0) { + s += '\n\n\n# TODO 把这些通用函数放到专门的一个 asserter.py 文件中 <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<\n\n' + + funDefs.join('\n\n\n') + + '\n\n# TODO 把这些通用函数放到专门的一个 asserter.py 文件中 >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>'; + } + } + + log('\nparsePythonResponseByStandard >> return s = ' + s + '\n >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> \n\n\n\n\n'); + return s; + }, + + /**生成 Web-TypeScript 解析 Response JSON 的代码 + * @param name_ + * @param resObj + * @param depth + * @param isSmart + * @return parseCode + */ + parseTypeScriptResponse: function(name_, resObj, depth, isSmart) { + if (depth == null || depth < 0) { + depth = 0; + } + + var name = name_; //解决生成多余的解析最外层的初始化代码 + if (StringUtil.isEmpty(name, true)) { + name = 'response'; + } + + var varKey = isSmart ? 'let' : 'var' + var quote = isSmart ? "'" : '"' + + return CodeUtil.parseCode(name, resObj, { + + onParseParentStart: function () { + return depth > 0 || StringUtil.isEmpty(name_, true) == false ? '' : CodeUtil.getBlank(depth) + varKey + ' ' + name + ': object = parseJSON(resultJson); \n'; + }, + + onParseParentEnd: function () { + return ''; + }, + + onParseChildArray: function (key, value, index) { + return this.onParseChildObject(key, value, index); + }, + + onParseChildObject: function (key, value, index) { + return this.onParseJSONObject(key, value, index); + }, + + onParseChildOther: function (key, value, index) { + + if (value instanceof Array) { + log(CodeUtil.TAG, 'parseTypeScriptResponse for typeof value === "array" >> ' ); + + return this.onParseJSONArray(key, value, index); + } + if (value instanceof Object) { + log(CodeUtil.TAG, 'parseTypeScriptResponse for typeof value === "array" >> ' ); + + return this.onParseJSONObject(key, value, index); + } + + var type = value == null ? 'any' : (typeof value); + var padding = '\n' + CodeUtil.getBlank(depth); + var varName = JSONResponse.getVariableName(key); + + return padding + varKey + ' ' + varName + ': ' + type + ' = ' + name + '[' + quote + key + quote + '];' + + padding + 'console.log("' + name + '.' + varName + ' = " + ' + varName + ');'; + }, + + onParseJSONArray: function (key, value, index) { + value = value || [] + + var padding = '\n' + CodeUtil.getBlank(depth); + var innerPadding = padding + CodeUtil.getBlank(1); + + var k = JSONResponse.getVariableName(key); + var itemName = StringUtil.addSuffix(k, 'Item') + (depth <= 0 ? '' : depth); + + //还有其它字段冲突以及for循环的i冲突,解决不完的,只能让开发者自己抽出函数 var item = StringUtil.addSuffix(k, 'Item'); + var type = value[0] == null ? 'any' : (typeof (value[0])); + + var s = '\n' + padding + '// ' + key + ' <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<'; + + s += padding + varKey + ' ' + k + ': ' + type + '[] = ' + name + '[' + quote + key + quote + ']' + ' || []; \n' + + s += '\n' + padding + '// TODO 把这段代码抽取一个函数,以免for循环嵌套时 i 冲突 或 id等其它字段冲突'; + + s += padding + varKey + ' ' + itemName + ': ' + type + ';'; + + var indexName = 'i' + (depth <= 0 ? '' : depth); + s += padding + 'for (' + varKey + ' ' + indexName + ' in ' + k + ') {'; // let i in arr; let item of arr + + s += innerPadding + itemName + ' = ' + k + '[' + indexName + '];'; + s += innerPadding + 'if (' + itemName + ' == null) {'; + s += innerPadding + ' continue;'; + s += innerPadding + '}'; + s += innerPadding + 'console.log("\\n' + itemName + ' = ' + k + '[" + ' + indexName + ' + "] = \\n" + ' + itemName + ' + "\\n\\n"' + ');'; + s += innerPadding + '// TODO 你的代码\n'; + + //不能生成N个,以第0个为准,可能会不全,剩下的由开发者自己补充。 for (var i = 0; i < value.length; i ++) { + if (value[0] instanceof Object) { + s += CodeUtil.parseTypeScriptResponse(itemName, value[0], depth + 1, isSmart); + } + // } + + s += padding + '}'; + + s += padding + '// ' + key + ' >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>\n'; + + return s; + }, + + onParseJSONObject: function (key, value, index) { + var padding = '\n' + CodeUtil.getBlank(depth); + var k = JSONResponse.getVariableName(key); + + var s = '\n' + padding + '// ' + key + ' <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<'; + + s += padding + varKey + ' ' + k + ': object = ' + name + '[' + quote + key + quote + ']' + ' || {}; \n' + + s += CodeUtil.parseTypeScriptResponse(k, value, depth, isSmart); + + s += padding + '// ' + key + ' >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>\n'; + + return s; + } + }) + + }, + + /**生成 Android-Kotlin 解析 Response JSON 的代码 + * 不能像 Java 那样执行 {} 代码段里的代码,所以不能用 Java 那种代码段隔离的方式 + * @param name_ + * @param resObj + * @param depth + * @return parseCode + */ + parseKotlinResponse: function(name_, resObj, depth, isTable, isSmart) { + if (depth == null || depth < 0) { + depth = 0; + } + + var name = name_; //解决生成多余的解析最外层的初始化代码 + if (StringUtil.isEmpty(name, true)) { + name = 'response'; + } + + var tab = CodeUtil.getBlank(1); + var blockBlank = tab.substring(1); + var padding = '\n' + CodeUtil.getBlank(depth); + var nextPadding = padding + tab; + var nextNextPadding = nextPadding + tab; + + return CodeUtil.parseCode(name, resObj, { + + onParseParentStart: function () { + // if (isSmart) { //导致里面的 [] 等字符全都转成 List 等,里面每用一个 key 取值都得 formatArrayKey 或所有对象类型用 JSONReseponse 等,不通用 + // return depth > 0 ? '' : CodeUtil.getBlank(depth) + 'JSONResponse ' + name + ' = new JSONResponse(resultJson);\n'; + // } + return depth > 0 || StringUtil.isEmpty(name_, true) == false ? '' : CodeUtil.getBlank(depth) + 'var ' + name + ': JSONObject = JSON.parseObject(resultJson)\n'; + }, + + onParseParentEnd: function () { + return ''; + }, + + onParseChildArray: function (key, value, index) { + return this.onParseChildObject(key, value, index); + }, + + onParseChildObject: function (key, value, index) { + return this.onParseJSONObject(key, value, index); + }, + + onParseChildOther: function (key, value, index) { + + if (value instanceof Array) { + log(CodeUtil.TAG, 'parseKotlinResponse for typeof value === "array" >> '); + + return this.onParseJSONArray(key, value, index); + } + if (value instanceof Object) { + log(CodeUtil.TAG, 'parseKotlinResponse for typeof value === "array" >> '); + + return this.onParseJSONObject(key, value, index); + } + + var type = CodeUtil.getJavaTypeFromJS(key, value, false, true); + if (type == 'Object') { + type = 'Any'; + } + var varName = JSONResponse.getVariableName(key); + + if (isSmart && isTable) { // JSONObject.isTableKey(name)) { + return padding + 'var ' + varName + ' = ' + name + '?.get' + StringUtil.firstCase(varName, true) + '()' + + padding + 'println("' + name + '.' + varName + ' = " + ' + varName + ')'; + } else { + return padding + 'var ' + varName + ' = ' + name + '?.get' + + (/[A-Z]/.test(type.substring(0, 1)) ? type : StringUtil.firstCase(type + 'Value', true)) + '("' + key + '")' + + padding + 'println("' + name + '.' + varName + ' = " + ' + varName + ');'; + } + }, + + onParseJSONArray: function (key, value, index) { + value = value || [] + + var vn = JSONResponse.getVariableName(key); + var k = vn + (depth <= 0 ? '' : depth); + var itemName = StringUtil.addSuffix(k, 'Item') + (depth <= 0 ? '' : depth); + //还有其它字段冲突以及for循环的i冲突,解决不完的,只能让开发者自己抽出函数 var item = StringUtil.addSuffix(k, 'Item'); + + var type = CodeUtil.getJavaTypeFromJS(itemName, value[0], true, false); + if (type == 'Object') { + type = 'Any'; + } + + var s = '\n' + padding + 'run {' + blockBlank + '// ' + key + ' <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<'; + + var t = JSONResponse.getTableName(key); + if (t.endsWith('[]')) { + t = t.substring(0, t.length - 2); + } + + var isTableKey = JSONObject.isTableKey(t); + if (isTable && isSmart) { + s += nextPadding + 'var ' + k + ': List<' + (isTableKey ? t : type) + '?>? = ' + name + '?.get' + StringUtil.firstCase(vn, true) + '()' + } + else if (isTableKey && isSmart) { + s += nextPadding + 'var ' + k + ': List<' + t + '?>? = JSON.parseArray(' + name + '?.getString("' + key + '"), ' + t + '::class.java)'; + } + else { + s += nextPadding + 'var ' + k + ': JSONArray? = ' + name + '?.getJSONArray("' + key + '")'; + } + + s += nextPadding + 'if (' + k + ' == null) {'; + s += nextNextPadding + k + ' = ' + ((isTable || isTableKey) && isSmart ? 'ArrayList' : 'JSONArray') + '();'; + s += nextPadding + '}\n'; + + s += nextPadding + 'var ' + itemName + ': ' + (isTableKey && isSmart ? t : (type == 'Integer' ? 'Int' : type)) + '?'; + + var indexName = 'i' + (depth <= 0 ? '' : depth); + s += nextPadding + 'for (' + indexName + ' in 0..' + k + '.size - 1) {'; + + s += nextNextPadding + itemName + ' = ' + k + '?.get' + (((isTable || isTableKey) && isSmart) || type == 'Any' ? '' : type) + '(' + indexName + ')'; + s += nextNextPadding + 'if (' + itemName + ' == null) {'; + s += nextNextPadding + tab + 'continue'; + s += nextNextPadding + '}'; + s += nextNextPadding + 'println("\\n' + itemName + ' = ' + k + '[" + ' + indexName + ' + "] = \\n" + ' + itemName + ' + "\\n\\n"' + ')'; + s += nextNextPadding + '// TODO 你的代码\n'; + + //不能生成N个,以第0个为准,可能会不全,剩下的由开发者自己补充。 for (var i = 0; i < value.length; i ++) { + if (value[0] instanceof Object) { + s += CodeUtil.parseKotlinResponse(itemName, value[0], depth + 2, isTableKey, isSmart); + } + // } + + s += nextPadding + '}'; + + s += padding + '}' + blockBlank + '// ' + key + ' >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>\n'; + + return s; + }, + + onParseJSONObject: function (key, value, index) { + var k = JSONResponse.getVariableName(key); + + var s = '\n' + padding + 'run {' + blockBlank + '// ' + key + ' <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<'; + + var t = JSONResponse.getTableName(key); + var isTableKey = JSONObject.isTableKey(t); + if (isTable && isSmart) { + s += nextPadding + 'var ' + k + ':' + (isTableKey ? t : 'JSONObject') + '? = ' + name + '?.get' + StringUtil.firstCase(k, true) + '()' + } + else if (isTableKey && isSmart) { + s += nextPadding + 'var ' + k + ':' + t + '? = ' + name + '?.getObject("' + key + '", ' + t + '::class.java)'; + } + else { + s += nextPadding + 'var ' + k + ': JSONObject? = ' + name + '?.getJSONObject("' + key + '")' + } + + s += nextPadding + 'if (' + k + ' == null) {'; + s += nextNextPadding + k + ' = ' + (isTableKey && isSmart ? t : 'JSONObject') + '()'; + s += nextPadding + '}\n'; + + s += CodeUtil.parseKotlinResponse(k, value, depth + 1, isTableKey, isSmart); + + s += padding + '}' + blockBlank + '// ' + key + ' >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>\n'; + + return s; + } + }) + (depth > 0 || ! isSmart ? '' : '\n\n\n' + CodeUtil.parseKotlinClasses('Response', resObj, 0, false, true)) + + }, + + + /**生成 Android-Java 解析 Response JSON 的代码 + * @param name_ + * @param resObj + * @param depth + * @param isTable + * @param isSmart + * @return parseCode + */ + parseJavaResponse: function(name_, resObj, depth, isTable, isSmart, onlyParseSimpleValue) { + if (depth == null || depth < 0) { + depth = 0; + } + + var name = name_; //解决生成多余的解析最外层的初始化代码 + if (StringUtil.isEmpty(name, true)) { + name = 'response'; + } + + var tab = CodeUtil.getBlank(1); + var blockBlank = tab.substring(1); + var padding = '\n' + CodeUtil.getBlank(depth); + var nextPadding = padding + tab; + var nextNextPadding = nextPadding + tab; + + return CodeUtil.parseCode(name, resObj, { + + onParseParentStart: function () { + // if (isSmart) { //导致里面的 [] 等字符全都转成 List 等,里面每用一个 key 取值都得 formatArrayKey 或所有对象类型用 JSONReseponse 等,不通用 + // return depth > 0 ? '' : CodeUtil.getBlank(depth) + 'JSONResponse ' + name + ' = new JSONResponse(resultJson);\n'; + // } + return depth > 0 || StringUtil.isEmpty(name_, true) == false ? '' : padding + 'JSONObject ' + name + ' = JSON.parseObject(resultJson);\n'; + }, + + onParseParentEnd: function () { + return ''; + }, + + onParseChildArray: function (key, value, index) { + if (onlyParseSimpleValue) { + return this.onParseChildOther(key, value, index); + } + return this.onParseChildObject(key, value, index); + }, + + onParseChildObject: function (key, value, index) { + if (onlyParseSimpleValue) { + return this.onParseChildOther(key, value, index); + } + return this.onParseJSONObject(key, value, index); + }, + + onParseChildOther: function (key, value, index) { + + if (onlyParseSimpleValue != true) { + if (value instanceof Array) { + log(CodeUtil.TAG, 'parseJavaResponse for typeof value === "array" >> ' ); + + return this.onParseJSONArray(key, value, index); + } + if (value instanceof Object) { + log(CodeUtil.TAG, 'parseJavaResponse for typeof value === "array" >> ' ); + + return this.onParseJSONObject(key, value, index); + } + } + + var type = CodeUtil.getJavaTypeFromJS(key, value, false, ! onlyParseSimpleValue); + var varName = JSONResponse.getVariableName(key); + + if (isSmart && isTable) { // JSONObject.isTableKey(name)) { + return padding + type + ' ' + varName + ' = ' + name + '.get' + StringUtil.firstCase(varName, true) + '();' + + padding + 'System.out.println("' + name + '.' + varName + ' = " + ' + varName + ');'; + } else { + return padding + type + ' ' + varName + ' = ' + name + '.get' + + (/[A-Z]/.test(type.substring(0, 1)) ? type : StringUtil.firstCase(type + 'Value', true)) + '("' + key + '");' + + padding + 'System.out.println("' + name + '.' + varName + ' = " + ' + varName + ');'; + } + }, + + onParseJSONArray: function (key, value, index) { + if (onlyParseSimpleValue) { + return this.onParseChildOther(key, value, index); + } + + value = value || [] + + var vn = JSONResponse.getVariableName(key); + var k = vn + (depth <= 0 ? '' : depth); + var itemName = StringUtil.addSuffix(k, 'Item') + (depth <= 0 ? '' : depth); + //还有其它字段冲突以及for循环的i冲突,解决不完的,只能让开发者自己抽出函数 var item = StringUtil.addSuffix(k, 'Item'); + + var s = '\n' + padding + '{' + blockBlank + '// ' + key + ' <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<'; + + var t = JSONResponse.getTableName(key); + if (t.endsWith('[]')) { + t = t.substring(0, t.length - 2); + } + + var isTableKey = JSONObject.isTableKey(t); + + var itemType = CodeUtil.getJavaTypeFromJS(itemName, value[0], false); + if (isTable && isSmart) { + s += nextPadding + 'List<' + (isTableKey ? t : itemType) + '> ' + k + ' = ' + name + '.get' + StringUtil.firstCase(vn, true) + '();' + } + else if (isTableKey && isSmart) { + s += nextPadding + 'List<' + t + '> ' + k + ' = JSON.parseArray(' + name + '.getString("' + key + '"), ' + t + '.class);'; + } + else { + s += nextPadding + 'JSONArray ' + k + ' = ' + name + '.getJSONArray("' + key + '");'; + } + s += nextPadding + 'if (' + k + ' == null) {'; + s += nextNextPadding + k + ' = new ' + ((isTable || isTableKey) && isSmart ? 'ArrayList<>' : 'JSONArray') + '();'; + s += nextPadding + '}\n'; + + var indexName = 'i' + (depth <= 0 ? '' : depth); + s += nextPadding + 'for (int ' + indexName + ' = 0; ' + indexName + ' < ' + k + '.size(); ' + indexName + ' ++) {'; + + s += nextNextPadding + (isTableKey && isSmart ? t : itemType) + ' ' + itemName + + ' = ' + k + '.get' + (((isTable || isTableKey) && isSmart) || itemType == 'Object' ? '' : itemType) + '(' + indexName + ');'; + s += nextNextPadding + 'if (' + itemName + ' == null) {'; + s += nextNextPadding + tab + 'continue;'; + s += nextNextPadding + '}'; + s += nextNextPadding + 'System.out.println("\\n' + itemName + ' = ' + k + '[" + ' + indexName + ' + "] = \\n" + ' + itemName + ' + "\\n\\n"' + ');'; + s += nextNextPadding + '// TODO 你的代码\n'; + + //不能生成N个,以第0个为准,可能会不全,剩下的由开发者自己补充。 for (var i = 0; i < value.length; i ++) { + if (value[0] instanceof Object) { + s += CodeUtil.parseJavaResponse(itemName, value[0], depth + 2, isTableKey, isSmart); + } + // } + + s += nextPadding + '}'; + + s += padding + '}' + blockBlank + '// ' + key + ' >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>\n'; + + return s; + }, + + onParseJSONObject: function (key, value, index) { + if (onlyParseSimpleValue) { + return this.onParseChildOther(key, value, index); + } + + var k = JSONResponse.getVariableName(key); + + var s = '\n' + padding + '{' + blockBlank + '// ' + key + ' <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<'; + + var t = JSONResponse.getTableName(key); + var isTableKey = JSONObject.isTableKey(t); + if (isTable && isSmart) { + s += nextPadding + (isTableKey ? t : 'JSONObject') + ' ' + k + ' = ' + name + '.get' + StringUtil.firstCase(k, true) + '();' + } + else if (isTableKey && isSmart) { + s += nextPadding + t + ' ' + k + ' = ' + name + '.getObject("' + key + '", ' + t + '.class);' + } + else { + s += nextPadding + 'JSONObject ' + k + ' = ' + name + '.getJSONObject("' + key + '");' + } + s += nextPadding + 'if (' + k + ' == null) {'; + s += nextNextPadding + k + ' = new ' + (isTableKey && isSmart ? t : 'JSONObject') + '();'; + s += nextPadding + '}\n'; + + s += CodeUtil.parseJavaResponse(k, value, depth + 1, isTableKey, isSmart); + + s += padding + '}' + blockBlank + '// ' + key + ' >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>\n'; + + return s; + } + }) + (depth > 0 || ! isSmart ? '' : '\n\n\n' + CodeUtil.parseKotlinClasses('Response', resObj, 0, false, ! isSmart)) + + }, + + + /**生成 Unity3D-C# 解析 Response JSON 的代码 + * @param name_ + * @param resObj + * @param depth + * @return parseCode + */ + parseCSharpResponse: function(name_, resObj, depth) { + if (depth == null || depth < 0) { + depth = 0; + } + + var name = name_; //解决生成多余的解析最外层的初始化代码 + if (StringUtil.isEmpty(name, true)) { + name = 'response'; + } + var blank = CodeUtil.getBlank(1); + + return CodeUtil.parseCode(name, resObj, { + + onParseParentStart: function () { + return depth > 0 || StringUtil.isEmpty(name_, true) == false ? '' : CodeUtil.getBlank(depth) + 'JObject ' + name + ' = JObject.Parse(resultJson);\n'; + }, + + onParseParentEnd: function () { + return ''; + }, + + onParseChildArray: function (key, value, index) { + return this.onParseChildObject(key, value, index); + }, + + onParseChildObject: function (key, value, index) { + return this.onParseJSONObject(key, value, index); + }, + + onParseChildOther: function (key, value, index) { + + if (value instanceof Array) { + log(CodeUtil.TAG, 'parseCSharpResponse for typeof value === "array" >> ' ); + + return this.onParseJSONArray(key, value, index); + } + if (value instanceof Object) { + log(CodeUtil.TAG, 'parseCSharpResponse for typeof value === "array" >> ' ); + + return this.onParseJSONObject(key, value, index); + } + + var type = CodeUtil.getCSharpTypeFromJS(key, value); + var padding = '\n' + CodeUtil.getBlank(depth); + var varName = JSONResponse.getVariableName(key); + + return padding + type + ' ' + varName + ' = ' + name + '["' + key + '"]' + + '.ToObject<' + type + '>()' + ';' + + padding + 'Console.WriteLine("' + name + '.' + varName + ' = " + ' + varName + ');'; + }, + + onParseJSONArray: function (key, value, index) { + value = value || [] + + var padding = '\n' + CodeUtil.getBlank(depth); + var innerPadding = '\n' + CodeUtil.getBlank(depth + 1); + var innerPadding2 = '\n' + CodeUtil.getBlank(depth + 2); + + var k = JSONResponse.getVariableName(key) + (depth <= 0 ? '' : depth); + var itemName = StringUtil.addSuffix(k, 'Item') + (depth <= 0 ? '' : depth); + + //还有其它字段冲突以及for循环的i冲突,解决不完的,只能让开发者自己抽出函数 var item = StringUtil.addSuffix(k, 'Item'); + var type = CodeUtil.getCSharpTypeFromJS('item', value[0]); + + var s = '\n' + padding + '{ // ' + key + ' <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<'; + + s += innerPadding + 'JArray ' + k + ' = ' + name + '["' + key + '"].ToObject();'; + s += innerPadding + 'if (' + k + ' == null) {'; + s += innerPadding + blank + k + ' = new JArray();'; + s += innerPadding + '}\n'; + + s += '\n' + innerPadding + '// TODO 把这段代码抽取一个函数,以免for循环嵌套时 i 冲突 或 id等其它字段冲突'; + + s += innerPadding + 'foreach (' + type + ' ' + itemName + ' in ' + k + ') {'; + + s += innerPadding2 + 'if (' + itemName + ' == null) {'; + s += innerPadding2 + blank + 'continue;'; + s += innerPadding2 + '}'; + s += innerPadding2 + 'Console.WriteLine("\\n' + itemName + ' in ' + k + ' = \\n" + ' + itemName + ' + "\\n\\n"' + ');'; + s += innerPadding2 + '// TODO 你的代码\n'; + + //不能生成N个,以第0个为准,可能会不全,剩下的由开发者自己补充。 for (var i = 0; i < value.length; i ++) { + if (value[0] instanceof Object) { + s += CodeUtil.parseCSharpResponse(itemName, value[0], depth + 2); + } + // } + + s += innerPadding + '}'; + + s += padding + '} //' + key + ' >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>\n'; + + return s; + }, + + onParseJSONObject: function (key, value, index) { + var padding = '\n' + CodeUtil.getBlank(depth); + var innerPadding = '\n' + CodeUtil.getBlank(depth + 1); + var k = JSONResponse.getVariableName(key); + + var s = '\n' + padding + '{ // ' + key + ' <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<'; + + s += innerPadding + 'JObject ' + k + ' = ' + name + '["' + key + '"].ToObject();' + s += innerPadding + 'if (' + k + ' == null) {'; + s += innerPadding + blank + k + ' = new JObject();'; + s += innerPadding + '}\n'; + + s += CodeUtil.parseCSharpResponse(k, value, depth + 1); + + s += padding + '} //' + key + ' >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>\n'; + + return s; + } + }) + + }, + + + + initEmptyValue4Type: function(type, isSmart, isKotlin) { + if (isSmart) { + type = type || '' + switch (type) { + case 'Boolean': + return ' = false'; + case 'Number': + case 'Integer': + case 'Int': + return ' = 0'; + case 'Long': + return isKotlin ? ' = 0' : ' = 0L'; + case 'Float': + return ' = 0f'; + case 'Double': + return isKotlin ? ' = 0.0' : ' = 0d'; + case 'String': + return ' = ""'; + case 'Array': + case 'List': + return isKotlin ? ' = mutableListOf()' : ' = new ArrayList<>()'; + case 'Map': + return isKotlin ? ' = mutableMapOf()' : ' = new LinkedHashMap<>()'; + case 'Object': + case 'Any': + return '? = null'; + default: + return ' = ' + type + '()'; + } + } + + return '? = null' + (isSmart ? '' : ' //' + CodeUtil.initEmptyValue4Type(type, true, isKotlin)); + }, + + getCode4JavaArgValues: function (reqObj, useVar4ComplexValue) { + var str = ''; + if (reqObj != null) { + var first = true; + + for (var k in reqObj) { + var v = reqObj[k]; + + if (useVar4ComplexValue && v instanceof Object) { + str += (first ? '' : ', ') + JSONResponse.getVariableName(k); + } + else { + str += (first ? '' : ', ') + CodeUtil.getCode4Value(CodeUtil.LANGUAGE_JAVA, v, k, 0); + } + first = false; + } + } + + return str; + }, + + getCode4KotlinArgValues: function (reqObj, useVar4ComplexValue) { + var str = ''; + if (reqObj != null) { + var first = true; + + for (var k in reqObj) { + var v = reqObj[k]; + + if (useVar4ComplexValue && v instanceof Object) { + str += (first ? '' : ', ') + JSONResponse.getVariableName(k); + } + else { + str += (first ? '' : ', ') + CodeUtil.getCode4Value(CodeUtil.LANGUAGE_KOTLIN, v, k, 0); + } + first = false; + } + } + + return str; + }, + /**获取参数代码 + * @param reqObj 对象 + * @param withType 带上类型 + * @param annotionType null, Server: RequestParam, Param; Android: Query, Field, Part, Query-QueryMap, Query-QueryMap(encode=true), Part-PartMap + * @param rawType true-使用 JDK 有的原始类型,其它-使用 JSONObject, JSONRequest 等第三方库封装类型 + * @param complex2String 将复杂类型转为 String,值转为 toJSONString + */ + getCode4JavaArgs: function(reqObj, withType, annotationType, rawType, complex2String) { + var str = ''; + if (reqObj != null) { + var first = true; + + for (var k in reqObj) { + var v = reqObj[k]; + var t = withType ? CodeUtil.getJavaTypeFromJS(k, v, false, false, rawType) : null; + + var at = annotationType; + if (annotationType != null) { //简单数据注解类型-复杂数据注解类型 + var index = annotationType.indexOf('-'); + if (index >= 0) { + at = v instanceof Object ? annotationType.substring(index + 1, annotationType.length) : annotationType.substring(0, index); + } + } + + var vk = JSONResponse.getVariableName(k); + + if (complex2String && v instanceof Object) { + if (withType) { + t = 'String'; + } + else { + vk = 'JSON.toJSONString(' + vk + ')'; + } + } + + str += (first ? '' : ', ') + (at == null ? '' : '@' + at + '("' + k + '") ' ) + (t == null ? '' : t + ' ') + vk; + first = false; + } + } + + return str; + }, + /**获取参数代码 + * @param reqObj 对象 + * @param withType 带上类型 + * @param annotionType null, Server: RequestParam, Param; Android: Query, Field, Part, Query-QueryMap, Query-QueryMap(encode=true), Part-PartMap + * @param rawType true-使用 JDK 有的原始类型,其它-使用 JSONObject, JSONRequest 等第三方库封装类型 + * @param complex2String 将复杂类型转为 String,值转为 toJSONString + */ + getCode4KotlinArgs: function(reqObj, withType, annotationType, rawType, isSmart, complex2String) { + var str = ''; + if (reqObj != null) { + var first = true; + + for (var k in reqObj) { + var v = reqObj[k]; + var t = withType ? CodeUtil.getKotlinTypeFromJS(k, v, false, false, rawType, isSmart) : null; + + var at = annotationType; + if (annotationType != null) { //简单数据注解类型-复杂数据注解类型 + var index = annotationType.indexOf('-'); + if (index >= 0) { + at = v instanceof Object ? annotationType.substring(index + 1, annotationType.length) : annotationType.substring(0, index); + } + } + + var vk = JSONResponse.getVariableName(k); + + if (complex2String && v instanceof Object) { + if (withType) { + t = 'String'; + } + else { + vk = 'JSON.toJSONString(' + vk + ')'; + } + } + + str += (first ? '' : ', ') + (at == null ? '' : '@' + at + '("' + k + '") ' ) + vk + (t == null ? '' : ': ' + t + '?'); + first = false; + } + } + + return str; + }, + + + /**TODO 用带注释的 JSON 来解析,能把注释也带上 + * 生成 Android-Kotlin 解析 Response JSON 的为 class 和 field 的静态代码 + * 不能像 Java 那样执行 {} 代码段里的代码,所以不能用 Java 那种代码段隔离的方式 + * @param name_ + * @param resObj + * @param depth + * @return parseCode + */ + parseKotlinClasses: function(name, resObj, depth, isTable, isSmart) { + if (depth == null || depth < 0) { + depth = 0; + } + + + var tab = CodeUtil.getBlank(1); + var padding = CodeUtil.getBlank(depth); + var nextPadding = padding + tab; + + return CodeUtil.parseCode(name, resObj, { + + onParseParentStart: function () { + if (StringUtil.isEmpty(name, true)) { + return '' + } + + var s = '\n'; + // if (depth <= 0) { + // s += padding + 'package apijson.demo.model\n'; + // } + + var c = CodeUtil.getCommentFromDoc(CodeUtil.tableList, name, null, 'GET', CodeUtil.database, CodeUtil.language, true); + if (StringUtil.isEmpty(c, true) == false) { + s += '\n' + CodeUtil.getComment(c, true, padding); + } + + s += '\n' + padding + '@Keep' + + '\n' + padding + 'open class ' + name + ' {'; + + return s; + }, + + onParseParentEnd: function () { + return '\n\n' + padding + '}'; + }, + + onParseChildArray: function (key, value, index) { + return this.onParseChildObject(key, value, index); + }, + + onParseChildObject: function (key, value, index) { + return this.onParseJSONObject(key, value, index); + }, + + onParseChildOther: function (key, value, index) { + + if (value instanceof Array) { + log(CodeUtil.TAG, 'parseKotlinResponse for typeof value === "array" >> '); + + return this.onParseJSONArray(key, value, index); + } + if (value instanceof Object) { + log(CodeUtil.TAG, 'parseKotlinResponse for typeof value === "array" >> '); + + return this.onParseJSONObject(key, value, index); + } + + var type = CodeUtil.getKotlinTypeFromJS(key, value, false, false); + var varName = JSONResponse.getVariableName(key); + + var s = '\n\n' + nextPadding + '@SerializedName("' + key + '")' + + '\n' + nextPadding + 'open var ' + varName + ': ' + type + CodeUtil.initEmptyValue4Type(type, isSmart, true) + + CodeUtil.getComment(CodeUtil.getCommentFromDoc(CodeUtil.tableList, name, key, 'GET' + , CodeUtil.database, CodeUtil.language, true), false, ' '); + return s; + }, + + onParseJSONArray: function (key, value, index) { + value = value || [] + + var vn = JSONResponse.getVariableName(key); + var k = vn + (depth <= 0 ? '' : depth); + var itemName = StringUtil.firstCase(k, true) + 'Item' + (depth <= 0 ? '' : depth); + //还有其它字段冲突以及for循环的i冲突,解决不完的,只能让开发者自己抽出函数 var item = StringUtil.addSuffix(k, 'Item'); + + var s = '\n\n' + nextPadding + '// ' + key + ' <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<'; + s += '\n\n' + nextPadding + '@SerializedName("' + key + '")'; + + var t = JSONResponse.getTableName(key); + var isAPIJSONArray = false + if (t.endsWith('[]')) { + t = t.substring(0, t.length - 2); + isAPIJSONArray = true + } + var isTableKey = JSONObject.isTableKey(t); + + var type = value[0] instanceof Object ? (isAPIJSONArray && t.length > 1 ? StringUtil.firstCase(t, true) : itemName) : CodeUtil.getKotlinTypeFromJS(itemName, value[0], false, false); + + s += '\n' + nextPadding + 'open var ' + k + ': List<' + type + '?>' + CodeUtil.initEmptyValue4Type('List', isSmart, true) + + CodeUtil.getComment(CodeUtil.getCommentFromDoc(CodeUtil.tableList, name, key, 'GET', CodeUtil.database, CodeUtil.language, true), false, ' '); + + if (value[0] instanceof Object) { + s += CodeUtil.parseKotlinClasses(type, value[0], depth + 1, isTableKey, isSmart); + } + + s += '\n\n' + nextPadding + '// ' + key + ' >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>\n'; + + return s; + }, + + onParseJSONObject: function (key, value, index) { + var k = JSONResponse.getVariableName(key); + var s = '\n\n' + nextPadding + '// ' + key + ' <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<'; + var t = JSONResponse.getTableName(key); + var isAPIJSONArray = false; + if (t.endsWith('[]')) { + t = t.substring(0, t.length - 2); + isAPIJSONArray = true; + } + var isTableKey = JSONObject.isTableKey(t); + + var type = (StringUtil.firstCase(t, true) || (isAPIJSONArray ? 'Item' : 'Any')) + s += '\n\n' + nextPadding + '@SerializedName("' + key + '")' + + '\n' + nextPadding + 'open var ' + k + ': ' + type + CodeUtil.initEmptyValue4Type(type, isSmart, true) + + CodeUtil.getComment(CodeUtil.getCommentFromDoc(CodeUtil.tableList, name, key, 'GET', CodeUtil.database, CodeUtil.language, true), false, ' '); + + // if (['Boolean', 'Number', 'Integer', 'Long', 'String', 'List', 'Map', 'Any'].indexOf(type) < 0) { + if (['Boolean', 'Number', 'Integer', 'Int', 'Long', 'String'].indexOf(type) < 0) { + s += CodeUtil.parseKotlinClasses(StringUtil.firstCase(type, true), value, depth + 1, isTableKey, isSmart); + } + + s += '\n\n' + nextPadding + '// ' + key + ' >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>\n'; + + return s; + } + }) + + }, + + + + + /**生成 Server-Java API 相关代码 + * @param type + * @param url + * @param reqObj + * @param isSmart + * @return + */ + parseJavaServer: function(type, url, database, schema, reqObj, isSmart) { + var requestMethod = StringUtil.isEmpty(type, true) || type == 'PARAM' ? 'GET' : 'POST'; + + url = url || ''; + + var lastIndex = url.lastIndexOf(CodeUtil.DIVIDER); + var methodUri = lastIndex < 0 ? url : url.substring(lastIndex); + var methodName = JSONResponse.getVariableName(lastIndex < 0 ? url : url.substring(lastIndex + 1)); + + url = url.substring(0, lastIndex); + lastIndex = url.lastIndexOf(CodeUtil.DIVIDER); + var varName = JSONResponse.getVariableName(lastIndex < 0 ? url : url.substring(lastIndex + 1)); + var modelName = StringUtil.firstCase(varName, true); + + if (StringUtil.isEmpty(modelName, true)) { + return ''; + } + + var controllerUri = url; // lastIndex < 0 ? '' : url.substring(0, lastIndex); + + var isPost = type != 'PARAM' && (methodUri.indexOf('post') >= 0 || methodUri.indexOf('add') >= 0 || methodUri.indexOf('create') >= 0); + var isPut = type != 'PARAM' && (methodUri.indexOf('put') >= 0|| methodUri.indexOf('edit') >= 0 || methodUri.indexOf('update') >= 0); + var isDelete = type != 'PARAM' && (methodUri.indexOf('delete') >= 0 || methodUri.indexOf('remove') >= 0 || methodUri.indexOf('del') >= 0); + var isWrite = isPost || isPut || isDelete; + var isGet = ! isWrite; // methodUri.indexOf('get') >= 0 || methodUri.indexOf('fetch') >= 0 || methodUri.indexOf('query') >= 0; + var isList = isGet && (methodUri.indexOf('list') >= 0 || methodUri.indexOf('List') >= 0 || typeof reqObj.pageNum == 'number'); + + var dataType = isWrite ? 'Integer' : (isList ? 'List<' + modelName + '>' : modelName); + + var params = isList ? (reqObj.params || {}) : null; + var pageSize = isList ? (reqObj.pageSize || params.pageSize) : null; + var pageNum = isList ? (reqObj.pageNum || params.pageNum) : null; + + var orderBy = isList ? (reqObj.orderBy) : null; + + /** + * @param annotionType RequestParam, Param, null + */ + function getOrderStr(orderBy) { + if (typeof orderBy == 'string') { + return orderBy; + } + + if (orderBy instanceof Array == false && orderBy instanceof Object) { + var str = ''; + var first = true; + for (var k in orderBy) { + var v = orderBy[k]; + str += (first ? '' : ',') + k + ' ' + v; + first = false; + } + + return str; + } + + return null; + } + + var typeArgStr = CodeUtil.getCode4JavaArgs(reqObj, true, null, ! isSmart); + var argStr = CodeUtil.getCode4JavaArgs(reqObj, false, null, ! isSmart); + + var code = '@RestController("' + controllerUri + '")\n' + + 'public class ' + modelName + 'Controller {\n' + + '\n' + + ' @Autowired\n' + + ' ' + modelName + 'Service ' + varName + 'Service;\n' + + '\n' + + ' @' + (requestMethod == 'POST' ? 'Post' : 'Get') + 'Mapping("' + methodUri + '") //与下面的 @RequestMapping 任选一个\n' + + ' //@RequestMapping("' + methodUri + '", method = RequestMethod.' + requestMethod + ')\n' + + ' public ' + (isSmart && isList ? 'PageInfo<' + modelName + '>' : dataType) + ' ' + methodName + '(' + (type == 'JSON' ? '@RequestBody String body' : CodeUtil.getCode4JavaArgs(reqObj, true, 'RequestParam', ! isSmart)) + ') {\n'; + + if (type == 'JSON') { + //TODO 是否必要转成 User 类?还要考虑 PageHelper 可能是从里面取出来对象 + // var t = JSONResponse.getTableName(key); + // var isTableKey = JSONObject.isTableKey(t); + // if (isTable && isSmart) { + // code += nextPadding + (isTableKey ? t : 'JSONObject') + ' ' + k + ' = ' + name + '.get' + StringUtil.firstCase(k, true) + '();' + // } + // else if (isTableKey && isSmart) { + // code += nextPadding + t + ' ' + k + ' = ' + name + '.getObject("' + key + '", ' + t + '.class);' + // } + // else { + // code += nextPadding + 'JSONObject ' + k + ' = ' + name + '.getJSONObject("' + key + '");' + // } + var nextPadding = CodeUtil.getBlank(2); + var nextNextPadding = CodeUtil.getBlank(3); + + code += nextPadding + 'JSONObject request = JSON.parseObject(body);' + '\n'; + code += nextPadding + 'if (request == null) {' + '\n'; + code += nextNextPadding + 'request = new JSONObject();' + '\n'; + code += nextPadding + '}'; + + code += CodeUtil.parseJavaResponse('request', reqObj, 2, false, isSmart, true) + '\n'; + } + + if (isSmart && isList) { + delete reqObj.params; + delete reqObj.pageSize; + delete reqObj.pageNum; + + delete reqObj.orderBy; + + typeArgStr = CodeUtil.getCode4JavaArgs(reqObj, true, null, ! isSmart); + argStr = CodeUtil.getCode4JavaArgs(reqObj, false, null, ! isSmart); + } + + var orderStr = getOrderStr(orderBy); + var isOrderEmpty = StringUtil.isEmpty(orderStr, true); + + if (isSmart && isList) { + if (pageSize != null) { + code += '\n PageHelper.startPage(pageNum, pageSize' + (isOrderEmpty ? '' : ', orderby') + ');'; + } + else if (isOrderEmpty != true) { + code += '\n PageHelper.setOrderBy(' + orderStr + ');'; + } + } + + code += '\n' + + ' return ' + (isSmart && isList ? 'new PageInfo(' : '') + varName + 'Service.' + methodName + '(' + argStr + ')' + (isSmart && isList ? ')' : '') + ';\n' + + ' }\n' + + '}\n' + + '\n' + + 'public interface ' + modelName + 'Service {\n' + + ' ' + dataType + ' ' + methodName + '(' + typeArgStr + ');\n' + + '}\n' + + '\n' + + '@Service\n' + + 'public class ' + modelName + 'ServiceImpl implements ' + modelName + 'Service {\n' + + '\n' + + ' @Autowired\n' + + ' ' + modelName + 'Mapper ' + varName + 'Mapper;\n' + + '\n' + + ' @Override\n' + + ' public ' + dataType + ' ' + methodName + '(' + typeArgStr + ') {\n' + + ' return ' + varName + 'Mapper.' + methodName + '(' + argStr + ');\n' + + ' }\n' + + '}\n' + + '\n' + + '@Mapper\n' + + 'public interface ' + modelName + 'Mapper {\n' + + ' ' + dataType + ' ' + methodName + '(' + CodeUtil.getCode4JavaArgs(reqObj, true, 'Param', ! isSmart) + ');\n' + + '}'; + + + if (isList) { + delete reqObj.params; + delete reqObj.pageSize; + delete reqObj.pageNum; + + delete reqObj.orderBy; + } + + // var columnStr = (StringUtil.isEmpty(colums, true) ? '' : StringUtil.trim(colums)); + var quote = database == 'MYSQL' ? '`' : '"'; + var tablePath = (StringUtil.isEmpty(schema, true) ? '' : quote + schema + quote + '.') + quote + modelName + quote; + if (isPost) { + code += '\n\n' + + '\n' + + ' INSERT INTO ' + tablePath; + } + else if (isPut) { + code += '\n\n' + + '\n' + + ' UPDATE ' + tablePath; + } + else if (isDelete) { + code += '\n\n' + + '\n' + + ' DELETE FROM ' + tablePath; + } + else { + var colums = Object.keys(reqObj); + var cs = ''; + if (colums != null && colums.length > 0) { + for (var i = 0; i < colums.length; i++) { + cs += (i <= 0 ? '' : ', ') + quote + colums[i] + quote; //需要尽可能保留原字段 [] 肯定不是字段名 JSONResponse.getVariableName(colums[i]) + quote; + } + } + + code += '\n\n' + + ''; + } + + return code; + }, + + + + + /**解析出 生成请求JSON 的代码 + * @param name + * @param reqObj + * @param callback Object,带以下回调函数function: + * 解析父对象Parent的onParseParentStart和onParseParentEnd, + * 解析APIJSON数组Object的onParseArray, + * 解析普通Object的onParseObject, + * 解析其它键值对的onParseOther. * * 其中每个都必须返回String,空的情况下返回'' -> response += callback.fun(...) * @return */ - parseCode: function(name, reqObj, callback) { - if (reqObj == null || reqObj == '') { - log(CodeUtil.TAG, 'parseCode reqObj == null || reqObj.isEmpty() >> return null;'); - return null; + parseCode: function(name, reqObj, callback) { + // if (reqObj == null || reqObj == '') { + // log(CodeUtil.TAG, 'parseCode reqObj == null || reqObj.isEmpty() >> return null;'); + // return null; + // } + if (reqObj instanceof Object == false || reqObj instanceof Array) { // Array 居然也被判断成 object ! typeof reqObj != 'object') { + log(CodeUtil.TAG, 'parseCode typeof reqObj != object >> return null;'); + // return null; + return callback.onParseChildOther(name, reqObj, 0, true); + } + log(CodeUtil.TAG, '\n\n\n parseCode name = ' + name + '; reqObj = \n' + format(JSON.stringify(reqObj))); + + var response = callback.onParseParentStart(); + + var index = 0; //实际有效键值对key:value的所在reqObj内的位置 + var value; + for (var key in reqObj) { + log(CodeUtil.TAG, 'parseCode for key = ' + key); + //key == null || value == null 的键值对被视为无效 + value = key == null ? null : reqObj[key]; + // if (value == null) { + // continue; + // } + log(CodeUtil.TAG, 'parseCode for index = ' + index); + + if (value instanceof Object == false || value instanceof Array) { //typeof value != 'object') {//APIJSON Array转为常规JSONArray + response += callback.onParseChildOther(key, value, index); + } + else { // 其它Object,直接填充 + log(CodeUtil.TAG, 'parseCode for typeof value === "object" >> ' ); + + if (JSONObject.isArrayKey(key)) { // APIJSON Array转为常规JSONArray + log(CodeUtil.TAG, 'parseCode for JSONObject.isArrayKey(key) >> ' ); + + response += callback.onParseChildArray(key, value, index); + } + else { // 常规JSONObject,往下一级提取 + log(CodeUtil.TAG, 'parseCode for JSONObject.isArrayKey(key) == false >> ' ); + + response += callback.onParseChildObject(key, value, index); + } + } + + index ++; + } + + response += callback.onParseParentEnd(); + + log(CodeUtil.TAG, 'parseCode return response = \n' + response + '\n\n\n'); + return response; + }, + + + + + + + + + + /**用数据字典转为JavaBean + * @param docObj + */ + parseJavaBean: function(docObj, clazz, database) { + + //转为Java代码格式 + var doc = ''; + var item; + + var blank = CodeUtil.getBlank(1); + var blank2 = CodeUtil.getBlank(2); + + //[] <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< + var list = docObj == null ? null : docObj['[]']; + if (list != null) { + console.log('parseJavaBean [] = \n' + format(JSON.stringify(list))); + + var table; + var model; + var columnList; + var column; + for (var i = 0; i < list.length; i++) { + item = list[i]; + + //Table + table = item == null ? null : item.Table; + model = CodeUtil.getModelName(table == null ? null : table.table_name); + if (model != clazz) { + continue; + } + + console.log('parseJavaBean [] for i=' + i + ': table = \n' + format(JSON.stringify(table))); + + + doc += '/**' + + '\n *' + CodeUtil.APP_NAME + ' 自动生成 JavaBean\n *主页: https://github.com/TommyLemon/' + CodeUtil.APP_NAME + + '\n *使用方法:\n *1.修改包名 package \n *2.import 需要引入的类,可使用快捷键 Ctrl+Shift+O ' + + '\n */' + + '\npackage apijson.demo.server.model;\n\n\n' + + CodeUtil.getComment(database != 'POSTGRESQL' ? table.table_comment : (item.PgClass || {}).table_comment, true) + + '\n@MethodAccess' + + '\npublic class ' + model + ' implements Serializable {' + + '\n' + blank + 'private static final long serialVersionUID = 1L;'; + + //Column[] + columnList = item['[]']; + if (columnList != null) { + + console.log('parseJavaBean [] for ' + i + ': columnList = \n' + format(JSON.stringify(columnList))); + + doc += '\n' + + '\n' + blank + 'public ' + model + '() {' + + '\n' + blank2 + 'super();' + + '\n' + blank + '}' + + '\n' + blank + 'public ' + model + '(long id) {' + + '\n' + blank2 + 'this();' + + '\n' + blank2 + 'setId(id);' + + '\n' + blank + '}' + + '\n\n' + + var name; + var type; + + for (var j = 0; j < columnList.length; j++) { + column = (columnList[j] || {}).Column; + + name = CodeUtil.getFieldName(column == null ? null : column.column_name); + if (name == '') { + continue; + } + + column.column_type = CodeUtil.getColumnType(column, database); + type = CodeUtil.isId(name, column.column_type) ? 'Long' : CodeUtil.getJavaType(column.column_type, false); + + + console.log('parseJavaBean [] for j=' + j + ': column = \n' + format(JSON.stringify(column))); + + var o = database != 'POSTGRESQL' ? column : (columnList[j] || {}).PgAttribute + doc += '\n' + blank + 'private ' + type + ' ' + name + '; ' + CodeUtil.getComment((o || {}).column_comment, false); + + } + + doc += '\n\n' + + for (var j = 0; j < columnList.length; j++) { + column = (columnList[j] || {}).Column; + + name = CodeUtil.getFieldName(column == null ? null : column.column_name); + if (name == '') { + continue; + } + column.column_type = CodeUtil.getColumnType(column, database); + type = CodeUtil.isId(name, column.column_type) ? 'Long' : CodeUtil.getJavaType(column.column_type, false); + + console.log('parseJavaBean [] for j=' + j + ': column = \n' + format(JSON.stringify(column))); + + //getter + doc += '\n' + blank + 'public ' + type + ' ' + CodeUtil.getMethodName('get', name) + '() {' + + '\n' + blank2 + 'return ' + name + ';' + + '\n' + blank + '}'; + + //setter + doc += '\n' + blank + 'public ' + model + ' ' + CodeUtil.getMethodName('set', name) + '(' + type + ' ' + name + ') {' + + '\n' + blank2 + 'this.' + name + ' = ' + name + ';' + + '\n' + blank2 + 'return this;' + + '\n' + blank + '}\n'; + + } + } + + doc += '\n\n}'; + + } + } + //[] >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> + + return doc; + }, + + /**用数据字典转为JavaBean + * @param docObj + */ + parseCppStruct: function(docObj, clazz, database) { + + //转为Java代码格式 + var doc = ''; + var item; + + var blank = CodeUtil.getBlank(1); + var blank2 = CodeUtil.getBlank(2); + + //[] <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< + var list = docObj == null ? null : docObj['[]']; + if (list != null) { + console.log('parseCppStruct [] = \n' + format(JSON.stringify(list))); + + var table; + var model; + var columnList; + var column; + for (var i = 0; i < list.length; i++) { + item = list[i]; + + //Table + table = item == null ? null : item.Table; + model = CodeUtil.getModelName(table == null ? null : table.table_name); + if (model != clazz) { + continue; + } + + console.log('parseCppStruct [] for i=' + i + ': table = \n' + format(JSON.stringify(table))); + + + doc += '/**' + + '\n *' + CodeUtil.APP_NAME + ' 自动生成 C++ Struct\n *主页: https://github.com/TommyLemon/' + CodeUtil.APP_NAME + + '\n *使用方法:\n *1.修改包名 namespace \n *2.#include 需要引入的类,可使用快捷键 Ctrl+Shift+O ' + + '\n */\n' + + '\n#include ' + + '\n#include ' + + '\n#include ' + + '\n#include ' + + '\n#include ' + + '\n\n\nusing namespace std;\n\n\n' + + CodeUtil.getComment(database != 'POSTGRESQL' ? table.table_comment : (item.PgClass || {}).table_comment, true) + + '\nstruct ' + model + ' {'; + + //Column[] + columnList = item['[]']; + if (columnList != null) { + + console.log('parseCppStruct [] for ' + i + ': columnList = \n' + format(JSON.stringify(columnList))); + + var constructor = '\n\n' + blank + model + '()'; + var constructorWithArgs = '\n\n' + blank + model + '('; + var fields = '\n\n'; + + var name; + var type; + + var first = true; + for (var j = 0; j < columnList.length; j++) { + column = (columnList[j] || {}).Column; + + name = CodeUtil.getFieldName(column == null ? null : column.column_name); + if (name == '') { + continue; + } + + column.column_type = CodeUtil.getColumnType(column, database); + type = CodeUtil.isId(name, column.column_type) ? 'long' : CodeUtil.getCppType(column.column_type, false); + + console.log('parseCppStruct [] for j=' + j + ': column = \n' + format(JSON.stringify(column))); + + constructorWithArgs += (first ? '' : ', ') + type + ' ' + name; + first = false; + } + + constructorWithArgs += ')'; + + var first2 = true; + for (var j = 0; j < columnList.length; j++) { + column = (columnList[j] || {}).Column; + + name = CodeUtil.getFieldName(column == null ? null : column.column_name); + if (name == '') { + continue; + } + + column.column_type = CodeUtil.getColumnType(column, database); + type = CodeUtil.isId(name, column.column_type) ? 'long' : CodeUtil.getCppType(column.column_type, false); + + console.log('parseCppStruct [] for j=' + j + ': column = \n' + format(JSON.stringify(column))); + + var o = database != 'POSTGRESQL' ? column : (columnList[j] || {}).PgAttribute + fields += '\n' + blank + type + ' ' + name + '; ' + CodeUtil.getComment((o || {}).column_comment, false); + + constructor += (first2 ? ' : ' : ', ') + name + '()'; + constructorWithArgs += (first2 ? '\n' + blank2 + ': ' : ', ') + name + '(' + name + ')'; + first2 = false; + } + + constructor += ' {}'; + constructorWithArgs += ' {}'; + + doc += constructor + constructorWithArgs + fields; + } + + doc += '\n\n};'; + + } + } + //[] >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> + + return doc; + }, + + +// /**用数据字典转为JavaBean +// * @param docObj +// */ +// parseObjectiveCEntity: function(docObj, clazz, database) { +// +// //转为Java代码格式 +// var doc = ''; +// var item; +// +// var blank = CodeUtil.getBlank(1); +// var blank2 = CodeUtil.getBlank(2); +// +// //[] <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< +// var list = docObj == null ? null : docObj['[]']; +// if (list != null) { +// console.log('parseJavaBean [] = \n' + format(JSON.stringify(list))); +// +// var table; +// var model; +// var columnList; +// var column; +// for (var i = 0; i < list.length; i++) { +// item = list[i]; +// +// //Table +// table = item == null ? null : item.Table; +// model = CodeUtil.getModelName(table == null ? null : table.table_name); +// if (model != clazz) { +// continue; +// } +// +// console.log('parseJavaBean [] for i=' + i + ': table = \n' + format(JSON.stringify(table))); +// +// +// doc += '/**' +// + '\n *' + CodeUtil.APP_NAME + ' 自动生成 JavaBean\n *主页: https://github.com/TommyLemon/' + CodeUtil.APP_NAME +// + '\n *使用方法:\n *1.修改包名 package \n *2.import 需要引入的类,可使用快捷键 Ctrl+Shift+O ' +// + '\n */' +// + '\npackage apijson.demo.server.model;\n\n\n' +// + CodeUtil.getComment(database != 'POSTGRESQL' ? table.table_comment : (item.PgClass || {}).table_comment, true) +// + '\n@MethodAccess' +// + '\npublic class ' + model + ' implements Serializable {' +// + '\n' + blank + 'private static final long serialVersionUID = 1L;'; +// +// //Column[] +// columnList = item['[]']; +// if (columnList != null) { +// +// console.log('parseJavaBean [] for ' + i + ': columnList = \n' + format(JSON.stringify(columnList))); +// +// doc += '\n' +// + '\n' + blank + 'public ' + model + '() {' +// + '\n' + blank2 + 'super();' +// + '\n' + blank + '}' +// + '\n' + blank + 'public ' + model + '(long id) {' +// + '\n' + blank2 + 'this();' +// + '\n' + blank2 + 'setId(id);' +// + '\n' + blank + '}' +// + '\n\n' +// +// var name; +// var type; +// +// for (var j = 0; j < columnList.length; j++) { +// column = (columnList[j] || {}).Column; +// +// name = CodeUtil.getFieldName(column == null ? null : column.column_name); +// if (name == '') { +// continue; +// } +// +// column.column_type = CodeUtil.getColumnType(column, database); +// type = CodeUtil.isId(name, column.column_type) ? 'Long' : CodeUtil.getJavaType(column.column_type, false); +// +// +// console.log('parseJavaBean [] for j=' + j + ': column = \n' + format(JSON.stringify(column))); +// +// var o = database != 'POSTGRESQL' ? column : (columnList[j] || {}).PgAttribute +// doc += '\n' + blank + 'private ' + type + ' ' + name + '; ' + CodeUtil.getComment((o || {}).column_comment, false); +// +// } +// +// doc += '\n\n' +// +// for (var j = 0; j < columnList.length; j++) { +// column = (columnList[j] || {}).Column; +// +// name = CodeUtil.getFieldName(column == null ? null : column.column_name); +// if (name == '') { +// continue; +// } +// column.column_type = CodeUtil.getColumnType(column, database); +// type = CodeUtil.isId(name, column.column_type) ? 'Long' : CodeUtil.getJavaType(column.column_type, false); +// +// console.log('parseJavaBean [] for j=' + j + ': column = \n' + format(JSON.stringify(column))); +// +// //getter +// doc += '\n' + blank + 'public ' + type + ' ' + CodeUtil.getMethodName('get', name) + '() {' +// + '\n' + blank2 + 'return ' + name + ';' +// + '\n' + blank + '}'; +// +// //setter +// doc += '\n' + blank + 'public ' + model + ' ' + CodeUtil.getMethodName('set', name) + '(' + type + ' ' + name + ') {' +// + '\n' + blank2 + 'this.' + name + ' = ' + name + ';' +// + '\n' + blank2 + 'return this;' +// + '\n' + blank + '}\n'; +// +// } +// } +// +// doc += '\n\n}'; +// +// } +// } +// //[] >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> +// +// return doc; +// }, + + + /**用数据字典转为 PHP 实体类 + * @param docObj + */ + parsePHPEntity: function(docObj, clazz, database) { + + //转为Java代码格式 + var doc = ''; + var item; + + var blank = CodeUtil.getBlank(1); + var blank2 = CodeUtil.getBlank(2); + + //[] <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< + var list = docObj == null ? null : docObj['[]']; + if (list != null) { + console.log('parseJavaBean [] = \n' + format(JSON.stringify(list))); + + var table; + var model; + var columnList; + var column; + for (var i = 0; i < list.length; i++) { + item = list[i]; + + //Table + table = item == null ? null : item.Table; + model = CodeUtil.getModelName(table == null ? null : table.table_name); + if (model != clazz) { + continue; + } + + console.log('parsePHPEntity [] for i=' + i + ': table = \n' + format(JSON.stringify(table))); + + + doc += 'construct();' + // + '\n' + blank2 + '$this->setId($id);' + // + '\n' + blank + '}' + + '\n\n' + + var name; + + for (var j = 0; j < columnList.length; j++) { + column = (columnList[j] || {}).Column; + + name = CodeUtil.getFieldName(column == null ? null : column.column_name); + if (name == '') { + continue; + } + + column.column_type = CodeUtil.getColumnType(column, database); + + + console.log('parsePHPEntity [] for j=' + j + ': column = \n' + format(JSON.stringify(column))); + + var o = database != 'POSTGRESQL' ? column : (columnList[j] || {}).PgAttribute + doc += '\n' + blank + 'private $' + name + '; ' + CodeUtil.getComment((o || {}).column_comment, false); + + } + + doc += '\n\n' + + for (var j = 0; j < columnList.length; j++) { + column = (columnList[j] || {}).Column; + + name = CodeUtil.getFieldName(column == null ? null : column.column_name); + if (name == '') { + continue; + } + column.column_type = CodeUtil.getColumnType(column, database); + + console.log('parsePHPEntity [] for j=' + j + ': column = \n' + format(JSON.stringify(column))); + + //getter + doc += '\n' + blank + 'public function ' + CodeUtil.getMethodName('get', name) + '() {' + + '\n' + blank2 + 'return $' + name + ';' + + '\n' + blank + '}'; + + //setter + doc += '\n' + blank + 'public function ' + CodeUtil.getMethodName('set', name) + '($' + name + ') {' + + '\n' + blank2 + '$this->$' + name + ' = $' + name + ';' + + '\n' + blank2 + 'return $this;' + + '\n' + blank + '}\n'; + + } + } + + doc += '\n\n}'; + doc += '\n?>'; + + } + } + //[] >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> + + return doc; + }, + + + /**用数据字典转为JavaBean + * @param docObj + */ + parseGoEntity: function(docObj, clazz, database) { + + //转为Java代码格式 + var doc = ''; + var item; + + var blank = CodeUtil.getBlank(1); + var blank2 = CodeUtil.getBlank(2); + + //[] <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< + var list = docObj == null ? null : docObj['[]']; + if (list != null) { + console.log('parseGoEntity [] = \n' + format(JSON.stringify(list))); + + var table; + var model; + var columnList; + var column; + for (var i = 0; i < list.length; i++) { + item = list[i]; + + //Table + table = item == null ? null : item.Table; + model = CodeUtil.getModelName(table == null ? null : table.table_name); + if (model != clazz) { + continue; + } + + console.log('parseGoEntity [] for i=' + i + ': table = \n' + format(JSON.stringify(table))); + + + doc += '/**' + + '\n *' + CodeUtil.APP_NAME + ' 自动生成 Go struct\n *主页: https://github.com/TommyLemon/' + CodeUtil.APP_NAME + + '\n *使用方法:\n *1.修改包名 package \n *2.import 需要引入的类,可使用快捷键 Ctrl+Shift+O ' + + '\n */' + + '\npackage model\n\n\n' + + CodeUtil.getComment(database != 'POSTGRESQL' ? table.table_comment : (item.PgClass || {}).table_comment, true) + + '\n// @MethodAccess' + + '\ntype ' + model + ' struct {'; + + //Column[] + columnList = item['[]']; + if (columnList != null) { + + console.log('parseGoEntity [] for ' + i + ': columnList = \n' + format(JSON.stringify(columnList))); + + var name; + var type; + + for (var j = 0; j < columnList.length; j++) { + column = (columnList[j] || {}).Column; + + name = CodeUtil.getFieldName(column == null ? null : column.column_name); + if (name == '') { + continue; + } + + column.column_type = CodeUtil.getColumnType(column, database); + type = CodeUtil.isId(name, column.column_type) ? 'int64' : CodeUtil.getType4Language(CodeUtil.LANGUAGE_GO, column.column_type, false); + + + console.log('parseGoEntity [] for j=' + j + ': column = \n' + format(JSON.stringify(column))); + + var o = database != 'POSTGRESQL' ? column : (columnList[j] || {}).PgAttribute + doc += '\n' + blank + name + ' ' + type + ' `json:"' + name + '"` ' + CodeUtil.getComment((o || {}).column_comment, false); + + } + + } + + doc += '\n}'; + + } + } + //[] >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> + + return doc; + }, + + + /**用数据字典转为JavaBean + * @param docObj + */ + parseCSharpEntity: function(docObj, clazz, database) { + + //转为Java代码格式 + var doc = ''; + var item; + + var blank = CodeUtil.getBlank(1); + var blank2 = CodeUtil.getBlank(2); + + //[] <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< + var list = docObj == null ? null : docObj['[]']; + if (list != null) { + console.log('parseCSharpEntity [] = \n' + format(JSON.stringify(list))); + + var table; + var model; + var columnList; + var column; + for (var i = 0; i < list.length; i++) { + item = list[i]; + + //Table + table = item == null ? null : item.Table; + model = CodeUtil.getModelName(table == null ? null : table.table_name); + if (model != clazz) { + continue; + } + + console.log('parseCSharpEntity [] for i=' + i + ': table = \n' + format(JSON.stringify(table))); + + + doc += '/**' + + '\n *' + CodeUtil.APP_NAME + ' 自动生成 C# Bean\n *主页: https://github.com/TommyLemon/' + CodeUtil.APP_NAME + + '\n *使用方法:\n *1.修改包名 namespace \n *2. using 需要引入的类,可使用快捷键 Ctrl+Shift+O ' + + '\n */\n' + + '\nnamespace apijson.demo.server.model' + + '\n{' + + '\n' + blank + '[MethodAccess]' + + '\n' + blank + '[Serializable]' + + '\n' + blank + 'public class ' + model + ' ' + CodeUtil.getComment(database != 'POSTGRESQL' ? table.table_comment : (item.PgClass || {}).table_comment, false) + + '\n' + blank + '{'; + + //Column[] + columnList = item['[]']; + if (columnList != null) { + + console.log('parseCSharpBean [] for ' + i + ': columnList = \n' + format(JSON.stringify(columnList))); + + doc += '\n' + + '\n' + blank2 + 'public ' + model + '()' + + '\n' + blank2 + '{' + + '\n' + blank2 + '}' + + '\n' + blank2 + 'public ' + model + '(long id)' + + '\n' + blank2 + '{' + + '\n' + blank2 + blank + 'setId(id);' + + '\n' + blank2 + '}' + + '\n\n' + + var name; + var type; + + for (var j = 0; j < columnList.length; j++) { + column = (columnList[j] || {}).Column; + + name = CodeUtil.getFieldName(column == null ? null : column.column_name); + if (name == '') { + continue; + } + + column.column_type = CodeUtil.getColumnType(column, database); + type = CodeUtil.isId(name, column.column_type) ? 'Int64' : CodeUtil.getType4Language(CodeUtil.LANGUAGE_C_SHARP, column.column_type, false); + + + console.log('parseCSharpBean [] for j=' + j + ': column = \n' + format(JSON.stringify(column))); + + var o = database != 'POSTGRESQL' ? column : (columnList[j] || {}).PgAttribute + doc += '\n' + blank2 + 'private ' + type + ' ' + name + ' { get; set; } ' + CodeUtil.getComment((o || {}).column_comment, false); + + } + + } + + doc += '\n\n' + blank + '}'; + doc += '\n\n}'; + + } } - if (typeof reqObj != 'object') { - log(CodeUtil.TAG, 'parseCode typeof reqObj != object >> return null;'); - return null; + //[] >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> + + return doc; + }, + + + /**用数据字典转为 TypeScript 类 + * @param docObj + */ + parseTypeScriptEntity: function(docObj, clazz, database) { + + //转为Java代码格式 + var doc = ''; + var item; + + //[] <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< + var list = docObj == null ? null : docObj['[]']; + if (list != null) { + console.log('parseTypeScriptClass [] = \n' + format(JSON.stringify(list))); + + var table; + var model; + var columnList; + var column; + for (var i = 0; i < list.length; i++) { + item = list[i]; + + //Table + table = item == null ? null : item.Table; + model = CodeUtil.getModelName(table == null ? null : table.table_name); + if (model != clazz) { + continue; + } + + console.log('parseTypeScriptClass [] for i=' + i + ': table = \n' + format(JSON.stringify(table))); + + + doc += '/**' + + '\n *' + CodeUtil.APP_NAME + ' 自动生成 TypeScript Entity\n *主页: https://github.com/TommyLemon/' + CodeUtil.APP_NAME + + '\n */\n\n\n' + + CodeUtil.getComment(database != 'POSTGRESQL' ? table.table_comment : (item.PgClass || {}).table_comment, true) + + '\n@MethodAccess' + + '\nclass ' + model + ' {\n'; + + //Column[] + columnList = item['[]']; + if (columnList != null) { + + console.log('parseTypeScriptClass [] for ' + i + ': columnList = \n' + format(JSON.stringify(columnList))); + + var name; + var type; + + doc += '\n constructor('; + + for (var j = 0; j < columnList.length; j++) { + column = (columnList[j] || {}).Column; + + name = CodeUtil.getFieldName(column == null ? null : column.column_name); + if (name == '') { + continue; + } + column.column_type = CodeUtil.getColumnType(column, database); + type = CodeUtil.isId(name, column.column_type) ? 'number' : CodeUtil.getType4Language(CodeUtil.LANGUAGE_TYPE_SCRIPT, column.column_type, false); + + console.log('parseTypeScriptClass [] for j=' + j + ': column = \n' + format(JSON.stringify(column))); + + var o = database != 'POSTGRESQL' ? column : (columnList[j] || {}).PgAttribute + doc += '\n public '+ name + ': ' + type + ', ' + CodeUtil.getComment((o || {}).column_comment, false); + + } + + doc += '\n ) { }'; + + } + + doc += '\n\n}'; + + } } - log(CodeUtil.TAG, '\n\n\n parseCode name = ' + name + '; reqObj = \n' + format(JSON.stringify(reqObj))); + //[] >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> + + return doc; + }, + + + + /**用数据字典转为 Python 类 + * @param docObj + */ + parsePythonEntity: function(docObj, clazz, database) { + //转为Java代码格式 + var doc = ''; + var item; + + var blank = CodeUtil.getBlank(1); + var blank2 = CodeUtil.getBlank(2); + + //[] <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< + var list = docObj == null ? null : docObj['[]']; + if (list != null) { + console.log('parsePythonEntity [] = \n' + format(JSON.stringify(list))); + + var table; + var model; + var columnList; + var column; + for (var i = 0; i < list.length; i++) { + item = list[i]; + + //Table + table = item == null ? null : item.Table; + model = CodeUtil.getModelName(table == null ? null : table.table_name); + if (model != clazz) { + continue; + } + + console.log('parsePythonEntity [] for i=' + i + ': table = \n' + format(JSON.stringify(table))); + + + doc += '/**' + + '\n *' + CodeUtil.APP_NAME + ' 自动生成 Python Entity\n *主页: https://github.com/TommyLemon/' + CodeUtil.APP_NAME + + '\n *使用方法:\n *1.修改包名 package \n *2.import 需要引入的类,可使用快捷键 Ctrl+Shift+O ' + + '\n */' + + '\npackage apijson.demo.server.model;\n\n\n' + + CodeUtil.getComment(database != 'POSTGRESQL' ? table.table_comment : (item.PgClass || {}).table_comment, true) + + '\n@MethodAccess' + + '\nclass ' + model + ':'; + + //Column[] + columnList = item['[]']; + if (columnList != null) { + + console.log('parsePythonEntity [] for ' + i + ': columnList = \n' + format(JSON.stringify(columnList))); + + doc += '\n' + + '\n' + blank + 'def __init__(self, id: int = 0):' + + '\n' + blank2 + 'super().__init__()' + + '\n' + blank2 + 'setId(id)' + + '\n\n'; + + var name; + var type; + + for (var j = 0; j < columnList.length; j++) { + column = (columnList[j] || {}).Column; + + name = CodeUtil.getFieldName(column == null ? null : column.column_name); + if (name == '') { + continue; + } + + column.column_type = CodeUtil.getColumnType(column, database); + type = CodeUtil.getType4Language(CodeUtil.LANGUAGE_PYTHON, column.column_type, false); + + + console.log('parseCSharpEntity [] for j=' + j + ': column = \n' + format(JSON.stringify(column))); + + var o = database != 'POSTGRESQL' ? column : (columnList[j] || {}).PgAttribute + doc += '\n' + blank + name + ': ' + type + ' = None ' + CodeUtil.getComment((o || {}).column_comment, false); + + } + + doc += '\n\n' + + for (var j = 0; j < columnList.length; j++) { + column = (columnList[j] || {}).Column; + + name = CodeUtil.getFieldName(column == null ? null : column.column_name); + if (name == '') { + continue; + } + column.column_type = CodeUtil.getColumnType(column, database); + type = CodeUtil.getType4Language(CodeUtil.LANGUAGE_PYTHON, column.column_type, false); + + console.log('parsePythonEntity [] for j=' + j + ': column = \n' + format(JSON.stringify(column))); + + //getter + doc += '\n' + blank + 'def ' + CodeUtil.getMethodName('get', name) + '() -> ' + type + ':' + + '\n' + blank2 + 'return ' + name; + + //setter + doc += '\n' + blank + 'def ' + CodeUtil.getMethodName('set', name) + '(' + name + ': ' + type + '):' + + '\n' + blank2 + 'self.' + name + ' = ' + name + + '\n' + blank2 + 'return this'; + + } + } + + doc += '\n\n}'; + + } + } + //[] >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> + + return doc; + }, + + + + /**用数据字典转为 Swift Entity 类 + * @param docObj + */ + parseSwiftStruct: function(docObj, clazz, database) { + + //转为Java代码格式 + var doc = ''; + var item; + + var blank = CodeUtil.getBlank(1); + var blank2 = CodeUtil.getBlank(2); + + //[] <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< + var list = docObj == null ? null : docObj['[]']; + if (list != null) { + console.log('parseSwiftStruct [] = \n' + format(JSON.stringify(list))); + + var table; + var model; + var columnList; + var column; + for (var i = 0; i < list.length; i++) { + item = list[i]; + + //Table + table = item == null ? null : item.Table; + model = CodeUtil.getModelName(table == null ? null : table.table_name); + if (model != clazz) { + continue; + } + + console.log('parseSwiftStruct [] for i=' + i + ': table = \n' + format(JSON.stringify(table))); + + + doc += '/**' + + '\n *' + CodeUtil.APP_NAME + ' 自动生成 Swift Struct\n *主页: https://github.com/TommyLemon/' + CodeUtil.APP_NAME + + '\n *使用方法:\n *1.修改包名 package \n *2.import 需要引入的类,可使用快捷键 Ctrl+Shift+O ' + + '\n */' + + '\npackage apijson.demo.server.model\n\n\n' + + CodeUtil.getComment(database != 'POSTGRESQL' ? table.table_comment : (item.PgClass || {}).table_comment, true) + + '\n@MethodAccess' + + '\nstruct ' + model + ': Codable {'; + + //Column[] + columnList = item['[]']; + if (columnList != null) { - var response = callback.onParseParentStart(); + console.log('parseSwiftStruct [] for ' + i + ': columnList = \n' + format(JSON.stringify(columnList))); - var index = 0; //实际有效键值对key:value的所在reqObj内的位置 - var value; - for (var key in reqObj) { - log(CodeUtil.TAG, 'parseCode for key = ' + key); - //key == null || value == null 的键值对被视为无效 - value = key == null ? null : reqObj[key]; - if (value == null) { - continue; - } + var name; + var type; - log(CodeUtil.TAG, 'parseCode for index = ' + index); + for (var j = 0; j < columnList.length; j++) { + column = (columnList[j] || {}).Column; - if (value instanceof Object && (value instanceof Array) == false) {//APIJSON Array转为常规JSONArray - log(CodeUtil.TAG, 'parseCode for typeof value === "object" >> ' ); + name = CodeUtil.getFieldName(column == null ? null : column.column_name); + if (name == '') { + continue; + } + column.column_type = CodeUtil.getColumnType(column, database); + type = CodeUtil.isId(name, column.column_type) ? 'Int' : CodeUtil.getType4Language(CodeUtil.LANGUAGE_SWIFT, column.column_type, false); - if (JSONObject.isArrayKey(key)) { // APIJSON Array转为常规JSONArray - log(CodeUtil.TAG, 'parseCode for JSONObject.isArrayKey(key) >> ' ); + console.log('parseSwiftStruct [] for j=' + j + ': column = \n' + format(JSON.stringify(column))); - response += callback.onParseChildArray(key, value, index); - } - else { // 常规JSONObject,往下一级提取 - log(CodeUtil.TAG, 'parseCode for JSONObject.isArrayKey(key) == false >> ' ); + var o = database != 'POSTGRESQL' ? column : (columnList[j] || {}).PgAttribute + doc += '\n' + blank + 'var '+ name + ': ' + type + '? ' + CodeUtil.getComment((o || {}).column_comment, false); + + } - response += callback.onParseChildObject(key, value, index); } - } - else { // 其它Object,直接填充 - response += callback.onParseChildOther(key, value, index); - } + doc += '\n\n}'; - index ++; + } } + //[] >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> + return doc; + }, - response += callback.onParseParentEnd(); + /**用数据字典转为 TypeScript 类 + * @param docObj + */ + parseJavaScriptEntity: function(docObj, clazz, database) { - log(CodeUtil.TAG, 'parseCode return response = \n' + response + '\n\n\n'); - return response; - }, + //转为Java代码格式 + var doc = ''; + var item; + //[] <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< + var list = docObj == null ? null : docObj['[]']; + if (list != null) { + console.log('parseJavaScriptClass [] = \n' + format(JSON.stringify(list))); + var table; + var model; + var columnList; + var column; + for (var i = 0; i < list.length; i++) { + item = list[i]; + //Table + table = item == null ? null : item.Table; + model = CodeUtil.getModelName(table == null ? null : table.table_name); + if (model != clazz) { + continue; + } + console.log('parseJavaScriptClass [] for i=' + i + ': table = \n' + format(JSON.stringify(table))); + doc += '/**' + + '\n *' + CodeUtil.APP_NAME + ' 自动生成 JavaScript Entity\n *主页: https://github.com/TommyLemon/' + CodeUtil.APP_NAME + + '\n */\n\n\n' + + CodeUtil.getComment(database != 'POSTGRESQL' ? table.table_comment : (item.PgClass || {}).table_comment, true) + + '\n@MethodAccess' + + '\nclass ' + model + ' {\n'; + //Column[] + columnList = item['[]']; + if (columnList != null) { + console.log('parseJavaScriptClass [] for ' + i + ': columnList = \n' + format(JSON.stringify(columnList))); - /**用数据字典转为JavaBean + var name; + + doc += '\n constructor('; + + for (var j = 0; j < columnList.length; j++) { + column = (columnList[j] || {}).Column; + + name = CodeUtil.getFieldName(column == null ? null : column.column_name); + if (name == '') { + continue; + } + + console.log('parseJavaScriptClass [] for j=' + j + ': column = \n' + format(JSON.stringify(column))); + + doc += (j <= 0 ? '' : ', ') + name; + } + + doc += ') {\n'; + + for (var j = 0; j < columnList.length; j++) { + column = (columnList[j] || {}).Column; + + name = CodeUtil.getFieldName(column == null ? null : column.column_name); + if (name == '') { + continue; + } + + console.log('parseJavaScriptClass [] for j=' + j + ': column = \n' + format(JSON.stringify(column))); + + var o = database != 'POSTGRESQL' ? column : (columnList[j] || {}).PgAttribute + doc += '\n this.'+ name + ' = ' + name + ' ' + CodeUtil.getComment((o || {}).column_comment, false); + + } + + doc += '\n }'; + + } + + doc += '\n\n}'; + + } + } + //[] >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> + + return doc; + }, + + /**用数据字典转为 Kotlin Data 类 * @param docObj */ - parseJavaBean: function(docObj, clazz) { + parseKotlinDataClass: function(docObj, clazz, database) { //转为Java代码格式 var doc = ''; var item; + var blank = CodeUtil.getBlank(1); + var blank2 = CodeUtil.getBlank(2); + //[] <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< var list = docObj == null ? null : docObj['[]']; if (list != null) { - console.log('parseJavaBean [] = \n' + format(JSON.stringify(list))); + console.log('parseKotlinDataClass [] = \n' + format(JSON.stringify(list))); var table; var model; @@ -496,209 +5182,521 @@ var CodeUtil = { //Table table = item == null ? null : item.Table; - model = CodeUtil.getModelName(table == null ? null : table.TABLE_NAME); + model = CodeUtil.getModelName(table == null ? null : table.table_name); if (model != clazz) { continue; } - console.log('parseJavaBean [] for i=' + i + ': table = \n' + format(JSON.stringify(table))); + console.log('parseKotlinDataClass [] for i=' + i + ': table = \n' + format(JSON.stringify(table))); - doc += '/*' - + '\nAPIJSONAuto自动生成JavaBean\n主页: http://apijson.cn' - + '\n\n使用方法\n1.修改包名package \n2.import需要引入的类,可使用快捷键Ctrl+Shift+O ' - + '\n*/\n' - + '\npackage apijson.demo.server.model;\n\n\n' - + CodeUtil.getComment(table.TABLE_COMMENT, true) + doc += '/**' + + '\n *' + CodeUtil.APP_NAME + ' 自动生成 Kotlin Data Class\n *主页: https://github.com/TommyLemon/' + CodeUtil.APP_NAME + + '\n *使用方法:\n *1.修改包名 package \n *2.import 需要引入的类,可使用快捷键 Ctrl+Shift+O ' + + '\n */' + + '\npackage apijson.demo.server.model\n\n\n' + + CodeUtil.getComment(database != 'POSTGRESQL' ? table.table_comment : (item.PgClass || {}).table_comment, true) + '\n@MethodAccess' - + '\npublic class ' + model + ' implements Serializable {' - + '\n private static final long serialVersionUID = 1L;\n'; + + '\nopen class ' + model + ' {'; //Column[] - columnList = item['Column[]']; + columnList = item['[]']; if (columnList != null) { - console.log('parseJavaBean [] for ' + i + ': columnList = \n' + format(JSON.stringify(columnList))); + console.log('parseKotlinDataClass [] for ' + i + ': columnList = \n' + format(JSON.stringify(columnList))); var name; var type; for (var j = 0; j < columnList.length; j++) { - column = columnList[j]; + column = (columnList[j] || {}).Column; - name = CodeUtil.getFieldName(column == null ? null : column.COLUMN_NAME); + name = CodeUtil.getFieldName(column == null ? null : column.column_name); if (name == '') { continue; } - type = name == 'id' ? 'Long' : CodeUtil.getJavaType(column.COLUMN_TYPE, false); - + column.column_type = CodeUtil.getColumnType(column, database); + type = CodeUtil.isId(name, column.column_type) ? 'Long' : CodeUtil.getType4Language(CodeUtil.LANGUAGE_KOTLIN, column.column_type, false); - console.log('parseJavaBean [] for j=' + j + ': column = \n' + format(JSON.stringify(column))); + console.log('parseKotlinDataClass [] for j=' + j + ': column = \n' + format(JSON.stringify(column))); - doc += '\n private ' + type + ' ' + name + '; ' + CodeUtil.getComment(column.COLUMN_COMMENT, false); + var o = database != 'POSTGRESQL' ? column : (columnList[j] || {}).PgAttribute + doc += '\n' + blank + 'var '+ name + ': ' + type + '? = null ' + CodeUtil.getComment((o || {}).column_comment, false); } - doc += '\n\n' - + '\n public ' + model + '() {' - + '\n super();' - + '\n }' - + '\n public ' + model + '(long id) {' - + '\n this();' - + '\n setId(id);' - + '\n }' - + '\n\n\n\n' + } + + doc += '\n\n}'; + + } + } + //[] >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> + + return doc; + }, + + + + /**获取model类名 + * @param tableName + * @return {*} + */ + getModelName: function(tableName) { + var model = StringUtil.noBlank(tableName); + if (model == '') { + return model; + } + var lastIndex = model.lastIndexOf('_'); + if (lastIndex >= 0) { + model = model.substring(lastIndex + 1); + } + return StringUtil.firstCase(model, true); + }, + /**获取model成员变量名 + * @param columnName + * @return {*} + */ + getFieldName: function(columnName) { + return StringUtil.firstCase(StringUtil.noBlank(columnName), false); + }, + /**获取model方法名 + * @param prefix @notNull 前缀,一般是get,set等 + * @param field @notNull + * @return {*} + */ + getMethodName: function(prefix, field) { + if (field.startsWith('_')) { + field = '_' + field; //get_name 会被fastjson解析为name而不是_name,所以要多加一个_ + } + return prefix + StringUtil.firstCase(field, true); + }, + + /**获取注释 + * @param comment + * @param multiple 多行 + * @param prefix 多行注释的前缀,一般是空格 + * @return {*} + */ + getComment: function(comment, multiple, prefix) { + comment = comment == null ? '' : comment.trim(); + if (prefix == null) { + prefix = ''; + } + if (multiple == false) { + return prefix + '// ' + comment.replace(/\n/g, ' '); + } + + + //多行注释,需要每行加 * 和空格 + + var newComment = prefix + '/**'; + var index; + do { + newComment += '\n'; + index = comment.indexOf('\n'); + if (index < 0) { + newComment += prefix + ' * ' + comment; + break; + } + newComment += prefix + ' * ' + comment.substring(0, index); + comment = comment.substring(index + 2); + } + while(comment != '') + + return newComment + '\n' + prefix + ' */'; + }, + + /**获取Java的值 + * @param value + * @return {*} + */ + getCode4Value: function (language, value, key, depth, isSmart, isArrayItem, callback) { + language = language || ''; + if (value == null) { + switch (language) { + case CodeUtil.LANGUAGE_KOTLIN: + case CodeUtil.LANGUAGE_JAVA: + case CodeUtil.LANGUAGE_C_SHARP: + break; + + case CodeUtil.LANGUAGE_SWIFT: + case CodeUtil.LANGUAGE_OBJECTIVE_C: + return 'nil'; + + case CodeUtil.LANGUAGE_GO: + return 'nil'; + case CodeUtil.LANGUAGE_C_PLUS_PLUS: + return 'Value().Move()'; //报错:AddMemeber 不允许 NULL ! 'NULL'; + + case CodeUtil.LANGUAGE_TYPE_SCRIPT: + case CodeUtil.LANGUAGE_JAVA_SCRIPT: + break; + + case CodeUtil.LANGUAGE_PHP: + break; + case CodeUtil.LANGUAGE_PYTHON: + return 'None'; + } + return 'null'; + } + + if (value === false) { + switch (language) { + case CodeUtil.LANGUAGE_KOTLIN: + case CodeUtil.LANGUAGE_JAVA: + case CodeUtil.LANGUAGE_C_SHARP: + break; + + case CodeUtil.LANGUAGE_SWIFT: + case CodeUtil.LANGUAGE_OBJECTIVE_C: + break; + + case CodeUtil.LANGUAGE_GO: + case CodeUtil.LANGUAGE_C_PLUS_PLUS: + break; + + //以下都不需要解析,直接用左侧的 JSON + case CodeUtil.LANGUAGE_TYPE_SCRIPT: + case CodeUtil.LANGUAGE_JAVA_SCRIPT: + break; + + case CodeUtil.LANGUAGE_PHP: + break; + case CodeUtil.LANGUAGE_PYTHON: + return 'False'; + } + return 'false'; + } + if (value === true) { + switch (language) { + case CodeUtil.LANGUAGE_KOTLIN: + case CodeUtil.LANGUAGE_JAVA: + case CodeUtil.LANGUAGE_C_SHARP: + break; + + case CodeUtil.LANGUAGE_SWIFT: + case CodeUtil.LANGUAGE_OBJECTIVE_C: + break; + + case CodeUtil.LANGUAGE_GO: + case CodeUtil.LANGUAGE_C_PLUS_PLUS: + break; + + case CodeUtil.LANGUAGE_TYPE_SCRIPT: + case CodeUtil.LANGUAGE_JAVA_SCRIPT: + break; + + case CodeUtil.LANGUAGE_PHP: + break; + case CodeUtil.LANGUAGE_PYTHON: + return 'True'; + } + return 'true'; + } + + if (typeof value == 'number') { + log(CodeUtil.TAG, 'getCode4Value value == null || typeof value == "boolean" || typeof value == "number" >> return value;'); + return value; + } + if (typeof value == 'string') { + log(CodeUtil.TAG, 'getCode4Value typeof value === "string" >> return " + value + ";' ); + if (isSmart && [CodeUtil.LANGUAGE_JAVA_SCRIPT, CodeUtil.LANGUAGE_TYPE_SCRIPT, CodeUtil.LANGUAGE_PHP, CodeUtil.LANGUAGE_PYTHON].indexOf(language) >= 0) { + return (language == CodeUtil.LANGUAGE_PYTHON ? 'u' : '') + "'" + value + "'"; + } + return (language == CodeUtil.LANGUAGE_PYTHON ? 'u' : '') + '"' + value + '"'; + } + + if (callback == null) { + return value; + } + + depth = (depth || 0) + return '\n' + CodeUtil.getBlank(depth + 1) + callback(key, value, depth + 1, isSmart, isArrayItem);// + '\n' + CodeUtil.getBlank(depth); + }, + + getJavaTypeFromJS: function (key, value, isArrayItem, baseFirst, rawType, isSmart) { + var t = JSONResponse.getType(value); + if (t == 'boolean') { + return baseFirst ? 'boolean' : 'Boolean'; + } + if (t == 'number') { + if (Number.isInteger(value) != true) { + return baseFirst ? 'double' : 'Double'; + } + } + + if (t == 'number' || t == 'integer') { + if (Math.abs(value) >= 2147483647 || CodeUtil.isId(key, 'bigint', isArrayItem)) { + return baseFirst ? 'long' : 'Long'; + } + return baseFirst ? 'int' : 'Integer'; + } + + if (t == 'string') { + return 'String'; + } + if (t == 'array') { + return rawType ? 'List' : (! isSmart ? 'JSONArray' : 'List<' + StringUtil.firstCase(JSONResponse.getTableName(key), true) + '>'); + } + if (t == 'object') { + return rawType ? 'Map' : (! isSmart ? 'JSONObject' : StringUtil.firstCase(JSONResponse.getTableName(key), true)); + } + + return 'Object'; + }, + + getKotlinTypeFromJS: function (key, value, isArrayItem, baseFirst, rawType, isSmart) { + var t = JSONResponse.getType(value); + if (t == 'boolean') { + return baseFirst ? 'boolean' : 'Boolean'; + } + + if (t == 'number') { + if (Number.isInteger(value) != true) { + return baseFirst ? 'double' : 'Double'; + } + } + + if (t == 'number' || t == 'integer') { + if (Math.abs(value) >= 2147483647 || CodeUtil.isId(key, 'bigint', isArrayItem)) { + return baseFirst ? 'long' : 'Long'; + } + return baseFirst ? 'int' : 'Int'; + } + + if (t == 'string') { + return 'String'; + } + if (t == 'array') { + return rawType ? 'List' : (! isSmart ? 'JSONArray' : 'List<' + StringUtil.firstCase(JSONResponse.getTableName(key), true) + '>'); + } + if (t == 'object') { + return rawType ? 'Map' : (! isSmart ? 'JSONObject' : StringUtil.firstCase(JSONResponse.getTableName(key), true)); + } + + return 'Any'; + }, + + getCSharpTypeFromJS: function (key, value, baseFirst) { + var t = JSONResponse.getType(value); + if (t == 'boolean') { + return baseFirst ? 'bool' : 'Boolean'; + } + + if (t == 'number') { + if (Number.isInteger(value) != true) { + return baseFirst ? 'double' : 'Double'; + } + } + + if (t == 'number' || t == 'integer') { + if (Math.abs(value) >= 2147483647 || CodeUtil.isId(key, 'bigint', isArrayItem)) { + return baseFirst ? 'long' : 'Int64'; + } + return baseFirst ? 'int' : 'Int32'; + } + + if (t == 'string') { + return 'String'; + } + if (t == 'array') { + return 'JArray'; + } + if (t == 'object') { + return 'JObject'; + } + + return baseFirst ? 'object' : 'Object'; + }, + getSwiftTypeFromJS: function (key, value) { + var t = JSONResponse.getType(value); + if (t == 'boolean') { + return 'Bool'; + } - for (var j = 0; j < columnList.length; j++) { - column = columnList[j]; + if (t == 'number') { + if (Number.isInteger(value) != true) { + return 'Double'; + } + } - name = CodeUtil.getFieldName(column == null ? null : column.COLUMN_NAME); - if (name == '') { - continue; - } - type = name == 'id' ? 'Long' : CodeUtil.getJavaType(column.COLUMN_TYPE); + if (t == 'number' || t == 'integer') { + return 'Int'; + } - console.log('parseJavaBean [] for j=' + j + ': column = \n' + format(JSON.stringify(column))); + if (t == 'string') { + return 'String'; + } + if (t == 'array') { + return 'NSArray'; + } + if (t == 'object') { + return 'NSDictionary'; + } - //getter - doc += '\n public ' + type + ' ' + CodeUtil.getMethodName('get', name) + '() {' - + '\n return ' + name + ';\n }\n'; + return 'NSObject'; + }, - //setter - doc += '\n public ' + model + ' ' + CodeUtil.getMethodName('set', name) + '(' + type + ' ' + name + ') {' - + '\n this.' + name + ' = ' + name + ';' - + '\n return this;\n }\n'; - } - } + getCppTypeFromJS: function (key, value, isArrayItem) { + var t = JSONResponse.getType(value); + if (t == 'boolean') { + return 'bool'; + } - doc += '\n\n}'; + if (t == 'number') { + if (Number.isInteger(value) != true) { + return 'double'; + } + } + if (t == 'number' || t == 'integer') { + if (Math.abs(value) >= 2147483647 || CodeUtil.isId(key, 'bigint', isArrayItem)) { + return 'long' } + return 'int'; } - //[] >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> - return doc; + if (t == 'string') { + return 'const char*'; //CLion 报错 'rapidjson::Value::Ch*'; + } + if (t == 'array') { + return 'rapidjson::Value::Array'; + } + if (t == 'object') { + return 'rapidjson::Value::Object'; + } + + return 'rapidjson::Value&'; }, + getCppGetterFromJS: function (key, value, isArrayItem) { + var t = JSONResponse.getType(value); + if (t == 'boolean') { + return 'GetBool'; + } + if (t == 'number') { + if (Number.isInteger(value) != true) { + return 'GetDouble'; + } + } - /**获取model类名 - * @param tableName - * @return {*} - */ - getModelName: function(tableName) { - var model = StringUtil.noBlank(tableName); - if (model == '') { - return model; + if (t == 'number' || t == 'integer') { + if (Math.abs(value) >= 2147483647 || CodeUtil.isId(key, 'bigint', isArrayItem)) { + return 'GetInt64'; + } + return 'GetInt'; } - var lastIndex = model.lastIndexOf('_'); - if (lastIndex >= 0) { - model = model.substring(lastIndex + 1); + + if (t == 'string') { + return 'GetString'; } - return StringUtil.firstCase(model, true); - }, - /**获取model成员变量名 - * @param columnName - * @return {*} - */ - getFieldName: function(columnName) { - return StringUtil.firstCase(StringUtil.noBlank(columnName), false); - }, - /**获取model方法名 - * @param prefix @NotNull 前缀,一般是get,set等 - * @param field @NotNull - * @return {*} - */ - getMethodName: function(prefix, field) { - if (field.startsWith('_')) { - field = '_' + field; //get_name 会被fastjson解析为name而不是_name,所以要多加一个_ + if (t == 'array') { + return 'GetArray'; } - return prefix + StringUtil.firstCase(field, true); + if (t == 'object') { + return 'GetObject'; + } + + return 'Get'; }, - /**获取注释 - * @param comment - * @param multiple 多行 - * @param prefix 多行注释的前缀,一般是空格 - * @return {*} - */ - getComment: function(comment, multiple, prefix) { - comment = comment == null ? '' : comment.trim(); - if (prefix == null) { - prefix = ''; - } - if (multiple == false) { - return prefix + '//' + comment.replace(/\n/g, ' '); + getPythonTypeFromJS: function (key, value) { + return CodeUtil.getPythonTypeFromJSType(key, value, null); + }, + getPythonTypeFromJSType: function (key, value, type) { + var t = value == null ? type : JSONResponse.getType(value); + if (t == 'boolean') { + return 'bool'; } + if (t == 'number') { + if (Number.isInteger(value) != true) { + return 'float'; + } + } - //多行注释,需要每行加 * 和空格 + if (t == 'number' || t == 'integer') { + return 'int'; + } - var newComment = prefix + '/**'; - var index; - do { - newComment += '\n'; - index = comment.indexOf('\n'); - if (index < 0) { - newComment += prefix + ' * ' + comment; - break; - } - newComment += prefix + ' * ' + comment.substring(0, index); - comment = comment.substring(index + 2); + if (t == 'string') { + return 'str'; + } + if (t == 'array') { + return 'list'; + } + if (t == 'object') { + return 'dict'; } - while(comment != '') - return newComment + '\n' + prefix + ' */'; + return 'any'; }, - /**获取Java的值 - * @param value - * @return {*} - */ - getJavaValue: function (name, key, value) { - var v; //避免改变原来的value - if (typeof value == 'string') { - log(CodeUtil.TAG, 'parseJava for typeof value === "string" >> ' ); + getGoTypeFromJS: function (key, value) { + var t = JSONResponse.getType(value); + if (t == 'boolean') { + return 'bool'; + } - v = '"' + value + '"'; + if (t == 'number') { + if (Number.isInteger(value) != true) { + return 'double'; + } } - else if (value instanceof Array) { - log(CodeUtil.TAG, 'parseJava for typeof value === "array" >> ' ); - v = 'new Object[]{' + CodeUtil.getArrayString(value, '...' + name + '/' + key) + '}'; + if (t == 'number' || t == 'integer') { + return 'int'; } - else { - v = value + + if (t == 'string') { + return 'string'; } - return v; + if (t == 'array') { + return '[]interface{}'; + } + if (t == 'object') { + return 'map[string]interface{}'; + } + + return 'interface{}'; }, - getJavaTypeFromJS: function (key, value, baseFirst) { - if (typeof value == 'boolean') { - return baseFirst ? 'boolean' : 'Boolean'; + getColumnType: function (column, database) { + if (column == null) { + return 'text'; } - if (typeof value == 'number') { - if (String(value).indexOf(".") >= 0) { - return baseFirst ? 'double' : 'Double'; + + log(CodeUtil.TAG, 'getColumnType database = ' + database + '; column = ' + JSON.stringify(column, null, ' ')); + + if (column.column_type == null) { // && database == 'POSTGRESQL') { + var dt = column.data_type || ''; + log(CodeUtil.TAG, 'getColumnType column.data_type = ' + column.data_type); + + var len; + if (column.character_maximum_length != null) { // dt.indexOf('char') >= 0) { + log(CodeUtil.TAG, 'getColumnType column.character_maximum_length != null >> column.character_maximum_length = ' + column.character_maximum_length); + + len = '(' + column.character_maximum_length + ')'; } - if (Math.abs(value) >= 2147483647 || CodeUtil.isId(key)) { - return baseFirst ? 'long' : 'Long'; + else if (column.numeric_precision != null) { // dt.indexOf('int') >= 0) { + log(CodeUtil.TAG, 'getColumnType column.numeric_precision != null >> column.numeric_precision = ' + column.numeric_precision + '; column.numeric_scale = ' + column.numeric_scale); + + len = '(' + column.numeric_precision + (column.numeric_scale == null || column.numeric_scale <= 0 ? '' : ',' + column.numeric_scale) + ')'; } - return baseFirst ? 'int' : 'Integer'; - } - if (typeof value == 'string') { - return 'String'; - } - if (value instanceof Array) { - return 'JSONArray'; - } - if (value instanceof Object) { - return 'JSONObject'; + else { + len = '' + } + + log(CodeUtil.TAG, 'getColumnType return dt + len; = ' + (dt + len)); + return dt + len; } - return 'Object'; + log(CodeUtil.TAG, 'getColumnType return column.column_type; = ' + column.column_type); + return column.column_type; }, /**根据数据库类型获取Java类型 @@ -706,48 +5704,366 @@ var CodeUtil = { * @param saveLength */ getJavaType: function(type, saveLength) { + return CodeUtil.getType4Language(CodeUtil.LANGUAGE_JAVA, type, saveLength); + }, + /**根据数据库类型获取Java类型 + * @param t + * @param saveLength + */ + getCppType: function(type, saveLength) { + return CodeUtil.getType4Language(CodeUtil.LANGUAGE_C_PLUS_PLUS, type, saveLength); + }, + getType4Language: function(language, type, saveLength) { + log(CodeUtil.TAG, 'getJavaType type = ' + type + '; saveLength = ' + saveLength); type = StringUtil.noBlank(type); var index = type.indexOf('('); var t = index < 0 ? type : type.substring(0, index); - if (t == '') { - return 'Object'; + if (t == '' || t == 'object') { + return CodeUtil.getType4Any(language, ''); } var length = index < 0 || saveLength != true ? '' : type.substring(index); - if (t.endsWith('char') || t.endsWith('text') || t == 'enum' || t == 'set') { - return 'String' + length; + if (t.indexOf('char') >= 0 || t.indexOf('text') >= 0 || t == 'enum' || t == 'set') { + return CodeUtil.getType4String(language, length); } - if (t.endsWith('int') || t == 'integer') { - return (t == 'bigint' ? 'Long' : 'Integer') + length; + if (t.indexOf('int') >= 0) { + return t == 'bigint' ? CodeUtil.getType4Long(language, length) : CodeUtil.getType4Integer(language, length); } - if (t.endsWith('binary') || t.endsWith('blob')) { - return 'byte[]' + length; + if (t.endsWith('binary') || t.indexOf('blob') >= 0 || t.indexOf('clob') >= 0) { + return CodeUtil.getType4ByteArray(language, length); + } + if (t.indexOf('timestamp') >= 0) { + return CodeUtil.getType4Timestamp(language, length); } switch (t) { case 'id': - return 'Long' + length; + return CodeUtil.getType4Long(language, length); case 'bit': - return 'Boolean' + length; case 'bool': //同tinyint case 'boolean': //同tinyint - return 'Integer' + length; + return CodeUtil.getType4Boolean(language, length); case 'datetime': - return 'Timestamp' + length; + return CodeUtil.getType4Timestamp(language, length); case 'year': - return 'Date' + length; + return CodeUtil.getType4Date(language, length); case 'decimal': - return 'BigDecimal' + length; + case 'number': + case 'numberic': + return CodeUtil.getType4Decimal(language, length); case 'json': - return 'List' + length; + case 'jsonb': + case 'array': + return CodeUtil.getType4Array(language); + case 'string': + return CodeUtil.getType4String(language, length); default: return StringUtil.firstCase(t, true) + length; } }, + getType4Any: function (language, length) { + length = length || ''; + switch (language) { + case CodeUtil.LANGUAGE_KOTLIN: + return 'Any' + length; + case CodeUtil.LANGUAGE_JAVA: + return 'Object' + length; + case CodeUtil.LANGUAGE_C_SHARP: + return 'object' + length; + + case CodeUtil.LANGUAGE_SWIFT: + return 'Any' + length; + case CodeUtil.LANGUAGE_OBJECTIVE_C: + return 'NSObject' + length; + + case CodeUtil.LANGUAGE_GO: + return 'interface{}' + length; + case CodeUtil.LANGUAGE_C_PLUS_PLUS: + return 'GenericValue'; + + case CodeUtil.LANGUAGE_TYPE_SCRIPT: + case CodeUtil.LANGUAGE_JAVA_SCRIPT: + return 'object' + length; + + case CodeUtil.LANGUAGE_PHP: + case CodeUtil.LANGUAGE_PYTHON: + return 'any' + length; + } + return 'Object' + length; //以 JSON 类型为准 + }, + getType4Boolean: function (language, length) { + length = length || ''; + switch (language) { + case CodeUtil.LANGUAGE_KOTLIN: + case CodeUtil.LANGUAGE_JAVA: + case CodeUtil.LANGUAGE_C_SHARP: + return 'Boolean' + length; + + case CodeUtil.LANGUAGE_SWIFT: + return 'Bool' + length; + case CodeUtil.LANGUAGE_OBJECTIVE_C: + return 'bool' + length; + + case CodeUtil.LANGUAGE_GO: + case CodeUtil.LANGUAGE_C_PLUS_PLUS: + return 'bool' + length; + + case CodeUtil.LANGUAGE_TYPE_SCRIPT: + case CodeUtil.LANGUAGE_JAVA_SCRIPT: + return 'boolean' + length; + + case CodeUtil.LANGUAGE_PHP: + case CodeUtil.LANGUAGE_PYTHON: + return 'bool' + length; + } + return 'Boolean' + length; //以 JSON 类型为准 + }, + getType4Integer: function (language, length) { + length = length || ''; + switch (language) { + case CodeUtil.LANGUAGE_KOTLIN: + return 'Int' + length; + case CodeUtil.LANGUAGE_JAVA: + break; + case CodeUtil.LANGUAGE_C_SHARP: + return 'Int32' + length; + + case CodeUtil.LANGUAGE_SWIFT: + return 'Int' + length; + case CodeUtil.LANGUAGE_OBJECTIVE_C: + return 'NSInteger' + length; + + + case CodeUtil.LANGUAGE_GO: + case CodeUtil.LANGUAGE_C_PLUS_PLUS: + return 'int' + length; + + case CodeUtil.LANGUAGE_TYPE_SCRIPT: + case CodeUtil.LANGUAGE_JAVA_SCRIPT: + return 'number' + length; + + case CodeUtil.LANGUAGE_PHP: + case CodeUtil.LANGUAGE_PYTHON: + return 'int' + length; + } + return 'Integer' + length; //以 JSON 类型为准 + }, + getType4Long: function (language, length) { + length = length || '' + switch (language) { + case CodeUtil.LANGUAGE_KOTLIN: + return 'Int' + length; + case CodeUtil.LANGUAGE_JAVA: + return 'Long' + length; + case CodeUtil.LANGUAGE_C_SHARP: + return 'Int64' + length; + + case CodeUtil.LANGUAGE_SWIFT: + case CodeUtil.LANGUAGE_OBJECTIVE_C: + return 'Int' + length; + + case CodeUtil.LANGUAGE_GO: + return 'int64' + length; + case CodeUtil.LANGUAGE_C_PLUS_PLUS: + return 'long' + length; + + case CodeUtil.LANGUAGE_TYPE_SCRIPT: + case CodeUtil.LANGUAGE_JAVA_SCRIPT: + return 'number' + length; + + case CodeUtil.LANGUAGE_PHP: + case CodeUtil.LANGUAGE_PYTHON: + return 'int' + length; + } + return CodeUtil.getType4Integer(language, length); + }, + + getType4Decimal: function (language, length) { + length = length || '' + switch (language) { + case CodeUtil.LANGUAGE_KOTLIN: + case CodeUtil.LANGUAGE_JAVA: + return 'BigDecimal' + length; + case CodeUtil.LANGUAGE_C_SHARP: + return 'decimal' + length; + + case CodeUtil.LANGUAGE_SWIFT: + return 'Decimal' + length; + case CodeUtil.LANGUAGE_OBJECTIVE_C: + return 'NSDecimal' + length; + + case CodeUtil.LANGUAGE_GO: + return 'float64' + length; + case CodeUtil.LANGUAGE_C_PLUS_PLUS: + return 'double' + length; + + case CodeUtil.LANGUAGE_TYPE_SCRIPT: + case CodeUtil.LANGUAGE_JAVA_SCRIPT: + return 'number' + length; + + case CodeUtil.LANGUAGE_PHP: + case CodeUtil.LANGUAGE_PYTHON: + return 'float' + length; + } + return 'Number' + length; //以 JSON 类型为准 + }, + getType4String: function (language, length) { + length = length || '' + switch (language) { + case CodeUtil.LANGUAGE_KOTLIN: + case CodeUtil.LANGUAGE_JAVA: + case CodeUtil.LANGUAGE_C_SHARP: + break; + + case CodeUtil.LANGUAGE_SWIFT: + break; + case CodeUtil.LANGUAGE_OBJECTIVE_C: + return 'NSString' + length; + + case CodeUtil.LANGUAGE_GO: + case CodeUtil.LANGUAGE_C_PLUS_PLUS: + return 'string' + length; + + case CodeUtil.LANGUAGE_TYPE_SCRIPT: + case CodeUtil.LANGUAGE_JAVA_SCRIPT: + return 'string' + length; + + case CodeUtil.LANGUAGE_PHP: + return 'string' + length; + case CodeUtil.LANGUAGE_PYTHON: + return 'str' + length; + } + return 'String' + length; //以 JSON 类型为准 + }, + getType4Date: function (language, length) { + length = length || '' + switch (language) { + case CodeUtil.LANGUAGE_KOTLIN: + case CodeUtil.LANGUAGE_JAVA: + return 'Date' + length; + case CodeUtil.LANGUAGE_C_SHARP: + return 'DateTime' + length; + + case CodeUtil.LANGUAGE_SWIFT: + return 'Date' + length; + case CodeUtil.LANGUAGE_OBJECTIVE_C: + return 'NSDate' + length; + + case CodeUtil.LANGUAGE_GO: + return 'time.Time' + length; + case CodeUtil.LANGUAGE_C_PLUS_PLUS: + return 'tm' + length; + + case CodeUtil.LANGUAGE_JAVA_SCRIPT: + case CodeUtil.LANGUAGE_TYPE_SCRIPT: + return 'Date' + length; + + case CodeUtil.LANGUAGE_PHP: + break; + case CodeUtil.LANGUAGE_PYTHON: + return 'datetime' + length; + } + return CodeUtil.getType4String(language, length); + }, + getType4Timestamp: function (language, length) { + length = length || '' + switch (language) { + case CodeUtil.LANGUAGE_KOTLIN: + case CodeUtil.LANGUAGE_JAVA: + return 'Timestamp' + length; + case CodeUtil.LANGUAGE_C_SHARP: + return 'TimeSpan' + length; + + case CodeUtil.LANGUAGE_SWIFT: + return 'TimeInterval' + length; + case CodeUtil.LANGUAGE_OBJECTIVE_C: + break; + + case CodeUtil.LANGUAGE_GO: + return 'time.Time' + length; + case CodeUtil.LANGUAGE_C_PLUS_PLUS: + return 'time_t' + length; + + case CodeUtil.LANGUAGE_JAVA_SCRIPT: + return 'string'; + case CodeUtil.LANGUAGE_TYPE_SCRIPT: + break; + + case CodeUtil.LANGUAGE_PHP: + break; + case CodeUtil.LANGUAGE_PYTHON: + return 'datetime' + length; + } + return CodeUtil.getType4Integer(language, length); + }, + getType4Object: function (language) { + switch (language) { + case CodeUtil.LANGUAGE_KOTLIN: + case CodeUtil.LANGUAGE_JAVA: + return 'JSONObject'; + case CodeUtil.LANGUAGE_C_SHARP: + return 'JObject'; + + case CodeUtil.LANGUAGE_SWIFT: + return 'Dictionary'; + case CodeUtil.LANGUAGE_OBJECTIVE_C: + return 'NSDictionary'; + + case CodeUtil.LANGUAGE_GO: + return 'map[string]interface{}'; + case CodeUtil.LANGUAGE_C_PLUS_PLUS: + return 'map'; + + case CodeUtil.LANGUAGE_JAVA_SCRIPT: + case CodeUtil.LANGUAGE_TYPE_SCRIPT: + return 'object'; + + case CodeUtil.LANGUAGE_PHP: + return 'object'; + case CodeUtil.LANGUAGE_PYTHON: + return 'dict[str, any]'; + } + return 'Object'; //以 JSON 类型为准 + }, + getType4ByteArray: function (language) { + return 'byte[]'; + }, + getType4Array: function (language) { + length = length || '' + switch (language) { + case CodeUtil.LANGUAGE_KOTLIN: + case CodeUtil.LANGUAGE_JAVA: + case CodeUtil.LANGUAGE_C_SHARP: + return 'List'; + + case CodeUtil.LANGUAGE_SWIFT: + return 'Array'; + case CodeUtil.LANGUAGE_OBJECTIVE_C: + return 'NSArray'; + + case CodeUtil.LANGUAGE_GO: + return '[]interface{}'; + case CodeUtil.LANGUAGE_C_PLUS_PLUS: + return 'vector'; + + case CodeUtil.LANGUAGE_JAVA_SCRIPT: + return 'object[]'; + case CodeUtil.LANGUAGE_TYPE_SCRIPT: + return 'any[]'; + + case CodeUtil.LANGUAGE_PHP: + return 'any[]'; + case CodeUtil.LANGUAGE_PYTHON: + return 'list[any]'; + } + return 'Array'; //以 JSON 类型为准 + }, + + /**获取字段对应值的最大长度 * @param columnType * @return {string} @@ -762,10 +6078,17 @@ var CodeUtil = { * @param depth * @return {string} */ - getBlank: function(depth) { + getBlank: function(depth, unit) { var s = ''; + var one = ' '; + if (unit != null && unit > 0 && unit != 4) { + one = '' + for (var i = 0; i < unit; i ++) { + one += ' '; + } + } for (var i = 0; i < depth; i ++) { - s += ' '; + s += one; } return s; }, @@ -815,13 +6138,74 @@ var CodeUtil = { * @param column * @return {boolean} */ - isId: function (column) { - if (column == null) { + isId: function (column, type, isArrayItem) { + if (column == null || type == null || type.indexOf('int') < 0) { + return false; + } + + if (isArrayItem) { + // if (column.endsWith('[]')) { + // column = column.substring(0, column.length - '[]'.length); + // } + // + // if (column.endsWith('Item')) { + // column = column.substring(0, column.length - 'Item'.length); + // } + // else if (column.endsWith('Element')) { + // column = column.substring(0, column.length - 'Element'.length); + // } + // + // if (column.endsWith('List')) { + // column = column.substring(0, column.length - 'List'.length); + // } + // else if (column.endsWith('Array')) { + // column = column.substring(0, column.length - 'Array'.length); + // } + // else if (column.endsWith('Vector')) { + // column = column.substring(0, column.length - 'Vector'.length); + // } + // else if (column.endsWith('Set')) { + // column = column.substring(0, column.length - 'Set'.length); + // } + // else if (column.endsWith('Collection')) { + // column = column.substring(0, column.length - 'Collection'.length); + // } + // else if (column.endsWith('Arr')) { + // column = column.substring(0, column.length - 'Arr'.length); + // } + // else if (column.endsWith('s')) { + // column = column.substring(0, column.length - 's'.length); + // } + + while (true) { + var index = column == null || column.length < 2 ? -1 : column.lastIndexOf('d'); + if (index <= 0) { + break; + } + + var prefix = column.substring(index <= 2 ? 0 : index - 2, index); + + if (prefix.endsWith('I') || (prefix.endsWith('i') && /[A-Za-z]/.test(prefix.length < 2 ? '' : prefix.substring(0, 1)) == false)) { + + var suffix = column.length <= index + 1 ? '' : column.substring(index + 1, index + 3); + var after = suffix.length < 1 ? '' : suffix.substring(0, 1); + + // id%, %_id, %Id%, %ids%, %_ids%, %Ids% + if (/[a-z]/.test(after) == false || (after == 's' && /[a-z]/.test(suffix.length < 2 ? '' : suffix.substring(1, 2)) == false)) { + return true; + } + } + + column = index < 2 ? null : column.substring(0, index - 2); + } + return false; } + if (column.endsWith('Id')) { // lowerCamelCase return true; } + var index = column.lastIndexOf('_'); // snake_case var id = index < 0 ? column : column.substring(index + 1); return id.toLowerCase() == 'id'; @@ -829,38 +6213,326 @@ var CodeUtil = { - QUERY_TYPES: ['数据', '数量', '全部'], - QUERY_TYPE_KEYS: [0, 1, 2], - QUERY_TYPE_CONSTS: ["JSONRequest.QUERY_TABLE", "JSONRequest.QUERY_TOTAL", "JSONRequest.QUERY_ALL"], - REQUEST_ROLE_KEYS: ['UNKNOWN', 'LOGIN', 'CONTACT', 'CIRCLE', 'OWNER', 'ADMIN'], - REQUEST_ROLE: { - UNKNOWN: '未登录', - LOGIN: '已登录', - CONTACT: '联系人', - CIRCLE: '圈子成员', - OWNER: '拥有者', - ADMIN: '管理员' + QUERY_TYPES: ['数据', '数量', '全部'], + JOIN_TYPES: {"@": 'APP', "<": 'LEFT', ">": 'RIGHT', "*": 'CROSS', "&": 'INNER', "|": 'FULL', "!": 'OUTER', "^": 'SIDE', "(": 'ANTI', ")": 'FOREIGN'}, + CACHE_TYPES: ['全部', '磁盘', '内存'], + SUBQUERY_RANGES: ['ANY', 'ALL'], + QUERY_TYPE_KEYS: [0, 1, 2], + CACHE_TYPE_KEYS: [0, 1, 2], + QUERY_TYPE_CONSTS: ["JSONRequest.QUERY_TABLE", "JSONRequest.QUERY_TOTAL", "JSONRequest.QUERY_ALL"], + ROLE_KEYS: ['UNKNOWN', 'LOGIN', 'CONTACT', 'CIRCLE', 'OWNER', 'ADMIN'], + ROLES: { + UNKNOWN: '未登录', + LOGIN: '已登录', + CONTACT: '联系人', + CIRCLE: '圈子成员', + OWNER: '拥有者', + ADMIN: '管理员' + }, + DATABASE_KEYS: ['MYSQL', 'POSTGRESQL', 'SQLSERVER', 'ORACLE', 'DB2', 'DAMENG', 'KINGBASE', 'MARIADB', 'SQLITE', 'INFLUXDB', 'TDENGINE', 'PRESTO', 'TRINO', 'HIVE', 'TIDB', 'CLICKHOUSE', 'ELASTICSEARCH', 'REDIS', 'IOTDB', 'SURREALDB', 'DUCKDB', 'CASSANDRA', 'MONGODB', 'SNOWFLAKE', 'DATABRICKS', 'MILVUS'], // , 'KAFKA'], + + getComment4Function: function (funCallStr, method, language) { + if (typeof funCallStr != 'string') { + return '远程函数 value 必须是 String 类型!'; + } + + var start = funCallStr == null ? -1 : funCallStr.indexOf('(') + if (start <= 0 || funCallStr.endsWith(')') != true) { + throw new Error('远程函数调用格式非法!必须为 fun(arg0,arg1..) 这种形式!不允许多余的空格!') + } + + var fun = funCallStr.substring(0, start) + if (StringUtil.isName(fun) != true) { + throw new Error('远程函数名称 ' + fun + ' 非法!必须为大小写英文字母开头且其它字符只能是字母/下划线/数字!') + } + + var funObj = CodeUtil.getFunctionFromList(fun, method) + if (funObj == null) { + throw new Error('远程函数 ' + fun + ' 非法!只能传后端 Function 表中配置的!') + } + + // 不做校验,似乎怎么写都是对的 + var argStr = funCallStr.substring(start + 1, funCallStr.length - 1) + var args = StringUtil.isEmpty(argStr) ? null : StringUtil.split(argStr) + var argLen = args == null ? 0 : args.length + + // if (args != null) { + // for (var i = 0; i < args.length; i++) { + // var a = args[i] + // if (a.startsWith("'") && a.endsWith("'")) { + // continue + // } + // + // if (a.startsWith('`') && a.endsWith('`')) { + // a = a.substring(1, a.length - 1) + // if (StringUtil.isName(a) != true) { + // throw new Error('远程函数名称 ' + fun + ' 非法!必须为大小写英文字母开头且其它字符只能是字母/下划线/数字!') + // } + // } + // } + // } + + var allowArgStr = funObj.arguments + var allowArgs = StringUtil.isEmpty(allowArgStr) ? null : StringUtil.split(allowArgStr) + var allowArgLen = allowArgs == null ? 0 : allowArgs.length + if (argLen != allowArgLen) { + throw new Error('远程函数参数数量 ' + argLen + ' 非法!必须是 ' + allowArgLen + ' 个!格式为 ' + fun + '(' + StringUtil.trim(allowArgStr) + ')') + } + + return CodeUtil.getType4Language(language, funObj.returnType) + ', ' + (funObj.rawDetail || funObj.detail) + }, + + getFunctionFromList: function (name, method) { + if (StringUtil.isEmpty(name)) { + return null + } + + var functionMap = CodeUtil.functionMap; + var funObj = functionMap == null ? null : functionMap[name] + if (funObj != null) { + return funObj; + } + + var functionList = CodeUtil.functionList; + if (functionList != null) { + for (var i = 0; i < functionList.length; i++) { + var f = functionList[i]; + if (f != null && f.name == name) { + if (functionMap == null) { + functionMap = {}; + } + functionMap[name] = f; + CodeUtil.functionMap = functionMap; + return f; + } + } + } + + return null; }, - REQUEST_DATABASE_KEYS: ['MYSQL', 'POSTGRESQL', 'ORACLE'], /**获取请求JSON的注释 * @param tableList * @param name * @param key * @param value + * @param isInSubquery + * @param database */ - getComment4Request: function (tableList, name, key, value, method) { + getComment4Request: function (tableList, name, key, value, method, isInSubquery, database, language, isReq, names, isRestful, standardObj, isWarning, isAPIJSONRouter) { // alert('name = ' + name + '; key = ' + key + '; value = ' + value + '; method = ' + method); if (key == null) { return ''; } - if (value == null || value instanceof Object) { + var typeOfValue = CodeUtil.getType4Request(value); + var isValueNotString = typeOfValue != 'string'; + var isValueNotInteger = typeOfValue != 'integer'; + // var isValueNotNumber = isValueNotInteger && typeOfValue != 'number'; + var isValueNotBoolean = typeOfValue != 'boolean'; + var isValueNotEmpty = isValueNotString ? (typeOfValue != 'array' ? value != null : value.length > 0) : StringUtil.isNotEmpty(value, true); + + var extraComment = ''; + if (isAPIJSONRouter) { + var ks = key.split('.') + if (ks != null && ks.length >= 2) { + name = ks[ks.length - 2]; + key = ks[ks.length - 1]; + names = ks.slice(0, ks.length - 1) + + var nk = name.endsWith('[]') ? name.substring(0, name.length - 2) : name; + if (JSONObject.isTableKey(nk) != true) { + nk = name; + } + + extraComment = CodeUtil.getComment4Request(tableList, null, nk, { [key]:value }, method, isInSubquery, database, language, isReq, ks.slice(0, ks.length - 2), isRestful, standardObj, isWarning, false).trim(); + if (StringUtil.isNotEmpty(extraComment, true)) { + extraComment = ' < ' + nk + ': ' + (extraComment.startsWith('//') ? extraComment.substring(2).trim() : extraComment); + } + } + } + + if (isRestful == true || (standardObj != null && key.indexOf('@') < 0)) { + if (StringUtil.isEmpty(key, true)) { + return ''; + } + + var pathKeys = []; // slice 居然每次都返回数字 1 names == null || names.length < 2 ? null : names.slice(2).push(key) + if (names != null && names.length > 1) { + for (var i = 1; i < names.length; i++) { + pathKeys.push(names[i]); + } + } + + // FIXME names 居然出现 ['', 'user', 'user'] if (value instanceof Object == false) { + pathKeys.push(key); + // } + + try { + var c = CodeUtil.getCommentFromDoc(tableList, name, key, method, database, language, isReq != true || isRestful, isReq, pathKeys, isRestful, value == null ? {} : value, true, standardObj, null, isWarning); + if (isRestful == true || StringUtil.isEmpty(c) == false) { // TODO 最好都放行,查不到都去数据库查表和字段属性 + if (c.startsWith(' ! ')) { + return c; + } + return StringUtil.isEmpty(c) ? ' ! 字段 ' + key + ' 不存在!' : (isWarning ? '' : CodeUtil.getComment(c, false, ' ')) + extraComment; + } + } + catch (e) { + if (isRestful == true) { + return e.message; + } + } + } + + + if (isRestful != true || isReq != true) { // 解决 APIJSON 批量 POST/PUT "Table[]": [{ key:value }] 中 {} 及 key:value 不显示注释 + if (StringUtil.isEmpty(key, true)) { + // 这里处理将不显示表名,且空格少一个不能让注释和下方 key 对齐 + // if ((method == 'POST' || method == 'PUT') && names != null && names.length >= 1 && JSONObject.isArrayKey(name)) { + // var aliaIndex = name.indexOf(':'); + // var objName = name.substring(0, aliaIndex >= 0 ? aliaIndex : name.length - 2); + // + // if (JSONObject.isTableKey(objName)) { + // key = objName; + // } + // } + } + else if (StringUtil.isEmpty(name, true) && (isReq != true || method == 'POST' || method == 'PUT') + && names != null && names.length >= 2 && names[names.length - 1] == name) { + + var arrName = names[names.length - 2]; + + if (JSONObject.isArrayKey(arrName)) { + var aliaIndex = arrName.indexOf(':'); + var objName = arrName.substring(0, aliaIndex >= 0 ? aliaIndex : arrName.length - 2); + + if (JSONObject.isTableKey(objName)) { + name = objName; + } + } + } + } + + if (isRestful != true && key != null && key.startsWith('@') != true && key.endsWith('()')) { // 方法,查询完后处理,先用一个Map保存? + if (['GET', 'HEAD'].indexOf(method) < 0) { + return ' ! 远程函数只能用于 GET,HEAD 请求!!'; + } + + if (value != null && isValueNotString) { + return ' ! 远程函数 value 必须是 String 类型!'; + } + + // if (value != null) { + // var startIndex = value.indexOf("("); + // if (startIndex <= 0 || value.endsWith(")") == false) { + // return ' ! 远程函数 value 必须符合 fun(arg0,arg1..) 这种格式!且不要有任何多余的空格!'; + // } + // var fun = value.substring(0, startIndex); + // if (StringUtil.isName(fun) != true) { + // return '! 函数名' + fun + '不合法!value 必须符合 fun(arg0,arg1..) 这种格式!且不要有任何多余的空格!'; + // } + // } + + var c = '' + if (StringUtil.isNotEmpty(value)) { // isValueNotEmpty 居然不对 + try { + c = CodeUtil.getComment4Function(value, method, language) + } catch (e) { + return ' ! ' + e.message + } + } + + if (isWarning) { + return ' '; + } + + var priority = ''; + if (key.endsWith("-()")) { + priority = ' < 在解析所在对象前优先执行'; + } + else if (key.endsWith("+()")) { + priority = ' < 在解析所在对象后滞后执行'; + } + else { + priority = ' < 执行时机在解析所在对象后,解析子对象前,可以在 () 前用 + - 设置优先级,例如 key-() 优先执行'; + } + + return CodeUtil.getComment('远程函数' + (isValueNotEmpty ? (StringUtil.isEmpty(c, true) ? '' : ':' + c) + priority + : ',例如 "isContain(praiseUserIdList,userId)"'), false, ' '); + } + + + // if (value == null) { + // return ' ! key:value 中 key 或 value 任何一个为 null 时,该 key:value 都无效!' + // } + if (value instanceof Array) { + if ((isReq != true || method == 'POST' || method == 'PUT') && JSONObject.isArrayKey(key)) { + var aliaIndex = key.indexOf(':'); + var objName = key.substring(0, aliaIndex >= 0 ? aliaIndex : key.length - 2); + + if (JSONObject.isTableKey(objName)) { + var c = CodeUtil.getCommentFromDoc(tableList, objName, null, method, database, language, isReq != true || isRestful, isReq, pathKeys, isRestful, value, null, null, null, isWarning); + if (c != null && c.startsWith(' ! ')) { + return c; + } + return StringUtil.isEmpty(c) ? ' ! 表 ' + objName + ' 不存在!' : (isWarning ? '' : CodeUtil.getComment( + (aliaIndex < 0 ? '' : '新建别名: ' + key.substring(aliaIndex + 1, key.length - 2) + ' < ') + objName + ': ' + c, false, ' ')) + extraComment; + } + } + + if (isReq == true && isRestful != true && method != 'POST' && method != 'PUT') { + return StringUtil.isEmpty(extraComment, true) ? '' : CodeUtil.getComment(extraComment.substring(3), false, ' '); + } + } + else if (value instanceof Object) { + if ((isReq != true || isRestful != true) && StringUtil.isEmpty(key, true)) { + if (names == null || names.length <= 0) { + return isReq != true || isWarning ? '' : ' ' + CodeUtil.getComment('根对象,可在内部加 Table:{}' + + (method == null || method == 'GET' || method == 'GETS' ? ', []:{}' : (method == 'POST' || method == 'PUT' ? ', []:[{}]' : '')) + + ' 等或 format,tag,version,@role,@database,@schema,@datasource,@explain,@cache 等全局关键词键值对', false, ' '); + } + + // 解决 APIJSON 批量 POST/PUT "Table[]": [{ key:value }] 中 {} 不显示注释 + if ((isReq != true || method == 'POST' || method == 'PUT') && JSONObject.isArrayKey(name)) { + var aliaIndex = name.indexOf(':'); + var objName = name.substring(0, aliaIndex >= 0 ? aliaIndex : name.length - 2); + + if (JSONObject.isTableKey(objName)) { + var c = CodeUtil.getCommentFromDoc(tableList, objName, null, method, database, language, isReq != true || isRestful, isReq, pathKeys, isRestful, value, null, null, null, isWarning); + if (c.startsWith(' ! ')) { + return c; + } + return StringUtil.isEmpty(c) ? ' ! 表 ' + objName + ' 不存在!' : (isWarning ? '' : ' ' + CodeUtil.getComment(objName + ': ' + c, false, ' ')) + extraComment; + } + } + } + + if (isRestful != true && key.endsWith('@')) { + if (isWarning) { + return ''; + } + + if (key == '@from@') { + return CodeUtil.getComment('数据来源:子查询' + (isValueNotEmpty ? ',里面必须有 "from":Table, Table:{}' : ',例如 { "from":"User", "User":{} }'), false, ' ') + extraComment; + } + + var aliaIndex = name == null ? -1 : name.indexOf(':'); + var objName = aliaIndex < 0 ? name : name.substring(0, aliaIndex); + if (JSONObject.isTableKey(objName)) { + return CodeUtil.getComment('子查询,里面必须有 "from":Table, Table:{} < ' + CodeUtil.getCommentFromDoc(tableList, objName, key.substring(0, key.length - 1), + method, database, language, isReq != true || isRestful, isReq, pathKeys, isRestful, value, null, null, true, isWarning), false, ' ') + extraComment; + } + return CodeUtil.getComment('子查询,可在内部加 Table:{} 或 from,range 或 数组关键词 等键值对,需要被下面的表字段相关 key 引用赋值', false, ' ') + extraComment; + } + + if (isRestful != true && JSONObject.isArrayKey(key)) { + if (method != 'GET' && method != 'GETS') { + return ' ! key[]:{} 只支持 GET,GETS 方法!'; + } - if (JSONObject.isArrayKey(key)) { - if (method != 'GET') { - return ' ! key[]:{}只支持GET方法!'; + if (isWarning) { + return ''; } key = key.substring(0, key.lastIndexOf('[]')); @@ -872,107 +6544,224 @@ var CodeUtil = { var firstIndex = objName.indexOf('-'); var firstKey = firstIndex < 0 ? objName : objName.substring(0, firstIndex); alias = alias.length <= 0 ? '' : '新建别名: ' + alias + ' < '; - return CodeUtil.getComment((JSONObject.isTableKey(firstKey) ? '提取' + objName + ' < ' : '') + alias + '数组', false, ' '); + return CodeUtil.getComment((JSONObject.isTableKey(firstKey) ? '提取' + objName + ' < ' : '') + alias + + '数组,可在内部加 Table:{}, []:{} 等或 count,page,query,compat,join 等关键词键值对', false, ' ') + extraComment; } var aliaIndex = key.indexOf(':'); var objName = aliaIndex < 0 ? key : key.substring(0, aliaIndex); - if (JSONObject.isTableKey(objName)) { - var c = CodeUtil.getCommentFromDoc(tableList, objName, null, method); - return StringUtil.isEmpty(c) ? ' ! 表不存在!' : CodeUtil.getComment( - (aliaIndex < 0 ? '' : '新建别名: ' + key.substring(aliaIndex + 1, key.length) + ' < ' + objName + ': ') + c, false, ' '); + var isTableKey = JSONObject.isTableKey(objName) + if (isRestful == true || isTableKey) { + var c = CodeUtil.getCommentFromDoc(tableList, objName, null, method, database, language + , isReq != true || isRestful, isReq, pathKeys, isRestful, value, null, null, null, isWarning); + if (c.startsWith(' ! ')) { + return c; + } + return StringUtil.isEmpty(c) ? ' ! 表不存在!' : (isWarning ? '' : CodeUtil.getComment( + (aliaIndex < 0 ? '' : '新建别名: ' + key.substring(aliaIndex + 1, key.length) + ' < ' + objName + ': ') + c, false, ' ')) + extraComment; } - return ''; + if (isWarning != true && isRestful != true && isTableKey != true && StringUtil.isEmpty(objName) != true) { + return CodeUtil.getComment('普通对象。如果要对应数据库表请把 ' + objName + ' 改成 ' + StringUtil.firstCase(objName, true) + + ' 这种以大写字母开头的 APIJSON 表名!数据库表不一样要这样,MySQL 默认大小写不敏感。', false, ' ') + extraComment; + } + + return StringUtil.isEmpty(extraComment, true) ? '' : CodeUtil.getComment(extraComment.substring(3), false, ' '); } - if (JSONObject.isArrayKey(name)) { + if (isRestful != true && (isInSubquery || JSONObject.isArrayKey(name))) { switch (key) { case 'count': - return CodeUtil.getType4Request(value) != 'number' ? ' ! value必须是Number类型!' : CodeUtil.getComment('最多数量: 例如 5 10 20 ...', false, ' '); + return value != null && isValueNotInteger ? ' ! value必须是Integer类型!' : (isWarning ? '' : CodeUtil.getComment('每页数量' + (isValueNotEmpty ? '' : ',例如 5 10 20 等'), false, ' ')) + extraComment; case 'page': - if (CodeUtil.getType4Request(value) != 'number') { - return ' ! value必须是Number类型!'; + if (value != null && isValueNotInteger) { + return ' ! value必须是Integer类型!'; } - return value < 0 ? ' ! 必须 >= 0 !' : CodeUtil.getComment('分页页码: 例如 0 1 2 ...', false, ' '); + return value != null && value < 0 ? ' ! 必须 >= 0 !' : (isWarning ? '' : CodeUtil.getComment('分页页码' + (isValueNotEmpty ? '' : ': 例如 0 1 2 ...'), false, ' ')) + extraComment; case 'query': var query = CodeUtil.QUERY_TYPES[value]; - return StringUtil.isEmpty(query) ? ' ! value必须是[' + CodeUtil.QUERY_TYPE_KEYS.join() + ']中的一种!' : CodeUtil.getComment('查询内容:0-数据 1-总数 2-全部', false, ' '); + return StringUtil.isEmpty(query) ? ' ! value必须是[' + CodeUtil.QUERY_TYPE_KEYS.join() + ']中的一种!' : (isWarning ? '' : CodeUtil.getComment('查询内容:0-对象 1-总数和分页详情 2-数据、总数和分页详情', false, ' ')) + extraComment; case 'join': - return CodeUtil.getType4Request(value) != 'string' ? ' ! value必须是String类型!' : CodeUtil.getComment('多表连接:例如 &/User/id@, 0) { + + var chars = Object.keys(CodeUtil.JOIN_TYPES); + + for (var i = 0; i < items.length; i++) { + var item = items[i] || ''; + + if (item.endsWith('@') != true) { + return ' ! ' + item + ' 不合法 ! 必须以 @ 结尾' + (isValueNotEmpty ? '' : ',例如 "&/User/id@" !'); + } + + var index = item.indexOf('/'); + var lastIndex = item.lastIndexOf('/'); + + if (index < 0 || lastIndex <= index + 1) { + return ' ! ' + item + ' 不合法 ! 必须有两个不相邻的 /' + (isValueNotEmpty ? '' : ',例如 "&/User/id@" !'); + } + + var c = index <= 0 ? '|' : item.substring(0, index); + if (chars.indexOf(c) < 0) { + return ' ! JOIN 类型 ' + c + ' 不合法 ! 必须是 [' + chars.join(', ') + '] 中的一种!'; + } + + var t = item.substring(index + 1, lastIndex); + var ind = t.indexOf(':') + var a = ind < 0 ? '' : t.substring(ind + 1) + t = ind < 0 ? t : t.substring(0, ind) + + if (JSONObject.isTableKey(t) != true) { + return ' ! 表名 ' + t + ' 不合法 ! 必须是 Table 这种大驼峰格式' + (isValueNotEmpty ? '' : ',例如 "User" "Comment" "ViewTable" 等 !'); + } + + if (isWarning != true) { + s += CodeUtil.JOIN_TYPES[c] + ' JOIN ' + t + (a.length <= 0 ? '' : ' AS ' + a); + must += (i > 0 ? ', ' : ',同一层级必须有 "') + t + '":{ "' + item.substring(lastIndex + 1) + '":"/../.." }'; + } + } + } + + return isWarning ? '' : CodeUtil.getComment('多表连接:' + (s + must || '例如 &/User/id@, RIGHT, * CROSS, & INNER, | FULL, ! OUTER, ^ SIDE, ( ANTI, ) FOREIGN'), false, ' ') + extraComment; + default: + if (isInSubquery) { + switch (key) { + case 'range': + if (isValueNotString) { + return ' ! value必须是String类型!'; + } + return CodeUtil.SUBQUERY_RANGES.indexOf(value) < 0 ? ' ! value必须是[' + CodeUtil.SUBQUERY_RANGES.join() + ']中的一种!' : (isWarning ? '' : CodeUtil.getComment('比较范围:ANY-任意 ALL-全部', false, ' ')) + extraComment; + case 'from': + return isValueNotString ? ' ! value必须是String类型!' : (isWarning ? '' : CodeUtil.getComment('数据来源' + (isValueNotEmpty ? ',同一层级必须有 "' + value + '":{...}' : ',例如 "User",同一层级必须有 "User":{...}'), false, ' ')) + extraComment; + } + } + break; } - return ''; + + return StringUtil.isEmpty(extraComment, true) ? '' : CodeUtil.getComment(extraComment.substring(3), false, ' '); } - var aliaIndex = name.indexOf(':'); + var aliaIndex = name == null ? -1 : name.indexOf(':'); var objName = aliaIndex < 0 ? name : name.substring(0, aliaIndex); - if (JSONObject.isTableKey(objName)) { + if (isRestful != true && JSONObject.isTableKey(objName)) { switch (key) { case '@column': - return CodeUtil.getType4Request(value) != 'string' ? ' ! value必须是String类型!' : CodeUtil.getComment('返回字段:例如 id,name;json_length(contactIdList):contactCount;...', false, ' '); + return isValueNotString ? ' ! value必须是String类型!' : (isWarning ? '' : CodeUtil.getComment( + '返回字段' + (isValueNotEmpty ? ',可传 字段(:别名)、SQL 函数(:别名,用分号 ; 隔开)、表达式,以及部分 SQL 关键词' + : ':例如 "name" "toId:parentId" "id,userId;json_length(praiseUserIdList):praiseCount" 等'), false, ' ')) + extraComment; + case '@from@': //value 类型为 Object 时 到不了这里,已在上方处理 + return isValueNotString && typeOfValue != 'object' ? ' ! value必须是String或Object类型!' : (isWarning ? '' : CodeUtil.getComment( + '数据来源:引用赋值 子查询 "' + value + '@":{...} ', false, ' ')) + extraComment; case '@group': - return CodeUtil.getType4Request(value) != 'string' ? ' ! value必须是String类型!' : CodeUtil.getComment('分组方式:例如 userId,momentId,...', false, ' '); + return isValueNotString ? ' ! value必须是String类型!' : (isWarning ? '' : CodeUtil.getComment( + '分组方式' + (isValueNotEmpty ? '' : ',例如 "userId" "momentId,toId" 等'), false, ' ')) + extraComment; case '@having': - return CodeUtil.getType4Request(value) != 'string' ? ' ! value必须是String类型!' : CodeUtil.getComment('SQL函数:例如 max(id)>100;sum(balance)<=10000;...', false, ' '); + return isValueNotString ? ' ! value必须是String类型!' : (isWarning ? '' : CodeUtil.getComment( + '聚合函数' + (isValueNotEmpty ? ',可传 SQL 函数(用分号 ; 隔开)、表达式,以及部分 SQL 关键词' + : ',例如 "max(id)>100" "length(phone)>0;sum(balance)<=10000" 等'), false, ' ')) + extraComment; case '@order': - return CodeUtil.getType4Request(value) != 'string' ? ' ! value必须是String类型!' : CodeUtil.getComment('排序方式:+升序,-降序,例如 name+,date-,...', false, ' '); - case '@combine': - return CodeUtil.getType4Request(value) != 'string' ? ' ! value必须是String类型!' : CodeUtil.getComment('条件组合:例如 name?,|tag?,&id{},!id,...', false, ' '); + return isValueNotString ? ' ! value必须是String类型!' : (isWarning ? '' : CodeUtil.getComment( + '排序方式:+升序,-降序' + (isValueNotEmpty ? '' : ',例如 "date-" "name+,id-" 等'), false, ' ')) + extraComment; + case '@combine': //TODO 解析 value 并直接给出条件组合结果 + return isValueNotString ? ' ! value必须是String类型!' : (isWarning ? '' : CodeUtil.getComment( + '条件组合' + (isValueNotEmpty ? ',| 可省略。合并同类,外层按照 & | ! 顺序,内层按传参顺序组合成 (key0 & key1 & key6 & 其它key) & (key2 | key3 | key7) & !(key4 | key5)' + : ',例如 "name$,tag$" "!userId<,!toId" 等'), false, ' ')) + extraComment; + case '@raw': + return isValueNotString ? ' ! value必须是String类型!' : (isWarning ? '' : CodeUtil.getComment( + '原始SQL片段' + (isValueNotEmpty ? ',由后端 RAW_MAP 代码配置指定 "key0,key1.." 中每个 key 对应 key:"SQL片段" 中的 SQL片段' + : ',例如 "@column" "id{},@having" 等'), false, ' ')) + extraComment; + case '@json': + return isValueNotString ? ' ! value必须是String类型!' : (isWarning ? '' : CodeUtil.getComment( + '转为JSON' + (isValueNotEmpty ? '' : ',例如 "request" "gets,heads" 等'), false, ' ')) + extraComment; + case '@null': + return isValueNotString ? ' ! value必须是String类型!' : (isWarning ? '' : CodeUtil.getComment( + 'NULL值字段' + (isValueNotEmpty ? '' : ',例如 "tag" "content,praiseUserIdList" 等'), false, ' ')) + extraComment; + case '@cast': + return isValueNotString ? ' ! value必须是String类型!' : (isWarning ? '' : CodeUtil.getComment( + '类型转换' + (isValueNotEmpty ? '' : ',例如 "date:DATETIME" "date>:DATETIME,id{}:JSON" 等'), false, ' ')) + extraComment; case '@schema': - return CodeUtil.getType4Request(value) != 'string' ? ' ! value必须是String类型!' : CodeUtil.getComment('集合空间:例如 sys apijson ...', false, ' '); + return isValueNotString ? ' ! value必须是String类型!' : (isWarning ? '' : CodeUtil.getComment( + '集合空间(数据库名/模式)' + (isValueNotEmpty ? '' : ',例如 "sys" "apijson" "postgres" "dbo" 等'), false, ' ')) + extraComment; case '@database': - try { - value = value.substring(1, value.length - 1).toUpperCase(); - } catch (e) {} - return CodeUtil.REQUEST_DATABASE_KEYS.indexOf(value) < 0 ? ' ! value必须是[' + CodeUtil.REQUEST_DATABASE_KEYS.join() + ']中的一种!' : CodeUtil.getComment('数据库:例如 MYSQL POSTGRESQL ORACLE ...', false, ' '); + return CodeUtil.DATABASE_KEYS.indexOf(value) < 0 ? ' ! value必须是[' + CodeUtil.DATABASE_KEYS.join() + ']中的一种!' : (isWarning ? '' : CodeUtil.getComment( + '数据库类型:例如 "MYSQL" "POSTGRESQL" "SQLSERVER" "ORACLE" "DB2" "CLICKHOUSE" 等', false, ' ')) + extraComment; + case '@datasource': + return isValueNotString ? ' ! value必须是String类型!' : (isWarning ? '' : CodeUtil.getComment( + '跨数据源' + (isValueNotEmpty ? '' : ',例如 "DRUID" "HIKARICP" 等'), false, ' ')) + extraComment; case '@role': - try { - value = value.substring(1, value.length - 1).toUpperCase(); - } catch (e) {} - var role = CodeUtil.REQUEST_ROLE[value]; - return StringUtil.isEmpty(role) ? ' ! value必须是[' + CodeUtil.REQUEST_ROLE_KEYS.join() + ']中的一种!' : CodeUtil.getComment('来访角色:' + role, false, ' '); + var role = CodeUtil.ROLES[value]; + return StringUtil.isEmpty(role) ? ' ! value必须是[' + CodeUtil.ROLE_KEYS.join() + ']中的一种!' : (isWarning ? '' : CodeUtil.getComment( + '来访角色:' + role + ',限制可操作的数据,假定真实强制匹配', false, ' ')) + extraComment; + case '@cache': + var cache = CodeUtil.CACHE_TYPES[value]; + return StringUtil.isEmpty(cache) ? ' ! value必须是[' + CodeUtil.CACHE_TYPE_KEYS.join() + ']中的一种!' : (isWarning ? '' : CodeUtil.getComment( + '缓存方式:0-全部 1-磁盘 2-内存', false, ' ')) + extraComment; + case '@explain': + return isValueNotBoolean ? ' ! value必须是Boolean类型!' : (isWarning ? '' : CodeUtil.getComment( + '性能分析:true-开启 false-关闭,返回执行的 SQL 及查询计划', false, ' ')) + extraComment; } if (key.startsWith('@')) { - return ''; + if (key.endsWith('()')) { + return isValueNotString ? ' ! value必须是String类型!' : (isWarning ? '' : CodeUtil.getComment( + '存储过程' + (isValueNotEmpty ? ',触发调用数据库存储过程' : ':例如 "getCommentByUserId(id,@limit,@offset)"'), false, ' ')) + extraComment; + } + return StringUtil.isEmpty(extraComment, true) ? '' : CodeUtil.getComment(extraComment.substring(3), false, ' '); } - var c = CodeUtil.getCommentFromDoc(tableList, objName, key, method); - return StringUtil.isEmpty(c) ? ' ! 字段不存在!' : CodeUtil.getComment(c, false, ' '); + var c = CodeUtil.getCommentFromDoc(tableList, objName, key, method, database, language + , isReq != true || isRestful, isReq, pathKeys, isRestful, value, null, null, null, isWarning); + if (c.startsWith(' ! ')) { + return c; + } + return StringUtil.isEmpty(c) ? ' ! 字段不存在!' : (isWarning ? '' : CodeUtil.getComment(c, false, ' ')) + extraComment; } // alert('name = ' + name + '; key = ' + key); - if (StringUtil.isEmpty(name)) { + if (isRestful != true && StringUtil.isEmpty(name)) { switch (key) { case 'tag': - if (method == 'GET' || method == 'HEAD') { - return ''; - } - return CodeUtil.getType4Request(value) != 'string' ? ' ! value必须是String类型!' : CodeUtil.getComment('请求密钥:例如 User Comment[] Privacy-CIRCLE ...', false, ' '); + return isValueNotString ? ' ! value必须是String类型!' : (isWarning ? '' : CodeUtil.getComment( + '请求标识' + (method == 'GET' || method == 'HEAD' ? ',GET,HEAD 请求不会自动解析,仅为后续迭代可能的手动优化而预留' + : (isValueNotEmpty ? ',用来区分不同请求并校验,由后端 Request 表中指定' : ',例如 "User" "Comment[]" "Privacy-CIRCLE" 等')), false, ' ')); case 'version': - if (method == 'GET' || method == 'HEAD') { - return ''; - } - return CodeUtil.getType4Request(value) != 'number' ? ' ! value必须是Number类型!' : CodeUtil.getComment('版本号: 例如 1 2 3 ...', false, ' '); + return isValueNotInteger ? ' ! value必须是Integer类型!' : (isWarning ? '' : CodeUtil.getComment( + '版本号' + (method == 'GET' || method == 'HEAD' ? ',GET,HEAD 请求不会自动解析,仅为后续迭代可能的手动优化而预留' + : (isValueNotEmpty ? ',用来使用特定版本的校验规则,由后端 Request 表中指定' : ',例如 1 2 3 等')), false, ' ')); case 'format': - return CodeUtil.getType4Request(value) != 'boolean' ? ' ! value必须是Boolean类型!' : CodeUtil.getComment('格式化: true-是 false-否', false, ' '); + return isValueNotBoolean ? ' ! value必须是Boolean类型!' : (isWarning ? '' : CodeUtil.getComment( + '格式化: true-是 false-否,将 TableName 转为 tableName, TableName[] 转为 tableNameList, Table:alias 转为 alias 等小驼峰格式', false, ' ')); case '@schema': - return CodeUtil.getType4Request(value) != 'string' ? ' ! value必须是String类型!' : CodeUtil.getComment('集合空间:例如 sys apijson ...', false, ' '); + return isValueNotString ? ' ! value必须是String类型!' : (isWarning ? '' : CodeUtil.getComment( + '集合空间(数据库名/模式)' + (isValueNotEmpty ? '' : ',例如 "sys" "apijson" "postgres" "dbo" 等'), false, ' ')); + case '@datasource': + return isValueNotString ? ' ! value必须是String类型!' : (isWarning ? '' : CodeUtil.getComment( + '跨数据源' + (isValueNotEmpty ? '' : ',例如 "DRUID" "HIKARICP" 等'), false, ' ')); case '@database': - try { - value = value.substring(1, value.length - 1).toUpperCase(); - } catch (e) {} - return CodeUtil.REQUEST_DATABASE_KEYS.indexOf(value) < 0 ? ' ! value必须是[' + CodeUtil.REQUEST_DATABASE_KEYS.join() + ']中的一种!' : CodeUtil.getComment('数据库:例如 MYSQL POSTGRESQL ORACLE ...', false, ' '); + return CodeUtil.DATABASE_KEYS.indexOf(value) < 0 ? ' ! value必须是[' + CodeUtil.DATABASE_KEYS.join() + ']中的一种!' : (isWarning ? '' : CodeUtil.getComment( + '数据库' + (isValueNotEmpty ? '' : ',例如 "MYSQL" "POSTGRESQL" "SQLSERVER" "ORACLE" 等'), false, ' ')); case '@role': - try { - value = value.substring(1, value.length - 1).toUpperCase(); - } catch (e) {} - var role = CodeUtil.REQUEST_ROLE[value]; - return StringUtil.isEmpty(role) ? ' ! value必须是[' + CodeUtil.REQUEST_ROLE_KEYS.join() + ']中的一种!' : CodeUtil.getComment('默认角色:' + role, false, ' '); + var role = CodeUtil.ROLES[value]; + return StringUtil.isEmpty(role) ? ' ! value必须是[' + CodeUtil.ROLE_KEYS.join() + ']中的一种!' : (isWarning ? '' : CodeUtil.getComment( + '默认角色:' + role, false, ' ')); + case '@cache': + var cache = CodeUtil.CACHE_TYPES[value]; + return StringUtil.isEmpty(cache) ? ' ! value必须是[' + CodeUtil.CACHE_TYPE_KEYS.join() + ']中的一种!' : (isWarning ? '' : CodeUtil.getComment( + '缓存方式:0-全部 1-磁盘 2-内存', false, ' ')); + case '@explain': + return isValueNotBoolean ? ' ! value必须是Boolean类型!' : (isWarning ? '' : CodeUtil.getComment( + '性能分析:true-开启 false-关闭,返回执行的 SQL 及查询计划', false, ' ')); } } - return ''; + return StringUtil.isEmpty(extraComment, true) ? '' : CodeUtil.getComment(extraComment.substring(3), false, ' '); }, /** @@ -980,15 +6769,242 @@ var CodeUtil = { * @param tableName * @param columnName * @param method + * @param database + * @param language + * @param onlyTableAndColumn * @return {*} */ - getCommentFromDoc: function (tableList, tableName, columnName, method) { - log('getCommentFromDoc tableName = ' + tableName + '; columnName = ' + columnName + '; method = ' + method + '; tableList = \n' + JSON.stringify(tableList)); + getCommentFromDoc: function (tableList, tableName, columnName, method, database, language, onlyTableAndColumn + , isReq, pathKeys, isRestful, value, ignoreError, standardObj, isSubquery, isWarning) { + log('getCommentFromDoc tableName = ' + tableName + '; columnName = ' + columnName + + '; method = ' + method + '; database = ' + database + '; language = ' + language + + '; onlyTableAndColumn = ' + onlyTableAndColumn + '; tableList = \n' + JSON.stringify(tableList)); + + var typeOfValue = CodeUtil.getType4Request(value); + var isValueNotArray = typeOfValue != 'array'; + var isValueNotObject = typeOfValue != 'object'; + + if (standardObj != null) { + var parentObj = pathKeys == null || pathKeys.length <= 0 ? null : JSONResponse.getStandardByPath(standardObj, pathKeys.slice(0, pathKeys.length - 1)); + var targetValues = parentObj == null ? null : parentObj.values; + var targetObj = targetValues == null ? null : (targetValues[0] || {})[pathKeys[pathKeys.length - 1]]; // JSONResponse.getStandardByPath(standardObj, pathKeys); + + var t = targetObj == null ? null : targetObj.type; + var targetComment = targetObj == null ? null : targetObj.comment; + var c = StringUtil.isEmpty(targetComment, true) ? null : CodeUtil.getType4Language(language, t, true) + + (targetObj.notEmpty ? '! ' : (targetObj.notNull ? ', ' : '? ')) + StringUtil.trim(targetComment); + if (CodeUtil.isTypeMatch(t, CodeUtil.getType4Request(value)) != true) { + c = ' ! value必须是' + CodeUtil.getType4Language(language, t) + '类型!' + (isWarning ? ' ' : CodeUtil.getComment(c, false, ' ')); + if (ignoreError != true) { + throw new Error(c); + } + + if (isWarning) { + return c; + } + } + + if (StringUtil.isNotEmpty(targetComment, true)) { // 如果这里没注释就从数据库/第三方平台取 + return c; + } + + var parentName = parentObj == null || StringUtil.isEmpty(parentObj.name, true) ? null : parentObj.name; + var name = targetObj == null || StringUtil.isEmpty(targetObj.name, true) ? null : targetObj.name; + if (StringUtil.isNotEmpty(parentName, true) || StringUtil.isNotEmpty(name, true)) { + var pn = parentName || tableName; + var n = name || columnName; + var isValObj = isValueNotObject != true; // && StringUtil.isName(pn) + + c = CodeUtil.getCommentFromDoc(tableList, isValObj ? n : pn, isValObj ? null : n + , method, database, language, onlyTableAndColumn, isReq, pathKeys, value, ignoreError, null, isSubquery, isWarning); + if (StringUtil.isNotEmpty(c, true)) { + return (isValObj && StringUtil.isNotEmpty(name, true) ? StringUtil.trim(name) : CodeUtil.getType4Language(language, t, true)) + + (targetObj.notEmpty ? '! ' : (targetObj.notNull ? ', ' : '? ')) + StringUtil.trim(c); + } + } + } + + var isValueNotString = typeOfValue != 'string'; + var isValueNotInteger = typeOfValue != 'integer'; + var isValueNotNumber = isValueNotInteger && typeOfValue != 'number'; + var isValueNotStringOrObject = isValueNotString && isValueNotObject; + var isValueNotStringOrArray = isValueNotString && isValueNotArray; + var isValueNotStringOrNumber = isValueNotString && isValueNotNumber; + var isValueNotStringOrNumberOrObject = isValueNotStringOrNumber && isValueNotObject; + var isValueNotStringOrArrayOrObject = isValueNotString && isValueNotArray && isValueNotObject; + var isValueNotEmpty = isValueNotString ? (typeOfValue != 'array' ? value != null : value.length > 0) : StringUtil.isEmpty(value, true) != true; + + if (isRestful == true && StringUtil.isEmpty(columnName, true) == false && StringUtil.isEmpty(CodeUtil.thirdParty, true) == false) { // } && CodeUtil.thirdParty == 'YAPI') { + var apiMap = CodeUtil.thirdPartyApiMap; + if (apiMap == null) { + // 用 下方 tableList 兜底 return isWarning ? ' ' : '...'; + } + else { + var api = apiMap[(method.startsWith('/') ? '' : '/') + method]; + var doc = api == null ? null : (isReq ? (api.request || api.parameters) : api.response); + if (doc != null) { + var parentDoc = api; + + if (pathKeys != null && pathKeys.length > 0) { + for (var i = 0; i < pathKeys.length; i++) { + var p = pathKeys[i]; + + if (doc instanceof Array) { + var find = false; + for (var j = 0; j < doc.length; j++) { + var d = doc[j]; + if (d != null && d.name == p) { + // parentDoc = doc; + doc = d; + find = true; + break; + } + } + + if (find == false) { + doc = null; + } + } + else if (doc instanceof Object) { + if ((doc.type == 'object' || doc.type == null) && JSONResponse.getType(doc) == 'object') { + parentDoc = doc; + doc = doc.properties || parentDoc.parameters; + } + else if (doc.type == 'array') { + parentDoc = doc; + doc = doc.items; + + try { + if (p != null && p != '' && Number.isNaN(+p)) { + i--; + } + } catch (e) { + } + + continue; + } + + if (doc.type != 'object') { + parentDoc = doc; + } + + if (doc instanceof Array) { + } + else if (properties instanceof Object) { + doc = doc[p]; + } + } + } + } + else if (doc instanceof Array) { + doc = null; + } + + if (doc == null && parentDoc != null) { + var properties = parentDoc.properties || parentDoc.parameters; + var required = parentDoc.required; + + var cols = ''; + if (properties instanceof Array) { + var first = true; + for (var i = 0; i < properties.length; i ++) { + + var para = properties[i]; + var pn = para == null ? null : para.name; + + if (StringUtil.isEmpty(pn, true) == false) { + cols += (first ? '' : ',') + pn; + first = false; + } + } + } + else if (properties instanceof Object) { + cols = Object.keys(properties).join(); + } + + var musts = required == null ? '' : required.join(); + + return ' ! 字段 ' + columnName + ' 不存在!只能是 [' + cols + '] 中的一个!' + (StringUtil.isEmpty(musts, true) ? '' : '其中 [' + musts + '] 必传!'); + } + + var t = doc == null ? null : doc.type; + var c = doc == null ? null : CodeUtil.getType4Language(language, t, true) + (doc.required ? ', ' : '? ') + StringUtil.trim(doc.description || doc.title); + if (t == null) { + // 避免崩溃 + } + else if (t.endsWith('[]')) { + t = 'array'; + } + else if (t == 'integer') { + t = 'number'; + } + + if (CodeUtil.isTypeMatch(t, CodeUtil.getType4Request(value)) != true) { + c = ' ! value必须是' + CodeUtil.getType4Language(language, t) + '类型!' + (isWarning ? ' ' : CodeUtil.getComment(c, false, ' ')) + if (ignoreError != true) { + throw new Error(c); + } + return c; + } + else { + if (c != null) { // 可能存在但只是没注释 StringUtil.isEmpty(c, true) == false) { + return isWarning ? ' ' : c; + } + } + + } + + } + + } + + // if (isRestful != true && onlyTableAndColumn != true && columnName != null && columnName.endsWith('()')) { // 方法,查询完后处理,先用一个Map保存? + // if (['GET', 'HEAD'].indexOf(method) < 0) { + // return ' ! 远程函数只能用于 GET,HEAD 请求!!'; + // } + // + // if (value != null && isValueNotString) { + // return ' ! value必须是String类型!'; + // } + // if (value != null) { + // var startIndex = value.indexOf("("); + // if (startIndex <= 0 || value.endsWith(")") == false) { + // return ' ! value必须符合 fun(arg0,arg1..) 这种格式!且不要有任何多余的空格!'; + // } + // var fun = value.substring(0, startIndex); + // if (StringUtil.isName(fun) != true) { + // return '! 函数名' + fun + '不合法!value必须符合 fun(arg0,arg1..) 这种格式!且不要有任何多余的空格!'; + // } + // } + // + // if (isWarning) { + // return ' '; + // } + // + // var priority = ''; + // if (columnName.endsWith("-()")) { + // priority = ' < 在解析所在对象前优先执行'; + // } + // else if (columnName.endsWith("+()")) { + // priority = ' < 在解析所在对象后滞后执行'; + // } + // else { + // priority = ',执行时机在解析所在对象后,解析子对象前,可以在 () 前用 + - 设置优先级,例如 key-() 优先执行'; + // } + // + // return '远程函数' + (isValueNotEmpty ? ',触发调用后端对应的方法/函数' + priority : ',例如 "isContain(praiseUserIdList,userId)"'); + // } if (tableList == null || tableList.length <= 0) { - return '...'; + return isWarning ? ' ' : '...'; + } + + if (StringUtil.isEmpty(tableName, true)) { + return ' '; } + var isTSQL = ['ORACLE', 'DAMENG'].indexOf(database) >= 0; + var item; var table; @@ -998,131 +7014,305 @@ var CodeUtil = { item = tableList[i]; //Table - table = item == null ? null : item.Table; - if (table == null || tableName != CodeUtil.getModelName(table.TABLE_NAME)) { + table = item == null ? null : (isTSQL ? item.AllTable : (database != 'SQLSERVER' ? item.Table : item.SysTable)); + var table_name = table == null ? null : table.table_name; + if (table_name == null || table_name.replaceAll('_', '').toLowerCase().endsWith(tableName.replaceAll('_', '').toLowerCase()) != true) { // tableName != CodeUtil.getModelName(table.table_name)) { continue; } log('getDoc [] for i=' + i + ': table = \n' + format(JSON.stringify(table))); if (StringUtil.isEmpty(columnName)) { - return table.TABLE_COMMENT; + return /*没必要,常识,太占地方,而且自动生成代码就有 CodeUtil.getType4Object(language) + ', ' + */ ( + isTSQL ? (item.AllTableComment || {}).table_comment : (database == 'POSTGRESQL' + ? (item.PgClass || {}).table_comment + : (database == 'SQLSERVER' + ? (item.ExtendedProperty || {}).table_comment + : table.table_comment + )) + ); } + var at = ''; + var fun = ''; + var key; + var logic = ''; - //功能符 <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< + var verifyType = isSubquery != true && value != null; - if (columnName.endsWith("()")) {//方法,查询完后处理,先用一个Map保存? - return '远程函数'; + if (onlyTableAndColumn) { + key = StringUtil.get(columnName); } + else { + //功能符 <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< + if (columnName.endsWith("()")) {//方法,查询完后处理,先用一个Map保存? + if (['GET', 'HEAD'].indexOf(method) < 0) { + return ' ! 远程函数只能用于 GET,HEAD 请求!!'; + } - var at = ''; - if (columnName.endsWith("@")) {//引用,引用对象查询完后处理。fillTarget中暂时不用处理,因为非GET请求都是由给定的id确定,不需要引用 - at = '引用赋值'; - columnName = columnName.substring(0, columnName.length - 1); - } + if (value != null && isValueNotString) { + return ' ! value必须是String类型!'; + } + if (value != null) { + var startIndex = value.indexOf("("); + if (startIndex <= 0 || value.endsWith(")") == false) { + return ' ! value必须符合 fun(arg0,arg1..) 这种格式!且不要有任何多余的空格!'; + } + var fun = value.substring(0, startIndex); + if (StringUtil.isName(fun) != true) { + return '! 函数名' + fun + '不合法!value必须符合 fun(arg0,arg1..) 这种格式!且不要有任何多余的空格!'; + } + } - var fun; - var key; - if (columnName.endsWith("$")) {//搜索,查询时处理 - fun = '模糊搜索'; - key = columnName.substring(0, columnName.length - 1); - } - else if (columnName.endsWith("?")) {//匹配正则表达式,查询时处理 - fun = '正则匹配'; - key = columnName.substring(0, columnName.length - 1); - } - else if (columnName.endsWith("{}")) {//被包含,或者说key对应值处于value的范围内。查询时处理 - fun = '匹配 选项/条件'; - key = columnName.substring(0, columnName.length - 2); - } - else if (columnName.endsWith("<>")) {//包含,或者说value处于key对应值的范围内。查询时处理 - fun = '包含选项'; - key = columnName.substring(0, columnName.length - 2); - } - else if (columnName.endsWith("+")) {//延长,PUT查询时处理 - if (method != 'PUT') {//不为PUT就抛异常 - return ' ! 功能符 + - 只能用于PUT请求!'; + if (isWarning) { + return ' '; + } + + var priority = ''; + if (columnName.endsWith("-()")) { + priority = ' < 在解析所在对象前优先执行'; + } + else if (columnName.endsWith("+()")) { + priority = ' < 在解析所在对象后滞后执行'; + } + else { + priority = ',执行时机在解析所在对象后,解析子对象前,可以在 () 前用 + - 设置优先级,例如 key-() 优先执行'; + } + + return '远程函数' + (isValueNotEmpty ? ',触发调用后端对应的方法/函数' + priority : ',例如 "isContain(praiseUserIdList,userId)"'); } - fun = '增加/扩展'; - key = columnName.substring(0, columnName.length - 1); - } - else if (columnName.endsWith("-")) {//缩减,PUT查询时处理 - if (method != 'PUT') {//不为PUT就抛异常 - return ' ! 功能符 + - 只能用于PUT请求!'; + + var hasAt = false; + if (columnName.endsWith("@")) {//引用,引用对象查询完后处理。fillTarget中暂时不用处理,因为非GET请求都是由给定的id确定,不需要引用 + // 没传 value 进来,不好解析,而且太长导致后面的字段属性被遮住 + // var lastIndex = value.lastIndexOf('/'); + // var refLastPath = + // at = '引用赋值: ' + tableName + '.' + columnName + '=' + ; + hasAt = true; + + at = '引用赋值' + (isValueNotEmpty ? (value.startsWith('/') ? ',从对象父级开始的相对(缺省)路径' : ',从最外层开始的绝对(完整)路径') : ',例如 "User/id" "[]/Moment/id" 等'); + columnName = columnName.substring(0, columnName.length - 1); + + if (value != null && isValueNotStringOrObject) { + return ' ! value必须是String或Object类型!'; + } + + verifyType = false; } - fun = '减少/去除'; - key = columnName.substring(0, columnName.length - 1); - } - else { - fun = ''; - key = new String(columnName); - } + if (columnName.endsWith("$")) {//搜索,查询时处理 + if (verifyType && hasAt != true && isValueNotStringOrArray) { + return ' ! value必须是String或Array类型!'; + } + + fun = '模糊搜索' + (isValueNotEmpty ? '' : ',例如 "%c%" "S%" "%end" 等'); + key = columnName.substring(0, columnName.length - 1); + } + else if (columnName.endsWith("~")) {//匹配正则表达式,查询时处理 + if (verifyType && hasAt != true && isValueNotStringOrArray) { + return ' ! value必须是String或Array类型!'; + } - var logic; - if (key.endsWith("&")) { - if (fun.length <= 0) { - return ' ! 逻辑运算符 & | 后面必须接其它功能符!'; + fun = '正则匹配' + (isValueNotEmpty ? '' : ',例如 "C" "^[0-9]+$" "^[a-zA-Z]+$" 等'); + key = columnName.substring(0, columnName.length - 1); + if (key.endsWith("*")) { + key = key.substring(0, key.length - 1); + fun += '(忽略大小写)'; + } } - logic = '符合全部'; - } - else if (key.endsWith("|")) { - if (fun.length <= 0) { - return ' ! 逻辑运算符 & | 后面必须接其它功能符!'; + else if (columnName.endsWith("%")) {//连续范围 BETWEEN AND,查询时处理 + if (verifyType && hasAt != true && isValueNotStringOrArray) { + return ' ! value必须是String或Array类型!'; + } + + fun = '连续范围' + (isValueNotEmpty ? '' : ',例如 "82001,82020" "2018-01-01,2020-01-01" ["1-10", "90-100"] 等'); + key = columnName.substring(0, columnName.length - 1); } - logic = '符合任意'; - } - else if (key.endsWith("!")) { - logic = '都不符合'; - } - else { - logic = ''; - } + else if (columnName.endsWith("{}")) {//被包含,或者说key对应值处于value的范围内。查询时处理 + if (verifyType && hasAt != true && isValueNotStringOrArray) { + return ' ! value必须是String或Array类型!'; + } + fun = (isValueNotString ? '匹配选项' : '匹配条件') + (isValueNotEmpty ? '' : ',例如 ' + (isValueNotString ? '[1, 2, 3] ["%c%", "S%", "%end"] 等' : '">100" "%2=0;<=100000" 等')); + key = columnName.substring(0, columnName.length - 2); - if (logic.length > 0) { - if (method != 'GET' && method != 'HEAD' && method != 'GETS' && method != 'HEADS') {//逻辑运算符仅供GET,HEAD方法使用 - return ' ! 逻辑运算符 & | ! 只能用于查询(GET,HEAD,GETS,HEADS)请求!'; + verifyType = false; } - key = key.substring(0, key.length - 1); - } + else if (columnName.endsWith("<>")) {//包含,或者说value处于key对应值的范围内。查询时处理 + fun = '包含选项' + (isValueNotEmpty ? '' : ',例如 1 "Test" [82001, 82002] 等'); + key = columnName.substring(0, columnName.length - 2); - if (StringUtil.isName(key) == false) { - return ' ! 字符 ' + key + ' 不合法!'; - } + verifyType = false; + } + else if (columnName.endsWith("}{")) {//存在,EXISTS。查询时处理 + if (verifyType && hasAt != true && isSubquery != true) { + return ' ! key}{ 后面必须接 @,写成 key}{@:{} 格式!'; + } + if (verifyType && isValueNotObject) { + return ' ! value必须是Object类型!'; + } + + fun = '是否存在' + (isValueNotEmpty ? '' : ',例如 { "from":"Comment", "Comment":{ "@column":"userId" } }'); + key = columnName.substring(0, columnName.length - 2); + + verifyType = false; + } + else if (columnName.endsWith("+")) {//延长,PUT查询时处理 + if (method != 'PUT') {//不为PUT就抛异常 + return ' ! 功能符 + - 只能用于PUT请求!'; + } + fun = '增加/扩展' + (isValueNotEmpty ? '' : ',例如 1 9.9 "a" [82001, 82002] 等'); + key = columnName.substring(0, columnName.length - 1); + } + else if (columnName.endsWith("-")) {//缩减,PUT查询时处理 + if (method != 'PUT') {//不为PUT就抛异常 + return ' ! 功能符 + - 只能用于PUT请求!'; + } + fun = '减少/去除' + (isValueNotEmpty ? '' : ',例如 1 9.9 "a" [82001, 82002] 等'); + key = columnName.substring(0, columnName.length - 1); + } + else if (columnName.endsWith(">=")) {//大于或等于 + if (verifyType && hasAt != true && isValueNotStringOrNumber) { + return ' ! value必须是String或Number类型!'; + } + + fun = '大于或等于' + (isValueNotEmpty ? '' : ',例如 1 9.9 "2020-01-01" 等'); + key = columnName.substring(0, columnName.length - 2); + } + else if (columnName.endsWith("<=")) {//小于或等于 + if (verifyType && hasAt != true && isValueNotStringOrNumber) { + return ' ! value必须是String或Number类型!'; + } + + fun = '小于或等于' + (isValueNotEmpty ? '' : ',例如 1 9.9 "2020-01-01" 等'); + key = columnName.substring(0, columnName.length - 2); + } + else if (columnName.endsWith(">")) {//大于 + if (verifyType && hasAt != true && isValueNotStringOrNumber) { + return ' ! value必须是String或Number类型!'; + } + + fun = '大于' + (isValueNotEmpty ? '' : ',例如 1 9.9 "2020-01-01" 等'); + key = columnName.substring(0, columnName.length - 1); + } + else if (columnName.endsWith("<")) {//小于 + if (verifyType && hasAt != true && isValueNotStringOrNumber) { + return ' ! value必须是String或Number类型!'; + } + + fun = '小于' + (isValueNotEmpty ? '' : ',例如 1 9.9 "2020-01-01" 等'); + key = columnName.substring(0, columnName.length - 1); + } + else { + fun = ''; + key = StringUtil.get(columnName); + } + + + if (key.endsWith("&")) { + if (fun.length <= 0) { + return ' ! 逻辑运算符 & | 后面必须接其它功能符!'; + } + logic = '符合全部'; + } + else if (key.endsWith("|")) { + if (fun.length <= 0) { + return ' ! 逻辑运算符 & | 后面必须接其它功能符!'; + } + logic = '符合任意'; + } + else if (key.endsWith("!")) { + logic = '都不符合'; + } + else { + logic = ''; + } + + if (logic.length > 0) { + if (['GET', 'HEAD', 'GETS', 'HEADS', 'PUT', 'DELETE'].indexOf(method) < 0) {//逻辑运算符仅供GET,HEAD方法使用 + return ' ! 逻辑运算符 & | ! 只能用于 GET,HEAD,GETS,HEADS,PUT,DELETE 请求!'; + } + key = key.substring(0, key.length - 1); + } - //功能符 >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> + if (StringUtil.isName(key) == false) { + return ' ! 字符 ' + key + ' 不合法!'; + } + //功能符 >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> + } - columnList = item['Column[]']; + columnList = item['[]']; if (columnList == null) { continue; } log('getDoc [] for ' + i + ': columnList = \n' + format(JSON.stringify(columnList))); var name; + var columnNames = [] for (var j = 0; j < columnList.length; j++) { - column = columnList[j]; - name = column == null ? null : column.COLUMN_NAME; + column = (columnList[j] || {})[isTSQL ? 'AllColumn' : 'Column']; + name = column == null ? null : column.column_name; if (name == null || key != name) { + if (name != null) { + columnNames.push(name) + } continue; } var p = (at.length <= 0 ? '' : at + ' < ') + (fun.length <= 0 ? '' : fun + ' < ') + (logic.length <= 0 ? '' : logic + ' < '); - return (p.length <= 0 ? '' : p + key + ': ') + CodeUtil.getJavaType(column.COLUMN_TYPE, true) + ', ' + column.COLUMN_COMMENT; + + var o = isTSQL ? (columnList[j] || {}).AllColumnComment : (database == 'POSTGRESQL' + ? (columnList[j] || {}).PgAttribute + : (database == 'SQLSERVER' + ? (columnList[j] || {}).ExtendedProperty + : column + )); + + column.column_type = CodeUtil.getColumnType(column, database); + var t = CodeUtil.getType4Language(language, column.column_type, true); + var c = (p.length <= 0 ? '' : p + key + ': ') + t + (column.is_nullable == 'YES' ? '? ' : ', ') + (o || {}).column_comment; + + var ct = CodeUtil.getType4Language(CodeUtil.LANGUAGE_JAVA_SCRIPT, column.column_type, false); + if (verifyType && t != null && CodeUtil.isTypeMatch(ct, CodeUtil.getType4Language(CodeUtil.LANGUAGE_JAVA_SCRIPT, typeOfValue)) != true) { + // c = ' ! value必须是' + t + '类型!' + CodeUtil.getComment(c, false, ' ') + // if (ignoreError != true) { + // throw new Error(c); + // } + return ' ! value必须是' + t + '类型!' + (isWarning ? ' ' : CodeUtil.getComment(c, false, ' ')); + } + + return isWarning ? ' ' : c; } - break; + return onlyTableAndColumn ? '' : ' ! 字段 ' + key + ' 不存在!只能是 [' + columnNames.join() + '] 中的一个!'; } return ''; }, getType4Request: function (value) { - return typeof JSON.parse(value); + // return t != 'string' ? t : typeof parseJSON(value); + if (value instanceof Array) { + return 'array' + } + if (Number.isInteger(value)) { + return 'integer'; + } + return typeof value; + }, + + isTypeMatch: function(targetType, realType) { + if (targetType == null || targetType == realType) { + return true; + } + return (targetType == 'number' && realType == 'integer') || (targetType == 'string' && ['date', 'time', 'datetime'].indexOf(realType) >= 0); } -} \ No newline at end of file +}; + +if (typeof module == 'object') { + module.exports = CodeUtil; +} diff --git a/apijson/JSONObject.js b/apijson/JSONObject.js index fcbf4f4..6fb1c37 100644 --- a/apijson/JSONObject.js +++ b/apijson/JSONObject.js @@ -1,4 +1,4 @@ -/*Copyright ©2017 TommyLemon(https://github.com/TommyLemon/APIJSONAuto) +/*Copyright ©2017 TommyLemon(https://github.com/TommyLemon/APIAuto) Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -19,23 +19,120 @@ var JSONObject = { TAG: 'JSONObject', + log: function (tag, msg) { + + }, + + isEmpty: function (obj) { + return obj == null || Object.keys(obj).length <= 0; + }, + /**判断key是否为表名 * @param key * @return */ - isTableKey: function(key) { - log(this.TAG, 'isTableKey typeof key = ' + (typeof key)); - return key != null && /^[A-Z][A-Za-z0-9_]*$/.test(key); + isTableKey: function(key, value, isRestful) { + JSONObject.log(this.TAG, 'isTableKey typeof key = ' + (typeof key)); + if (key == null) { + return false; + } + + if (value != null && typeof value != 'object') { + return false; + } + + if (isRestful == true) { + return true; + } + + return /^[A-Z][A-Za-z0-9_]*$/.test(key); }, /**判断key是否为数组名 * @param key * @return */ - isArrayKey: function(key) { - log(this.TAG, 'isArrayKey typeof key = ' + (typeof key)); - return key != null && key.endsWith('[]'); + isArrayKey: function(key, value, isRestful) { + JSONObject.log(this.TAG, 'isArrayKey typeof key = ' + (typeof key)); + + if (key == null) { + return false; + } + + if (isRestful == true) { + return value == null || typeof value == 'array'; + } + + if (value != null && value instanceof Object == false) { + return false; + } + + return key.endsWith('[]'); + }, + + isAPIJSONPath: function (method) { + var info = JSONObject.parseUri(method, true) + return info != null && info.isRestful != true; + }, + + parseUri: function (method, isReq) { + method = method || 'get'; + var isRestful = true; + + var ind = method.indexOf('?'); + if (ind >= 0) { + method = method.substring(0, ind); + } + if (method.startsWith("/")) { + method = method.substring(1); + } + + if (method.endsWith("/")) { + method = method.substring(0, method.length - 1); + } + + var startName = null; + var tag = null; + + var mIndex = method.lastIndexOf('/'); + if (mIndex < 0) { + isRestful = APIJSON_METHODS.indexOf(method) < 0; + } + else if (APIJSON_METHODS.indexOf(method.substring(mIndex+1)) >= 0) { + isRestful = false; + method = method.substring(mIndex+1); + } + else { + var suffix = method.substring(mIndex + 1); + method = method.substring(0, mIndex); + + mIndex = method.lastIndexOf("/"); + if (mIndex >= 0) { + method = method.substring(mIndex+1); + } + + isRestful = APIJSON_METHODS.indexOf(method) < 0; + + if (isReq && ! isRestful) { + tag = suffix; + var tbl = tag.endsWith("[]") ? tag.substring(0, tag.length - 2) : tag; + if (JSONObject.isTableKey(tbl)) { + startName = method == 'put' || method == 'delete' ? tbl : tag; + } + } + } + + return { + method: method, + isRestful: isRestful, + tag: tag, + table: startName + } } +}; + +if (typeof module == 'object') { + module.exports = JSONObject; } -//TODO 取消注释 Object.freeze(JSONObject) //不可修改 \ No newline at end of file +//TODO 取消注释 Object.freeze(JSONObject) //不可修改 diff --git a/apijson/JSONRequest.js b/apijson/JSONRequest.js index 4c95cbc..7f6c8ef 100644 --- a/apijson/JSONRequest.js +++ b/apijson/JSONRequest.js @@ -1,4 +1,4 @@ -/*Copyright ©2017 TommyLemon(https://github.com/TommyLemon/APIJSONAuto) +/*Copyright ©2017 TommyLemon(https://github.com/TommyLemon/APIAuto) Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -28,6 +28,7 @@ var URL_HEADS = URL_BASE + "/heads"; // 通过POST来HEAD数据,不显示请 var URL_POST = URL_BASE + "/post"; // 新增(或者说插入)数据 var URL_PUT = URL_BASE + "/put"; // 修改数据,只修改传入字段对应的值 var URL_DELETE = URL_BASE + "/delete"; // 删除数据 +var APIJSON_METHODS = ["get", "head", "gets", "heads", "post", "put", "delete"] /**请求,全走HTTP POST @@ -150,13 +151,71 @@ function encode(json) { return json; } +/**解码JSON,反转义所有String + * @param json 任意类型 + */ +function decode(json) { + // alertOfDebug("decode before:\n" + format(JSON.stringify(json))); + + if (typeof json == "string") { //json instanceof String) { + json = decodeURIComponent(json); + } + else if (json instanceof Array) { + // alertOfDebug("decode json instanceof Array"); + + for (var i = 0; i < json.length; i ++) { + // alertOfDebug("json[" + i + "] = " + format(JSON.stringify(json[i]))); + json[i] = decode(json[i]); + } + } + else if (json instanceof Object) { + // alertOfDebug("decode json instanceof Object"); + for (var key in json) { + // alertOfDebug("decode json[" + key + "] = " + format(JSON.stringify(json[key]))); + json[key] = decode(json[key]); + } + } + // alertOfDebug("decode after:\n" + format(JSON.stringify(json))); + + return json; +} + +/**编码JSON,转义所有String + * @param data 任意类型 + */ +function toFormData(data) { + if (data == null) { + return null + } + if (data instanceof Object == false || data instanceof Array) { + alert('toFormData data instanceof Object == false || data instanceof Array ! >> return') + return + } + + var first = true + var ret = '' + for (var key in data) { + var val = data[key] + if (typeof val != 'string') { + val = JSON.stringify(val) + } + ret += (first ? '' : '&') + encodeURIComponent(key) + '=' + encodeURIComponent(val) + first = false + } + return ret; +} + /**格式化JSON串 * @param json */ function format(json) { + if (json instanceof Object) { + return JSON.stringify(json, null, "\t"); + } + try { - return JSON.stringify(JSON.parse(json), null, "\t"); + return JSON.stringify(parseJSON(json), null, "\t"); } catch(e) { log(TAG_REQUEST_UTIL, 'format try { ... } catch (err) { \n ' + e); return json; @@ -175,9 +234,9 @@ function format(json) { // var jsonObj; // if (typeof json == 'string'){ // try { - // jsonObj = JSON.parse(json); + // jsonObj = parseJSON(json); // } catch (err) { - // console.log('format try { jsonObj = JSON.parse(json); } catch (err) { \n ' + err); + // console.log('format try { jsonObj = parseJSON(json); } catch (err) { \n ' + err); // return json; // } // } @@ -196,18 +255,21 @@ function log(tag, msg) { * @param s */ function parseJSON(s) { - if (s instanceof Object) { - alertOfDebug("parseJSON s instanceof JSON >> return s;"); - return s; - } - if (typeof s != "string") { alertOfDebug("parseJSON typeof json != string >> s = \"\" + s;"); - s = "" + s; + return s; } // alertOfDebug("parseJSON s = \n" + s); - return JSON.parse(s); + if (StringUtil.isEmpty(s, true)) { + return null; + } + + try { + return JSON.parse(s); + } catch (e) { + return JSON5.parse(s) + } } /**测试用的提示 @@ -412,5 +474,8 @@ function newArrayString(table, json, count, page) { + table + "\":" + JSON.stringify(json) + "}}"; } +if (typeof module == 'object') { + module.exports = this; +} -//常用请求>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> \ No newline at end of file +//常用请求>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> diff --git a/apijson/JSONResponse.js b/apijson/JSONResponse.js index 7305f99..065396f 100644 --- a/apijson/JSONResponse.js +++ b/apijson/JSONResponse.js @@ -1,4 +1,4 @@ -/*Copyright ©2017 TommyLemon(https://github.com/TommyLemon/APIJSONAuto) +/*Copyright ©2017 TommyLemon(https://github.com/TommyLemon/APIAuto) Licensed under the Apache License, Version 2.0 (the "License"); you may not use JSONResponse file except in compliance with the License. @@ -17,6 +17,18 @@ * @author Lemon */ +if (typeof window == 'undefined') { + try { + eval(` + var StringUtil = require("./StringUtil"); + var JSONObject = require("./JSONObject"); + var JSONRequest = require("./JSONRequest"); + var CodeUtil = require("./CodeUtil"); + `) + } catch (e) { + console.log(e) + } +} //状态信息,非GET请求获得的信息<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< @@ -47,15 +59,146 @@ const KEY_ID_IN = KEY_ID + "{}"; const KEY_COUNT = "count"; const KEY_TOTAL = "total"; +var FORMAT_ANY = ''; +var FORMAT_MONTH = 'YYYY-MM'; +var FORMAT_DATE = 'YYYY-MM-DD'; +var FORMAT_MINUTE = 'hh:mm'; +var FORMAT_TIME = 'hh:mm:ss'; +var FORMAT_DATETIME = 'YYYY-MM-DD hh:mm:ss'; +var FORMAT_URL = 'URL'; +var FORMAT_URI = 'URI'; +var FORMAT_FILE_URI = 'file://a/b'; +var FORMAT_HTTP = 'http://a.b/'; +var FORMAT_RPC = 'rpc://a.b'; +var FORMAT_PATH = 'root/folder'; +var FORMAT_PACKAGE = 'com.package'; +var FORMAT_NAME = 'NAME'; +var FORMAT_CONST_NAME = 'NAME:CONST'; +var FORMAT_BIG_NAME = 'NAME:Big'; +var FORMAT_SMALL_NAME = 'NAME:small'; +var FORMAT_FILE = '.file'; +var FORMAT_IMAGE = '.image'; +var FORMAT_AUDIO = '.audio'; +var FORMAT_VIDEO = '.video'; +var FORMAT_IMAGE_HTTP = 'http://?.image'; +var FORMAT_AUDIO_HTTP = 'http://?.audio'; +var FORMAT_VIDEO_HTTP = 'http://?.video'; +var FORMAT_IMAGE_FILE = 'file://?.image'; +var FORMAT_AUDIO_FILE = 'file://?.audio'; +var FORMAT_VIDEO_FILE = 'file://?.video'; +var FORMAT_PRIORITY_ANY = 0; +var FORMAT_PRIORITY_MONTH = 3; +var FORMAT_PRIORITY_DATE = 2; +var FORMAT_PRIORITY_MINUTE = 3; +var FORMAT_PRIORITY_TIME = 2; +var FORMAT_PRIORITY_DATETIME = 1; +var FORMAT_PRIORITY_URL = 2; +var FORMAT_PRIORITY_URI = 1; +var FORMAT_PRIORITY_FILE_URI = 2; +var FORMAT_PRIORITY_HTTP = 2; +var FORMAT_PRIORITY_RPC = 2; +var FORMAT_PRIORITY_PATH = 1; +var FORMAT_PRIORITY_PACKAGE = 1; +var FORMAT_PRIORITY_NAME = 1; +var FORMAT_PRIORITY_CONST_NAME = 2; +var FORMAT_PRIORITY_BIG_NAME = 2; +var FORMAT_PRIORITY_SMALL_NAME = 2; +var FORMAT_PRIORITY_FILE = 1; +var FORMAT_PRIORITY_IMAGE = 2; +var FORMAT_PRIORITY_AUDIO = 2; +var FORMAT_PRIORITY_VIDEO = 2; +var FORMAT_PRIORITY_IMAGE_HTTP = 3; +var FORMAT_PRIORITY_AUDIO_HTTP = 3; +var FORMAT_PRIORITY_VIDEO_HTTP = 3; +var FORMAT_PRIORITY_IMAGE_FILE = 3; +var FORMAT_PRIORITY_AUDIO_FILE = 3; +var FORMAT_PRIORITY_VIDEO_FILE = 3; + +var FORMAT_PRIORITIES = { // 在 JSONResponse 中定义,会导致存不进值,因为 FORMAT_ANY 等 key 还没初始化 + [FORMAT_ANY]: FORMAT_PRIORITY_ANY, + [FORMAT_MONTH]: FORMAT_PRIORITY_MONTH, + [FORMAT_DATE]: FORMAT_PRIORITY_DATE, + [FORMAT_MINUTE]: FORMAT_PRIORITY_MINUTE, + [FORMAT_TIME]: FORMAT_PRIORITY_TIME, + [FORMAT_DATETIME]: FORMAT_PRIORITY_DATETIME, + [FORMAT_URL]: FORMAT_PRIORITY_URL, + [FORMAT_URI]: FORMAT_PRIORITY_URI, + [FORMAT_FILE_URI]: FORMAT_PRIORITY_FILE_URI, + [FORMAT_RPC]: FORMAT_PRIORITY_RPC, + [FORMAT_HTTP]: FORMAT_PRIORITY_HTTP, + [FORMAT_PATH]: FORMAT_PRIORITY_PATH, + [FORMAT_PACKAGE]: FORMAT_PRIORITY_PACKAGE, + [FORMAT_NAME]: FORMAT_PRIORITY_NAME, + [FORMAT_BIG_NAME]: FORMAT_PRIORITY_BIG_NAME, + [FORMAT_SMALL_NAME]: FORMAT_PRIORITY_SMALL_NAME, + [FORMAT_PRIORITY_CONST_NAME]: FORMAT_PRIORITY_CONST_NAME, + [FORMAT_FILE]: FORMAT_PRIORITY_FILE, + [FORMAT_IMAGE]: FORMAT_PRIORITY_IMAGE, + [FORMAT_AUDIO]: FORMAT_PRIORITY_AUDIO, + [FORMAT_VIDEO]: FORMAT_PRIORITY_VIDEO, + [FORMAT_IMAGE_HTTP]: FORMAT_PRIORITY_IMAGE_HTTP, + [FORMAT_AUDIO_HTTP]: FORMAT_PRIORITY_AUDIO_HTTP, + [FORMAT_VIDEO_HTTP]: FORMAT_PRIORITY_VIDEO_HTTP, + [FORMAT_IMAGE_FILE]: FORMAT_PRIORITY_IMAGE_FILE, + [FORMAT_AUDIO_FILE]: FORMAT_PRIORITY_AUDIO_FILE, + [FORMAT_VIDEO_FILE]: FORMAT_PRIORITY_VIDEO_FILE, +}; + +var FORMAT_VERIFIERS = { + [FORMAT_IMAGE_HTTP]: StringUtil.isImageHttpUrl, + [FORMAT_AUDIO_HTTP]: StringUtil.isAudioHttpUrl, + [FORMAT_VIDEO_HTTP]: StringUtil.isVideoHttpUrl, + [FORMAT_IMAGE_FILE]: StringUtil.isImageFilePath, + [FORMAT_AUDIO_FILE]: StringUtil.isAudioFilePath, + [FORMAT_VIDEO_FILE]: StringUtil.isVideoFilePath, + [FORMAT_MONTH]: StringUtil.isMonth, + [FORMAT_DATE]: StringUtil.isDate, + [FORMAT_MINUTE]: StringUtil.isMinute, + [FORMAT_TIME]: StringUtil.isTime, + [FORMAT_DATETIME]: StringUtil.isDatetime, + [FORMAT_FILE_URI]: StringUtil.isFileUri, + [FORMAT_RPC]: StringUtil.isRpcUrl, + [FORMAT_HTTP]: StringUtil.isHttpUrl, + [FORMAT_URL]: StringUtil.isUrl, + [FORMAT_URI]: StringUtil.isUri, + [FORMAT_PATH]: StringUtil.isPath, + [FORMAT_PACKAGE]: StringUtil.isPackage, + [FORMAT_IMAGE]: StringUtil.isImage, + [FORMAT_AUDIO]: StringUtil.isAudio, + [FORMAT_VIDEO]: StringUtil.isVideo, + [FORMAT_FILE]: StringUtil.isFile, + [FORMAT_BIG_NAME]: StringUtil.isBigName, + [FORMAT_SMALL_NAME]: StringUtil.isSmallName, + [FORMAT_NAME]: StringUtil.isName, +}; + + + +function log(msg) { + // console.log(msg); +} + var JSONResponse = { TAG: 'JSONResponse', + KEY_CODE: 'code', + KEY_MSG: 'msg', + KEY_THROW: 'throw', + CODE_SUCCESS: 200, /**是否成功 * @param code * @return */ - isSuccess: function(code) { - return code == CODE_SUCCESS; + isSuccess: function(obj) { + if (obj == null) { + return false + } + + if (obj instanceof Array == false && obj instanceof Object) { + return obj[JSONResponse.KEY_CODE] == JSONResponse.CODE_SUCCESS; + } + + return obj == JSONResponse.CODE_SUCCESS }, /**校验服务端是否存在table @@ -87,13 +230,13 @@ var JSONResponse = { value = object[key]; if (value instanceof Array) { // JSONArray,遍历来format内部项 - formattedObject[JSONResponse.replaceArray(key)] = JSONResponse.formatArray(value); + formattedObject[JSONResponse.formatArrayKey(key)] = JSONResponse.formatArray(value); } else if (value instanceof Object) { // JSONObject,往下一级提取 - formattedObject[JSONResponse.getSimpleName(key)] = JSONResponse.formatObject(value); + formattedObject[JSONResponse.formatObjectKey(key)] = JSONResponse.formatObject(value); } else { // 其它Object,直接填充 - formattedObject[JSONResponse.getSimpleName(key)] = value; + formattedObject[JSONResponse.formatOtherKey(key)] = value; } } @@ -131,24 +274,6 @@ var JSONResponse = { return formattedArray; }, - /**替换key+KEY_ARRAY为keyList - * @param key - * @return getSimpleName(isArrayKey(key) ? getArrayKey(...) : key) {@link #getSimpleName(String)} - */ - replaceArray: function(key) { - if (JSONObject.isArrayKey(key)) { - key = JSONResponse.getArrayKey(key.substring(0, key.lastIndexOf('[]'))); - } - return JSONResponse.getSimpleName(key); - }, - /**获取列表变量名 - * @param key => getNoBlankString(key) - * @return empty ? "list" : key + "List" 且首字母小写 - */ - getArrayKey: function(key) { - return StringUtil.addSuffix(key, "List"); - }, - /**获取简单名称 * @param fullName name 或 name:alias * @return name => name; name:alias => alias @@ -159,47 +284,245 @@ var JSONResponse = { return index < 0 ? fullName : fullName.substring(0, index); }, - /**获取简单名称 - * @param fullName name 或 name:alias 或 User-name 或 User-name:alias - * @return name => name; name:alias 或 User-name:alias => alias; User-name => userName + /**获取变量名 + * @param fullName + * @return {@link #formatKey(String, boolean, boolean, boolean)} formatColon = true, formatAt = true, formatHyphen = true, firstCase = true */ - getSimpleName: function(fullName) { - //key:alias -> alias - var index = fullName == null ? -1 : fullName.indexOf(":"); + getVariableName: function(fullName, listSuffix) { + if (StringUtil.isEmpty(fullName, true)) { + return null; + } + if (JSONObject.isArrayKey(fullName)) { + fullName = StringUtil.addSuffix(fullName.substring(0, fullName.length - 2), listSuffix || "list"); + } + var n = JSONResponse.formatKey(fullName, true, true, true, true, true, true); + return /0-9/.test(n.substring(0, 1)) ? '_' + n : n; + }, + + /**格式化数组的名称 key[] => keyList; key:alias[] => aliasList; Table-column[] => tableColumnList + * @param key empty ? "list" : key + "List" 且首字母小写 + * @return {@link #formatKey(String, boolean, boolean, boolean)} formatColon = false, formatAt = true, formatHyphen = true, firstCase = true + */ + formatArrayKey: function(key) { + if (JSONObject.isArrayKey(key)) { + key = StringUtil.addSuffix(key.substring(0, key.length - 2), "list"); + } + var index = key == null ? -1 : key.indexOf(":"); + if (index >= 0) { + return key.substring(index + 1); //不处理自定义的 + } + + return JSONResponse.formatKey(key, false, true, true, true); //节约性能,除了表对象 Table-column:alias[] ,一般都符合变量命名规范 + }, + + /**格式化对象的名称 name => name; name:alias => alias + * @param key name 或 name:alias + * @return {@link #formatKey(String, boolean, boolean, boolean)} formatColon = false, formatAt = true, formatHyphen = false, firstCase = true + */ + formatObjectKey: function(key) { + var index = key == null ? -1 : key.indexOf(":"); if (index >= 0) { - return fullName.substring(index + 1); + return key.substring(index + 1); //不处理自定义的 + } + + return JSONResponse.formatKey(key, false, true, false, true); //节约性能,除了表对象 Table:alias ,一般都符合变量命名规范 + }, + + /**格式化普通值的名称 name => name; name:alias => alias + * @param fullName name 或 name:alias + * @return {@link #formatKey(String, boolean, boolean, boolean)} formatColon = false, formatAt = true, formatHyphen = false, firstCase = false + */ + formatOtherKey: function(fullName) { + return JSONResponse.formatKey(fullName, false, true, false, false); //节约性能,除了关键词 @key ,一般都符合变量命名规范,不符合也原样返回便于调试 + }, + + /**格式化名称 + * @param fullName name 或 name:alias + * @param formatAt 去除前缀 @ , @a => a + * @param formatAlias 去除别名分隔符 : , A:b => AAsB + * @param formatHyphen 去除分隔符 - , A-b-cd-Efg => aBCdEfg + * @param firstCase 第一个单词首字母小写,后面的首字母大写, Ab => ab ; A-b-Cd => aBCd + * @return name => name; name:alias => alias + */ + formatKey: function(fullName, formatAlias, formatAt, formatHyphen, firstCase, formatUnderline, formatFunChar) { + if (fullName == null) { + log(JSONResponse.TAG, "formatKey fullName == null >> return null;"); + return null; } - var left = index < 0 ? fullName : fullName.substring(0, index); + if (formatAlias) { + fullName = JSONResponse.formatAlias(fullName); + } + if (formatAt) { //关键词只去掉前缀,不格式化单词,例如 @a-b 返回 a-b ,最后不会调用 setter + fullName = JSONResponse.formatAt(fullName); + } + if (formatHyphen) { + fullName = JSONResponse.formatHyphen(fullName, firstCase); + } + if (formatUnderline) { + fullName = JSONResponse.formatUnderline(fullName, true); + } + if (formatFunChar) { + fullName = JSONResponse.formatFunChar(fullName, true); + } + + return firstCase ? StringUtil.firstCase(fullName) : fullName; //不格式化普通 key:value (value 不为 [], {}) 的 key + }, + + /**"@key" => "key" + * @param key + * @return + */ + formatAt: function(key) { + var k = key.startsWith("@") ? key.substring(1) : key; + return k; //由 formatFunChar 实现去掉末尾的 @ k.endsWith("@") ? k.substring(0, k.length - 1) : k; + }, + /**key:alias => keyAsAlias + * @param key + * @return + */ + formatAlias: function(key) { + var index = key.indexOf(":"); + return index < 0 ? key : key.substring(0, index) + 'As' + StringUtil.firstCase(key.substring(index + 1), true); + }, + /**A-b-cd-Efg => ABCdEfg + * @param key + * @return + */ + formatHyphen: function(key, firstCase) { var first = true; - var name = ''; + var index; + + var name = ""; var part; do { - index = left.indexOf("-"); - part = index < 0 ? left : left.substring(0, index); + index = key.indexOf("-"); + part = index < 0 ? key : key.substring(0, index); - name += StringUtil.firstCase(part, ! first); - left = left.substring(index + 1); + name += firstCase && first == false ? StringUtil.firstCase(part, true) : part; + key = key.substring(index + 1); first = false; } - while (index >=0) + while (index >= 0); return name; }, + /**A_b_cd_Efg => ABCdEfg + * @param key + * @return + */ + formatUnderline: function(key, firstCase) { + var first = true; + var index; + + var name = ""; + var part; + do { + index = key.indexOf("_"); + part = index < 0 ? key : key.substring(0, index); + + name += firstCase && first == false ? StringUtil.firstCase(part, true) : part; + key = key.substring(index + 1); + + first = false; + } + while (index >= 0); + + return name; + }, + /**id{} => idIn; userId@ => userIdAt; ... + * @param key + * @return + */ + formatFunChar: function(key) { + var name = key.replace(/@/g, 'At'); + name = name.replace(/{}/g, 'In') + name = name.replace(/}{/g, 'Exists') + name = name.replace(/\(\)/g, 'Function') + name = name.replace(/\[\]/g, 'List') + name = name.replace(/\$/g, 'Search') + name = name.replace(/~/g, 'Regexp') + name = name.replace(/:/g, 'As') + name = name.replace(/\+/g, 'Add') + name = name.replace(/-/g, 'Remove') + name = name.replace(/>=/g, 'Gte') + name = name.replace(/<=/g, 'Lte') + name = name.replace(/>/g, 'Gt') + name = name.replace(/ ' + rStatus, + path: '' + } + } + codeName = StringUtil.isEmpty(codeName, true) ? JSONResponse.KEY_CODE : codeName; + var tCode = (isMachineLearning != true && noBizCode) ? 0 : (target || {})[codeName]; + var rCode = noBizCode ? tCode : (real || {})[codeName]; + + //解决了弹窗提示机器学习更新标准异常,但导致所有项测试结果都变成状态码 code 改变 + // if (real == null) { + // return { + // code: JSONResponse.COMPARE_ERROR, //未上传对比标准 + // msg: 'response 为 null!', + // path: folder == null ? '' : folder + // }; + // } + + if (tCode == null) { + if (typeof rCode == 'number' && (rCode%10 != 0 || (rCode >= 400 && rCode < 600))) { + return { + code: JSONResponse.COMPARE_CODE_CHANGE, //未上传对比标准 + msg: '没有校验标准,且状态码 ' + rCode + ' 在 [400, 599] 内或不是 0, 200 等以 0 结尾的数', + path: folder == null ? '' : folder + }; + } + + if (real != null && real.throw != null) { + return { + code: JSONResponse.COMPARE_CODE_CHANGE, //未上传对比标准 + msg: '没有校验标准,且 throw 是 ' + real.throw, + path: folder == null ? '' : folder + }; + } + + if (real == null || real.data == null) { + return { + code: JSONResponse.COMPARE_KEY_LESS, //未上传对比标准 + msg: '没有校验标准,且缺少有效 data 值', + path: folder == null ? '' : folder + }; + } + return { code: JSONResponse.COMPARE_NO_STANDARD, //未上传对比标准 msg: '没有校验标准!', path: folder == null ? '' : folder }; } - if (target.code != real.code) { - return { + + var tThrw = target.throw; + var rThrw = noBizCode ? tThrw : real.throw; + + var exceptions = target.exceptions || []; + if (rCode != tCode || rThrw != tThrw) { + + var find = null; + for (var i = 0; i < exceptions.length; i++) { + var ei = exceptions[i]; + if (ei != null && ei[codeName] == rCode && ei.throw == rThrw) { + find = ei; + break; + } + } + + if (find != null) { + return { + code: JSONResponse.COMPARE_EQUAL_EXCEPTION, + msg: '符合异常分支 ' + rCode + (StringUtil.isEmpty(rThrw) ? '' : ' ' + rThrw + ':') + ' ' + StringUtil.trim(find.msg), + path: folder == null ? '' : folder + }; + } + + return rCode != tCode ? { code: JSONResponse.COMPARE_CODE_CHANGE, - msg: '状态码改变!', + msg: '状态码 ' + codeName + ' 改变!' + tCode + ' -> ' + rCode, + path: folder == null ? '' : folder + } : { + code: JSONResponse.COMPARE_THROW_CHANGE, + msg: '异常 throw 改变!' + tThrw + ' -> ' + rThrw, path: folder == null ? '' : folder }; } - var tCode = target.code; - var rCode = real.code; - - delete target.code; - delete real.code; + if (noBizCode != true) { + delete target[codeName]; + delete real[codeName]; + delete target.throw; + delete real.throw; + } //可能提示语变化,也要提示 // delete target.msg; // delete real.msg; - var result = JSONResponse.compareWithBefore(target, real, folder); + var result = null + try { + result = isMachineLearning == true + ? JSONResponse.compareWithStandard(target, real, folder, exceptKeys, ignoreTrend) + : JSONResponse.compareWithBefore(target, real, folder, exceptKeys); + } finally { + if (isMachineLearning || noBizCode != true) { + target[codeName] = tCode; + } + if (noBizCode != true) { + real[codeName] = rCode; + target.throw = tThrw; + real.throw = rThrw; + } + } - target.code = tCode; - real.code = rCode; + if (exceptions.length > 0 && (target.repeat || 0) <= 0 && (result || {}).code < JSONResponse.COMPARE_VALUE_CHANGE) { + return { + code: JSONResponse.COMPARE_VALUE_CHANGE, + msg: '状态码' + codeName + ' 违背首次成功、后续失败的趋势', + path: folder == null ? '' : folder + } + } return result; }, @@ -249,7 +666,7 @@ var JSONResponse = { 3-缺少字段/整数变小数,黄色; 4-类型/code 改变,红色; */ - compareWithBefore: function(target, real, folder) { + compareWithBefore: function(target, real, folder, exceptKeys) { folder = folder == null ? '' : folder; if (target == null) { @@ -269,19 +686,6 @@ var JSONResponse = { }; } - var type = typeof target; - if (type != typeof real) { //类型改变 - return { - code: JSONResponse.COMPARE_TYPE_CHANGE, - msg: '值改变', - path: folder, - value: real - }; - } - - // var max = JSONResponse.COMPARE_EQUAL; - // var each = JSONResponse.COMPARE_EQUAL; - var max = { code: JSONResponse.COMPARE_EQUAL, msg: '结果正确', @@ -289,27 +693,56 @@ var JSONResponse = { value: null //导致正确时也显示 real }; - var each; + var type = JSONResponse.getType(target); // typeof target; + var realType = JSONResponse.getType(real); + if (type != realType) { //类型改变 + if (type != "integer" || realType != "number") { + return { + code: JSONResponse.COMPARE_TYPE_CHANGE, + msg: '值类型改变!' + type + " -> " + realType, + path: folder, + value: real + }; + } + + max.code = JSONResponse.COMPARE_NUMBER_TYPE_CHANGE; + max.msg = '整数变小数'; + max.path = folder; + max.value = real; + } + + // var max = JSONResponse.COMPARE_EQUAL; + // var each = JSONResponse.COMPARE_EQUAL; if (target instanceof Array) { // JSONArray - var all = target[0]; - for (var i = 1; i < length; i++) { //合并所有子项, Java类型是稳定的,不会出现两个子项间同名字段对应值类型不一样 - all = JSONResponse.deepMerge(all, target[i]); + if (max.code < JSONResponse.COMPARE_KEY_LESS && real.length < target.length) { + max = { + code: JSONResponse.COMPARE_KEY_LESS, + msg: '是缺少的', + path: JSONResponse.getAbstractPath(folder, real.length), + value: target[real.length] + } + } + else if (max.code < JSONResponse.COMPARE_KEY_MORE && real.length > target.length) { + max = { + code: JSONResponse.COMPARE_KEY_MORE, + msg: '是新增的', + path: JSONResponse.getAbstractPath(folder, target.length), + value: real[target.length] + } } - //下载需要看源JSON real = [all]; - each = JSONResponse.compareWithBefore(target[0], all, JSONResponse.getAbstractPath(folder, i)); + var minLen = Math.min(target.length, real.length) + for (var i = 0; i < minLen; i++) { //合并所有子项, Java类型是稳定的,不会出现两个子项间同名字段对应值类型不一样 + var each = JSONResponse.compareWithBefore(target[i], real[i], JSONResponse.getAbstractPath(folder, i), exceptKeys); - if (max.code < each.code) { - max = each; - } + var code = each == null ? 0 : each.code; + if (max.code < code) { + max = each; + } - if (max.code < JSONResponse.COMPARE_VALUE_CHANGE) { - if (target.length != real.length || (JSON.stringify(target) != JSON.stringify(real))) { - max.code = JSONResponse.COMPARE_VALUE_CHANGE; - max.msg = '值改变'; - max.path = folder; - max.value = real; + if (max.code >= JSONResponse.COMPARE_TYPE_CHANGE) { + break; } } } @@ -318,26 +751,28 @@ var JSONResponse = { var key; for (var i = 0; i < tks.length; i++) { //遍历并递归下一层 key = tks[i]; - if (key == null) { + if (key == null || (exceptKeys != null && exceptKeys.indexOf(key) >= 0)) { continue; } - each = JSONResponse.compareWithBefore(target[key], real[key], JSONResponse.getAbstractPath(folder, key)); - if (max.code < each.code) { + var each = JSONResponse.compareWithBefore(target[key], real[key], JSONResponse.getAbstractPath(folder, key), exceptKeys); + var code = each == null ? 0 : each.code; + + if (max.code < code) { max = each; } + if (max.code >= JSONResponse.COMPARE_TYPE_CHANGE) { break; } } - if (max.code < JSONResponse.COMPARE_KEY_MORE) { //多出key for (var k in real) { - if (k != null && tks.indexOf(k) < 0) { + if (k != null && real[k] != null && target[k] == null) { //解决 null 值总是提示是新增的,且无法纠错 tks.indexOf(k) < 0) { max.code = JSONResponse.COMPARE_KEY_MORE; max.msg = '是新增的'; - max.path = JSONResponse.getAbstractPath(folder, k); + max.path = JSONResponse.getAbstractPath(folder, k); max.value = real[k]; break; } @@ -345,7 +780,7 @@ var JSONResponse = { } } else { // Boolean, Number, String - if (type == 'number') { //数字类型由整数变为小数 + if (max.code < JSONResponse.COMPARE_NUMBER_TYPE_CHANGE && type == 'number') { //数字类型由整数变为小数 if (String(target).indexOf('.') < 0 && String(real).indexOf('.') >= 0) { max.code = JSONResponse.COMPARE_NUMBER_TYPE_CHANGE; max.msg = '整数变小数'; @@ -394,7 +829,7 @@ var JSONResponse = { } if (right instanceof Object) { - var m = JSON.parse(JSON.stringify(left)); + var m = parseJSON(JSON.stringify(left)); for (var k in right) { m[k] = JSONResponse.deepMerge(m[k], right[k]); } @@ -405,14 +840,1408 @@ var JSONResponse = { }, - getAbstractPath: function (folder, name) { + /**测试compare: 对比 新的请求与从历史请求结果提取的校验模型 TODO 新增 exceptions(删除等部分接口只有第一次成功) 和字符串格式 format(DATE, TIME, NUMBER) + 0-相同,无颜色; + 1-新增字段/新增值,绿色; + 2-值改变,蓝色; + 3-缺少字段/整数变小数,黄色; + 4-类型/code 改变,红色; + */ + compareWithStandard: function(target, real, folder, exceptKeys, ignoreTrend, callback) { folder = folder == null ? '' : folder; - name = name == null ? '' : name; //导致 0 变为 '' name = name || ''; - return StringUtil.isEmpty(folder, true) ? name : folder + '/' + name; - }, - log(msg) { - // console.log(msg); - } + if (target == null) { + return { + code: real == null ? JSONResponse.COMPARE_EQUAL : JSONResponse.COMPARE_KEY_MORE, + msg: real == null ? '结果正确' : '是新增的', + path: real == null ? '' : folder, + value: real + }; + } + if (target instanceof Array) { // JSONArray + throw new Error('Standard 在 ' + folder + ' 语法错误,不应该有 array!'); + } + + log('\n\n\n\n\ncompareWithStandard <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<\n' + + ' \ntarget = ' + JSON.stringify(target, null, ' ') + '\n\n\nreal = ' + JSON.stringify(real, null, ' ')); + + var guess = target.guess; + log('compareWithStandard guess = target.guess = ' + guess + ' >>'); + + var notNull = target.notNull; + log('compareWithStandard notNull = target.notNull = ' + notNull + ' >>'); + + var notEmpty = target.notEmpty; + log('compareWithStandard notEmpty = target.notEmpty = ' + notEmpty + ' >>'); + + var type = target.type; + log('compareWithStandard type = target.type = ' + type + ' >>'); + + var valueLevel = target.valueLevel; + log('compareWithStandard valueLevel = target.valueLevel = ' + valueLevel + ' >>'); + + var values = target.values; + log('compareWithStandard values = target.values = ' + JSON.stringify(values, null, ' ') + ' >>'); + var firstVal = values == null || values.length <= 0 ? null : values[0]; + + if (firstVal == null && (type == 'object' || type == 'array')) { + if (notNull == true) { // values{} values&{} + throw new Error('Standard 在 ' + folder + ' 语法错误,Object 或 Array 在 notNull: true 时 values 必须为有值的数组 !'); + } + + log('compareWithStandard values == null; real ' + (real == null ? '=' : '!') + '= null >> return ' + (real == null ? 'COMPARE_EQUAL' : 'COMPARE_KEY_MORE')); + return { + code: real == null ? JSONResponse.COMPARE_EQUAL : JSONResponse.COMPARE_KEY_MORE, + msg: real == null ? '结果正确' : '是新增的', + path: real == null ? '' : folder, + value: real + }; + } + + if (real == null) { //少了key + log('compareWithStandard real == null >> return ' + (notNull == true ? 'COMPARE_KEY_LESS' : 'COMPARE_EQUAL')); + return { + code: notNull == true ? JSONResponse.COMPARE_KEY_LESS : JSONResponse.COMPARE_EQUAL, + msg: notNull == true ? '是缺少的' : '结果正确', + path: notNull == true ? folder : '', + value: real + }; + } + + if (notEmpty == true && typeof real != 'boolean' && typeof real != 'number' && StringUtil.isEmpty(real, true)) { // 空 + log('compareWithStandard notEmpty == true && StringUtil.isEmpty(real, true) >> return COMPARE_VALUE_EMPTY'); + return { + code: JSONResponse.COMPARE_VALUE_EMPTY, + msg: '是空的', + path: folder, + value: real + }; + } + + var max = { + code: JSONResponse.COMPARE_EQUAL, + msg: '结果正确', + path: '', //导致正确时也显示 folder, + value: null //导致正确时也显示 real + }; + + var realType = JSONResponse.getType(real); + if (type != realType && type != 'undefined' && (type != 'number' || realType != 'integer')) { //类型改变 + log('compareWithStandard type != realType && type != undefined && (type != number || realType != integer) >> return COMPARE_TYPE_CHANGE'); + + max = { + code: JSONResponse.COMPARE_TYPE_CHANGE, + msg: '不是 ' + type + ' 类型', + path: folder, + value: real + }; + + if (guess != true) { + return max; + } + + max.code -= 1; + } + + + var each; + + if (type == 'array') { // JSONArray + log('compareWithStandard type == array >> '); + + for (var i = 0; i < real.length; i ++) { //检查real的每一项 + log('compareWithStandard for i = ' + i + ' >> '); + + each = JSONResponse.compareWithStandard(firstVal, real[i], JSONResponse.getAbstractPath(folder, i), exceptKeys, ignoreTrend); + + if (max.code < each.code) { + max = each; + } + if (max.code >= JSONResponse.COMPARE_TYPE_CHANGE) { + log('compareWithStandard max >= COMPARE_TYPE_CHANGE >> return max = ' + max); + return max; + } + } + + if (max.code < JSONResponse.COMPARE_LENGTH_CHANGE + && JSONResponse.isValueCorrect(target.lengthLevel, target.lengths, real.length) != true) { + var lengths = target.lengths + var maxVal = lengths == null || lengths.length <= 0 ? null : lengths[0]; + var minVal = lengths == null || lengths.length <= 0 ? null : lengths[lengths.length - 1]; + + max.code = JSONResponse.COMPARE_LENGTH_CHANGE; + max.msg = '长度 ' + real.length + ' 超出范围 [' + minVal + ', ' + maxVal + ']'; + max.path = folder; + max.value = real.length; + } + } + else if (type == 'object') { // JSONObject + log('compareWithStandard type == object >> '); + + var tks = values == null ? [] : Object.keys(firstVal); + var tk; + for (var i = 0; i < tks.length; i++) { //遍历并递归下一层 + tk = tks[i]; + if (tk == null || (exceptKeys != null && exceptKeys.indexOf(tk) >= 0)) { + continue; + } + log('compareWithStandard for tk = ' + tk + ' >> '); + + each = JSONResponse.compareWithStandard(firstVal[tk], real[tk], JSONResponse.getAbstractPath(folder, tk), exceptKeys); + if (max.code < each.code) { + max = each; + } + if (max.code >= JSONResponse.COMPARE_TYPE_CHANGE) { + log('compareWithStandard max >= COMPARE_TYPE_CHANGE >> return max = ' + max); + return max; + } + } + + + //不能注释,前面在 JSONResponse.compareWithStandard(firstVal[tk], real[tk] 居然没有判断出来 COMPARE_KEY_MORE + if (max.code < JSONResponse.COMPARE_KEY_MORE) { //多出key + log('compareWithStandard max < COMPARE_KEY_MORE >> '); + + for (var k in real) { + log('compareWithStandard for k = ' + k + ' >> '); + + if (k != null && real[k] != null && (firstVal == null || firstVal[k] == null) + && (exceptKeys == null || exceptKeys.indexOf(tk) >= 0)) { //解决 null 值总是提示是新增的,且无法纠错 tks.indexOf(k) < 0) { + log('compareWithStandard k != null && tks.indexOf(k) < 0 >> max = COMPARE_KEY_MORE;'); + + max.code = JSONResponse.COMPARE_KEY_MORE; + max.msg = '是新增的'; + max.path = JSONResponse.getAbstractPath(folder, k); + max.value = real[k]; + break; + } + } + } + + } + else { // Boolean, Number, String + log('compareWithStandard type == boolean | number | string >> '); + + if (max.code < JSONResponse.COMPARE_TYPE_CHANGE && type == 'string') { + var format = target.format; + if (typeof format == 'string' && FORMAT_PRIORITIES[format] != null) { + var verifier = max.code < JSONResponse.COMPARE_FORMAT_CHANGE && StringUtil.isNotEmpty(format, true) + ? FORMAT_VERIFIERS[format] : null; + if (typeof verifier == 'function' && verifier(real) != true) { + max.code = JSONResponse.COMPARE_FORMAT_CHANGE - (guess != true ? 0 : 1); + max.msg = '不是 ' + format + " 格式!"; + max.path = folder; + max.value = real; + } + } + else if (format instanceof Array == false && format instanceof Object) { + try { + var realObj = parseJSON(real); + var result = JSONResponse.compareWithStandard(format, realObj, folder, exceptKeys, ignoreTrend); + if (guess == true) { + result.code -= 1; + } + + if (result.code > max.code) { + max = result; + } + } catch (e) { + log(e) + } + } + } + + var valueCompare = max.code >= JSONResponse.COMPARE_VALUE_CHANGE + ? 0 : JSONResponse.compareValue(valueLevel, values, real, target.trend, target.repeat); + + if (valueCompare > 0) { + max.code = valueCompare; + max.path = folder; + max.value = real; + + var isNum = CodeUtil.isTypeMatch('number', type) + + if (isNum && valueCompare == JSONResponse.COMPARE_VALUE_REPEAT && (target.repeat == null || target.repeat <= 0) + && values != null && values.indexOf(real) >= 0) { + max.msg = '值与历史值重复:' + real; + } + else if (target.valueLevel != 1 || isNum != true) { + max.msg = '值超出范围'; + } + else if (valueCompare == JSONResponse.COMPARE_VALUE_MORE) { + max.msg = '值是新增的'; + } + else { // 刚上传完是不是不应该对比?还是 ignoreTrend = ">,<,!" 忽略特定的对比?因为很可能是原来的 + var select = ignoreTrend ? null : (target.trend || {}).select; + var maxVal = firstVal; + var minVal = values == null || values.length <= 0 ? null : values[values.length - 1]; + + if (select == '>') { + max.msg = '值违背必增趋势 > ' + maxVal; + } + else if (select == '>=') { + max.msg = '值违背增长趋势 >= ' + maxVal; + } + else if (select == '<') { + max.msg = '值违背必减趋势 < ' + minVal; + } + else if (select == '<=') { + max.msg = '值违背缩减趋势 <= ' + minVal; + } + else { + max.msg = '值超出范围 [' + minVal + ', ' + maxVal + ']'; + } + } + } + + if (max.code < JSONResponse.COMPARE_LENGTH_CHANGE) { + log('compareWithStandard max < COMPARE_LENGTH_CHANGE >> '); + + var realLength = JSONResponse.getLength(real); + log('compareWithStandard realLength = ' + realLength + ' >> '); + + if (realLength != null && JSONResponse.isValueCorrect(target.lengthLevel, target.lengths, realLength) != true) { + var lengths = target.lengths + var maxVal = lengths == null || lengths.length <= 0 ? null : lengths[0]; + var minVal = lengths == null || lengths.length <= 0 ? null : lengths[lengths.length - 1]; + + max.code = JSONResponse.COMPARE_LENGTH_CHANGE; + max.msg = '长度 ' + realLength + ' 超出范围 [' + minVal + ', ' + maxVal + ']'; + max.path = folder; + max.value = realLength; + } + } + } + + log('\ncompareWithStandard >> return max = ' + max + '\n >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> \n\n\n\n\n'); + return max; + }, + + + isValueCorrect: function(level, target, real, trend, repeat) { + return JSONResponse.compareValue(level, target, real, trend, repeat) <= 0; + }, + compareValue: function(level, target, real, trend, repeat) { + log('isValueCorrect \nlevel = ' + level + '; \ntarget = ' + JSON.stringify(target) + + '\nreal = ' + JSON.stringify(real, null, ' ')); + if (target == null || real == null) { + log('isValueCorrect target == null >> return true;'); + return 0; + } + if (level == null) { + level = 0; + } + if (repeat == null) { + repeat = 0; + } + + if (level == 0) { + if (target.indexOf(real) < 0) { // 'key{}': [0, 1] + log('isValueCorrect target.indexOf(real) < 0 >> return false;'); + return JSONResponse.COMPARE_VALUE_CHANGE; + } + + return repeat <= 0 && typeof real == 'number' ? JSONResponse.COMPARE_VALUE_REPEAT : 0; + } + else if (level == 1) { //real <= max; real >= min + if (target.length <= 0) { + log('isValueCorrect target.length <= 0 >> return true;'); + return 0; + } + + var max = target[0] + if (target.length == 1) { + log('isValueCorrect target.length == 1 >> return max == real;'); + return max == real ? 0 : JSONResponse.COMPARE_VALUE_CHANGE; + } + var min = target[target.length - 1] + if (max == null || min == null) { + log('isValueCorrect max == null || min == null 这种情况不该出现!!!updateStandard 不应该把 null 值设置进去! >> return false;'); + alertOfDebug('isValueCorrect max == null || min == null 这种情况不该出现!!!updateStandard 不应该把 null 值设置进去!'); + return JSONResponse.COMPARE_VALUE_CHANGE; + } + + //趋势分析,新值落在五个区域之一的次数,"trend":{ "select": ">", "above": 5, "top":4, "center":3, "bottom":2, "below":1 } + var select = (trend || {}).select + if (select == '>' && max >= real) { // above,例如 自增主键、创建时间 等总是递增 + log('isValueCorrect select == > && max >= real >> return false;'); + return JSONResponse.COMPARE_VALUE_CHANGE; + } + else if (select == '>=' && max > real) { // top or above,例如 统计不删改字段的 总数、总金额 等总是不变或增长 + log('isValueCorrect select == >= && max > real >> return false;'); + return JSONResponse.COMPARE_VALUE_CHANGE; + } + else if (select == '<' && min <= real) { // below,例如 电池寿命 等总是递减 + log('isValueCorrect select == < && min <= real >> return false;'); + return JSONResponse.COMPARE_VALUE_CHANGE; + } + else if (select == '<=' && min < real) { // bottom or below,例如 促销活动倒计时 等总是总是不变或减小 + log('isValueCorrect select == <= && min < real >> return false;'); + return JSONResponse.COMPARE_VALUE_CHANGE; + } + else if (select == null || select == '%') { // center + log('isValueCorrect select == null || select == % >> '); + if (max < real) { + log('isValueCorrect max < real >> return false;'); + return JSONResponse.COMPARE_VALUE_CHANGE; + } + if (min > real) { + log('isValueCorrect min > real >> return false;'); + return JSONResponse.COMPARE_VALUE_CHANGE; + } + + return 0; + } + + return target.indexOf(real) < 0 ? JSONResponse.COMPARE_VALUE_MORE + : (repeat <= 0 && typeof real == 'number' ? JSONResponse.COMPARE_VALUE_REPEAT : 0); // 为了提示上传新值,方便以后校验 + } + else if (level == 2) { + for (var i = 0; i < target.length; i ++) { + + if (eval(real + target[i]) != true) { + log('isValueCorrect eval(' + (real + target[i]) + ') != true >> return false;'); + return JSONResponse.COMPARE_VALUE_CHANGE; + } + } + } + else { + //不限 + } + + log('isValueCorrect >> return true;'); + return 0; + }, + + getType: function(o) { //typeof [] = 'object' + if (o == null) { + return 'object'; // FIXME return null + } + + if (o instanceof Array) { + return 'array'; + } + + if (JSONResponse.isBoolean(o)) { + return 'boolean'; + } + + if (JSONResponse.isInteger(o)) { + return 'integer'; + } + + if (JSONResponse.isNumber(o)) { + return 'number'; + } + + if (JSONResponse.isString(o)) { + return 'string'; + } + + return typeof o; + }, + + isObject: function(o) { + return o instanceof Array == false && o instanceof Object; + }, + + isArray: function(o) { + return o instanceof Array; + }, + isString: function(o) { + return typeof o == 'string' || o instanceof String; + }, + isNumber: function(o) { + return typeof o == 'number' || o instanceof Number; + }, + isInteger: function(o) { + return Number.isInteger(o); + }, + isBoolean: function(o) { + return typeof o == 'boolean' || o instanceof Boolean; + }, + + + updateFullStandard: function (standard, currentResponse, isML, noBizCode) { + if (currentResponse == null) { + return standard; + } + + if (standard == null) { + standard = {}; + } + + var code = currentResponse.code; + var thrw = currentResponse.throw; + var msg = currentResponse.msg; + + var hasCode = standard.code != null; + var isCodeChange = noBizCode != true && standard.code != code; + var exceptions = standard.exceptions || []; + + delete currentResponse.code; //code必须一致 + delete currentResponse.throw; //throw必须一致 + + var find = false; + if (isCodeChange && hasCode) { // 走异常分支 + for (var i = 0; i < exceptions.length; i++) { + var ei = exceptions[i]; + if (ei != null && ei.code == code && ei.throw == thrw) { + find = true; + ei.repeat = (ei.repeat || 0) + 1; // 统计重复出现次数 + break; + } + } + + if (find) { + delete currentResponse.msg; + } + } + + var stddObj = isML ? (isCodeChange && hasCode ? standard : JSONResponse.updateStandard(standard, currentResponse)) : {}; + +// if (noBizCode != true) { + currentResponse.code = code; + currentResponse.throw = thrw; +// } + + if (hasCode || isML) { + stddObj.code = code || 0; + } + + if (isCodeChange) { + if (hasCode != true) { // 走正常分支 + stddObj.code = code; + stddObj.throw = thrw; + } + else { // 走异常分支 + currentResponse.msg = msg; + + if (find != true) { + exceptions.push({ + code: code, + 'throw': thrw, + msg: msg + }) + + stddObj.exceptions = exceptions; + } + } + } + else { + stddObj.repeat = (stddObj.repeat || 0) + 1; // 统计重复出现次数 + } + + return stddObj; + }, + + /**更新测试标准,通过原来的标准与最新的数据合并来实现 + */ + updateStandard: function(target, real, exceptKeys, ignoreTrend, key) { + if (target instanceof Array) { // JSONArray + throw new Error("Standard 语法错误,不应该有 array!"); + } + if (real == null && StringUtil.isEmpty(key, true)) { // 少了key + log('updateStandard real == null && StringUtil.isEmpty(key, true) >> return target;'); + return target; + } + + if (target == null) { + target = {}; + } + + log('\n\n\n\n\nupdateStandard <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<\n' + + ' \ntarget = ' + JSON.stringify(target, null, ' ') + '\n\n\nreal = ' + JSON.stringify(real, null, ' ')); + + var notNull = target.notNull; + log('updateStandard notNull = target.notNull = ' + notNull + ' >>'); + if (notNull == null) { + notNull = target.notNull = real != null; + } + + var notEmpty = target.notEmpty; + log('updateStandard notEmpty = target.notEmpty = ' + notEmpty + ' >>'); + if (real != null && typeof real != 'boolean' && typeof real != 'number') { + notEmpty = target.notEmpty = StringUtil.isNotEmpty(real, true); + } + + var type = target.type; + if (type == null || type == 'undefined') { + target.type = type = null; + } + + var rtype = JSONResponse.getType(real); + if ((rtype == null || real == null) && StringUtil.isEmpty(type, true) && StringUtil.isNotEmpty(key, true)) { + target.guess = true; + if (StringUtil.isBoolKey(key)) { + target.type = 'boolean'; + } + else if (StringUtil.isDateKey(key)) { + target.type = 'integer'; // 'string'; + if (target.format == null) { + target.format = FORMAT_DATE; + } + } + else if (StringUtil.isTimeKey(key)) { + target.type = 'integer'; // 'string'; + if (target.format == null) { + target.format = FORMAT_TIME; + } + } + else if (StringUtil.isUrlKey(key)) { + target.type = 'string'; + if (target.format == null) { + target.format = FORMAT_HTTP; + } + } + else if (StringUtil.isUriKey(key)) { + target.type = 'string'; + if (target.format == null) { + target.format = FORMAT_URI; + } + } + else if (StringUtil.isPathKey(key)) { + target.type = 'string'; + if (target.format == null) { + target.format = FORMAT_PATH; + } + } + else if (StringUtil.isNameKey(key)) { + target.type = 'string'; + if (target.format == null) { + target.format = FORMAT_BIG_NAME; + } + } + else if (StringUtil.isDictKey(key) || StringUtil.isMapKey(key) || StringUtil.isObjKey(key)) { + target.type = 'object'; + } + else if (StringUtil.isCollectionKey(key)) { + target.type = 'array'; + } + else if (StringUtil.isPriceKey(key) || StringUtil.isPercentKey(key) || StringUtil.isAmountKey(key) + || StringUtil.isMoneyKey(key) || StringUtil.isCashKey(key) || StringUtil.isDiscountKey(key) + || StringUtil.isDecimalKey(key) || StringUtil.isFloatKey(key) || StringUtil.isDoubleKey(key) + ) { + target.type = 'number'; + } + else if (StringUtil.isNumKey(key) || StringUtil.isCountKey(key) || StringUtil.isPageKey(key) + || StringUtil.isSizeKey(key) || StringUtil.isCapKey(key) || StringUtil.isIntKey(key) || StringUtil.isLongKey(key) + || StringUtil.isLevelKey(key)|| StringUtil.isGradeKey(key) || StringUtil.isScoreKey(key) || StringUtil.isTotalKey(key) + || StringUtil.isIdKey(key) || StringUtil.isHashKey(key) + ) { + target.type = 'integer'; + } + else if (StringUtil.isStrKey(key)) { + target.type = 'string'; + } + else { + var cm = StringUtil.CATEGORY_MAP; + if (cm == null || Object.keys(cm).length <= 0) { + cm = {} + var tcks = StringUtil.TYPE_CATEGORY_KEYS || {}; + for (var k in tcks) { + var arr = tcks[k] || []; + for (var i = 0; i < arr.length; i++) { + var k2 = arr[i]; + cm[k2] = k; + } + } + + StringUtil.CATEGORY_MAP = cm; + } + + for (var k in cm) { + if (StringUtil.isKeyOfCategory(key, k)) { + target.type = cm[k]; + break; + } + } + } + + } + else { + target.guess = rtype == null || real == null ? (ignoreTrend ? target.guess : false) : undefined; + } + + if (real == null) { // 少了key + log('updateStandard real == null >> return target;'); + return target; + } + + log('updateStandard type = target.type = ' + type + ' >>'); + if (type == null || CodeUtil.isTypeMatch(target.type, rtype) != true) { //强制用real的类型替代 + type = target.type = rtype; + } + log('updateStandard type = target.type = getType(real) = ' + type + ' >>'); + + + var lengthLevel = target.lengthLevel; + var lengths = target.lengths; + log('updateStandard lengthLevel = target.lengthLevel = ' + lengthLevel + ' >>'); + log('updateStandard lengths = target.lengths = ' + lengths + ' >>'); + + + var valueLevel = target.valueLevel; + var values = target.values; + log('updateStandard valueLevel = target.valueLevel = ' + valueLevel + ' >>'); + log('updateStandard values = target.values = ' + JSON.stringify(values, null, ' ') + ' >>'); + + if (valueLevel == null) { + log('updateStandard valueLevel == null >> valueLevel = target.valueLevel = 0;'); + valueLevel = target.valueLevel = 0; + } + + + if (type == 'array') { + log('updateStandard type == array >> '); + + if (values == null) { + values = []; + } + if (values[0] == null) { + values[0] = {}; + } + + var child = values[0]; + for (var i = 0; i < real.length; i ++) { + log('updateStandard for i = ' + i + '; child = ' + + JSON.stringify(child, null, ' ') + ';\n real[i] = ' + JSON.stringify(real[i], null, ' ') + ' >>'); + + child = JSONResponse.updateStandard(child, real[i], exceptKeys, true, key == null ? 'item' : key + 'Item'); //FIXME ignoreTrend 固定取 true 导致批量创建后多个 id [1,2,3] -> [3,4,5] 漏报趋势异常 + } + if (child == null) { + log('updateStandard child == null >> child = {}'); + child = {} //啥都确定不了,level为null默认用0替代 + } + + values = [child]; + target = JSONResponse.setValue(target, real.length, lengthLevel == null ? 1 : lengthLevel, lengths, true, true); + target = JSONResponse.setValue(target, null, valueLevel, values, false, ignoreTrend); + } + else if (type == 'object') { + log('updateStandard type == object >> '); + + target.valueLevel = valueLevel; + + + if (values == null) { + values = []; + } + + var firstVal = values[0]; + if (firstVal == null) { + values[0] = firstVal = {}; + } + + var realKeys = Object.keys(real) || []; + for(var k2 in firstVal) { //解决real不含k2时导致notnull不能变成false + // log('updateStandard for k2 in firstVal = ' + k2 + ' >>'); + if (realKeys.indexOf(k2) < 0) { + // log('updateStandard Object.keys(real).indexOf(k2) < 0 >> real[k2] = null;'); + // 解决总是报错缺少字段 delete real[k2]; // 解决总是多出来 key: null real[k2] = null; + + if (firstVal[k2] == null) { + firstVal[k2] = { notNull: false }; + } + else { + firstVal[k2].notNull = false; + } + } + } + + for(var k in real) { + if (k == null || (exceptKeys != null && exceptKeys.indexOf(k) >= 0)) { + continue + } + + log('updateStandard for k in real = ' + k + '; firstVal[k] = ' + + JSON.stringify(firstVal[k], null, ' ') + ';\n real[k] = ' + JSON.stringify(real[k], null, ' ') + ' >>'); + firstVal[k] = JSONResponse.updateStandard(firstVal[k], real[k], exceptKeys, ignoreTrend, k); + } + + target.values = values; + } + else { + log('updateStandard type == other >> '); + if (type == 'string') { + var format = target.format; + try { + var priority = format == null ? null : FORMAT_PRIORITIES[format] + if (priority == null) { + priority = Number.MAX_SAFE_INTEGER; + } + + var format2 = null; + var priorities = FORMAT_PRIORITIES; + var verifiers = FORMAT_VERIFIERS; + for (var fmt in verifiers) { + try { + var verifier = verifiers[fmt]; + var pty = priorities[fmt]; + if (pty == null) { + pty = Number.MAX_SAFE_INTEGER - 1; + } + + if (pty < priority && typeof verifier == 'function' && ( + priorities[format2] == null || pty > priorities[format2]) + ) { + if (verifier(real)) { + target.format = format2 = fmt; + } + } + } + catch (e) { + log(e) + } + } + + if (priority > 0 && format2 == null) { + throw new Error("try other format"); + } + } catch (e) { + log(e) + try { + var realObj = parseJSON(real); + var format2 = JSONResponse.updateStandard(target.format, realObj, exceptKeys, ignoreTrend, key); + if (format2 != null) { + target.format = format2; + } + } catch (e2) { + log(e2) + target.format = '' + } + } + } + + if (values == null) { + values = []; + } + if (valueLevel < 1 && type == 'number' && ! Number.isSafeInteger(real)) { //double 1.23 + valueLevel = 1; + } + target.values = values; + + target = JSONResponse.setValue(target, JSONResponse.getLength(real), lengthLevel == null ? 1 : lengthLevel, lengths, true, true); + target = JSONResponse.setValue(target, real, valueLevel, values, false, ignoreTrend); + } + + log('\nupdateStandard >> return target = ' + JSON.stringify(target, null, ' ') + '\n >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> \n\n\n\n\n'); + + return target; + }, + + /**根据 APIJSON 引用赋值路径精准地获取值 + */ + getValByPath: function(target, pathKeys, isTry) { + if (target == null) { + return null; + } + + var tgt = target; + var depth = pathKeys == null ? 0 : pathKeys.length + if (depth <= 0) { + return target; + } + + for (var i = 0; i < depth; i ++) { + if (tgt == null) { + return null; + } + + var k = pathKeys[i]; + if (k == null) { + return null; + } + k = decodeURI(k) + + if (tgt instanceof Object) { + if (k == '') { + if (tgt instanceof Array) { + k = 0; + } else { + ks = Object.keys(tgt); + k = ks == null ? null : ks[0]; + if (k == null) { + return null; + } + } + } + else { + k = decodeURI(k) + if (tgt instanceof Array) { + try { + var n = Number.parseInt(k); + if (Number.isSafeInteger(n)) { + k = n >= 0 ? n : n + tgt.length; + } + } catch (e) { + } + } + } + + tgt = tgt[k]; + + continue; + } + + if (isTry != true) { + throw new Error('getValByPath 语法错误,' + k + ': value 中 value 类型应该是 Object 或 Array !'); + } + + return null; + } + + return tgt; + }, + + /**根据 APIJSON 引用赋值路径精准地修改值 + */ + setValByPath: function(target, pathKeys, val, isTry) { + var depth = pathKeys == null ? 0 : pathKeys.length + if (depth <= 0) { + return target; + } + + var tgt = target; + var parent = target; + for (var i = 0; i < depth - 1; i ++) { + var k = pathKeys[i]; + if (k == null) { + return null; + } + k = decodeURI(k); + + if (tgt instanceof Object) { + if (k == '') { + if (tgt instanceof Array) { + k = 0; + } else { + ks = Object.keys(tgt); + k = ks == null ? null : ks[0]; + if (k == null) { + return null; + } + } + } + else { + if (tgt instanceof Array) { + try { + var n = Number.parseInt(k); + if (Number.isSafeInteger(n)) { + k = n >= 0 ? n : n + tgt.length; + } + } catch (e) { + } + } + } + + parent = tgt; + tgt = tgt[k]; + continue; + } + + if (tgt == null) { + try { + var n = Number.parseInt(k); + if (Number.isSafeInteger(n)) { + k = n >= 0 ? n : n + tgt.length; + } + } catch (e) { + } + + tgt = Number.isInteger(k) ? [] : {}; + if (i == 0 && parent == null) { + parent = target = tgt; + } else { + parent[k] = tgt; + } + + parent = tgt; + tgt = tgt[k]; + + continue; + } + + if (isTry != true) { + throw new Error('setValByPath 语法错误,' + k + ': value 中 value 类型应该是 Object 或 Array !'); + } + + return null; + } + + tgt[pathKeys[depth - 1]] = val; + + return target; + }, + + /**根据路径精准地更新测试标准中的键值对 + */ + getStandardByPath: function(target, pathKeys) { + if (target instanceof Array) { // JSONArray + var path = pathKeys == null ? null : path.join('/') + throw new Error('Standard 语法错误,' + path + ': value 中 value 类型不应该是 Array!'); + } + if (target == null) { + return null; + } + + var tgt = target; + var depth = pathKeys == null ? 0 : pathKeys.length + if (depth <= 0) { + return target; + } + + for (var i = 0; i < depth; i ++) { + if (tgt == null) { + return null; + } + + var k = pathKeys[i]; + if (k == null) { + return null; + } + + if (k == '') { + k = 0; + } + else { + k = decodeURI(k) + if (tgt instanceof Array) { + try { + var n = Number.parseInt(k); + if (Number.isSafeInteger(n)) { + k = n > 0 ? n : n + tgt.length; + } + } catch (e) { + } + } + } + + if (tgt instanceof Array == false && tgt instanceof Object) { + if (tgt.values == null) { + return null; + } + + var child = tgt.values[0]; + if (child == null) { + return null; + } + + tgt = child[k]; + } + else { + throw new Error('Standard 语法错误,' + k + ': value 中 value 类型应该是 Object !'); + } + } + + return tgt; + }, + + + /**根据路径精准地更新测试标准中的键值对 + */ + updateStandardByPath: function(target, names, key, real, comment) { + if (target instanceof Array) { // JSONArray + throw new Error('Standard 语法错误,' + key + ': value 中 value 类型不应该是 Array!'); + } + if (target == null) { + target = {}; + } + + var tgt = target; + var depth = names == null ? 0 : names.length + if (depth <= 1 && (key == null || key == '')) { + return target; + } + + for (var i = 1; i < depth + 1; i ++) { + var k = i >= depth ? key : names[i]; + if (k == null) { + return target; + } + + if (k == '') { + k = 0; + } + else { + try { + var n = Number.parseInt(k); + if (Number.isSafeInteger(n)) { + k = 0; + } + } catch (e) { + } + } + + if (tgt instanceof Array == false && tgt instanceof Object) { + if (tgt.values == null) { + tgt.values = []; + } + + var child = tgt.values[0]; + if (child == null) { + child = {}; + child.type = typeof k == 'number' ? 'array' : 'number'; // TODO 没看懂为啥是 array + child.notNull = false; + tgt.values[0] = child; + } + + if (child[k] == null) { + child[k] = {}; + } + + tgt = child[k]; + } + else { + throw new Error('Standard 语法错误,' + k + ': value 中 value 类型应该是 Object !'); + } + } + + comment = comment == null ? '' : comment.trim(); + + if (tgt == null) { + tgt = {}; + } + var ind = comment.indexOf(', ') + if (ind < 0) { + ind = comment.indexOf(',') + } + + var prefix = ind <= 0 ? '' : comment.substring(0, ind).trim() + comment = ind < 0 ? comment : comment.substring(ind + 1) + var nullable = prefix.endsWith('?') + var notEmpty = prefix.endsWith('!') + var name = nullable || notEmpty ? prefix.substring(0, prefix.length - 1) : prefix + if (StringUtil.isName(name)) { + tgt.name = name + } else { + nullable = notEmpty = null + } + + tgt.type = JSONResponse.getType(real) + tgt.notNull = notEmpty == true || nullable == false || (nullable == null && real != null) + tgt.notEmpty = notEmpty == true || (notEmpty == null && StringUtil.isNotEmpty(real)) + tgt.comment = comment + + return target; + }, + + + getLength: function(value) { + var type = JSONResponse.getType(value); + if (type == 'array') { + log('getLength type == array >> return value.length = ' + value.length); + return value.length; + } + if (type == 'object') { + log('getLength type == object >> return null;'); + return null; + } + + if (CodeUtil.isTypeMatch('number', type)) { + log('getLength CodeUtil.isTypeMatch(number, type) >> '); + + var rs = String(value); + + //Double 比较整数长度 + var index = rs.indexOf("."); + if (index >= 0) { + rs = rs.substring(0, index); + } + + log('getLength >> return rs.length = ' + rs.length); + return rs.length + } + + if (type == 'string') { + log('getLength type == string >> return value.length = ' + value.length); + return value.length + } + + //Boolean 不需要比较长度 + log('getLength type == other >> return null;'); + return null; + }, + + /** + * @param target + * @param value + * @param level 0 - [] , 1 - min-max, 2 - "conditions", 3 - 任何值都行 + * @param origin + * @param isLength + * @param ignoreTrend 解决在一次结果中多个值会自己与自己比较分析趋势,导致一直误报违背趋势 + * @return {*} + */ + setValue: function(target, real, level, origin, isLength, ignoreTrend) { + log('setValue level = ' + level + '; isLength = ' + isLength + + ' ;\n target = ' + JSON.stringify(target, null, ' ') + + ' ;\n real = ' + JSON.stringify(real, null, ' ') + + ' ;\n origin = ' + JSON.stringify(origin, null, ' ') + + ' >> '); + + if (target == null) { + target = {}; + } + + // 导致出错,必须设置里面的 leval, values 等字段 + // if (real == null) { + // return target; + // } + + var type = target.type; + log('setValue type = target.type = ' + type + ' >> '); + + if (level == null) { + level = 0; + } + + // 似乎无论怎样都要把 real 加进 values if (isLength || (type != 'object' || type != 'array')) { + + var levelName = isLength != true ? 'valueLevel' : 'lengthLevel'; + target[levelName] = level; + if (level >= 3) { //无限 + return target; + } + + //String 类型在 长度超过一定值 或 不是 常量名 时,改成 无限模型 + //不用 type 判断类型,这样可以保证 lengthType 不会自动升级 + if (isLength != true && typeof real == 'string' && (real.length > 20 || StringUtil.isConstName(real) != true)) { + if (level != 2) { //自定义模型不受影响 + target[levelName] = 3; + } + return target; + } + + var vals = []; + + if (level == 0 || level == 1) { + if (origin == null) { + origin = []; + } + + if (real != null) { + //趋势分析,新值落在五个区域之一的次数,"trend":{ "select": ">", "above": 5, "top":4, "center":3, "bottom":2, "below":1 } + if (ignoreTrend != true && isLength != true && origin.length > 0 && CodeUtil.isTypeMatch('number', type)) { + log('setValue isLength != true && origin.length > 0 >> '); + + var trend = target.trend || {}; + var select = trend.select; + + log('setValue trend.select = ' + select + ' >> '); + if (trend.select != '%') { // 不再统计,可以保证容易调整判断逻辑 + log('setValue trend.select != % >> '); + + var above = trend.above || 0; + var top = trend.top || 0; + var center = trend.center || 0; + var bottom = trend.bottom || 0; + var below = trend.below || 0; + + log('setValue BEFORE select: ' + select + ', above: ' + above + ', top: ' + top + ', center: ' + center + ', bottom: ' + bottom + ', below: ' + below + ' >>'); + + if (real > origin[0]) { + trend.above = above = above + 1; + } + else if (real == origin[0]) { + trend.top = top = top + 1; + } + else if (real == origin[origin.length - 1]) { + trend.bottom = bottom = bottom + 1; + } + else if (real < origin[origin.length - 1]) { + trend.below = below = below + 1; + } + else { + trend.center = center = center + 1; + } + + // = null 还有在下面判断,否则会把 trend.select 从非 null 值又设置回 null。 var select = null; + if (center > 0) { + select = '%'; // level: 1 时永远是 % 兜底 above + below <= 0 ? '%' : '~'; + } + else if (bottom + below <= 0) { + if (trend.above > 0) { + select = trend.top > 0 ? '>=' : '>'; + } + } + else if (top + above <= 0) { + if (trend.below > 0) { + select = trend.bottom > 0 ? '<=' : '<'; + } + } + + if (trend.select == null || trend.select == select) { + // || (trend.select == '<' && select == '<=') + // || (trend.select == '>' && select == '>=')) { + log('setValue trend.select == null || trend.select == select >> trend.select = select;'); + trend.select = select; + } + else if (trend.select == '<') { // 已简化 + log('setValue trend.select == < >> trend.select = select == <= ? select : %;'); + trend.select = select == '<=' ? select : '%'; + } + else if (trend.select == '>') { // 已简化 + log('setValue trend.select == > >> trend.select = select == >= ? select : %;'); + trend.select = select == '>=' ? select : '%'; + } + else { // 其它情况都未被了 递增或递减 的趋势,只能用 [min, max] 这种包括在内的区间范围 + log('setValue else >> trend.select = %;'); + trend.select = '%'; + } + + log('setValue AFTER select: ' + trend.select + ', above: ' + trend.above + ', top: ' + trend.top + + ', center: ' + trend.center + ', bottom: ' + trend.bottom + ', below: ' + trend.below + ' >>'); + target.trend = trend; + } + + } + + if (origin.indexOf(real) < 0) { + origin.push(real); + } + else { + var repeat = target.repeat == null ? 0 : target.repeat + target.repeat = repeat + 1 + } + } + + vals = origin; + } + else { + if (real != null) { + vals.push(real); + } + } + + if (vals.length > 1 && (isLength || CodeUtil.isTypeMatch('number', type))) { + vals = vals.sort(function (x, y) { //倒序排列,一般都是用最大长度(数据count,字符串长度等) + if (x < y) { + return 1; + } + if (x > y) { + return -1; + } + return 0; + }) + } + + var name = isLength != true ? 'values' : 'lengths'; + log('setValue name = ' + name + '; vals = ' + JSON.stringify(vals, null, ' ') + ' >> '); + + switch (level) { + case 0: + case 1: + var realIsNum = CodeUtil.isTypeMatch('number', typeof real) + //当 离散区间模型 可取值超过最大数量时自动转为 连续区间模型 + var maxCount = isLength ? 3 : JSONResponse.getMaxValueCount(type); + var extraCount = maxCount <= 0 ? 0 : vals.length - maxCount; + if (extraCount > 0 && level < 1) { + if (realIsNum != true) { // 只有数字才可能有连续区间模型 + target[levelName] = 3; + return target; + } + + target[levelName] = 1; // 只有数字才可能有连续区间模型 + } + else if (level < 1 && realIsNum && (real < -10 || real > 10000 || Number.isSafeInteger(real) != true)) { + target[levelName] = 1; // 超出了正常的枚举值范围 + } + + //从中间删除多余的值 + while (extraCount > 0) { + vals.splice(Math.ceil(vals.length/2), 1); + extraCount -= 1; + } + target[name] = vals; + break; + case 2: //自定义的复杂条件,一般是准确的,不会纠错 + // target[name] = (StringUtil.isEmpty(origin, true) ? '' : origin + ',') + // + ('<=' + vals[0] + (vals.length <= 1 ? '' : ',>=' + vals[vals.length - 1])); + break; + } + // } + + return target; + }, + + getMaxValueCount: function(type) { + switch (type) { + case 'boolean': + return 2; + case 'number': + return 10; + case 'string': + return 10; + } + + return 0; + }, + + + getAbstractPath: function (folder, name, divider) { + folder = folder == null ? '' : folder; + name = name == null ? '' : name; //导致 0 变为 '' name = name || ''; + divider = divider == null ? '/' : divider; + return StringUtil.isEmpty(folder, true) ? name : folder + divider + name; + }, + + getShowString: function(arr, lineItemCount) { + if (arr == null || arr.length <= 0) { + return ''; + } + if (lineItemCount == null || lineItemCount <= 0) { + return arr.join(); + } + + var s2 = ''; + for (var i = 0; i < arr.length; i += lineItemCount) { + var lineArr = arr.slice(i, i < arr.length - lineItemCount ? (i + lineItemCount) : arr.length); + s2 += (i > 0 ? '
' : '') + lineArr.join(); + } + + return s2; + }, + + /** [1, true, 'a', {}, []] => { '0': 1, '1': true, '2': 'a', '3': {}, '4': [] } + * @param value + * @param name + * @param onlyKeys + * @param containChild + * @return {*} + */ + array2object: function (value, name, onlyKeys, containChild) { + if (value instanceof Array) { + if (onlyKeys == null || onlyKeys.indexOf(name) >= 0) { + var obj = {} + for (var i = 0; i < value.length; i++) { + obj[String(i)] = containChild ? JSONResponse.array2object(value[i], i, onlyKeys, containChild) : value[i]; + } + return obj; + } + + for (var i = 0; i < value.length; i++) { + value[i] = JSONResponse.array2object(value[i], i, onlyKeys, containChild); + } + } + else if (value instanceof Object) { + for (var k in value) { + if (k != null) { + var v = value[k] + + if (containChild != true && (v instanceof Object == false || (onlyKeys != null && onlyKeys.indexOf(name) < 0))) { + continue + } + + value[k] = JSONResponse.array2object(v, k, onlyKeys, containChild); + } + } + } + + return value; + } + +}; -} \ No newline at end of file +if (typeof module == 'object') { + module.exports = JSONResponse; +} diff --git a/apijson/LICENSE b/apijson/LICENSE index ca41bcb..04d2bc8 100644 --- a/apijson/LICENSE +++ b/apijson/LICENSE @@ -186,7 +186,7 @@ same "printed page" as the copyright notice for easier identification within third-party archives. - Copyright @2017 TommyLemon(https://github.com/TommyLemon/APIJSONAuto) + Copyright @2017 TommyLemon(https://github.com/TommyLemon/APIAuto) Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/apijson/README.md b/apijson/README.md new file mode 100755 index 0000000..50f35dc --- /dev/null +++ b/apijson/README.md @@ -0,0 +1,87 @@ +## 1.自动生成代码 +https://github.com/TommyLemon/APIAuto/blob/master/apijson/CodeUtil.js + +![](https://github.com/TommyLemon/StaticResources/blob/master/APIAuto/APIAuto_generate_code_4_request_json.jpg?raw=true) +![](https://github.com/TommyLemon/StaticResources/blob/master/APIAuto/APIAuto_generate_code_4_entity.jpg?raw=true) +![](https://github.com/TommyLemon/StaticResources/blob/master/APIAuto/APIAuto_generate_code_4_response_json.jpg?raw=true) +Java, Kotlin, Swift, C#, PHP, Go, JavaScript, TypeScript, Python
+模型类 Entity、封装请求 JSON、解析结果 JSON 等代码。
+
+ +## 2.自动生成注释 +https://github.com/TommyLemon/APIAuto/blob/master/apijson/CodeUtil.js + +![](https://github.com/TommyLemon/StaticResources/blob/master/APIAuto/APIAuto_generate_comment_4_request_and_response.jpg?raw=true) +parseComment, getComment4Request, getCommentFromDoc
+* 对左侧请求 JSON 自动在每行右边生成字段的类型、长度、描述等 +* 对右侧结果 JSON 自动在光标移到字段时显示类型、长度、描述等 +
+ +## 3.自动静态检查 +https://github.com/TommyLemon/APIAuto/blob/master/apijson/CodeUtil.js + +![](https://github.com/TommyLemon/StaticResources/blob/master/APIAuto/APIAuto_static_checking.jpg?raw=true) +parseComment
+* 自动检查请求 JSON 是否符合 JSON 的格式 +* 自动检查表对象里的字段是否在表里真实存在 +* 自动检查 APIJSON 关键词对应的值是否合法 +
+ +## 4.自动化接口测试 +https://github.com/TommyLemon/APIAuto/blob/master/apijson/JSONResponse.js + +#### 前后对比测试 compareWithBefore +![](https://github.com/TommyLemon/StaticResources/blob/master/APIAuto/APIAuto_test_compare_with_before.jpg?raw=true) +不用写任何代码,只需要点一下 回测测试的图标按钮 (左区域右上角,类似刷新的图标),
+就会自动测试所有测试用例(除了登录和退出登录),并对比每一个测试用例前后两次请求的结果,
+然后给出结论:结果正确、新增字段、缺少字段、值改变、值类型改变、状态码改变等。
+每一个测试用例测完后都会有 左侧按钮用于显示测试结果和切换前后的请求结果,
+右侧按钮用于 纠错,中间的是下载按钮用于下载两次的请求,背景色用于标记接口变更的严重程度。
+如果这次的结果是对的,可以点击 [对的,纠错] 按钮来上传新的正确结果作为后续的对比标准。 + +#### 机器学习测试 compareWithStandard, updateStandard +![](https://github.com/TommyLemon/StaticResources/blob/master/APIAuto/APIAuto_test_machine_learning.jpg?raw=true) +![](https://github.com/TommyLemon/StaticResources/blob/master/APIAuto/APIAuto_machine_learning_design.jpg?raw=true) +在 前后对比测试 的基础上,通过 简单统计 + 场景优化 来提取返回结果 Response JSON 的校验模型,
+包括每一层的所有键值对的名称、类型、长度、取值范围等,它还能精准定位到数组内的数据,
+例如 []/7/Comment/id,原来的 前后对比测试 只能到 []。
+在第一次会生成校验模型,这时就已经比前后对比测试有约 20% 的准确度提升,
+随着纠错次数增加,模型会更新地越来越精准,一般一个测试用例达到 12 次后,
+就会相当于高级测试工程师对每个接口根据具体的业务需求来编写测试代码所能达到的效果。 + +开启和使用机器测试:
+1.点击右区域 第 3 个图标按钮(点击查看共享),会进入测试用例界面;
+2.点击 切换机器学习的按钮(机器学习:已关闭),会开启机器学习;
+3.点击左区域 最右侧的图标按钮(回归测试)。 + +#### 总结 +以前编写测试代码来实现自动化测试,解决手动测试的繁琐、无聊、易出错;
+APIAuto 的自动化接口回归测试连代码都不用写了,点点按钮就能完成整个自动化测试过程。
+不仅能节约大量的测试代码开发成本,省去接口测试与接口开发人员的沟通时间,避免各种原来导致的误会、争吵等;
+还能通过每次改动代码后跑一遍测试,及时且提前(在同事、领导、用户发现前)发现bug,
+减少后续发现甚至在线上发生问题导致大量损失的风险。 + +![](https://github.com/TommyLemon/StaticResources/blob/master/APIAuto/APIJSONAuto_Enterprise_Git_Commit_About_Mathine_Learning.jpg?raw=true) +![](https://github.com/TommyLemon/StaticResources/blob/master/APIAuto/APIJSON_Server_Enterprise_Git_Commit_About_Machine_Learning.jpg?raw=true) + +之前 机器学习测试 是作为一个付费功能在 APIAuto-自动化接口管理平台 上使用,
+从 2018年11月6日 开始上线,一年时间才两个付费用户充了几百元。
+现在也不像以前那样时间相对比较充裕了,我已将全部相关代码免费开源。
+原来是作为私有仓库托管在码云 Gitee 上
+https://gitee.com/TommyLemon/APIJSONAuto-Enterprise
+现在这个仓库也公开了,单独维护了两年,和开源版本的 APIAuto(原名 APIJSONAuto) 至少有大半年没同步了,
+花了几小时把机器学习相关代码提取出来,整合到开源的 APIAuto 里面的 JSONResponse.js 了。
+
+纯手写算法,没有用任何第三方库。 + +#### 目前仍然存在一些待优化的点: +1.每次只会显示最严重、最靠前的的一个 键值对/数组元素值,更新模型却会把所有 键值对/数组元素值 认为是正确的而一起更新
+解决方案:
+牺牲一些性能,把所有有问题的 键值对/数组元素值 全都用一个数组记录,每次递归都传递这个值。
+为了让 UI 显示比较简洁,可以只显示最严重的 键值对/数组元素值,下载的测试标准文本里显示所有有问题的 键值对/数组元素值。
+
+ +2.一些接口有流程上的先后顺序,需要先调用某个,再取出值作为参数调用另一个
+解决方案:
+UI 上支持添加工作流,设置好顺序以及每个接口按路径取出对应的值,再按路径替换下一个接口请求 JSON 里的值,然后一键顺序调用。
+
diff --git a/apijson/StringUtil.js b/apijson/StringUtil.js index 17cf6c8..dfe8f0d 100644 --- a/apijson/StringUtil.js +++ b/apijson/StringUtil.js @@ -1,4 +1,4 @@ -/*Copyright ©2017 TommyLemon(https://github.com/TommyLemon/APIJSONAuto) +/*Copyright ©2017 TommyLemon(https://github.com/TommyLemon/APIAuto) Licensed under the Apache License, Version 2.0 (the "License"); you may not use StringUtil file except in compliance with the License. @@ -24,7 +24,7 @@ var StringUtil = { * @return */ get: function(s) { - return s == null ? '' : s; + return s == null ? '' : (JSONResponse.isString(s) ? s : JSON.stringify(s)); }, /**获取去掉前后空格后的string,为null则返回'' @@ -40,7 +40,7 @@ var StringUtil = { * @return */ noBlank: function(s) { - return StringUtil.get(s).replace(/ /g, ''); + return s == null ? '' : s.replace(/ /g, ''); }, /**判断字符是否为空 @@ -52,14 +52,27 @@ var StringUtil = { if (s == null) { return true; } + if (s instanceof Array) { + return s.length <= 0; + } + if (s instanceof Object) { + return Object.keys(s).length <= 0; + } + if (typeof s == 'boolean') { + return s != true; + } + if (typeof s == 'number') { + return s <= 0; + } + if (trim) { s = s.trim(); } - if (s == '') { - return true; - } + return s.length <= 0; + }, - return false; + isNotEmpty: function(s, trim) { + return ! this.isEmpty(s, trim); }, /**判断是否为代码名称,只能包含字母,数字或下划线 @@ -67,7 +80,27 @@ var StringUtil = { * @return */ isName(s) { - return s != null && /^[0-9a-zA-Z_]+$/.test(s); + return s != null && s.length > 0 && /[a-zA-Z_]/.test(s.substring(0, 1)) && /^[0-9a-zA-Z_]+$/.test(s); + }, + + /**判断是否为代码名称,只能包含字母,数字或下划线 + * @param s + * @return + */ + isBigName(s) { + return s != null && s.length > 0 && /[A-Z]/.test(s.substring(0, 1)) && /^[0-9a-zA-Z_]+$/.test(s); + }, + + /**判断是否为代码名称,只能包含字母,数字或下划线 + * @param s + * @return + */ + isSmallName(s) { + return s != null && s.length > 0 && /[a-z]/.test(s.substring(0, 1)) && /^[0-9a-zA-Z_]+$/.test(s); + }, + + isConstName(s) { + return s != null && s.length > 0 && /[A-Z_]/.test(s.substring(0, 1)) && /^[0-9A-Z_]+$/.test(s); }, @@ -85,6 +118,7 @@ var StringUtil = { }, /**首字母大写或小写 + * TODO upper == null 时不处理,false 小写,true 大写 * @param key * @param upper * @return @@ -119,22 +153,464 @@ var StringUtil = { return s.toLowerCase(); }, - split: function (s, separator) { - if (s == null) { + split: function (s, separator, trim) { + if (StringUtil.isEmpty(s, trim)) { return null; } + if (trim) { + s = s.trim(); + } + if (separator == null) { separator = ','; } + if (trim) { + while (s.startsWith(separator)) { + s = s.substring(1); + } + while (s.endsWith(separator)) { + s = s.substring(0, s.length - 1); + } + } + if (s.indexOf(separator) < 0) { return [s]; } - return s.split(separator) + return s.split(separator); + }, + + isNumber: function (s) { + return typeof s == 'string' && /^[0-9]+$/.test(s); + }, + + join: function (arr, separator) { + return arr == null ? '' : arr.join(separator); + }, + length: function (s) { + return s == null ? 0 : s.length; + }, + limitLength: function (s, maxLen, ellipsize) { + var l = StringUtil.length(s); + if (maxLen == null || maxLen <= 0 || l <= maxLen) { + return s; + } + if (ellipsize == 'start') { + return '..' + s.substring(l - maxLen); + } + if (ellipsize == 'middle') { + var m = Math.floor(maxLen/2); + return s.substring(0, m) + '..' + s.substring(l - m); + } + return s.substring(0, maxLen) + '..'; + }, + + isUri: function (s) { + var ind = s == null ? -1 : s.indexOf('://'); + return ind > 0; + }, + isUrl: function (s, schemas) { + var ind = s == null ? -1 : s.indexOf('://'); + if (ind <= 0) { + return false; + } + var schema = s.substring(0, ind); + if (schemas != null && schemas.indexOf(schema) < 0) { + return false; + } + + var rest = s.substring(ind + 3); + var ind2 = rest.indexOf('/'); + ind2 = ind2 >= 0 ? ind2 : rest.indexOf('?'); + var host = ind2 < 0 ? rest : rest.substring(0, ind2); + var arr = StringUtil.split(host, '.'); + if (arr == null || arr.length <= 1) { + return false; + } + + for (var i = 0; i < arr.length; i ++) { + if (StringUtil.isName(arr[i]) != true) { + return false; + } + } + + return true; + }, + + isHttpUrl: function (s) { + return StringUtil.isUrl(s, ['http', 'https']); + }, + isRpcUrl: function (s) { + return StringUtil.isUrl(s) && ! this.isHttpUrl(s); + }, + isFileUrl: function (s) { + return StringUtil.isUrl(s, ['file']); + }, + isPath: function (s) { + var arr = StringUtil.split(s, '/'); + if (arr == null || arr.length <= 1) { + return false; + } + + for (var i = 0; i < arr.length; i ++) { + if (StringUtil.isName(arr[i]) != true) { + return false; + } + } + + return true; + }, + isPackage: function (s) { + var arr = StringUtil.split(s, '.'); + if (arr == null || arr.length <= 1) { + return false; + } + + for (var i = 0; i < arr.length; i ++) { + if (StringUtil.isName(arr[i]) != true) { + return false; + } + } + + return true; + }, + isMonth: function (s) { + try { + if (/\d{4}-\d{1,2}/g.test(s) != true) { + return false; + } + + var date = new Date(s); + return date.getMonth() > 0 && date.getDay() == 0 && date.getHours() == 0 + && date.getMinutes() == 0 && date.getSeconds() == 0 && date.getMilliseconds() == 0; + } + catch (e) { + log(e) + } + return false; + }, + isDate: function (s) { + try { + if (/\d{4}-\d{1,2}-\d{1,2}/g.test(s) != true) { + return false; + } + + var arr = StringUtil.split(s, '-'); + if (arr == null || arr.length != 3) { + return false; + } + + for (var i = 0; i < arr.length; i ++) { + if ([null, 0].indexOf(Number.parseInt(arr[i])) >= 0) { + return false; + } + } + + var date = new Date(s); + return date.getDay() > 0 && date.getHours() == 0 + && date.getMinutes() == 0 && date.getSeconds() == 0 && date.getMilliseconds() == 0; + } + catch (e) { + log(e) + } + return false; + }, + isMinute: function (s) { + try { + if (/\d{1,2}:\d{1,2}/g.test(s) != true) { + return false; + } + + var date = new Date(s); + return date.getDay() <= 0 && date.getTime() % 60*1000 == 0; + } + catch (e) { + log(e) + } + return false; + }, + isTime: function (s) { + try { + if (/\d{1,2}:\d{1,2}:\d{1,2}/g.test(s) != true) { + return false; + } + + var date = new Date(s); + return date.getDay() <= 0 && date.getTime() % 1000 > 0; + } + catch (e) { + log(e) + } + return false; + }, + isDatetime: function (s) { + try { + var date = new Date(s); + return date.getDay() > 0 && date.getTime() % 1000 > 0; + } + catch (e) { + log(e) + } + return false; + }, + isFileUri: function (s) { + return s != null && s.startsWith('file://') && StringUtil.isPath(s.substring('file://'.length)); + }, + isFile: function (s) { + var ind = s == null ? -1 : s.lastIndexOf('.'); + var prefix = s.substring(0, ind); + var suffix = StringUtil.toLowerCase(s.substring(ind + 1)); + return StringUtil.isName(suffix) && StringUtil.isNotEmpty(prefix, true); + }, + isImage: function (s) { + var ind = s == null ? -1 : s.lastIndexOf('.'); + var prefix = s.substring(0, ind); + var suffix = StringUtil.toLowerCase(s.substring(ind + 1)); + + return ['jpg', 'jpeg', 'png', 'bmp', 'gif'].indexOf(suffix) >= 0 && StringUtil.isNotEmpty(prefix, true); + }, + isAudio: function (s) { + var ind = s == null ? -1 : s.lastIndexOf('.'); + var prefix = s.substring(0, ind); + var suffix = StringUtil.toLowerCase(s.substring(ind + 1)); + + return ['mp3', 'wav', 'flac', 'ogg', 'aiff'].indexOf(suffix) >= 0 && StringUtil.isNotEmpty(prefix, true); + }, + isVideo: function (s) { + var ind = s == null ? -1 : s.lastIndexOf('.'); + var prefix = s.substring(0, ind); + var suffix = StringUtil.toLowerCase(s.substring(ind + 1)); + + return ['mp4', 'mov', 'flv', 'rm', 'fmvb', 'wmv', 'asf', 'asx', '3gp', 'dvd', 'avi'].indexOf(suffix) >= 0 && StringUtil.isNotEmpty(prefix, true); + }, + isImageHttpUrl: function (s) { + return StringUtil.isImage(s) && StringUtil.isHttpUrl(s); + }, + isAudioHttpUrl: function (s) { + return StringUtil.isAudio(s) && StringUtil.isHttpUrl(s); + }, + isVideoHttpUrl: function (s) { + return StringUtil.isVideo(s) && StringUtil.isHttpUrl(s); + }, + isImageFilePath: function (s) { + return StringUtil.isImage(s) && StringUtil.isFileUrl(s); + }, + isAudioFilePath: function (s) { + return StringUtil.isAudio(s) && StringUtil.isFileUrl(s); + }, + isVideoFilePath: function (s) { + return StringUtil.isVideo(s) && StringUtil.isFileUrl(s); + }, + + isBoolKey: function (key) { + if (StringUtil.isEmpty(key, true) || key.length < 3) { + return false; + } + if (key.toLowerCase().startsWith('enable') || key.toLowerCase().startsWith('disable') + || StringUtil.isKeyOfCategory(key, 'bool') || StringUtil.isKeyOfCategory(key, 'boolean')) { + return true; + } + + var k = key.substring(2, 3); + if (StringUtil.isEmpty(k, true)) { + return false; + } + + return ((key.startsWith('is') || key.startsWith('Is')) && /[a-z]/g.test(k) != true) + || (key.startsWith('IS') && /[A-Za-z]/g.test(k) != true); + }, + isIntKey: function (key) { + return StringUtil.isKeyOfCategory(key, 'Int') || StringUtil.isKeyOfCategory(key, 'Integer'); + }, + isLongKey: function (key) { + return StringUtil.isKeyOfCategory(key, 'Long'); + }, + isFloatKey: function (key) { + return StringUtil.isKeyOfCategory(key, 'Float'); + }, + isDoubleKey: function (key) { + return StringUtil.isKeyOfCategory(key, 'Double'); + }, + isDecimalKey: function (key) { + return StringUtil.isKeyOfCategory(key, 'Decimal'); + }, + isNumKey: function (key) { + return StringUtil.isKeyOfCategory(key, 'Num') || StringUtil.isKeyOfCategory(key, 'Number') || StringUtil.isKeyOfCategory(key, 'No'); + }, + isStrKey: function (key) { + return StringUtil.isKeyOfCategory(key, 'Str') || StringUtil.isKeyOfCategory(key, 'String') + || StringUtil.isKeyOfCategory(key, 'Txt') || StringUtil.isKeyOfCategory(key, 'Text') + || StringUtil.isKeyOfCategory(key, 'Msg') || StringUtil.isKeyOfCategory(key, 'Message') + || StringUtil.isKeyOfCategory(key, 'Title') || StringUtil.isKeyOfCategory(key, 'Content') + || StringUtil.isKeyOfCategory(key, 'Hint') || StringUtil.isKeyOfCategory(key, 'Alert') + || StringUtil.isKeyOfCategory(key, 'Remind') || StringUtil.isKeyOfCategory(key, 'Description') + || StringUtil.isKeyOfCategory(key, 'Detail') || StringUtil.isKeyOfCategory(key, 'Annotation'); + }, + isObjKey: function (key) { + return StringUtil.isKeyOfCategory(key, 'Obj') || StringUtil.isKeyOfCategory(key, 'Object'); + }, + isMapKey: function (key) { + return StringUtil.isKeyOfCategory(key, 'Map') || StringUtil.isKeyOfCategory(key, 'Table'); + }, + isDictKey: function (key) { + return StringUtil.isKeyOfCategory(key, 'Dict') || StringUtil.isKeyOfCategory(key, 'Dictionary'); + }, + isArrKey: function (key) { + if (StringUtil.isKeyOfCategory(key, 'Arr') || StringUtil.isKeyOfCategory(key, 'Array')) { + return true; + } + + var k = key == null || key.length < 3 ? null : key.substring(key.length - 3, key.length - 1); + if (StringUtil.isEmpty(k, true)) { + return false; + } + + return (key.endsWith('s') && /^[a-z]+$/g.test(k)) || (key.endsWith('S') && /^[A-Z]+$/g.test(k)); + }, + isListKey: function (key) { + return StringUtil.isKeyOfCategory(key, 'List'); + }, + isSetKey: function (key) { + return StringUtil.isKeyOfCategory(key, 'Set'); + }, + isCollectionKey: function (key) { + return StringUtil.isKeyOfCategory(key, 'Collection') || StringUtil.isArrKey(key) || StringUtil.isListKey(key) || StringUtil.isSetKey(key); + }, + isHashKey: function (key) { + return StringUtil.isKeyOfCategory(key, 'Hash'); + }, + isIdKey: function (key) { + return StringUtil.isKeyOfCategory(key, 'Id'); + }, + isSizeKey: function (key) { + return StringUtil.isKeyOfCategory(key, 'Size'); + }, + isCountKey: function (key) { + return StringUtil.isKeyOfCategory(key, 'Count'); + }, + isPageKey: function (key) { + return StringUtil.isKeyOfCategory(key, 'Page'); + }, + isTotalKey: function (key) { + return StringUtil.isKeyOfCategory(key, 'Total'); + }, + isCapKey: function (key) { + return StringUtil.isKeyOfCategory(key, 'Cap') || StringUtil.isKeyOfCategory(key, 'Capacity'); + }, + isLevelKey: function (key) { + return StringUtil.isKeyOfCategory(key, 'Level'); + }, + isGradeKey: function (key) { + return StringUtil.isKeyOfCategory(key, 'Grade'); + }, + isScoreKey: function (key) { + return StringUtil.isKeyOfCategory(key, 'Score'); + }, + isSexKey: function (key) { + return StringUtil.isKeyOfCategory(key, 'Sex'); + }, + isGenderKey: function (key) { + return StringUtil.isKeyOfCategory(key, 'Gender'); + }, + isAmountKey: function (key) { + return StringUtil.isKeyOfCategory(key, 'Amount'); + }, + isMoneyKey: function (key) { + return StringUtil.isKeyOfCategory(key, 'Money'); + }, + isBalanceKey: function (key) { + return StringUtil.isKeyOfCategory(key, 'Balance'); + }, + isLoanKey: function (key) { + return StringUtil.isKeyOfCategory(key, 'Loan'); + }, + isPriceKey: function (key) { + return StringUtil.isKeyOfCategory(key, 'Price'); + }, + isPercentKey: function (key) { + return StringUtil.isKeyOfCategory(key, 'Percent'); + }, + isRevenueKey: function (key) { + return StringUtil.isKeyOfCategory(key, 'Revenue'); + }, + isProfitKey: function (key) { + return StringUtil.isKeyOfCategory(key, 'Profit'); + }, + isCashKey: function (key) { + return StringUtil.isKeyOfCategory(key, 'Cash'); + }, + isDiscountKey: function (key) { + return StringUtil.isKeyOfCategory(key, 'Discount'); + }, + isLongitudeKey: function (key) { + return StringUtil.isKeyOfCategory(key, 'Longitude'); + }, + isLatitudeKey: function (key) { + return StringUtil.isKeyOfCategory(key, 'Latitude'); + }, + isNameKey: function (key) { + return StringUtil.isKeyOfCategory(key, 'Name'); + }, + isPathKey: function (key) { + return StringUtil.isKeyOfCategory(key, 'Path'); + }, + isUrlKey: function (key) { + return StringUtil.isKeyOfCategory(key, 'Url'); + }, + isUriKey: function (key) { + return StringUtil.isKeyOfCategory(key, 'Uri'); + }, + isDateKey: function (key) { + return StringUtil.isKeyOfCategory(key, 'Date'); + }, + isTimeKey: function (key) { + return StringUtil.isKeyOfCategory(key, 'Time'); + }, + isKeyOfCategory: function (key, category) { + if (StringUtil.isEmpty(key, true) || StringUtil.isEmpty(category, true) || key.length < category.length) { + return false; + } + + var lowerCate = category.toLowerCase(); + var upperCate = category.toUpperCase(); + var bigCate = StringUtil.firstCase(lowerCate, true); + if (key.endsWith(bigCate) || key == lowerCate || key == upperCate) { + return true; + } + + var len = key.length; + var k = len <= category.length ? null : key.substring(len - 3, len - 2); + if (StringUtil.isEmpty(k, true)) { + return false; + } + + return (key.endsWith(lowerCate) && /[a-z]/g.test(k) != true) + || (key.endsWith(upperCate) && /[A-Z]/g.test(k) != true); + }, + TYPE_CATEGORY_KEYS: { + 'boolean': ['bool', 'boolean'], + 'integer': ['count', 'page', 'size', 'num', 'number', 'no', 'cap', 'capacity', 'height', 'width', 'depth'], + 'number': ['float', 'double', 'price', 'amount', 'money', 'rest', 'balance', 'loan', 'discount', 'cash', 'cashback', 'weight', 'longitude', 'latitude'], + 'string': [ + 'str', 'string', 'txt', 'text', 'title', 'detail' + ], + 'array': [ + 'arr', 'array', 'list', 'set', 'collection', 'slice' + ], + 'object': [ + 'obj', 'object', 'dict', 'map', 'table' + ] + }, + CATEGORY_MAP: { // from TYPE_CATEGORY_KEYS +// 'count': 'integer' } +}; + +if (typeof module == 'object') { + module.exports = StringUtil; } -//校正(自动补全等)字符串>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> \ No newline at end of file +//校正(自动补全等)字符串>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> diff --git a/css/main.css b/css/main.css index 999f407..16693cf 100755 --- a/css/main.css +++ b/css/main.css @@ -263,7 +263,8 @@ textarea { } .right-bar { - height: 40px; + height: auto; + min-height: 40px; border-bottom: #DDD 1px solid; width: 100%; position: absolute; @@ -327,6 +328,10 @@ textarea { border-top: 0; } +.pop a { + padding: 10px; +} + .pop-btn { display: inline-block; position: relative @@ -374,26 +379,31 @@ textarea { position: absolute; text-align: center; width: 370px; - background-color: #b5b5b5; + background-color: #cccccc; left: 50%; - margin-left: -150px; - margin-top: 100px; - padding: 30px; - z-index: 100; - border: #dadada 10px solid; - + margin-left: -500px; + margin-top: 192px; + padding-top: 30px; + padding-bottom: 12px; + padding-left: 10px; + padding-right: 10px; + z-index: 300; + border: #bbbbbb 1px solid; + border-radius: 6px; } .save-box input { padding: 10px; border: none; outline: none; - width: 200px; + width: 100%; + text-align: center; } .save-box button { border: none; padding: 8px 20px; + margin-top: 10px; cursor: pointer; outline: none; } @@ -466,7 +476,7 @@ textarea { } /* .json-link:link, .json-link:visited { color: #717171; - text-decoration: none + text-decoration: none } */ .json-link:hover, .json-link:active { color: #3ab54a!important; @@ -479,7 +489,7 @@ textarea { top: 50px; bottom: 0; left: 40%; - z-index: 50; + z-index: 100; cursor: w-resize; border-right: rgba(221, 221, 221, 0.34) 1px solid; border-left: rgba(221, 221, 221, 0.34) 1px solid; @@ -545,7 +555,6 @@ table.diff thead th.texttitle { background-color: #FFF; } - .historys li { display: block; padding: 10px; @@ -572,6 +581,40 @@ table.diff thead th.texttitle { } +.projects { + margin: 0; + padding: 0; + width: 200px; + + background-color: #FFF; +} + +.projects li { + display: block; + padding: 10px; + border-bottom: #DDD 1px solid; + text-overflow: ellipsis; + white-space: nowrap; + overflow: hidden; + padding-right: 20px; + position: relative +} + +.projects li a { + display: inline-block; + width: 100%; +} + +.projects svg { + position: absolute; + display: inline-block; + cursor: pointer; + right: 5px; + width: 15px; + height: 15px; +} + + .pop-save { padding: 10px; width: 250px; @@ -622,4 +665,4 @@ table.diff thead th.texttitle { background-color: #d9edf7; border-color: #bce8f1; color: #31708f; -} \ No newline at end of file +} diff --git a/img/loading.gif b/img/loading.gif new file mode 100644 index 0000000..dc6af34 Binary files /dev/null and b/img/loading.gif differ diff --git a/img/logo.png b/img/logo.png index 9c0facb..e12a2ed 100644 Binary files a/img/logo.png and b/img/logo.png differ diff --git a/img/logo.psd b/img/logo.psd index 5980186..6bfbb4d 100644 Binary files a/img/logo.psd and b/img/logo.psd differ diff --git a/img/logo.svg b/img/logo.svg new file mode 100644 index 0000000..0415cc5 --- /dev/null +++ b/img/logo.svg @@ -0,0 +1,15 @@ + + + + + + + + + { } + + + \ No newline at end of file diff --git a/img/logo_big.png b/img/logo_big.png new file mode 100644 index 0000000..30c83d7 Binary files /dev/null and b/img/logo_big.png differ diff --git a/img/logo_ico.png b/img/logo_ico.png new file mode 100644 index 0000000..838ed79 Binary files /dev/null and b/img/logo_ico.png differ diff --git a/img/logo_ico.psd b/img/logo_ico.psd new file mode 100644 index 0000000..73eaa8d Binary files /dev/null and b/img/logo_ico.psd differ diff --git a/img/logo_ico.svg b/img/logo_ico.svg new file mode 100644 index 0000000..034e4d5 --- /dev/null +++ b/img/logo_ico.svg @@ -0,0 +1,15 @@ + + + + + + + + + { } + + + \ No newline at end of file diff --git a/img/logo_with_domain.png b/img/logo_with_domain.png new file mode 100644 index 0000000..5754897 Binary files /dev/null and b/img/logo_with_domain.png differ diff --git a/img/logo_with_domain.psd b/img/logo_with_domain.psd new file mode 100644 index 0000000..5467e41 Binary files /dev/null and b/img/logo_with_domain.psd differ diff --git a/img/logo_with_domain.svg b/img/logo_with_domain.svg new file mode 100644 index 0000000..d6218fb --- /dev/null +++ b/img/logo_with_domain.svg @@ -0,0 +1,21 @@ + + + + + + + + + { } + + + + + + apiauto.org + + \ No newline at end of file diff --git a/img/logo_with_domain_upper_case.psd b/img/logo_with_domain_upper_case.psd new file mode 100644 index 0000000..946fdd7 Binary files /dev/null and b/img/logo_with_domain_upper_case.psd differ diff --git a/img/logo_with_link.psd b/img/logo_with_link.psd new file mode 100644 index 0000000..174c855 Binary files /dev/null and b/img/logo_with_link.psd differ diff --git a/index.html b/index.html index dc6bce9..caf4e3a 100755 --- a/index.html +++ b/index.html @@ -1,594 +1,1100 @@ - - - - APIJSON.org - - - - - - - - - - - - - - - - - - -
- - - -
-
- - - -
- -
- -
- - -
-
-
- -
- -
-
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- -
-
    -
  • - - - -
  • -
- -
- -
- - - - - - - - - - - - - - - - - - - -
-
- -
-
-
-
- -
- - -
- - -
- -
- - -
-
- - - - - - -
-
- - - -
-
{{error.msg}}
-
- - - - -
-
- - - - - - - - - - -
- - - - - - -
- - -
- - - - - - -
- - -
- - - - - - -
- - - - - - - - -
- - -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + APIAuto-机器学习零代码测试、生成代码与静态检查、生成文档与光标悬浮注释 + + + + + + + + + + + + + + + + + + + +
+
+ + + + {{ StringUtil.isEmpty(projectHost.project, true) ? projectHost.host : projectHost.project }} + + + 2s + + + + + +
+ 文档 + 视频 + 生态 +
+ +
+ + + + + + + + + + + + + + 退出 + + + + 登录 + + + + + {{ User.id != null && User.id > 0 ? User.name : '设置' }} + + + + + + + + + +
+
+ + +
+
+ + + +
+ + +
+ +
+ +
+
+ + + + + +
+ + + + +
+ + + + + +
+
+
+ +
+ +
+ + + + + + + + + + + + + + + +
+
+ +
+ +
+ + + +
    +
  • + {{ (item.Random || {}).name }} + + +
    + + + + + +
    + + + +
  • +
+ +
+ + + + + 每页 + + +
+ + + + +
+ + + + + 每页 + + +
+ + +
+ + + + + +
+
+
+
+ +
+ +
+
+ + + +
+ + +
+ +
+ + +
+
+ + + + + + +
+
+
+ + + + + + 每页 + + + + + +
+ + +
+ + + +
+
{{error.msg}}
+
+ + + + +
+
+ + + + + + + + + + +
+ + + + + + +
+ + +
+
+ +
+ + + + + + +
+
+ + +
+ + + + + + +
+ + +
+ + + + + + +
+ + + + + + + + +
+ + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/js/main.js b/js/main.js index 147bd0b..edab8df 100755 --- a/js/main.js +++ b/js/main.js @@ -1,2022 +1,13706 @@ - -(function () { - Vue.component('vue-item', { - props: ['jsondata', 'theme'], - template: '#item-template' - }) - - Vue.component('vue-outer', { - props: ['jsondata', 'isend', 'theme'], - template: '#outer-template' - }) - - Vue.component('vue-expand', { - props: [], - template: '#expand-template' - }) - - Vue.component('vue-val', { - props: ['field', 'val', 'isend', 'theme'], - template: '#val-template' - }) - - Vue.use({ - install: function (Vue, options) { - - // 判断数据类型 - Vue.prototype.getTyp = function (val) { - return toString.call(val).split(']')[0].split(' ')[1] - } - - // 判断是否是对象或者数组,以对下级进行渲染 - Vue.prototype.isObjectArr = function (val) { - return ['Object', 'Array'].indexOf(this.getTyp(val)) > -1 - } - - // 折叠 - Vue.prototype.fold = function ($event) { - var target = Vue.prototype.expandTarget($event) - target.siblings('svg').show() - target.hide().parent().siblings('.expand-view').hide() - target.parent().siblings('.fold-view').show() - } - // 展开 - Vue.prototype.expand = function ($event) { - var target = Vue.prototype.expandTarget($event) - target.siblings('svg').show() - target.hide().parent().siblings('.expand-view').show() - target.parent().siblings('.fold-view').hide() - } - - //获取展开折叠的target - Vue.prototype.expandTarget = function ($event) { - switch($event.target.tagName.toLowerCase()) { - case 'use': - return $($event.target).parent() - case 'label': - return $($event.target).closest('.fold-view').siblings('.expand-wraper').find('.icon-square-plus').first() - default: - return $($event.target) - } - } - - // 格式化值 - Vue.prototype.formatVal = function (val) { - switch(Vue.prototype.getTyp(val)) { - case 'String': - return '"' + val + '"' - break - - case 'Null': - return 'null' - break - - default: - return val - - } - } - - // 判断值是否是链接 - Vue.prototype.isaLink = function (val) { - return /^((https|http|ftp|rtsp|mms)?:\/\/)[^\s]+/.test(val) - } - - // 计算对象的长度 - Vue.prototype.objLength = function (obj) { - return Object.keys(obj).length - } - } - }) - - - - var initJson = {} - - // 主题 [key, String, Number, Boolean, Null, link-link, link-hover] - var themes = [ - ['#92278f', '#3ab54a', '#25aae2', '#f3934e', '#f34e5c', '#717171'], - ['rgb(19, 158, 170)', '#cf9f19', '#ec4040', '#7cc500', 'rgb(211, 118, 126)', 'rgb(15, 189, 170)'], - ['#886', '#25aae2', '#e60fc2', '#f43041', 'rgb(180, 83, 244)', 'rgb(148, 164, 13)'], - ['rgb(97, 97, 102)', '#cf4c74', '#20a0d5', '#cd1bc4', '#c1b8b9', 'rgb(25, 8, 174)'] - ] - - - - - // APIJSON <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< - //这些全局变量不能放在data中,否则会报undefined错误 - - var baseUrl - var inputted - var handler - var docObj - var doc - var output - - var isSingle = true - - var doneCount - - // APIJSON >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> - - var App = new Vue({ - el: '#app', - data: { - baseview: 'formater', - view: 'output', - jsoncon: JSON.stringify(initJson), - jsonhtml: initJson, - compressStr: '', - error: {}, - historys: [], - history: {name: '请求0'}, - remotes: [], - locals: [], - testCases: [], - accounts: [ - { - 'isLoggedIn': false, - 'name': '测试账号', - 'phone': '13000082001', - 'password': '123456' - } - ], - currentAccountIndex: 0, - tests: [], - testProcess: '机器学习:已关闭', - compareColor: '#0000', - isDelayShow: false, - isSaveShow: false, - isExportShow: false, - isTestCaseShow: false, - isLoginShow: false, - isConfigShow: false, - isAdminOperation: false, - loginType: 'login', - isExportRemote: false, - isRegister: false, - isMLEnabled: false, - isLocalShow: false, - exTxt: { - name: 'APIJSON测试', - button: '保存', - index: 0 - }, - themes: themes, - checkedTheme: 0, - isExpand: true, - User: { - id: 0, - name: '', - head: '' - }, - Privacy: { - id: 0, - balance: null //点击更新提示需要判空 0.00 - }, - schema: 'sys', - server: 'http://vip.apijson.org/' - }, - methods: { - - // 全部展开 - expandAll: function () { - if (App.view != 'code') { - alert('请先获取正确的JSON Response!') - return - } - - $('.icon-square-min').show() - $('.icon-square-plus').hide() - $('.expand-view').show() - $('.fold-view').hide() - - App.isExpand = true; - }, - - // 全部折叠 - collapseAll: function () { - if (App.view != 'code') { - alert('请先获取正确的JSON Response!') - return - } - - $('.icon-square-min').hide() - $('.icon-square-plus').show() - $('.expand-view').hide() - $('.fold-view').show() - - App.isExpand = false; - }, - - // diff - diffTwo: function () { - var oldJSON = {} - var newJSON = {} - App.view = 'code' - try { - oldJSON = jsonlint.parse(App.jsoncon) - } catch (ex) { - App.view = 'error' - App.error = { - msg: '原 JSON 解析错误\r\n' + ex.message - } - return - } - - try { - newJSON = jsonlint.parse(App.jsoncon) - } catch (ex) { - App.view = 'error' - App.error = { - msg: '新 JSON 解析错误\r\n' + ex.message - } - return - } - - var base = difflib.stringAsLines(JSON.stringify(oldJSON, '', 4)) - var newtxt = difflib.stringAsLines(JSON.stringify(newJSON, '', 4)) - var sm = new difflib.SequenceMatcher(base, newtxt) - var opcodes = sm.get_opcodes() - $('#diffoutput').empty().append(diffview.buildView({ - baseTextLines: base, - newTextLines: newtxt, - opcodes: opcodes, - baseTextName: '原 JSON', - newTextName: '新 JSON', - contextSize: 2, - viewType: 0 - })) - }, - - baseViewToDiff: function () { - App.baseview = 'diff' - App.diffTwo() - }, - - // 回到格式化视图 - baseViewToFormater: function () { - App.baseview = 'formater' - App.view = 'code' - App.showJsonView() - }, - - // 根据json内容变化格式化视图 - showJsonView: function () { - if (App.baseview === 'diff') { - return - } - try { - if (this.jsoncon.trim() === '') { - App.view = 'empty' - } else { - App.view = 'code' - App.jsonhtml = jsonlint.parse(this.jsoncon) - } - } catch (ex) { - App.view = 'error' - App.error = { - msg: ex.message - } - } - }, - - - //设置基地址 - setBaseUrl: function () { - // 重新拉取文档 - var bu = this.getBaseUrl() - if (baseUrl != bu) { - baseUrl = bu; - doc = null //这个是本地的数据库字典及非开放请求文档 - this.saveCache('', 'URL_BASE', baseUrl) - - //已换成固定的管理系统URL - - // this.remotes = [] - - // var index = baseUrl.indexOf(':') //http://localhost:8080 - // App.server = (index < 0 ? baseUrl : baseUrl.substring(0, baseUrl)) + ':9090' - - } - }, - //获取基地址 - getBaseUrl: function () { - var url = new String(vUrl.value).trim() - var length = this.getBaseUrlLength(url) - url = length <= 0 ? '' : url.substring(0, length) - return url == '' ? URL_BASE : url - }, - //获取基地址长度,以://后的第一个/分割baseUrl和method - getBaseUrlLength: function (url_) { - var url = url_ == null ? '' : '' + url_ - var index = url.indexOf('://') - return index < 0 ? 0 : index + 3 + url.substring(index + 3).indexOf('/') - }, - //获取操作方法 - getMethod: function () { - var url = new String(vUrl.value).trim() - var index = this.getBaseUrlLength(url) - url = index <= 0 ? url : url.substring(index) - return url.startsWith('/') ? url.substring(1) : url - }, - //获取请求的tag - getTag: function () { - var req = null - try { - req = JSON.parse(new String(vInput.value)) - } catch (e) { - log('main.getTag', 'try { req = JSON.parse(new String(vInput.value)) \n } catch (e) {\n' + e.message) - } - return req == null ? null : req.tag - }, - - // 显示保存弹窗 - showSave: function (show) { - if (show) { - if (App.isTestCaseShow) { - alert('请先输入请求内容!') - return - } - - var tag = App.getTag() - App.history.name = App.getMethod() + ' ' + (StringUtil.isEmpty(tag, true) ? 'Test' : tag) + ' ' + App.formatTime() //不自定义名称的都是临时的,不需要时间太详细 - } - App.isSaveShow = show - }, - - // 显示导出弹窗 - showExport: function (show, isRemote) { - if (show) { - if (isRemote) { //共享测试用例 - if (App.isTestCaseShow) { - alert('请先输入请求内容!') - return - } - if (App.view != 'code') { - alert('请先测试请求,确保是正确可用的!') - return - } - var tag = App.getTag() - App.exTxt.name = App.getMethod() + ' ' + (StringUtil.isEmpty(tag, true) ? 'Test' : tag) - } - else { //下载到本地 - if (App.isTestCaseShow) { //文档 - App.exTxt.name = 'APIJSON自动化文档 ' + App.formatDateTime() - } - else if (App.view == 'markdown' || App.view == 'output') { - App.exTxt.name = 'User' - } - else { - App.exTxt.name = 'APIJSON测试 ' + App.getMethod() + ' ' + App.formatDateTime() - } - } - } - App.isExportShow = show - App.isExportRemote = isRemote - }, - - // 显示配置弹窗 - showConfig: function (show, index) { - App.isConfigShow = false - if (show) { - App.exTxt.index = index - switch (index) { - case 0: - case 1: - App.exTxt.name = index == 0 ? App.schema : App.server - App.isConfigShow = true - break - case 2: - App.getCurrentUser(true) - break - case 3: - App.showAndSend(App.server + '/get', { - 'Goods[]': { - 'Goods': { - '@column': 'name,detail' - } - } - }, true) - break - } - } - }, - - // 保存当前的JSON - save: function () { - if (App.history.name.trim() === '') { - Helper.alert('名称不能为空!', 'danger') - return - } - var val = { - name: App.history.name, - url: '/' + this.getMethod(), - request: inputted - } - var key = String(Date.now()) - localforage.setItem(key, val, function (err, value) { - Helper.alert('保存成功!', 'success') - App.showSave(false) - val.key = key - App.historys.push(val) - }) - }, - - // 清空本地历史 - clearLocal: function () { - this.locals.splice(0, this.locals.length) //UI无反应 this.locals = [] - this.saveCache('', 'locals', []) - }, - - // 删除已保存的 - remove: function (item, index, isRemote) { - if (isRemote == null || isRemote == false) { //null != false - localforage.removeItem(item.key, function () { - App.historys.splice(index, 1) - }) - } else { - if (App.isLocalShow) { - App.locals.splice(index, 1) - return - } - - App.isTestCaseShow = false - - var url = App.server + '/delete' - var req = { - 'Document': { - 'id': item.Document == null ? 0 : item.Document.id - }, - 'tag': 'Document' - } - App.request(true, url, req, function (url, res, err) { - App.onResponse(url, res, err) - - var rpObj = res.data - - if (rpObj != null && rpObj.Document != null && rpObj.Document.code == 200) { - App.remotes.splice(index, 1) - App.showTestCase(true, App.isLocalShow) - } - }) - } - }, - - // 根据历史恢复数据 - restoreRemote: function (item) { - this.restore(item.Document) - }, - // 根据历史恢复数据 - restore: function (item) { - localforage.getItem(item.key || '', function (err, value) { - baseUrl = App.getBaseUrl() - var branch = new String(item.url || '/get') - if (branch.startsWith('/') == false) { - branch = '/' + branch - } - vUrl.value = baseUrl + branch - App.showTestCase(false, App.isLocalShow) - vInput.value = item.request - App.onChange(false) - }) - }, - - // 获取所有保存的json - listHistory: function () { - localforage.iterate(function (value, key, iterationNumber) { - if (key[0] !== '#') { - value.key = key - App.historys.push(value) - } - if (key === '#theme') { - // 设置默认主题 - App.checkedTheme = value - } - }) - }, - - // 导出文本 - exportTxt: function () { - App.isExportShow = false - - if (App.isExportRemote == false) { //下载到本地 - - if (App.isTestCaseShow) { //文档 - saveTextAs('# ' + App.exTxt.name + '\n主页: https://github.com/TommyLemon/APIJSON' - + '\n\nBASE_URL: ' + this.getBaseUrl() - + '\n\n\n## 测试用例(Markdown格式,可用工具预览) \n\n' + App.getDoc4TestCase() - + '\n\n\n\n\n\n\n\n## 文档(Markdown格式,可用工具预览) \n\n' + doc - , App.exTxt.name + '.txt') - } - else if (App.view == 'markdown' || App.view == 'output') { //model - // saveTextAs('# ' + App.exTxt.name + '\n主页: https://github.com/TommyLemon/APIJSON' - // + '\n\n\n## 使用方法\n1.新建java文件,例如A.java
\n2.将以下与A同名的class代码复制粘贴到A文件内
\n3.import需要引入的类,可使用快捷键Ctrl+Shift+O
' - // + '\n\n## Java model类 \n\n' + CodeUtil.parseJavaBean(docObj) - // , App.exTxt.name + '.txt') - - - var clazz = App.exTxt.name - var txt = CodeUtil.parseJavaBean(docObj, clazz) - if (StringUtil.isEmpty(txt, true)) { - alert('找不到 ' + clazz + ' 对应的表!请检查数据库中是否存在!\n如果不存在,请重新输入存在的表;\n如果存在,请刷新网页后重试。') - return - } - saveTextAs(txt, clazz + '.java') - } - else { - saveTextAs('# ' + App.exTxt.name + '\n主页: https://github.com/TommyLemon/APIJSON' - + '\n\nURL: ' + vUrl.value - + '\n\nRequest:\n' + vInput.value - + '\n\n\nResponse:\n' + App.jsoncon - + '\n\n\n## Java解析Response的代码 \n\n' + CodeUtil.parseJavaResponse('', JSON.parse(App.jsoncon), 0) - , App.exTxt.name + '.txt') - } - } - else { //上传到远程服务器 - var id = App.User == null ? null : App.User.id - if (id == null || id <= 0) { - alert('请先登录!') - return - } - - App.isTestCaseShow = false - - var currentAccount = App.accounts[App.currentAccountIndex]; - - var url = App.server + '/post' - var req = { - 'Document': { - 'userId': App.User.id, - 'testAccountId': currentAccount.isLoggedIn ? currentAccount.id : null, - 'name': App.exTxt.name, - 'url': '/' + App.getMethod(), - 'request': App.toDoubleJSON(inputted) - }, - 'tag': 'Document' - } - - App.request(true, url, req, function (url, res, err) { - App.onResponse(url, res, err) - - var rpObj = res.data - - if (rpObj != null && rpObj.Document != null && rpObj.Document.code == 200) { - App.remotes = [] - App.showTestCase(true, false) - } - }) - } - }, - - // 保存配置 - saveConfig: function () { - App.isConfigShow = false - - if (App.exTxt.index == 0) { - App.schema = App.exTxt.name - App.saveCache('', 'schema', App.schema) - - doc = null - App.onChange(false) - } - else { - App.server = App.exTxt.name - App.saveCache('', 'server', App.server) - - // App.remotes = [] - // App.Privacy = {} - // App.showTestCase(false, false) //App.showTestCase(true) - App.logout(true) - } - }, - - - // 切换主题 - switchTheme: function (index) { - this.checkedTheme = index - localforage.setItem('#theme', index) - }, - - - // APIJSON <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< - - //格式化日期 - formatDate: function (date) { - if (date == null) { - date = new Date() - } - return date.getFullYear() + '-' + App.fillZero(date.getMonth() + 1) + '-' + App.fillZero(date.getDate()) - }, - //格式化时间 - formatTime: function (date) { - if (date == null) { - date = new Date() - } - return App.fillZero(date.getHours()) + ':' + App.fillZero(date.getMinutes()) - }, - formatDateTime: function (date) { - if (date == null) { - date = new Date() - } - return App.formatDate(date) + ' ' + App.formatTime(date) - }, - //填充0 - fillZero: function (num, n) { - if (num == null) { - num = 0 - } - if (n == null || n <= 0) { - n = 2 - } - var len = num.toString().length; - while(len < n) { - num = "0" + num; - len++; - } - return num; - }, - - - - - - - onClickAccount: function (index, item) { - if (this.currentAccountIndex == index) { - vAccount.value = item.phone - vPassword.value = item.password - - if (item.isLoggedIn) { - //logout FIXME 没法自定义退出,浏览器默认根据url来管理session的 - this.logout(false, function (url, res, err) { - App.onResponse(url, res, err) - - item.isLoggedIn = false - App.saveCache(App.getBaseUrl(), 'currentAccountIndex', App.currentAccountIndex) - App.saveCache(App.getBaseUrl(), 'accounts', App.accounts) - }); - } - else { - //login - this.login(false, function (url, res, err) { - App.onResponse(url, res, err) - - item.isLoggedIn = true - - var data = res.data || {} - var user = data.code == 200 ? data.User : null - if (user != null) { - App.accounts[App.currentAccountIndex].name = user.name - App.saveCache(App.getBaseUrl(), 'currentAccountIndex', App.currentAccountIndex) - App.saveCache(App.getBaseUrl(), 'accounts', App.accounts) - } - }); - } - - return; - } - - //退出当前账号 - var c = this.currentAccountIndex - var it = c == null || this.accounts == null ? null : this.accounts[c]; - if (it != null) { //切换 BASE_URL后 it = undefined 导致UI操作无法继续 - it.isLoggedIn = false //异步导致账号错位 this.onClickAccount(c, this.accounts[c]) - } - - //切换到这个tab - this.currentAccountIndex = index - - //目前还没做到同一标签页下测试账号切换后,session也跟着切换,所以干脆每次切换tab就重新登录 - item.isLoggedIn = false - this.onClickAccount(index, item) - }, - - removeAccountTab: function () { - if (App.accounts.length <= 1) { - alert('至少要 1 个测试账号!') - return - } - - App.accounts.splice(App.currentAccountIndex, 1) - if (App.currentAccountIndex >= App.accounts.length) { - App.currentAccountIndex = App.accounts.length - 1 - } - - App.saveCache(App.getBaseUrl(), 'currentAccountIndex', App.currentAccountIndex) - App.saveCache(App.getBaseUrl(), 'accounts', App.accounts) - }, - addAccountTab: function () { - App.showLogin(true, false) //TODO 登录窗口右上角加一个 X App.showLogin(! App.isLoginShow, false) - }, - - - //显示远程的测试用例文档 - showTestCase: function (show, isLocal) { - App.isTestCaseShow = show - App.isLocalShow = isLocal - - vOutput.value = show ? '' : (output || '') - App.showDoc() - - if (isLocal) { - App.testCases = App.locals || [] - return - } - App.testCases = App.remotes || [] - - if (show && App.testCases.length <= 0) { - App.isTestCaseShow = false - - var url = App.server + '/get' - var req = { - '[]': { - 'Document': { - '@order': 'version-,date-', - 'userId': App.User.id - }, - 'TestRecord': { - 'documentId@': '/Document/id', - '@order': 'date-', - '@column': 'id,userId,documentId,response', - 'userId': App.User.id - } - }, - '@role': 'login' - } - - App.onChange(false) - App.request(true, url, req, function (url, res, err) { - App.onResponse(url, res, err) - - var rpObj = res.data - - if (rpObj != null && rpObj.code === 200) { - App.isTestCaseShow = true - App.isLocalShow = false - App.testCases = App.remotes = rpObj['[]'] - vOutput.value = show ? '' : (output || '') - App.showDoc() - - //App.onChange(false) - } - }) - } - }, - - // 设置文档 - showDoc: function () { - if (this.setDoc(doc) == false) { - this.getDoc(function (d) { - App.setDoc(d); - }); - } - }, - - - saveCache: function (url, key, value) { - var cache = this.getCache(url); - cache[key] = value - localStorage.setItem(url, JSON.stringify(cache)) - }, - getCache: function (url, key) { - var cache = localStorage.getItem(url) - try { - cache = JSON.parse(cache) - } catch(e) { - App.log('login App.send >> try { cache = JSON.parse(cache) } catch(e) {\n' + e.message) - } - cache = cache || {} - return key == null ? cache : cache[key] - }, - - /**登录确认 - */ - confirm: function () { - switch (App.loginType) { - case 'login': - App.login(App.isAdminOperation) - break - case 'register': - App.register(App.isAdminOperation) - break - case 'forget': - App.resetPassword(App.isAdminOperation) - break - } - }, - - showLogin(show, isAdmin) { - App.isLoginShow = show - App.isAdminOperation = isAdmin - }, - - /**登录 - */ - login: function (isAdminOperation, callback) { - App.isLoginShow = false - - baseUrl = App.getBaseUrl() - var url = (isAdminOperation ? App.server : baseUrl) + '/login' - const req = { - type: 0, // 登录方式,非必须 0-密码 1-验证码 - phone: vAccount.value, - password: vPassword.value, - version: 1 // 全局默认版本号,非必须 - } - - if (isAdminOperation) { - App.request(isAdminOperation, url, req, function (url, res, err) { - if (callback) { - callback(url, res, err) - return - } - - var rpObj = res.data || {} - - if (rpObj.code != 200) { - alert('登录失败,请检查网络后重试。\n' + rpObj.msg + '\n详细信息可在浏览器控制台查看。') - } - else { - var user = rpObj.User || {} - - if (user.id > 0) { - App.User = user - } - - //保存User到缓存 - App.saveCache(App.server, 'User', user) - - //查询余额 - App.request(true, App.server + '/gets', { - 'Privacy': { - 'id': user.id - }, - 'tag': 'Privacy' - }, function (url, res, err) { - var data = res.data || {} - if (data.code == 200 && data.Privacy != null) { - App.Privacy = data.Privacy - } - }) - - - var item = App.accounts[App.currentAccountIndex] - item.isLoggedIn = false - App.onClickAccount(App.currentAccountIndex, item) //自动登录测试账号 - } - - }) - } - else { - if (callback == null) { - var item - for (var i in App.accounts) { - item = App.accounts[i] - if (item != null && req.phone == item.phone) { - alert(req.phone + ' 已在测试账号中!') - // App.currentAccountIndex = i - App.onClickAccount(i, item) - return - } - } - } - - vUrl.value = url - vInput.value = JSON.stringify(req, null, ' ') - App.showTestCase(false, App.isLocalShow) - App.onChange(false) - App.send(isAdminOperation, function (url, res, err) { - if (callback) { - callback(url, res, err) - return - } - - App.onResponse(url, res, err) - - //由login按钮触发,不能通过callback回调来实现以下功能 - var data = res.data || {} - if (data.code == 200) { - var user = data.User || {} - App.accounts.push( { - isLoggedIn: true, - id: user.id, - name: user.name, - phone: req.phone, - password: req.password - }) - App.currentAccountIndex = App.accounts.length - 1 - - App.saveCache(App.getBaseUrl(), 'currentAccountIndex', App.currentAccountIndex) - App.saveCache(App.getBaseUrl(), 'accounts', App.accounts) - } - }) - } - }, - - /**注册 - */ - register: function (isAdminOperation) { - baseUrl = App.getBaseUrl() - vUrl.value = (isAdminOperation ? App.server : baseUrl) + '/register' - vInput.value = JSON.stringify( - { - Privacy: { - phone: vAccount.value, - _password: vPassword.value - }, - User: { - name: 'APIJSONUser' - }, - verify: vVerify.value - }, - null, ' ') - App.showTestCase(false, false) - App.onChange(false) - App.send(isAdminOperation, function (url, res, err) { - App.onResponse(url, res, err) - - var rpObj = res.data - - if (rpObj != null && rpObj.code === 200) { - alert('注册成功') - - var privacy = rpObj.Privacy || {} - - vAccount.value = privacy.phone - App.loginType = 'login' - } - }) - }, - - /**重置密码 - */ - resetPassword: function (isAdminOperation) { - baseUrl = App.getBaseUrl() - vUrl.value = (isAdminOperation ? App.server : baseUrl) + '/put/password' - vInput.value = JSON.stringify( - { - verify: vVerify.value, - Privacy: { - phone: vAccount.value, - _password: vPassword.value - } - }, - null, ' ') - App.showTestCase(false, App.isLocalShow) - App.onChange(false) - App.send(isAdminOperation, function (url, res, err) { - App.onResponse(url, res, err) - - var rpObj = res.data - - if (rpObj != null && rpObj.code === 200) { - alert('重置密码成功') - - var privacy = rpObj.Privacy || {} - - vAccount.value = privacy.phone - App.loginType = 'login' - } - }) - }, - - /**退出 - */ - logout: function (isAdminOperation, callback) { - baseUrl = App.getBaseUrl() - var url = (isAdminOperation ? App.server : baseUrl) + '/logout' - var req = {} - - if (isAdminOperation) { - // alert('logout isAdminOperation this.saveCache(App.server, User, {})') - this.saveCache(App.server, 'User', {}) - } - - // alert('logout isAdminOperation = ' + isAdminOperation + '; url = ' + url) - if (isAdminOperation) { - this.request(isAdminOperation, url, req, function (url, res, err) { - if (callback) { - callback(url, res, err) - return - } - - // alert('logout clear admin ') - - App.clearUser() - App.onResponse(url, res, err) - App.showTestCase(false, App.isLocalShow) - }) - } - else { - vUrl.value = url - vInput.value = JSON.stringify(req, null, ' ') - this.showTestCase(false, App.isLocalShow) - this.onChange(false) - this.send(isAdminOperation, callback) - } - }, - - /**获取验证码 - */ - getVerify: function (isAdminOperation) { - baseUrl = App.getBaseUrl() - vUrl.value = (isAdminOperation ? App.server : baseUrl) + '/post/verify' - var type = App.loginType == 'login' ? 0 : (App.loginType == 'register' ? 1 : 2) - vInput.value = JSON.stringify( - { - type: type, - phone: vAccount.value - }, - null, ' ') - App.showTestCase(false, App.isLocalShow) - App.onChange(false) - App.send(isAdminOperation, function (url, res, err) { - App.onResponse(url, res, err) - - var data = res.data || {} - var obj = data.code == 200 ? data.Verify : null - var verify = obj == null ? null : obj.verify - if (verify != null) { //FIXME isEmpty校验时居然在verify=null! StringUtil.isEmpty(verify, true) == false) { - vVerify.value = verify - } - }) - }, - - /**获取当前用户 - */ - getCurrentUser: function (isAdminOperation, callback) { - baseUrl = App.getBaseUrl() - vUrl.value = (isAdminOperation ? App.server : baseUrl) + '/gets' - vInput.value = JSON.stringify( - { - Privacy: { - id: App.User.id - }, - tag: 'Privacy' - }, - null, ' ') - App.showTestCase(false, App.isLocalShow) - App.onChange(false) - App.send(isAdminOperation, function (url, res, err) { - if (callback) { - callback(url, res, err) - return - } - - App.onResponse(url, res, err) - if (isAdminOperation) { - var data = res.data || {} - if (data.code == 200 && data.Privacy != null) { - App.Privacy = data.Privacy - } - } - }) - }, - - clearUser: function () { - App.User = {} - App.Privacy = {} - App.remotes = [] - App.saveCache(App.server, 'User', {}) //应该用lastBaseUrl,baseUrl应随watch输入变化重新获取 - }, - - /**计时回调 - */ - onHandle: function (before) { - this.isDelayShow = false - if (inputted != before) { - clearTimeout(handler); - return; - } - - App.view = 'output'; - vComment.value = ''; - vOutput.value = 'resolving...'; - - //格式化输入代码 - try { - before = App.toDoubleJSON(before); - log('onHandle before = \n' + before); - - before = jsonlint.parse(before); - - before = JSON.stringify(before, null, " "); //用format不能catch! - - //关键词let在IE和Safari上不兼容 - var code = ''; - try { - code = this.getCode(before); //必须在before还是用 " 时使用,后面用会因为解析 ' 导致失败 - } catch(e) { - code = '\n\n\n建议:\n使用其它浏览器,例如 谷歌Chrome、火狐FireFox 或者 微软Edge, 因为这样能自动生成请求代码.' - + '\nError:\n' + e.message + '\n\n\n'; - } - - if (isSingle) { - if (before.indexOf('"') >= 0) { - before = before.replace(/"/g, "'"); - } - } - else { - if (before.indexOf("'") >= 0) { - before = before.replace(/'/g, '"'); - } - } - - vInput.value = before; - vSend.disabled = false; - vOutput.value = 'OK,请点击 [发送请求] 按钮来测试。[点击这里查看视频教程](http://i.youku.com/apijson)' + code; - - - App.showDoc() - - try { - vComment.value = isSingle ? '' : CodeUtil.parseComment(before, docObj == null ? null : docObj['[]'], App.getMethod()) - - onScrollChanged() - } catch (e) { - log('onHandle try { vComment.value = CodeUtil.parseComment >> } catch (e) {\n' + e.message); - } - } catch(e) { - log(e) - vSend.disabled = true - - App.view = 'error' - App.error = { - msg: 'JSON格式错误!请检查并编辑请求!\n\n如果JSON中有注释,请 手动删除 或 点击左边的 \'/" 按钮 来去掉。\n\n' + e.message - } - } - }, - - - /**输入内容改变 - */ - onChange: function (delay) { - this.setBaseUrl(); - inputted = new String(vInput.value); - vComment.value = ''; - - clearTimeout(handler); - - this.isDelayShow = delay; - - handler = setTimeout(function () { - App.onHandle(inputted); - }, delay ? 2*1000 : 0); - }, - - /**单双引号切换 - */ - transfer: function () { - isSingle = ! isSingle; - - this.isTestCaseShow = false - - // 删除注释 <<<<<<<<<<<<<<<<<<<<< - - var input = new String(vInput.value); - - var reg = /("([^\\\"]*(\\.)?)*")|('([^\\\']*(\\.)?)*')|(\/{2,}.*?(\r|\n))|(\/\*(\n|.)*?\*\/)/g // 正则表达式 - try { - input = input.replace(reg, function(word) { // 去除注释后的文本 - return /^\/{2,}/.test(word) || /^\/\*/.test(word) ? "" : word; - }) - - if (vInput.value != input) { - vInput.value = input - } - } catch (e) { - log('transfer delete comment in json >> catch \n' + e.message) - } - - // 删除注释 >>>>>>>>>>>>>>>>>>>>> - - - this.onChange(); - }, - - showAndSend: function (url, req, isAdminOperation, callback) { - vUrl.value = url - vInput.value = JSON.stringify(req, null, ' ') - App.showTestCase(false, App.isLocalShow) - App.onChange(false) - App.send(isAdminOperation, callback) - }, - - /**发送请求 - */ - send: function(isAdminOperation, callback) { - if (this.isTestCaseShow) { - alert('请先输入请求内容!') - return - } - this.onHandle(vInput.value) - - clearTimeout(handler); - - var real = new String(vInput.value); - if (real.indexOf("'") >= 0) { - real = real.replace(/'/g, "\""); - } - var req = JSON.parse(real); - - var url = new String(vUrl.value) - url = url.replace(/ /g, '') - vOutput.value = "requesting... \nURL = " + url; - this.view = 'output'; - - - this.setBaseUrl() - this.request(isAdminOperation, url, req, callback) - - this.locals = this.locals || [] - if (this.locals.length >= 1000) { //最多1000条,太多会很卡 - this.locals.splice(this.locals.length - 1, 1) - } - var method = App.getMethod() - this.locals.unshift({ - 'Document': { - 'userId': App.User.id, - 'name': method + ' ' + (StringUtil.isEmpty(req.tag, true) ? 'Test' : req.tag) + ' ' + App.formatDateTime(), - 'url': '/' + method, - 'request': real - } - }) - App.saveCache('', 'locals', this.locals) - }, - - //请求 - request: function (isAdminOperation, url, req, callback) { - // axios.defaults.withcredentials = true - axios({ - method: 'post', - url: url, - data: req, - withCredentials: true - }) - .then(function (res) { - res = res || {} - log('send >> success:\n' + JSON.stringify(res, null, ' ')) - - //未登录,清空缓存 - if (res.data != null && res.data.code == 407) { - // alert('request res.data != null && res.data.code == 407 >> isAdminOperation = ' + isAdminOperation) - if (isAdminOperation) { - // alert('request App.User = {} App.server = ' + App.server) - - App.clearUser() - } - else { - // alert('request App.accounts[App.currentAccountIndex].isLoggedIn = false ') - - App.accounts[App.currentAccountIndex].isLoggedIn = false - } - } - - if (callback != null) { - callback(url, res, null) - return - } - App.onResponse(url, res, null) - }) - .catch(function (err) { - log('send >> error:\n' + err) - if (callback != null) { - callback(url, {}, err) - return - } - App.onResponse(url, {}, err) - }) - }, - - - /**请求回调 - */ - onResponse: function (url, res, err) { - if (res == null) { - res = {} - } - log('onResponse url = ' + url + '\nerr = ' + err + '\nres = \n' + JSON.stringify(res)) - if (err != null) { - vOutput.value = "Response:\nurl = " + url + "\nerror = " + err.message; - } - else { - var json = res.data - if (isSingle) { - json = JSONResponse.formatObject(json); - } - App.jsoncon = JSON.stringify(json, null, ' '); - App.view = 'code'; - vOutput.value = ''; - } - }, - - - /**处理按键事件 - * @param event - */ - doOnKeyUp: function (event) { - var keyCode = event.keyCode ? event.keyCode : (event.which ? event.which : event.charCode); - if (keyCode == 13) { // enter - this.send(false); - } - }, - - - /**转为请求代码 - * @param rq - */ - getCode: function (rq) { - return '\n\n\n### 请求代码 \n\n#### <= Android-Java: 同名变量需要重命名\n ```java \n' - + StringUtil.trim(CodeUtil.parseJava(null, JSON.parse(rq), 0, isSingle)) - + '\n ``` \n注:' + (isSingle ? '用了APIJSON的JSONRequest类。也可使用其它类封装,只要JSON有序就行。' : 'LinkedHashMap()可替换为fastjson中的JSONObject(true)等有序JSON构造方法。') - + '\n\n#### <= iOS-Swift: 所有对象标识{}改为数组标识[]\n ```swift \n' - + CodeUtil.parseSwift(null, JSON.parse(rq)) - + '\n ``` \n注:空对象请用 [:] 表示。 \n\n#### <= Web-JavaScript 或 Python: 和左边的请求JSON一样 \n' - + '\n\n#### 开放源码 ' - + '\nAPIJSON 接口工具: [https://github.com/TommyLemon/APIJSONAuto](https://github.com/TommyLemon/APIJSONAuto) ' - + '\nAPIJSON -Java版: [https://github.com/TommyLemon/APIJSON](https://github.com/TommyLemon/APIJSON) ' - + '\nAPIJSON - C# 版: [https://github.com/liaozb/APIJSON.NET](https://github.com/liaozb/APIJSON.NET) ' - + '\nAPIJSON - PHP版: [https://github.com/orchie/apijson](https://github.com/orchie/apijson) ' - + '\nAPIJSON -Node版: [https://github.com/TEsTsLA/apijson](https://github.com/TEsTsLA/apijson) '; - }, - - - /**显示文档 - * @param d - **/ - setDoc: function (d) { - if (d == null || d == '') { - return false; - } - doc = d; - vOutput.value += ( - '\n\n\n## 文档 \n\n 通用文档见 [APIJSON通用文档](https://github.com/TommyLemon/APIJSON/blob/master/Document.md#3.2) \n\n' + d - ); - - App.view = 'markdown'; - markdownToHTML(vOutput.value); - return true; - }, - - - /** - * 获取文档 - */ - getDoc: function (callback) { - App.request(false, this.getBaseUrl() + '/get', { - '[]': { - 'Table': { - 'TABLE_SCHEMA': App.schema, - 'TABLE_TYPE': 'BASE TABLE', - 'TABLE_NAME!$': ['\\_%', 'sys\\_%', 'system\\_%'], - '@order': 'TABLE_NAME+', - '@column': 'TABLE_NAME,TABLE_COMMENT' - }, - 'Column[]': { - 'Column': { - 'TABLE_SCHEMA': App.schema, - 'TABLE_NAME@': '[]/Table/TABLE_NAME', - '@column': 'COLUMN_NAME,COLUMN_TYPE,COLUMN_COMMENT' - } - } - }, - 'Request[]': { - 'Request': { - '@order': 'version-,method-' - } - }, - 'Function[]': { - 'Function': { - '@order': 'date-,name+', - '@column': 'name,arguments,demo,detail', - 'demo()': 'getFunctionDemo()', - 'detail()': 'getFunctionDetail()', - 'r0()': 'removeKey(name)', - 'r1()': 'removeKey(arguments)' - } - } - }, function (url, res, err) { - if (err != null || res == null || res.data == null) { - log('getDoc err != null || res == null || res.data == null >> return;'); - return; - } - -// log('getDoc docRq.responseText = \n' + docRq.responseText); - docObj = res.data; - - //转为文档格式 - var doc = ''; - var item; - - //[] <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< - var list = docObj == null ? null : docObj['[]']; - if (list != null) { - log('getDoc [] = \n' + format(JSON.stringify(list))); - - var table; - var columnList; - var column; - for (var i = 0; i < list.length; i++) { - item = list[i]; - - //Table - table = item == null ? null : item.Table; - if (table == null) { - continue; - } - log('getDoc [] for i=' + i + ': table = \n' + format(JSON.stringify(table))); - - - doc += '### ' + (i + 1) + '. ' + CodeUtil.getModelName(table.TABLE_NAME) + '\n#### 说明: \n' + App.toMD(table.TABLE_COMMENT); - - //Column[] - doc += '\n\n#### 字段: \n 名称 | 类型 | 最大长度 | 详细说明' + - ' \n -------- | ------------ | ------------ | ------------ '; - - columnList = item['Column[]']; - if (columnList == null) { - continue; - } - log('getDoc [] for ' + i + ': columnList = \n' + format(JSON.stringify(columnList))); - - var name; - var type; - var length; - for (var j = 0; j < columnList.length; j++) { - column = columnList[j]; - name = column == null ? null : column.COLUMN_NAME; - if (name == null) { - continue; - } - type = CodeUtil.getJavaType(column.COLUMN_TYPE, false); - length = CodeUtil.getMaxLength(column.COLUMN_TYPE); - - log('getDoc [] for j=' + j + ': column = \n' + format(JSON.stringify(column))); - - doc += '\n' + name + ' | ' + type + ' | ' + length + ' | ' + App.toMD(column.COLUMN_COMMENT); - - } - - doc += '\n\n\n'; - - } - - } - - //[] >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> - - - //Request[] <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< - list = docObj == null ? null : docObj['Request[]']; - if (list != null) { - log('getDoc Request[] = \n' + format(JSON.stringify(list))); - - doc += '\n\n\n\n\n\n\n\n\n### 非开放请求' - + ' \n 版本 | 方法 | 数据和结构' - + ' \n -------- | ------------ | ------------ | ------------ '; - - for (var i = 0; i < list.length; i++) { - item = list[i]; - if (item == null) { - continue; - } - log('getDoc Request[] for i=' + i + ': item = \n' + format(JSON.stringify(item))); - - - doc += '\n' + item.version + ' | ' + item.method - + ' | ' + JSON.stringify(App.getStructure(item.structure, item.tag)); - } - - doc += '\n注: \n1.GET,HEAD方法不受限,可传任何 数据、结构。\n2.可在最外层传版本version来指定使用的版本,不传或 version <= 0 则使用最新版。\n\n\n\n\n\n\n'; - } - - - //Request[] >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> - - - //Function[] <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< - list = docObj == null ? null : docObj['Function[]']; - if (list != null) { - log('getDoc Function[] = \n' + format(JSON.stringify(list))); - - doc += '\n\n\n\n\n\n\n\n\n### 远程函数' - + ' \n 说明 | 示例' - + ' \n -------- | -------------- '; - - for (var i = 0; i < list.length; i++) { - item = list[i]; - if (item == null) { - continue; - } - log('getDoc Function[] for i=' + i + ': item = \n' + format(JSON.stringify(item))); - - - doc += '\n' + item.detail + ' | ' + JSON.stringify(item.demo); - } - - doc += '\n' //避免没数据时表格显示没有网格 - } - - //Function[] >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> - - App.onChange(false); - - - callback(doc); - -// log('getDoc callback(doc); = \n' + doc); - }); - - }, - - toDoubleJSON: function (json) { - if (json != null && json.indexOf("'") >= 0) { - json = json.replace(/'/g, '"'); - } - return json; - }, - - /**转为Markdown格式 - * @param s - * @return {*} - */ - toMD: function (s) { - if (s == null) { - s = ''; - } - else { - while (s.indexOf('|') >= 0) { - s = s.replace('|', '\|'); - } - while (s.indexOf('\n') >= 0) { - s = s.replace('\n', '
'); - } - } - - return s; - }, - - /**处理请求结构 - * @param obj - * @param tag - * @return {*} - */ - getStructure: function (obj, tag) { - if (obj == null) { - return null; - } - - log('getStructure tag = ' + tag + '; obj = \n' + format(JSON.stringify(obj))); - - if (obj instanceof Array) { - for (var i = 0; i < obj.length; i++) { - obj[i] = this.getStructure(obj[i]); - } - } - else if (obj instanceof Object) { - var v; - var nk; - for (var k in obj) { - if (k == null || k == '' || k == 'ADD' || k == 'REMOVE' || k == 'REPLACE' || k == 'PUT') { - delete obj[k]; - continue; - } - - v = obj[k]; - if (v == null) { - delete obj[k]; - continue; - } - - if (k == 'DISALLOW') { - nk = '不能传'; - } - else if (k == 'NECESSARY') { - nk = '必须传'; - } - else if (k == 'UNIQUE') { - nk = '不重复'; - } - else if (k == 'VERIFY') { - nk = '满足条件'; - } - else { - nk = null; - } - - if (v instanceof Object) { - v = this.getStructure(v); - } - else if (v === '!') { - v = '非必须传的字段'; - } - - if (nk != null) { - obj[nk] = v; - delete obj[k]; - } - } - } - - log('getStructure return obj; = \n' + format(JSON.stringify(obj))); - - if (tag != null) { - //补全省略的Table - if (this.isTableKey(tag) && obj[tag] == null) { - log('getStructure isTableKey(tag) && obj[tag] == null >>>>> '); - var realObj = {}; - realObj[tag] = obj; - obj = realObj; - log('getStructure realObj = \n' + JSON.stringify(realObj)); - } - obj.tag = tag; //补全tag - } - - return obj; - }, - - /**判断key是否为表名,用CodeUtil里的同名函数会在Safari上报undefined - * @param key - * @return - */ - isTableKey: function (key) { - log('isTableKey typeof key = ' + (typeof key)); - if (key == null) { - return false; - } - return /^[A-Z][A-Za-z0-9_]*$/.test(key); - }, - - log: function (msg) { - // App.log('Main. ' + msg) - }, - - getDoc4TestCase: function () { - var list = App.remotes || [] - var doc = '' - var item - for (var i = 0; i < list.length; i ++) { - item = list[i] == null ? null : list[i].Document - if (item == null || item.name == null) { - continue - } - doc += '\n\n#### ' + (item.version > 0 ? 'V' + item.version : 'V*') + ' ' + item.name + ' ' + item.url - doc += '\n```json\n' + JSON.stringify(JSON.parse(item.request), null, ' ') + '\n```\n' - } - return doc - }, - - enableML: function (enable) { - App.isMLEnabled = enable - App.testProcess = enable ? '机器学习:已开启,按量付费' : '机器学习:已关闭' - App.saveCache(App.server, 'isMLEnabled', enable) - }, - - /**回归测试 - * 原理: - 1.遍历所有上传过的测试用例(URL+请求JSON) - 2.逐个发送请求 - 3.对比同一用例的先后两次请求结果,如果不一致,就在列表中标记对应的用例(× 蓝黄红色下载(点击下载两个文件) √)。 - 4.如果这次请求结果正确,就把请求结果保存到和公司开发环境服务器的APIJSON Server,并取消标记 - - compare: 新的请求与上次请求的对比结果 - 0-相同,无颜色; - 1-对象新增字段或数组新增值,绿色; - 2-值改变,蓝色; - 3-对象缺少字段/整数变小数,黄色; - 4-code/值类型 改变,红色; - */ - test: function () { - var baseUrl = App.getBaseUrl() || '' - if (baseUrl == '') { - alert('请先输入有效的URL!') - return - } - //开放测试 - // if (baseUrl.indexOf('/apijson.cn') >= 0 || baseUrl.indexOf('/39.108.143.172') >= 0) { - // alert('请把URL改成你自己的!\n例如 http://localhost:8080') - // return - // } - if (baseUrl.indexOf('/apijson.org') >= 0) { - alert('请把URL改成 http://apijson.cn:8080 或 你自己的!\n例如 http://localhost:8080') - return - } - - const list = App.remotes || [] - const allCount = list.length; - doneCount = 0 - - if (allCount <= 0) { - alert('请先获取测试用例文档\n点击[查看共享]图标按钮') - return - } - App.testProcess = '正在测试: ' + 0 + '/' + allCount - - for (var i = 0; i < allCount; i ++) { - const item = list[i] - const document = item == null ? null : item.Document - if (document == null || document.name == null) { - doneCount ++ - continue - } - if (document.url == '/login' || document.url == '/logout') { //login会导致登录用户改变为默认的但UI上还显示原来的,单独测试OWNER权限时能通过很困惑 - App.log('test document.url == "/login" || document.url == "/logout" >> continue') - doneCount ++ - continue - } - App.log('test document = ' + JSON.stringify(document, null, ' ')) - - // App.restore(item) - // App.onChange(false) - - App.request(false, baseUrl + document.url, JSON.parse(document.request), function (url, res, err) { - - try { - App.onResponse(url, res, err) - App.log('test App.request >> res.data = ' + JSON.stringify(res.data, null, ' ')) - } catch (e) { - App.log('test App.request >> } catch (e) {\n' + e.message) - } - const response = JSON.stringify(res.data || {}) - - const it = item || {} //请求异步 - const d = it.Document || {} //请求异步 - const tr = it.TestRecord || {} //请求异步 - - if (App.isMLEnabled != true) { - const standardKey = App.isMLEnabled == true ? 'standard' : 'response'; - const standard = StringUtil.isEmpty(tr[standardKey], true) ? null : JSON.parse(tr[standardKey]); - - tr.compare = JSONResponse.compareResponse(standard, res.data, '', App.isMLEnabled) || {} - App.onTestResponse(allCount, it, d, tr, response, tr.compare || {}); - } - else { - App.request(false, App.server + '/get/testcompare/ml', { - "documentId": d.id, - "response": response, - "isML": true - }, function (url, res, err) { - var data = res.data || {} - if (data.code != 200) { - App.onResponse(url, res, err) - return - } - App.onTestResponse(allCount, it, d, tr, response, data.compare || {}); - }) - } - }) - } - }, - - onTestResponse: function(allCount, it, d, tr, response, cmp) { - doneCount ++ - App.testProcess = doneCount >= allCount ? (App.isMLEnabled ? '机器学习:已开启,按量付费' : '机器学习:已关闭') : '正在测试: ' + doneCount + '/' + allCount - - App.log('doneCount = ' + doneCount + '; d.name = ' + d.name + '; tr.compareType = ' + tr.compareType) - - tr.compare = cmp; - - it.compareType = tr.compare.code; - it.hintMessage = tr.compare.path + ' ' + tr.compare.msg; - switch (it.compareType) { - case JSONResponse.COMPARE_NO_STANDARD: - it.compareColor = 'white' - it.compareMessage = '确认正确后点击[这是对的]' - break; - case JSONResponse.COMPARE_KEY_MORE: - it.compareColor = 'green' - it.compareMessage = '新增字段/新增值' - break; - case JSONResponse.COMPARE_VALUE_CHANGE: - it.compareColor = 'blue' - it.compareMessage = '值改变' - break; - case JSONResponse.COMPARE_KEY_LESS: - it.compareColor = 'yellow' - it.compareMessage = '缺少字段/整数变小数' - break; - case JSONResponse.COMPARE_TYPE_CHANGE: - it.compareColor = 'red' - it.compareMessage = 'code/值类型 改变' - break; - default: - it.compareColor = 'white' - it.compareMessage = '查看结果' - break; - } - it.Document = d - it.TestRecord = tr - - var tests = App.tests || {} - tests[d.id] = response - App.tests = tests - // App.showTestCase(true) - - }, - - /** - * @param index - * @param item - */ - downloadTest: function (index, item) { - item = item || {} - var document = item.Document = item.Document || {} - var testRecord = item.TestRecord = item.TestRecord || {} - - saveTextAs( - '# APIJSON自动化回归测试-前\n主页: https://github.com/TommyLemon/APIJSON' - + '\n\n接口名称: \n' + (document.version > 0 ? 'V' + document.version : 'V*') + ' ' + document.name - + '\n返回结果: \n' + JSON.stringify(JSON.parse(testRecord.response || '{}'), null, ' ') - , '测试:' + document.name + '-前.txt' - ) - - /** - * 浏览器不允许连续下载,saveTextAs也没有回调。 - * 在第一个文本里加上第二个文本的信息? - * beyond compare会把第一个文件的后面一段与第二个文件匹配, - * 导致必须先删除第一个文件内的后面与第二个文件重复的一段,再重新对比。 - */ - setTimeout(function () { - var tests = App.tests || {} - saveTextAs( - '# APIJSON自动化回归测试-后\n主页: https://github.com/TommyLemon/APIJSON' - + '\n\n接口名称: \n' + (document.version > 0 ? 'V' + document.version : 'V*') + ' ' + document.name - + '\n返回结果: \n' + JSON.stringify(JSON.parse(tests[document.id] || '{}'), null, ' ') - , '测试:' + document.name + '-后.txt' - ) - - - if (StringUtil.isEmpty(testRecord.standard, true) == false) { - setTimeout(function () { - var tests = App.tests || {} - saveTextAs( - '# APIJSON自动化回归测试-标准\n主页: https://github.com/TommyLemon/APIJSON' - + '\n\n接口名称: \n' + (document.version > 0 ? 'V' + document.version : 'V*') + ' ' + document.name - + '\n测试结果: \n' + JSON.stringify(testRecord.compare || '{}', null, ' ') - + '\n测试标准: \n' + JSON.stringify(JSON.parse(testRecord.standard || '{}'), null, ' ') - , '测试:' + document.name + '-标准.txt' - ) - }, 5000) - } - - }, 5000) - - }, - - /** - * @param index - * @param item - */ - handleTest: function (right, index, item) { - item = item || {} - var document = item.Document = item.Document || {} - var testRecord = item.TestRecord = item.TestRecord || {} - - if (right) { - var tests = App.tests || {} - testRecord.response = tests[document.id] - } - - if (right != true) { - var isBefore = item.showType != 'before' - item.showType = isBefore ? 'before' : 'after' - Vue.set(App.remotes, index, item); - - App.view = 'code' - App.jsoncon = isBefore ? (testRecord.response || '') : (App.tests[document.id] || '') - } - else { - const isML = App.isMLEnabled - - var url - var req - if (isML != true) { - var response = StringUtil.isEmpty(testRecord.response, true) ? null : JSON.parse(testRecord.response); - var standard = StringUtil.isEmpty(testRecord.standard, true) ? null : JSON.parse(testRecord.standard); - - var code = response.code; - delete response.code; //code必须一致,下面没用到,所以不用还原 - - var stddObj = isML ? JSONResponse.updateStandard(standard || {}, response) : {}; - stddObj.code = code; - var stdd = isML ? JSON.stringify(stddObj) : null; - - url = App.server + '/post' - req = { - TestRecord: { - userId: App.User.id, //TODO 权限问题? item.userId, - documentId: document.id, - compare: JSON.stringify(testRecord.compare || {}), - response: testRecord.response - }, - tag: 'TestRecord' - } - } - else { - url = App.server + '/post/testrecord/ml' - req = { - documentId: document.id - } - } - - App.request(true, url, req, function (url, res, err) { - App.onResponse(url, res, err) - - var data = res.data || {} - if (data.code != 200) { - if (isML) { - alert('机器学习更新标准 异常:\n' + data.msg) - } - } - else { - item.compareType = 0 - item.compareMessage = '查看结果' - item.compareColor = 'white' - item.hintMessage = '结果正确' - testRecord.compare = {} - // testRecord.standard = stdd - App.showTestCase(true, false) - } - - }) - - } - }, - - //显示详细信息, :data-hint :data, :hint 都报错,只能这样 - setTestHint(index, item) { - this.$refs.testResultButtons[index].setAttribute('data-hint', item.hintMessage); - }, - -// APIJSON >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> - - }, - watch: { - jsoncon: function () { - App.showJsonView() - } - }, - computed: { - theme: function () { - var th = this.themes[this.checkedTheme] - var result = {} - var index = 0; - ['key', 'String', 'Number', 'Boolean', 'Null', 'link-link'].forEach(function(key) { - result[key] = th[index] - index++ - }) - return result - } - }, - created () { - try { //可能URL_BASE是const类型,不允许改,这里是初始化,不能出错 - var url = this.getCache('', 'URL_BASE') - if (StringUtil.isEmpty(url, true) == false) { - URL_BASE = url - } - var schema = this.getCache('', 'schema') - if (StringUtil.isEmpty(schema, true) == false) { - this.schema = schema - } - var server = this.getCache('', 'server') - if (StringUtil.isEmpty(server, true) == false) { - this.server = server - } - - this.locals = this.getCache('', 'locals') || [] - } catch (e) { - this.log('created try { ' + - '\nvar schema = this.getCache(, schema)' + - '\n} catch (e) {\n' + e.message) - } - try { //这里是初始化,不能出错 - var accounts = this.getCache(URL_BASE, 'accounts') - if (accounts != null) { - this.accounts = accounts - this.currentAccountIndex = this.getCache(URL_BASE, 'currentAccountIndex') - } - } catch (e) { - this.log('created try { ' + - '\nvar accounts = this.getCache(URL_BASE, accounts)' + - '\n} catch (e) {\n' + e.message) - } - - try { //可能URL_BASE是const类型,不允许改,这里是初始化,不能出错 - this.User = this.getCache(this.server, 'User') || {} - this.isMLEnabled = this.getCache(server, 'isMLEnabled') - this.testProcess = this.isMLEnabled ? '机器学习:已开启,按量付费' : '机器学习:已关闭' - } catch (e) { - this.log('created try { ' + - '\nthis.User = this.getCache(this.server, User) || {}' + - '\n} catch (e) {\n' + e.message) - } - - - //无效,只能在index里设置 vUrl.value = this.getCache('', 'URL_BASE') - this.listHistory() - this.transfer() - } - }) -})() + +(function () { + const DEBUG = false // true + const IS_NODE = typeof window == 'undefined' + const IS_BROWSER = typeof window == 'object' + + if (IS_NODE) { // 解决在 Node 环境下缺少相关变量/常量/函数导致报错 + try { + eval(` + var alert = function(msg) {console.log('alert: ' + msg)}; + // var console = {log: function(msg) {}}; + + var vUrl = {value: 'http://localhost:8080/get'}; + var vUrlComment = {value: ''}; + var vTransfer = {value: '', disabled: false}; + var vType = {value: 'JSON'}; + var vSend = {value: '', disabled: false}; + + var vInput = {value: ''}; + var vWarning = {value: ''}; + var vComment = {value: ''}; + var vHeader = {value: ''}; + var vRandom = {value: ''}; + var vScript = {value: ''}; + var vOutput = {value: ''}; + + var vAccount = {value: ''}; + var vPassword = {value: ''}; + var vVerify = {value: ''}; + var vRemember = {checked: true} + + var vRequestMarkdown = {value: ''}; + var vMarkdown = {value: ''}; + var vPage = {value: '0'}; + var vCount = {value: '100'}; + var vSearch = {value: ''}; + var vTestCasePage = {value: '0'}; + var vTestCaseCount = {value: '100'}; + var vTestCaseSearch = {value: ''}; + var vRandomPage = {value: '0'}; + var vRandomCount = {value: '100'}; + var vRandomSearch = {value: ''}; + var vRandomSubPage = {value: '0'}; + var vRandomSubCount = {value: '100'}; + var vRandomSubSearch = {value: ''}; + + var Vue = require('./vue.min'); // 某些版本不兼容 require('vue'); + var StringUtil = require('../apijson/StringUtil'); + var CodeUtil = require('../apijson/CodeUtil'); + var JSONObject = require('../apijson/JSONObject'); + var JSONResponse = require('../apijson/JSONResponse'); + var JSONRequest = require('../apijson/JSONRequest'); + var localforage = require('./localforage.min'); + var clipboard = require('./clipboard.min'); + var jsonlint = require('./jsonlint'); + var JSON5 = require('json5'); + // var window = {}; + // var $ = require('./jquery').jQuery; + // var $ = { + // isEmptyObject: function (obj) { + // return obj == null || Object.keys(obj).length <= 0; + // } + // }; + + // var LocalStorage = require('node-localstorage').LocalStorage; + // var localStorage = new LocalStorage('./scratch'); + var localStorage = { + getItem: function (key) {}, + setItem: function (key, value) {} + } + // var difflib = require('./difflib'); + // var diffview = require('./diffview'); + // var editor = require('./editor'); + // var FileSaver = require('./FileSaver'); + // var helper = require('./helper'); + // var jquery = require('./jquery'); + // var jsonlint = require('./jsonlint'); + // var parse = require('./parse'); + // var uuid = require('./uuid'); + + var axios = require('axios'); + var editormd = null; + `) + } catch (e) { + console.log(e) + } + } + + function log(msg) { + if (DEBUG) { + console.log(msg) + } + } + + Vue.component('vue-item', { + props: ['jsondata', 'theme', 'thiz'], + template: '#item-template' + }) + + Vue.component('vue-outer', { + props: ['jsondata', 'isend', 'thiz', 'path', 'theme'], + template: '#outer-template' + }) + + Vue.component('vue-expand', { + props: [], + template: '#expand-template' + }) + + Vue.component('vue-val', { + props: ['field', 'val', 'isend', 'thiz', 'path', 'theme'], + template: '#val-template' + }) + + Vue.use({ + install: function (Vue, options) { + + // 判断数据类型 + Vue.prototype.getTyp = function (val) { + return toString.call(val).split(']')[0].split(' ')[1] + } + + // 判断是否是对象或者数组,以对下级进行渲染 + Vue.prototype.isObjectArr = function (val) { + return ['Object', 'Array'].indexOf(this.getTyp(val)) > -1 + } + + // 折叠 + Vue.prototype.fold = function ($event) { + var target = Vue.prototype.expandTarget($event) + target.siblings('svg').show() + target.hide().parent().siblings('.expand-view').hide() + target.parent().siblings('.fold-view').show() + } + // 展开 + Vue.prototype.expand = function ($event) { + var target = Vue.prototype.expandTarget($event) + target.siblings('svg').show() + target.hide().parent().siblings('.expand-view').show() + target.parent().siblings('.fold-view').hide() + } + + //获取展开折叠的target + Vue.prototype.expandTarget = function ($event) { + switch($event.target.tagName.toLowerCase()) { + case 'use': + return $($event.target).parent() + case 'label': + return $($event.target).closest('.fold-view').siblings('.expand-wraper').find('.icon-square-plus').first() + default: + return $($event.target) + } + } + + // 格式化值 + Vue.prototype.formatVal = function (val) { + switch(Vue.prototype.getTyp(val)) { + case 'String': + return '"' + val + '"' + case 'Null': + return 'null' + default: + return val + } + } + + // 判断值是否是链接 + Vue.prototype.isaLink = function (val) { + return /^((https|http|ftp|rtsp|mms)?:\/\/)[^\s]+/.test(val) + } + + // 计算对象的长度 + Vue.prototype.objLength = function (obj) { + return Object.keys(obj).length + } + + /**渲染 JSON key:value 项 + * @author TommyLemon + * @param val + * @param key + * @return {boolean} + */ + Vue.prototype.onRenderJSONItem = function (val, key, path) { + if (isSingle || key == null) { + return true + } + if (key == '_$_this_$_') { + // return true + return false + } + + var method = App.getMethod(); + var isRestful = ! JSONObject.isAPIJSONPath(method); + + try { + if (val instanceof Array) { + // alert('onRenderJSONItem key = ' + key + '; val = ' + JSON.stringify(val)) + + var ckey = key == null ? null : key.substring(0, key.lastIndexOf('[]')); + + var aliaIndex = ckey == null ? -1 : ckey.indexOf(':'); + var objName = aliaIndex < 0 ? ckey : ckey.substring(0, aliaIndex); + + var firstIndex = objName == null ? -1 : objName.indexOf('-'); + var firstKey = firstIndex < 0 ? objName : objName.substring(0, firstIndex); + + for (var i = 0; i < val.length; i++) { + var cPath = (StringUtil.isEmpty(path, false) ? '' : path + '/') + key; + + var vi = val[i] + + if (JSONResponse.isObject(vi) && JSONObject.isTableKey(firstKey || '', vi, isRestful)) { + // var newVal = parseJSON(JSON.stringify(val[i])) + if (vi == null) { + continue + } + + var curPath = cPath + '/' + i; + var curTable = firstKey; + var thiz = { + _$_path_$_: curPath, + _$_table_$_: curTable + }; + + var newVal = {}; + for (var k in vi) { + newVal[k] = vi[k]; //提升性能 + if (App.isFullAssert) { + try { + var cri = App.currentRemoteItem || {}; + var tr = cri.TestRecord || {}; + var d = cri.Document || {}; + var standard = App.isMLEnabled ? tr.standard : tr.response; + var standardObj = StringUtil.isEmpty(standard, true) ? null : parseJSON(standard); + var tests = App.tests[String(App.currentAccountIndex)] || {}; + var responseObj = (tests[d.id] || {})[0] + + var pathUri = (StringUtil.isEmpty(curPath, false) ? '' : curPath + '/') + k; + var pathKeys = StringUtil.split(pathUri, '/'); + var target = App.isMLEnabled ? JSONResponse.getStandardByPath(standardObj, pathKeys) : JSONResponse.getValByPath(standardObj, pathKeys); + var real = JSONResponse.getValByPath(responseObj, pathKeys); + var cmp = App.isMLEnabled ? JSONResponse.compareWithStandard(target, real, pathUri) : JSONResponse.compareWithBefore(target, real, pathUri); +// cmp.path = pathUri; + var cmpShowObj = JSONResponse.getCompareShowObj(cmp); + thiz[k] = [cmpShowObj.compareType, cmpShowObj.compareColor, cmpShowObj.compareMessage]; + var countKey = '_$_' + cmpShowObj.compareColor + 'Count_$_'; + thiz[countKey] = thiz[countKey] == null ? 1 : thiz[countKey] + 1; + } catch (e) { + thiz[k] = [JSONResponse.COMPARE_ERROR, 'red', e.message]; + var countKey = '_$_redCount_$_'; + thiz[countKey] = thiz[countKey] == null ? 1 : thiz[countKey] + 1; + } + } + + delete vi[k] + } + + vi._$_this_$_ = JSON.stringify(thiz) + for (var k in newVal) { + vi[k] = newVal[k] + } + } + else { + this.onRenderJSONItem(vi, '' + i, cPath); + } + + // this.$children[i]._$_this_$_ = key + // alert('this.$children[i]._$_this_$_ = ' + this.$children[i]._$_this_$_) + } + } + else if (val instanceof Object) { + var aliaIndex = key == null ? -1 : key.indexOf(':'); + var objName = aliaIndex < 0 ? key : key.substring(0, aliaIndex); + + // var newVal = parseJSON(JSON.stringify(val)) + + var curPath = (StringUtil.isEmpty(path, false) ? '' : path + '/') + key; + var curTable = JSONObject.isTableKey(objName, val, isRestful) ? objName : null; + var thiz = { + _$_path_$_: curPath, + _$_table_$_: curTable + }; + + var newVal = {}; + for (var k in val) { + newVal[k] = val[k]; //提升性能 + if (App.isFullAssert) { + try { + var cri = App.currentRemoteItem || {}; + var tr = cri.TestRecord || {}; + var d = cri.Document || {}; + var standard = App.isMLEnabled ? tr.standard : tr.response; + var standardObj = StringUtil.isEmpty(standard, true) ? null : parseJSON(standard); + var tests = App.tests[String(App.currentAccountIndex)] || {}; + var responseObj = (tests[d.id] || {})[0] + + var pathUri = (StringUtil.isEmpty(curPath, false) ? '' : curPath + '/') + k; + var pathKeys = StringUtil.split(pathUri, '/'); + var target = App.isMLEnabled ? JSONResponse.getStandardByPath(standardObj, pathKeys) : JSONResponse.getValByPath(standardObj, pathKeys); + var real = JSONResponse.getValByPath(responseObj, pathKeys); + // c = JSONResponse.compareWithBefore(target, real, path); + var cmp = App.isMLEnabled ? JSONResponse.compareWithStandard(target, real, pathUri) : JSONResponse.compareWithBefore(target, real, pathUri); +// cmp.path = pathUri; + var cmpShowObj = JSONResponse.getCompareShowObj(cmp); + thiz[k] = [cmpShowObj.compareType, cmpShowObj.compareColor, cmpShowObj.compareMessage]; + var countKey = '_$_' + cmpShowObj.compareColor + 'Count_$_'; + thiz[countKey] = thiz[countKey] == null ? 1 : thiz[countKey] + 1; + } catch (e) { + thiz[k] = [JSONResponse.COMPARE_ERROR, 'red', e.message]; + var countKey = '_$_redCount_$_'; + thiz[countKey] = thiz[countKey] == null ? 1 : thiz[countKey] + 1; + } + } + + delete val[k]; + } + + val._$_this_$_ = JSON.stringify(thiz) + for (var k in newVal) { + val[k] = newVal[k]; + } + + // val = Object.assign({ _$_this_$_: objName }, val) //解决多显示一个逗号 , + + // this._$_this_$_ = key TODO 不影响 JSON 的方式,直接在组件读写属性 + // alert('this._$_this_$_ = ' + this._$_this_$_) + } + + } catch (e) { + if (DEBUG) { + alert('onRenderJSONItem try { ... } catch (e) {\n' + e.message) + } else { + console.log(e) + } + } + + return true + + } + + + /**显示 Response JSON 的注释 + * @author TommyLemon + * @param val + * @param key + * @param $event + */ + Vue.prototype.setResponseHint = function (val, key, $event, isAssert, color) { + console.log('setResponseHint') + this.$refs.responseKey.setAttribute('data-hint', isSingle ? '' : this.getResponseHint(val, key, $event, isAssert, color)); + } + /**获取 Response JSON 的注释 + * 方案一: + * 拿到父组件的 key,逐层向下传递 + * 问题:拿不到爷爷组件 "Comment[]": [ { "id": 1, "content": "content1" }, { "id": 2 }... ] + * + * 方案二: + * 改写 jsonon 的 refKey 为 key0/key1/.../refKey + * 问题:遍历,改 key;容易和特殊情况下返回的同样格式的字段冲突 + * + * 方案三: + * 改写 jsonon 的结构,val 里加 .path 或 $.path 之类的隐藏字段 + * 问题:遍历,改 key;容易和特殊情况下返回的同样格式的字段冲突 + * + * @author TommyLemon + * @param val + * @param key + * @param $event + */ + Vue.prototype.getResponseHint = function (val, key, $event, isAssert, color) { + // alert('setResponseHint key = ' + key + '; val = ' + JSON.stringify(val)) + + var s = '' + + try { + var standardObj = null; + try { + var currentItem = App.isTestCaseShow ? App.remotes[App.currentDocIndex] : App.currentRemoteItem; + standardObj = parseJSON(((currentItem || {}).TestRecord || {}).standard); + } catch (e3) { + log(e3) + } + + var path = null + var table = null + var column = null + + var method = App.isTestCaseShow ? ((App.currentRemoteItem || {}).Document || {}).url : App.getMethod(); + var isRestful = ! JSONObject.isAPIJSONPath(method); + + if (val instanceof Object && (val instanceof Array == false)) { + + var parent = $event.currentTarget.parentElement.parentElement + var valString = parent.textContent + + // alert('valString = ' + valString) + + var i = valString.indexOf('"_$_this_$_":') + if (i >= 0) { + valString = valString.substring(i + '"_$_this_$_":'.length) + i = valString.indexOf('}"') + var i2 = valString.indexOf('"{') + if (i >= 0 && i2 >= 0 && i2 < i) { + valString = valString.substring(i2 + 1, i + 1) + // alert('valString = ' + valString) + var _$_this_$_ = parseJSON(valString) || {} + path = _$_this_$_._$_path_$_ + table = _$_this_$_._$_table_$_ + } + + var aliaIndex = key == null ? -1 : key.indexOf(':'); + var objName = aliaIndex < 0 ? key : key.substring(0, aliaIndex); + + if (JSONObject.isTableKey(objName, val, isRestful)) { + table = objName + } + else if (JSONObject.isTableKey(table, val, isRestful)) { + column = key + } + + // alert('path = ' + path + '; table = ' + table + '; column = ' + column) + } + } + else { + var parent = $event.currentTarget.parentElement.parentElement + var valString = parent.textContent + + // alert('valString = ' + valString) + + var i = valString.indexOf('"_$_this_$_":') + if (i >= 0) { + valString = valString.substring(i + '"_$_this_$_":'.length) + i = valString.indexOf('}"') + var i2 = valString.indexOf('"{') + if (i >= 0 && i2 >= 0 && i2 < i) { + valString = valString.substring(i2 + 1, i + 1) + // alert('valString = ' + valString) + var _$_this_$_ = parseJSON(valString) || {} + path = _$_this_$_ == null ? '' : _$_this_$_._$_path_$_ + table = _$_this_$_ == null ? '' : _$_this_$_._$_table_$_ + } + } + + if (val instanceof Array && JSONObject.isArrayKey(key, val, isRestful)) { + var key2 = key == null ? null : key.substring(0, key.lastIndexOf('[]')); + + var aliaIndex = key2 == null ? -1 : key2.indexOf(':'); + var objName = aliaIndex < 0 ? key2 : key2.substring(0, aliaIndex); + + var firstIndex = objName == null ? -1 : objName.indexOf('-'); + var firstKey = firstIndex < 0 ? objName : objName.substring(0, firstIndex); + + // alert('key = ' + key + '; firstKey = ' + firstKey + '; firstIndex = ' + firstIndex) + if (JSONObject.isTableKey(firstKey || '', null, isRestful)) { + table = firstKey + + var s0 = ''; + if (firstIndex > 0) { + objName = objName.substring(firstIndex + 1); + firstIndex = objName.indexOf('-'); + column = firstIndex < 0 ? objName : objName.substring(0, firstIndex) + + var pathUri = (StringUtil.isEmpty(path) ? '' : path + '/') + key; + + var c = CodeUtil.getCommentFromDoc(docObj == null ? null : docObj['[]'], table, column, method, App.database, App.language, true, false, pathUri.split('/'), isRestful, val, true, standardObj); // this.getResponseHint({}, table, $event + s0 = column + (StringUtil.isEmpty(c, true) ? '' : ': ' + c) + } + + var pathUri = (StringUtil.isEmpty(path) ? '' : path + '/') + (StringUtil.isEmpty(column) ? key : column); + var c; + if (isAssert) { + try { + var tr = App.currentRemoteItem.TestRecord || {}; + var d = App.currentRemoteItem.Document || {}; + var standard = App.isMLEnabled ? tr.standard : tr.response; + var standardObj = StringUtil.isEmpty(standard, true) ? null : parseJSON(standard); + var tests = App.tests[String(App.currentAccountIndex)] || {}; + var responseObj = (tests[d.id] || {})[0] + + var pathKeys = StringUtil.split(pathUri, '/'); + var target = App.isMLEnabled ? JSONResponse.getStandardByPath(standardObj, pathKeys) : JSONResponse.getValByPath(standardObj, pathKeys); + var real = JSONResponse.getValByPath(responseObj, pathKeys); + // c = JSONResponse.compareWithBefore(target, real, path); + var cmp = App.isMLEnabled ? JSONResponse.compareWithStandard(target, real, path) : JSONResponse.compareWithBefore(target, real, path); +// cmp.path = pathUri; + return JSONResponse.getCompareShowObj(cmp); + } catch (e) { + s += '\n' + e.message + } + } else { + c = CodeUtil.getCommentFromDoc(docObj == null ? null : docObj['[]'], table, isRestful ? key : null, method, App.database, App.language, true, false, pathUri.split('/'), isRestful, val, true, standardObj); + } + + s = (StringUtil.isEmpty(path) ? '' : path + '/') + key + ' 中 ' + + ( + StringUtil.isEmpty(c, true) ? '' : table + ': ' + + c + ((StringUtil.isEmpty(s0, true) ? '' : ' - ' + s0) ) + ); + + return s; + } + //导致 key[] 的 hint 显示为 key[]key[] else { + // s = (StringUtil.isEmpty(path) ? '' : path + '/') + key + // } + } + else { + if (isRestful || JSONObject.isTableKey(table)) { + column = key + } + // alert('path = ' + path + '; table = ' + table + '; column = ' + column) + } + } + // alert('setResponseHint table = ' + table + '; column = ' + column) + + var pathUri = (StringUtil.isEmpty(path) ? '' : path + '/') + key; + var c; + if (isAssert) { + try { + var tr = App.currentRemoteItem.TestRecord || {}; + var d = App.currentRemoteItem.Document || {}; + var standard = App.isMLEnabled ? tr.standard : tr.response; + var standardObj = StringUtil.isEmpty(standard, true) ? null : parseJSON(standard); + var tests = App.tests[String(App.currentAccountIndex)] || {}; + var responseObj = (tests[d.id] || {})[0] + + var pathKeys = StringUtil.split(pathUri, '/'); + var target = App.isMLEnabled ? JSONResponse.getStandardByPath(standardObj, pathKeys) : JSONResponse.getValByPath(standardObj, pathKeys); + var real = JSONResponse.getValByPath(responseObj, pathKeys); +// c = JSONResponse.compareWithBefore(target, real, path); + var cmp = App.isMLEnabled ? JSONResponse.compareWithStandard(target, real, path) : JSONResponse.compareWithBefore(target, real, path); +// cmp.path = pathUri; + return JSONResponse.getCompareShowObj(cmp); + } catch (e) { + s += '\n' + e.message + } + } else { + c = CodeUtil.getCommentFromDoc(docObj == null ? null : docObj['[]'], table, isRestful ? key : column, method, App.database, App.language, true, false, pathUri.split('/'), isRestful, val, true, standardObj); + } + + s += pathUri + (StringUtil.isEmpty(c, true) ? '' : ': ' + c) + } + catch (e) { + s += '\n' + e.message + } + + return s; + }, + + /**显示 Response JSON 的注释 + * @author TommyLemon + * @param val + * @param key + * @param $event + */ + Vue.prototype.setResponseCounts = function (val, key, $event, isAssert) { + console.log('setResponseCounts') + this.$refs.responseGreenAssert.setAttribute('data-content', isSingle ? '' : this.getResponseGreenCount(val, key, $event)); + this.$refs.responseBlueAssert.setAttribute('data-content', isSingle ? '' : this.getResponseBlueCount(val, key, $event)); + this.$refs.responseOrangeAssert.setAttribute('data-content', isSingle ? '' : this.getResponseOrangeCount(val, key, $event)); + this.$refs.responseRedAssert.setAttribute('data-content', isSingle ? '' : this.getResponseRedCount(val, key, $event)); + }, + /**获取 Response 断言失败 的数量 + * @author TommyLemon + * @param val + * @param key + * @param $event + */ + Vue.prototype.setResponseAllCount = function (val, key, $event) { + return this.setResponseHint(val, key, $event, true, 'all') + }, + /**获取 Response 断言失败 的数量 + * @author TommyLemon + * @param val + * @param key + * @param $event + */ + Vue.prototype.getResponseAllCount = function (val, key, $event) { + return this.getResponseGreenCount(val, key, $event) + this.getResponseBlueCount(val, key, $event) + this.getResponseOrangeCount(val, key, $event) + this.getResponseRedCount(val, key, $event); + }, + Vue.prototype.setResponseColorCount = function (val, key, $event) { + console.log('setResponseColorCount') + var assert = isSingle ? null : this.getResponseHint(val, key, $event, true, 'all') + var responseAssert = $event.target; // this.$refs.responseAssert +// responseAssert.innerText = (StringUtil.trim(assert.count) || '1'); +// $(responseAssert).attr('data-hint', assert == null ? '' : StringUtil.trim(assert.hintMessage)); +// responseAssert.setAttribute('data-content', assert == null ? '' : (StringUtil.trim(assert.count) || '1')); + responseAssert.setAttribute('data-hint', assert == null ? '' : StringUtil.trim(assert.compareMessage)); +// $(responseAssert).attr('background', assert == null ? '' : assert.compareColor); +// $(responseAssert).attr('visibility', assert == null || assert.code == 0 ? 'hidden' : 'show'); + }, + Vue.prototype.setResponseCount = function (val, key, $event, color) { + console.log('setResponseCount') +// var count = isSingle ? 0 : this.getResponseCount(val, key, $event, color) + var assert = isSingle ? null : this.getResponseHint(val, key, $event, true, 'all') + var responseAssert = $event.target; // this.$refs['response' + StringUtil.firstCase(color, true) + 'Assert'] +// responseAssert.innerText = (StringUtil.trim(assert.count) || '1'); + $(responseAssert).attr('data-hint', assert == null ? '' : StringUtil.trim(assert.compareMessage)); +// responseAssert.setAttribute('data-content', assert == null ? '' : (StringUtil.trim(assert.count) || '1')); +// responseAssert.setAttribute('data-hint', assert == null ? '' : StringUtil.trim(assert.hintMessage)); +// $(responseAssert).attr('background', assert == null ? '' : assert.compareColor); +// $(responseAssert).attr('visibility', assert == null || assert.code == 0 ? 'hidden' : 'show'); + }, + Vue.prototype.getResponseCount = function (val, key, $event, color) { + return this.getResponseHint(val, key, $event, true, color) + }, + Vue.prototype.setResponseGreenCount = function (val, key, $event, isAssert) { + this.setResponseCount(val, key, $event, 'green') + }, + Vue.prototype.getResponseGreenCount = function (val, key, $event) { + return this.getResponseHint(val, key, $event, true, 'green') + }, + Vue.prototype.setResponseBlueCount = function (val, key, $event, isAssert) { + this.setResponseCount(val, key, $event, 'blue') + }, + Vue.prototype.getResponseBlueCount = function (val, key, $event) { + return this.getResponseHint(val, key, $event, true, 'blue') + }, + Vue.prototype.setResponseOrangeCount = function (val, key, $event, isAssert) { + this.setResponseCount(val, key, $event, 'orange') + }, + Vue.prototype.getResponseOrangeCount = function (val, key, $event) { + return this.getResponseHint(val, key, $event, true, 'orange') + }, + Vue.prototype.setResponseRedCount = function (val, key, $event, isAssert) { + this.setResponseCount(val, key, $event, 'red') + }, + Vue.prototype.getResponseRedCount = function (val, key, $event) { + return this.getResponseHint(val, key, $event, true, 'red') + } + } + }) + + + var initJson = {} + +// 主题 [key, String, Number, Boolean, Null, link-link, link-hover] + var themes = [ + ['#92278f', '#3ab54a', '#25aae2', '#f3934e', '#f34e5c', '#717171'], + ['rgb(19, 158, 170)', '#cf9f19', '#ec4040', '#7cc500', 'rgb(211, 118, 126)', 'rgb(15, 189, 170)'], + ['#886', '#25aae2', '#e60fc2', '#f43041', 'rgb(180, 83, 244)', 'rgb(148, 164, 13)'], + ['rgb(97, 97, 102)', '#cf4c74', '#20a0d5', '#cd1bc4', '#c1b8b9', 'rgb(25, 8, 174)'] + ] + + + + +// APIJSON <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< + + var ERR_MSG = `出现了一些问题,可以按照以下步骤解决: +1.检查网络连接是否畅通,可用浏览器打开右侧地址: https://www.baidu.com/s?wd=%22APIJSON%22 +2.检查 URL 是否为一个可用的 域名/IPV4 地址,可用浏览器打开试试:正常返回结果 或 非 GET 请求返回 Whitelabel Error Page,一般都没问题 +3.开启或关闭 右上方 设置>托管服务器代理,然后再试:如果开启后才通应该是 CORS 跨域问题;关闭后才通应该是用外网服务代理来访问内网导致,可退出登录后修改退关服务器地址为内网的 APIJSON 代理服务地址 +4.Disable 关闭 VPN 等 电脑/手机/平板 上的网络代理软件 App 客户端,或者切换代理服务器地址,然后再试 +5.按 Fn+F12 或 右键网页>Inspect 检查 查看 Network 接口调用信息和 Console 控制台日志 +6.查看请求目标服务器上的日志,优先找异常报错内容 +7.改用 Postman 等其它 HTTP API 接口工具测试同一个接口 +8.再试一次 + +# 问题与解答大全(可截屏后 New issue 上报问题等待解答): +https://github.com/TommyLemon/APIAuto/issues + +如果是请求 APIJSON 后端服务,则使用以下链接: +https://github.com/Tencent/APIJSON/issues + +There may be something wrong, you can follow by the steps: +1. Check whether the network connection is available, you can open the address with a browser: https://www.google.com/search?q=%22APIJSON%22 +2. Check whether the URL is an available domain name/IPV4 address, try opening it with a browser: if return the result normally or return a Whitelabel Error Page for a non-GET request, generally the URL is available +3. Turn it on or off at the top right, Settings>Server Proxy, and try again: If it is enabled, it should be a CORS cross-domain problem; and if it is turned off, it should be caused by using an external network service proxy to access the intranet, You can log out and modify the logout server address to the APIJSON proxy service address of the intranet +4. Disable the network proxy software App client on the computer/phone/tablet such as VPN, or switch the proxy server address, and then try again +5. Press Fn+F12 or right-click the webpage>Inspect to view the Network interface call information and Console console log +6. Check the log on the request target server, and give priority to find the abnormal error content +7. Use other HTTP API tools such as Postman to test the same interface +8. Try again + +# Questions and answers (you can report the problem after taking a screenshot and wait for the answer): +https://github.com/TommyLemon/APIAuto/issues + +If you are requesting an APIJSON backend service, use the following link: +https://github.com/Tencent/APIJSON/issues +`; + + + function getRequestFromURL(url_, tryParse) { + var url = url_ || window.location.search; + + var index = url == null ? -1 : url.indexOf("?") + if(index < 0) { //判断是否有参数 + return null; + } + + var theRequest = null; + var str = url.substring(index + 1); //从第一个字符开始 因为第0个是?号 获取所有除问号的所有符串 + var arr = str.split("&"); //截除“&”生成一个数组 + + var len = arr == null ? 0 : arr.length; + for(var i = 0; i < len; i++) { + var part = arr[i]; + var ind = part == null ? -1 : part.indexOf("="); + if (ind <= 0) { + continue + } + + if (theRequest == null) { + theRequest = {}; + } + + var v = decodeURIComponent(part.substring(ind+1)); + if (tryParse == true) { + try { + v = parseJSON(v) + } + catch (e) { + console.log(e) + } + } + + theRequest[part.substring(0, ind)] = v; + } + + return theRequest; + } + + + function markdownToHTML(md, isRequest) { + if (typeof editormd == 'undefined' || editormd == null) { + return; + } + + if (isRequest) { + vRequestMarkdown.innerHTML = ''; + } + else { + vMarkdown.innerHTML = ''; + } + editormd.markdownToHTML(isRequest ? 'vRequestMarkdown' : "vMarkdown", { + markdown : md ,//+ "\r\n" + $("#append-test").text(), + //htmlDecode : true, // 开启 HTML 标签解析,为了安全性,默认不开启 + htmlDecode : "style,script,iframe", // you can filter tags decode + //toc : false, + tocm : true, // Using [TOCM] + //tocContainer : "#custom-toc-container", // 自定义 ToC 容器层 + //gfm : false, + tocDropdown : true, + // markdownSourceCode : true, // 是否保留 Markdown 源码,即是否删除保存源码的 Textarea 标签 + taskList : true, + tex : true, // 默认不解析 + flowChart : true, // 默认不解析 + sequenceDiagram : true, // 默认不解析 + }); + } + + + + var PLATFORM_POSTMAN = 'POSTMAN' + var PLATFORM_SWAGGER = 'SWAGGER' + var PLATFORM_YAPI = 'YAPI' + var PLATFORM_RAP = 'RAP' + + var REQUEST_TYPE_PARAM = 'PARAM' // GET ?a=1&b=c&key=value + var REQUEST_TYPE_FORM = 'FORM' // POST x-www-form-urlencoded + var REQUEST_TYPE_DATA = 'DATA' // POST form-data + var REQUEST_TYPE_JSON = 'JSON' // POST application/json + var REQUEST_TYPE_GRPC = 'GRPC' // POST application/json + var REQUEST_TYPE_GET = 'GET' // GET ?a=1&b=c&key=value + var REQUEST_TYPE_POST = 'POST' // POST application/json + var REQUEST_TYPE_PUT = 'PUT' // PUT + var REQUEST_TYPE_PATCH = 'PATCH' // PATCH + var REQUEST_TYPE_DELETE = 'DELETE' // DELETE + var REQUEST_TYPE_HEAD = 'HEAD' // HEAD + var REQUEST_TYPE_OPTIONS = 'OPTIONS' // OPTIONS + var REQUEST_TYPE_TRACE = 'TRACE' // TRACE + var HTTP_METHODS = [REQUEST_TYPE_GET, REQUEST_TYPE_POST, REQUEST_TYPE_PUT, REQUEST_TYPE_PATCH, REQUEST_TYPE_DELETE, REQUEST_TYPE_HEAD, REQUEST_TYPE_OPTIONS, REQUEST_TYPE_TRACE] + var HTTP_POST_TYPES = [REQUEST_TYPE_POST, REQUEST_TYPE_JSON, REQUEST_TYPE_FORM, REQUEST_TYPE_DATA, REQUEST_TYPE_GRPC] + var HTTP_URL_ARG_TYPES = [REQUEST_TYPE_GET, REQUEST_TYPE_PARAM, REQUEST_TYPE_FORM] + var HTTP_JSON_TYPES = [REQUEST_TYPE_POST, REQUEST_TYPE_JSON, REQUEST_TYPE_GRPC] + var HTTP_FORM_DATA_TYPES = [REQUEST_TYPE_DATA, REQUEST_TYPE_PUT, REQUEST_TYPE_DELETE] + var HTTP_CONTENT_TYPES = [REQUEST_TYPE_PARAM, REQUEST_TYPE_FORM, REQUEST_TYPE_DATA, REQUEST_TYPE_JSON, REQUEST_TYPE_GRPC] + + var CONTENT_TYPE_MAP = { + // 'PARAM': 'text/plain', + 'FORM': 'application/x-www-form-urlencoded', + 'DATA': 'multipart/form-data', + 'JSON': 'application/json', + 'GRPC': 'application/json', + } + var CONTENT_VALUE_TYPE_MAP = { + 'text/plain': 'JSON', + 'application/x-www-form-urlencoded': 'FORM', + 'multipart/form-data': 'DATA', + 'application/json': 'JSON' + } + + var IGNORE_HEADERS = ['status code', 'remote address', 'referrer policy', 'connection', 'content-length' + , 'content-type', 'date', 'keep-alive', 'proxy-connection', 'set-cookie', 'vary', 'accept', 'cache-control', 'dnt' + , 'host', 'origin', 'pragma', 'referer', 'user-agent'] + + var PRE = 'PRE' // PRE() + var CUR = 'CUR' // CUR() + var NEXT = 'NEXT' // NEXT() + + var PRE_REQ = 'PRE_REQ' // PRE_REQ('[]/page') + var PRE_ARG = 'PRE_ARG' // PRE_ARG('[]/page') + var PRE_RES = 'PRE_RES' // PRE_RES('[]/0/User/id') + var PRE_DATA = 'PRE_DATA' // PRE_DATA('[]/0/User/id') + var CTX_GET = 'CTX_GET' // CTX_GET('key') + var CUR_REQ = 'CUR_REQ' // CUR_REQ('User/id') + var CUR_ARG = 'CUR_ARG' // CUR_REQ('User/id') + var CUR_RES = 'CUR_RES' // CUR_RES('[]/0/User/id') + var CUR_DATA = 'CUR_DATA' // CUR_DATA('[]/0/User/id') + var CTX_PUT = 'CTX_PUT' // CTX_PUT('key', val) + + function get4Path(obj, path, defaultVal, msg) { + var val = path == null || path == '' ? obj : JSONResponse.getValByPath(obj, StringUtil.split(path, '/')) + if (val == null && defaultVal == undefined) { + throw new Error('找不到 ' + path + ' 对应在 obj 中的非 null 值!' + StringUtil.get(msg)) + } + return val + } + + function put4Path(obj, key, val, msg) { + if (obj == null) { + throw new Error('obj = null !不能 put ' + key + ' !' + StringUtil.get(msg)) + } + obj[key] = val + } + + var RANDOM_DB = 'RANDOM_DB' + var RANDOM_IN = 'RANDOM_IN' + var RANDOM_INT = 'RANDOM_INT' + var RANDOM_NUM = 'RANDOM_NUM' + var RANDOM_STR = 'RANDOM_STR' + var RANDOM_BAD = 'RANDOM_BAD' + var RANDOM_BAD_BOOL = 'RANDOM_BAD_BOOL' + var RANDOM_BAD_NUM = 'RANDOM_BAD_NUM' + var RANDOM_BAD_STR = 'RANDOM_BAD_STR' + var RANDOM_BAD_IN = 'RANDOM_BAD_IN' + var RANDOM_BAD_ARR = 'RANDOM_BAD_ARR' + var RANDOM_BAD_OBJ = 'RANDOM_BAD_OBJ' + + var ORDER_DB = 'ORDER_DB' + var ORDER_IN = 'ORDER_IN' + var ORDER_INT = 'ORDER_INT' + var ORDER_BAD = 'ORDER_BAD' + var ORDER_BAD_BOOL = 'ORDER_BAD_BOOL' + var ORDER_BAD_NUM = 'ORDER_BAD_NUM' + var ORDER_BAD_STR = 'ORDER_BAD_STR' + var ORDER_BAD_IN = 'ORDER_BAD_IN' + var ORDER_BAD_ARR = 'ORDER_BAD_ARR' + var ORDER_BAD_OBJ = 'ORDER_BAD_OBJ' + + var ORDER_MAP = {} + + var BAD_BOOLS = [null, undefined, false, true, -1, 0, 1, 2, 3.14, 'null', 'undefined', 'None', 'nil', 'false', 'true', + '-1', '0', '1', '2', '3.14', '', ' ', '\\t', '\\r', '\\n', '\\a', '\\b', '\\v', '\\f', 'a', 'dY', [], {}, '[]', '{}'] + var BAD_NUMS = BAD_BOOLS.concat([ + -2049, -1025, -13, 13, 1025, 2049, Number.NaN, Number.POSITIVE_INFINITY, Number.NEGATIVE_INFINITY, Number.MIN_SAFE_INTEGER, + Number.MAX_SAFE_INTEGER, Number.MIN_SAFE_INTEGER - 1, Number.MAX_SAFE_INTEGER + 1, + '-2049', '-1025', '-13', '13', '1025', '2049', 'Number.NaN', 'Number.POSITIVE_INFINITY', 'Number.NEGATIVE_INFINITY', + 'Number.MIN_SAFE_INTEGER', 'Number.MAX_SAFE_INTEGER', '' + Number.MIN_SAFE_INTEGER, '' + Number.MAX_SAFE_INTEGER + ]) + var BAD_STRS = BAD_NUMS.concat([ + '`', '~', '!', '@', '#', '$', '%', '^', '&', '*', '(', ')', '-', '_', '=', '+', '[', ']', '{', '}', ';', ':', + "'", '\\"', ',', '.', '<', '>', '/', '?', '\\t\\r\\n\\a\\b\\v\\f', '`~!@#$%^&*()-_=+[]{};:\'\\",.<>/?', + 'qwertyuiopasdfghjklzxcvbnm', 'MNBVCZLKJHGFDSAPOIUYTREWQ', 'ä½ å¥½', 'ÄãºÃ', '浣犲ソ', '�����', + '鐢辨湀瑕佸ソ濂藉涔犲ぉ澶╁悜涓?', '����Ҫ�¨²�ѧϰ������', '由月è¦�å¥½å¥½å­¦ä¹ å¤©å¤©å�‘上', 'ÓÉÔÂÒªºÃºÃѧϰÌìÌìÏòÉÏ', + '由月要好好学习天天向??', '锟斤拷锟斤拷要锟矫猴拷学习锟斤拷锟斤拷锟斤拷' + ]) + // FIXME 打开时直接卡死崩溃 + var sl = Math.min(10, Math.floor(BAD_STRS.length/5)) + for (var i = 0; i < sl; i ++) { + var v = BAD_STRS[i] + for (var j = 0; j < sl; j ++) { + BAD_STRS.push(v + BAD_STRS[j]) + } + } + var sl2 = Math.min(5, Math.floor(sl/5)) + for (var i = 0; i < sl2; i ++) { + var v = BAD_STRS[i] + for (var j = 0; j < sl2; j ++) { + var v2 = v + BAD_STRS[j] + for (var k = 0; k < sl2; k ++) { + BAD_STRS.push(v2 + BAD_STRS[k]) + } + } + } + + var BAD_ARRS = [] + for (var i = 0; i < BAD_STRS.length; i ++) { + BAD_ARRS.push([BAD_STRS[i]]) + } + // FIXME 打开时直接卡死崩溃 + var al = Math.min(10, Math.floor(BAD_STRS.length/5)) + for (var i = 0; i < al; i ++) { + var v = BAD_STRS[i] + for (var j = 0; j < al; j ++) { + BAD_ARRS.push([v, BAD_STRS[j]]) + } + } + var al2 = Math.min(3, Math.floor(al/5)) + for (var i = 0; i < al2; i ++) { + var v = BAD_STRS[i] + for (var j = 0; j < al2; j ++) { + var v2 = BAD_STRS[j] + for (var k = 0; k < al2; k ++) { + BAD_ARRS.push([v, v2, BAD_STRS[k]]) + } + } + } + + var BAD_OBJS = [] + var ol = Math.min(10, Math.floor(BAD_STRS.length/5)) + for (var i = 10; i < ol; i ++) { // 太多就导致 RANDOM_BAD 基本每次随机出来都是对象 + var k = BAD_STRS[i] + var key = k == undefined ? 'undefined' : (typeof k == 'string' ? k : JSON.stringify(k)) + for (var j = 0; j < ol; j ++) { + BAD_OBJS.push({[key]: BAD_STRS[j]}) + } + } + // FIXME 打开时直接卡死崩溃 + for (var i = 0; i < ol; i ++) { + var k = BAD_STRS[i] + var key = k == undefined ? 'undefined' : (typeof k == 'string' ? k : JSON.stringify(k)) + for (var j = 0; j < ol; j ++) { + var val = BAD_STRS[j] + + for (var i2 = 0; i2 < ol; i2 ++) { + var k2 = BAD_STRS[i2] + var key2 = k2 == undefined ? 'undefined' : (typeof k2 == 'string' ? k2 : JSON.stringify(k)) + for (var j2 = 0; j2 < ol; j2 ++) { + BAD_OBJS.push({[key]: val, [key2]: BAD_STRS[j2]}) + } + } + } + } +// var ol = Math.min(10, Math.floor(BAD_OBJS.length/5)) +// for (var i = 0; i < ol; i ++) { +// var v = BAD_OBJS[BAD_OBJS.length - i] +// for (var j = 0; j < ol; j ++) { +// BAD_OBJS.push(Object.assign(v, BAD_OBJS[BAD_OBJS.length - j])) +// } +// } +// var ol2 = Math.min(2, Math.floor(ol/5)) +// for (var i = 0; i < ol2; i ++) { +// var v = BAD_OBJS[BAD_OBJS.length - i] +// for (var j = 0; j < ol2; j ++) { +// var v2 = Object.assign(v, BAD_OBJS[BAD_OBJS.length - j]) +// for (var k = 0; k < ol2; k ++) { +// BAD_OBJS.push(Object.assign(v2, BAD_OBJS[BAD_OBJS.length - k])) +// } +// } +// } + + var BADS = BAD_STRS.concat(BAD_ARRS).concat(BAD_OBJS) + + var PRIME_INTS = [ + 1, 2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97 + , 101, 103, 107, 109, 113, 127, 131, 137, 139, 149, 151, 157, 163, 167, 173, 179, 181, 191, 193, 197, 199 + , 211, 223, 227, 229, 233, 239, 241, 251, 257, 263, 269, 271, 277, 281, 283, 293 + , 307, 311, 313, 317, 331, 337, 347, 349, 353, 359, 367, 373, 379, 383, 389, 397 + , 401, 409, 419, 421, 431, 433, 439, 443, 449, 457, 461, 463, 467, 479, 487, 491, 499 + , 503, 509, 521, 523, 541, 547, 557, 563, 569, 571, 577, 587, 593, 599 + , 601, 607, 613, 617, 619, 631, 641, 643, 647, 653, 659, 661, 673, 677, 683, 691 + , 701, 709, 719, 727, 733, 739, 743, 751, 757, 761, 769, 773, 787, 797 + , 809, 811, 821, 823, 827, 829, 839, 853, 857, 859, 863, 877, 881, 883, 887 + , 907, 911, 919, 929, 937, 941, 947, 953, 967, 971, 977, 983, 991, 997 + , 1009, 1013, 1019, 1021, 1031, 1033, 1039, 1049, 1051, 1061, 1063, 1069, 1087, 1091, 1093, 1097 + , 1103, 1109, 1117, 1123, 1129, 1151, 1153, 1163, 1171, 1181, 1187, 1193 + , 1201, 1213, 1217, 1223, 1229, 1231, 1237, 1249, 1259, 1277, 1279, 1283, 1289, 1291, 1297 + , 1301, 1303, 1307, 1319, 1321, 1327, 1361, 1367, 1373, 1381, 1399 + , 1409, 1423, 1427, 1429, 1433, 1439, 1447, 1451, 1453, 1459, 1471, 1481, 1483, 1487, 1489, 1493, 1499 + , 1511, 1523, 1531, 1543, 1549, 1553, 1559, 1567, 1571, 1579, 1583, 1597 + , 1601, 1607, 1609, 1613, 1619, 1621, 1627, 1637, 1657, 1663, 1667, 1669, 1693, 1697, 1699 + , 1709, 1721, 1723, 1733, 1741, 1747, 1753, 1759, 1777, 1783, 1787, 1789 + , 1801, 1811, 1823, 1831, 1847, 1861, 1867, 1871, 1873, 1877, 1879, 1889 + , 1901, 1907, 1913, 1931, 1933, 1949, 1951, 1973, 1979, 1987, 1993, 1997, 1999 + ] + function randomPrimeInt() { + return PRIME_INTS[randomInt(0, PRIME_INTS.length - 1)] + } + function randomInt(min, max) { + return randomNum(min, max, 0); + } + function randomNum(min, max, precision) { + // 0 居然也会转成 Number.MIN_SAFE_INTEGER !!! + // start = start || Number.MIN_SAFE_INTEGER + // end = end || Number.MAX_SAFE_INTEGER + + if (min == null) { + min = 0 // Number.MIN_SAFE_INTEGER + } + if (max == null) { + max = Number.MAX_SAFE_INTEGER + } + if (precision == null) { + precision = 2 + } + + return + ((max - min)*Math.random() + min).toFixed(precision) + } + function randomStr(minLength, maxLength, availableChars) { + return 'Ab_Cd' + randomNum() + } + function randomIn(...args) { + return args == null || args.length <= 0 ? null : args[randomInt(0, args.length - 1)] + } + function randomBad(defaultArgs, ...args) { + if (defaultArgs == null) { + defaultArgs = BADS + } + + if (args == null || args.length <= 0) { + return defaultArgs[randomInt(0, defaultArgs.length - 1)] + } + + args = [] + for (var i = 0; i < defaultArgs.length; i++) { + args.push(defaultArgs[i]) + } + + return args[randomInt(0, args.length - 1)] + } + function randomBadBool(...args) { + return randomBad(BAD_BOOLS, args) + } + function randomBadNum(...args) { + return randomBad(BAD_NUMS, args) + } + function randomBadStr(...args) { + return randomBad(BAD_STRS, args) + } + function randomBadArr(...args) { + return randomBad(BAD_ARRS, args) + } + function randomBadObj(...args) { + return randomBad(BAD_OBJS, args) + } + + function orderInt(desc, index, min, max) { + if (min == null) { + min = 0 // Number.MIN_SAFE_INTEGER + } + if (max == null) { + max = Number.MAX_SAFE_INTEGER + } + + if (desc) { + return max - index%(max - min + 1) + } + return min + index%(max - min + 1) + } + function orderIn(desc, index, ...args) { + // alert('orderIn index = ' + index + '; args = ' + JSON.stringify(args)); + index = index || 0; + return args == null || args.length <= index ? null : args[desc ? args.length - 1 - index : index]; + } + function orderBad(defaultArgs, desc, index, ...args) { + // alert('orderIn index = ' + index + '; args = ' + JSON.stringify(args)); + if (defaultArgs == null) { + defaultArgs = BADS + } + + index = index || 0; + if (args == null || args.length <= 0) { + return defaultArgs[desc ? defaultArgs.length - index : index] + } + + args = [] + for (var i = 0; i < defaultArgs.length; i++) { + args.push(defaultArgs[i]) + } + + return args[desc ? args.length - index : index] + } + function orderBadBool(desc, index, ...args) { + return orderBad(BAD_BOOLS, desc, index, ...args) + } + function orderBadNum(desc, index, ...args) { + return orderBad(BAD_NUMS, desc, index, ...args) + } + function orderBadStr(desc, index, ...args) { + return orderBad(BAD_STRS, desc, index, ...args) + } + function orderBadArr(desc, index, ...args) { + return orderBad(BAD_ARRS, desc, index, ...args) + } + function orderBadObj(desc, index, ...args) { + return orderBad(BAD_OBJS, desc, index, ...args) + } + + function getOrderIndex(randomId, line, argCount, step) { + // alert('randomId = ' + randomId + '; line = ' + line + '; argCount = ' + argCount); + // alert('ORDER_MAP = ' + JSON.stringify(ORDER_MAP, null, ' ')); + + if (randomId == null) { + randomId = 0; + } + if (ORDER_MAP == null) { + ORDER_MAP = {}; + } + if (ORDER_MAP[randomId] == null) { + ORDER_MAP[randomId] = {}; + } + + var orderIndex = ORDER_MAP[randomId][line]; + // alert('orderIndex = ' + orderIndex) + + if (orderIndex == null || orderIndex < -1) { + orderIndex = -1; + } + if (argCount == null) { + argCount = 0; + } + if (step == null) { + step = 1; + } + + orderIndex ++ + ORDER_MAP[randomId][line] = orderIndex; + orderIndex = argCount <= 0 ? step*orderIndex : step*orderIndex%argCount; + + // alert('orderIndex = ' + orderIndex) + // alert('ORDER_MAP = ' + JSON.stringify(ORDER_MAP, null, ' ')); + return orderIndex; + } + //这些全局变量不能放在data中,否则会报undefined错误 + + var BREAK_ALL = 'BREAK_ALL' + var BREAK_LAST = 'BREAK_LAST' + + var baseUrl + var inputted + var handler + var errHandler + var docObj + var doc + var output + + var isSingle = true + + var currentTarget = vInput; + var isInputValue = false; + var isClickSelectInput = false; + var selectionStart = 0; + var selectionEnd = 0; + + function newDefaultScript() { + return { // index.html 中 v-model 绑定,不能为 null + case: { + 0: { + pre: { // 可能有 id + script: '' // index.html 中 v-model 绑定,不能为 null + }, + post: { + script: '' + } + }, + 1560244940013: { + pre: { // 可能有 id + script: '' // index.html 中 v-model 绑定,不能为 null + }, + post: { + script: '' + } + } + }, + account: { + 0: { + pre: { + script: '' + }, + post: { + script: '' + } + }, + 82001: { + pre: { + script: '' + }, + post: { + script: '' + } + } + }, + global: { + 0: { + pre: { + script: '' + }, + post: { + script: '' + } + } + } + } + } + +// APIJSON >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> + + var App = { + el: '#app', + data: { + baseview: 'formater', + view: 'output', + jsoncon: JSON.stringify(initJson), + jsonhtml: initJson, + compressStr: '', + error: {}, + requestVersion: 3, + requestCount: 1, + urlComment: '一对多关联查询。可粘贴浏览器/抓包工具/接口工具 的 Network/Header/Content 等请求信息,自动填充到界面,格式为 key: value', + selectIndex: 0, + options: [], // [{name:"id", type: "integer", comment:"主键"}, {name:"name", type: "string", comment:"用户名称"}], + historys: [], + history: {name: '请求0'}, + remotes: [], + locals: [], + chainPaths: [], + casePaths: [], + chainGroups: [], + caseGroups: [], + testCases: [], + randoms: [], + randomSubs: [], + account: '13000082001', + password: '123456', + logoutSummary: {}, + accounts: [ + { + 'id': 82001, + 'isLoggedIn': false, + 'name': '测试账号1', + 'phone': '13000082001', + 'password': '123456' + }, + { + 'id': 82002, + 'isLoggedIn': false, + 'name': '测试账号2', + 'phone': '13000082002', + 'password': '123456' + }, + { + 'id': 82003, + 'isLoggedIn': false, + 'name': '测试账号3', + 'phone': '13000082003', + 'password': '123456' + } + ], + otherEnvTokenMap: {}, + otherEnvCookieMap: {}, + allSummary: {}, + currentAccountIndex: 0, + currentChainGroupIndex: -1, + currentDocIndex: -1, + currentRandomIndex: -1, + currentRandomSubIndex: -1, + tests: { '-1':{}, '0':{}, '1':{}, '2': {} }, + crossProcess: '交叉账号:已关闭', + testProcess: '机器学习:已关闭', + randomTestTitle: '参数注入 Random Test', + testRandomCount: 1, + testRandomProcess: '', + compareColor: '#0000', + scriptType: 'case', + scriptBelongId: 0, + scripts: newDefaultScript(), + retry: 0, // 每个请求前的等待延迟 + wait: 0, // 每个请求前的等待延迟 + timeout: null, // 每个请求的超时时间 + loadingCount: 0, + isPreScript: true, + isRandomTest: false, + isDelayShow: false, + isSaveShow: false, + isExportShow: false, + isExportCheckShow: false, + isExportRandom: false, + isExportScript: false, + isOptionListShow: false, + isTestCaseShow: false, + isHeaderShow: false, + isScriptShow: false, + isRandomShow: true, // 默认展示 + isRandomListShow: false, + isRandomSubListShow: false, + isRandomEditable: false, + isCaseGroupEditable: false, + isLoginShow: false, + isConfigShow: false, + isDeleteShow: false, + groupShowType: 0, + caseShowType: 0, + chainShowType: 0, + statisticsShowType: 0, + currentHttpResponse: {}, + currentDocItem: {}, + currentRemoteItem: { + "Document": { + "id": 1560244940013 , + "userId": 82001 , + "testAccountId": 82001 , + "version": 3 , + "name": "测试查询" , + "method": "POST" , + "type": "JSON" , + "url": "/get" , + "date": "2019-06-11 17:22:20.0", +// 导致清空文本后,在说明文档后面重叠显示这个绿色注释 "detail": ` +// 以上 JSON 文本支持 JSON5 格式。清空文本内容可查看规则。 +// 注释可省略。行注释前必须有两个空格;段注释必须在 JSON 下方。 +// +// ## 快捷键 +// Ctrl + I 或 Command + I 格式化 JSON +// +// #### 右上角设置项 > 预览请求输入框,显示对应的预览效果` + }, + "TestRecord": { + "id": 1615135440014 , + "userId": 82001 , + "documentId": 1560244940013 + } + }, + currentRandomItem: {}, + isAdminOperation: false, + loginType: 'login', + isExportRemote: false, + isRegister: false, + isCrossEnabled: false, + isMLEnabled: false, + isDelegateEnabled: false, + isEnvCompareEnabled: false, + isPreviewEnabled: false, + isStatisticsEnabled: false, + isEncodeEnabled: true, + isEditResponse: false, + isLocalShow: false, + isChainShow: false, + uploadTotal: 0, + uploadDoneCount: 0, + uploadFailCount: 0, + uploadRandomCount: 0, + exTxt: { + name: 'APIJSON测试', + label: '发布简单接口', + button: '保存', + index: 0 + }, + themes: themes, + checkedTheme: 0, + isExpand: true, + reportId: null, + User: { + id: 0, + name: '', + head: '' + }, + Privacy: { + id: 0, + balance: null //点击更新提示需要判空 0.00 + }, + method: REQUEST_TYPE_POST, + methods: null, // HTTP_METHODS, + type: REQUEST_TYPE_JSON, + types: null, // [ REQUEST_TYPE_PARAM, REQUEST_TYPE_JSON, REQUEST_TYPE_FORM, REQUEST_TYPE_DATA], // 很多人喜欢用 GET 接口测试,默认的 JSON 看不懂 , REQUEST_TYPE_FORM, REQUEST_TYPE_DATA, REQUEST_TYPE_GRPC ], //默认展示 + host: '', + database: 'MYSQL', // 查文档必须,除非后端提供默认配置接口 // 用后端默认的,避免用户总是没有配置就问为什么没有生成文档和注释 'MYSQL',// 'POSTGRESQL', + schema: 'sys', // 查文档必须,除非后端提供默认配置接口 // 用后端默认的,避免用户总是没有配置就问为什么没有生成文档和注释 'sys', + otherEnv: 'http://localhost:8080/', // 其它环境服务地址,用来对比当前的 + server: 'http://apijson.cn:9090/', // 'http://localhost:8080/', // Chrome 90+ 跨域问题非常难搞,开发模式启动都不行了 + // server: 'http://47.74.39.68:9090/', // apijson.org + projectHost: {host: 'http://apijson.cn:8080/', project: 'APIJSON.cn'}, // apijson.cn + thirdParty: 'SWAGGER /v2/api-docs', //apijson.cn + // thirdParty: 'RAP /repository/joined /repository/get', + // thirdParty: 'YAPI /api/interface/list_menu /api/interface/get', + projectHosts: [ + {host: 'http://localhost:8080/', project: 'Localhost'}, + {host: 'http://apijson.cn:8080/', project: 'APIJSON.cn'}, + {host: 'http://apijson.cn:9090/', project: 'APIJSON.cn:9090'} + ], + language: CodeUtil.LANGUAGE_KOTLIN, + header: {}, + page: 0, + count: 50, + search: '', + chainGroupPage: 0, + chainGroupPages: {}, + chainGroupCount: 0, + chainGroupCounts: {}, + chainGroupSearch: '', + chainGroupSearches: {}, + caseGroupPage: 0, + caseGroupPages: {}, + caseGroupCount: 0, + caseGroupCounts: {}, + caseGroupSearch: '', + caseGroupSearches: {}, + testCasePage: 0, + testCasePages: {}, + testCaseCount: 50, + testCaseCounts: {}, + testCaseSearch: '', + testCaseSearches: {}, + randomPage: 0, + randomCount: 50, + randomSearch: '', + randomSubPage: 0, + randomSubCount: 50, + randomSubSearch: '', + doneCount: 0, + allCount: 0, + deepDoneCount: 0, + deepAllCount: 0, + randomDoneCount: 0, + randomAllCount: 0, + coverage: { + json: {}, + html: '' + } + }, + + methods: { + // 全部展开 + expandAll: function () { + if (this.view != 'code') { + alert('请先获取正确的JSON Response!') + return + } + + $('.icon-square-min').show() + $('.icon-square-plus').hide() + $('.expand-view').show() + $('.fold-view').hide() + + this.isExpand = true; + }, + + // 全部折叠 + collapseAll: function () { + if (this.view != 'code') { + alert('请先获取正确的JSON Response!') + return + } + + $('.icon-square-min').hide() + $('.icon-square-plus').show() + $('.expand-view').hide() + $('.fold-view').show() + + this.isExpand = false; + }, + + // diff + diffTwo: function () { + var oldJSON = {} + var newJSON = {} + this.view = 'code' + try { + oldJSON = jsonlint.parse(this.jsoncon) + } catch (ex) { + this.view = 'error' + this.error = { + msg: '原 JSON 解析错误\r\n' + ex.message + } + return + } + + try { + newJSON = jsonlint.parse(this.jsoncon) + } catch (ex) { + this.view = 'error' + this.error = { + msg: '新 JSON 解析错误\r\n' + ex.message + } + return + } + + var base = difflib.stringAsLines(JSON.stringify(oldJSON, '', 4)) + var newtxt = difflib.stringAsLines(JSON.stringify(newJSON, '', 4)) + var sm = new difflib.SequenceMatcher(base, newtxt) + var opcodes = sm.get_opcodes() + $('#diffoutput').empty().append(diffview.buildView({ + baseTextLines: base, + newTextLines: newtxt, + opcodes: opcodes, + baseTextName: '原 JSON', + newTextName: '新 JSON', + contextSize: 2, + viewType: 0 + })) + }, + + baseViewToDiff: function () { + this.baseview = 'diff' + this.diffTwo() + }, + + // 回到格式化视图 + baseViewToFormater: function () { + this.baseview = 'formater' + this.view = 'code' + this.showJsonView() + }, + + // 根据json内容变化格式化视图 + showJsonView: function () { + if (this.baseview === 'diff') { + return + } + try { + if (this.jsoncon.trim() === '') { + this.view = 'empty' + } else { + this.view = 'code' + + var ret = this.jsoncon + try { + ret = jsonlint.parse(this.jsoncon) + } catch (ex) { + log(ex) + } + + var method = this.getMethod(); // m 已经 toUpperCase 了 + var isRestful = ! JSONObject.isAPIJSONPath(method); + + var path = null; + var key = null; + + if (isSingle || ! JSONResponse.isObject(vi)) { + var val = ret; + if (isSingle != true && val instanceof Array) { + // alert('onRenderJSONItem key = ' + key + '; val = ' + JSON.stringify(val)) + var ckey = key == null ? null : key.substring(0, key.lastIndexOf('[]')); + + var aliaIndex = ckey == null ? -1 : ckey.indexOf(':'); + var objName = aliaIndex < 0 ? ckey : ckey.substring(0, aliaIndex); + + var firstIndex = objName == null ? -1 : objName.indexOf('-'); + var firstKey = firstIndex < 0 ? objName : objName.substring(0, firstIndex); + + for (var i = 0; i < val.length; i++) { + var vi = val[i] + + if (JSONResponse.isObject(vi) && JSONObject.isTableKey(firstKey || '', vi, isRestful)) { + + var curPath = '' + i; + var curTable = firstKey; + var thiz = { + _$_path_$_: curPath, + _$_table_$_: curTable + }; + + var newVal = {}; + for (var k in vi) { + newVal[k] = vi[k]; //提升性能 + if (this.isFullAssert) { + try { + var cri = this.currentRemoteItem || {}; + var tr = cri.TestRecord || {}; + var d = cri.Document || {}; + var standard = this.isMLEnabled ? tr.standard : tr.response; + var standardObj = StringUtil.isEmpty(standard, true) ? null : parseJSON(standard); + var tests = this.tests[String(this.currentAccountIndex)] || {}; + var responseObj = (tests[d.id] || {})[0] + + var pathUri = (StringUtil.isEmpty(curPath, false) ? '' : curPath + '/') + k; + var pathKeys = StringUtil.split(pathUri, '/'); + var target = this.isMLEnabled ? JSONResponse.getStandardByPath(standardObj, pathKeys) : JSONResponse.getValByPath(standardObj, pathKeys); + var real = JSONResponse.getValByPath(responseObj, pathKeys); + var cmp = this.isMLEnabled ? JSONResponse.compareWithStandard(target, real, pathUri) : JSONResponse.compareWithBefore(target, real, pathUri); +// cmp.path = pathUri; + var cmpShowObj = JSONResponse.getCompareShowObj(cmp); + thiz[k] = [cmpShowObj.compareType, cmpShowObj.compareColor, cmpShowObj.compareMessage]; + var countKey = '_$_' + cmpShowObj.compareColor + 'Count_$_'; + thiz[countKey] = thiz[countKey] == null ? 1 : thiz[countKey] + 1; + } catch (e) { + thiz[k] = [JSONResponse.COMPARE_ERROR, 'red', e.message]; + var countKey = '_$_redCount_$_'; + thiz[countKey] = thiz[countKey] == null ? 1 : thiz[countKey] + 1; + } + } + + delete vi[k] + } + + vi._$_this_$_ = JSON.stringify(thiz) + for (var k in newVal) { + vi[k] = newVal[k] + } + } + + } + } + + this.jsonhtml = val; + } + else { + var thiz = { + _$_path_$_: null, + _$_table_$_: null + }; + + for (var k in ret) { + if (this.isFullAssert) { + try { + var cri = this.currentRemoteItem || {}; + var tr = cri.TestRecord || {}; + var d = cri.Document || {}; + var standard = this.isMLEnabled ? tr.standard : tr.response; + var standardObj = StringUtil.isEmpty(standard, true) ? null : parseJSON(standard); + var tests = this.tests[String(this.currentAccountIndex)] || {}; + var responseObj = (tests[d.id] || {})[0] + + var pathUri = k; + var pathKeys = StringUtil.split(pathUri, '/'); + var target = this.isMLEnabled ? JSONResponse.getStandardByPath(standardObj, pathKeys) : JSONResponse.getValByPath(standardObj, pathKeys); + var real = JSONResponse.getValByPath(responseObj, pathKeys); + // c = JSONResponse.compareWithBefore(target, real, path); + var cmp = this.isMLEnabled ? JSONResponse.compareWithStandard(target, real, pathUri) : JSONResponse.compareWithBefore(target, real, pathUri); +// cmp.path = pathUri; + var cmpShowObj = JSONResponse.getCompareShowObj(cmp); + thiz[k] = [cmpShowObj.compareType, cmpShowObj.compareColor, cmpShowObj.compareMessage]; + var countKey = '_$_' + cmpShowObj.compareColor + 'Count_$_'; + thiz[countKey] = thiz[countKey] == null ? 1 : thiz[countKey] + 1; + } catch (e) { + thiz[k] = [JSONResponse.COMPARE_ERROR, 'red', e.message]; + var countKey = '_$_redCount_$_'; + thiz[countKey] = thiz[countKey] == null ? 1 : thiz[countKey] + 1; + } + } + } + + this.jsonhtml = Object.assign({ + _$_this_$_: JSON.stringify(thiz) + }, ret) + } + + } + } catch (ex) { + this.view = 'error' + this.error = { + msg: ex.message + } + } + }, + + + showUrl: function (isAdminOperation, branchUrl) { + if (StringUtil.isEmpty(this.host, true)) { //显示(可编辑)URL Host + if (isAdminOperation != true) { + baseUrl = this.getBaseUrl(vUrl.value, true) + } + vUrl.value = (isAdminOperation ? this.server : baseUrl) + branchUrl + } + else { //隐藏(固定)URL Host + if (isAdminOperation) { + this.host = this.server + } + vUrl.value = branchUrl + } + + vUrlComment.value = isSingle || StringUtil.isEmpty(this.urlComment, true) + ? '' : CodeUtil.getBlank(StringUtil.length(vUrl.value), 1) + CodeUtil.getComment(this.urlComment, false, ' ') + + ' - ' + (this.requestVersion > 0 ? 'V' + this.requestVersion : 'V*'); + }, + + //设置基地址 + setBaseUrl: function () { + if (StringUtil.isEmpty(this.host, true) != true) { + return + } + // 重新拉取文档 + var bu = this.getBaseUrl(vUrl.value, true) + if (baseUrl != bu) { + baseUrl = bu + doc = null //这个是本地的数据库字典及非开放请求文档 + this.saveCache('', 'URL_BASE', baseUrl) + + //已换成固定的管理系统URL + + // this.remotes = [] + + // var index = baseUrl.indexOf(':') //http://localhost:8080 + // this.server = (index < 0 ? baseUrl : baseUrl.substring(0, baseUrl)) + ':9090' + + var accounts = [] + if (StringUtil.isNotEmpty(bu, true)) { + accounts = this.getCache(bu, 'accounts', []) +// for (var i = 0; i < accounts.length; i ++) { +// var ats = accounts[i] +// if (ats == null || (StringUtil.isNotEmpty(ats.baseUrl, true) && ats.baseUrl != bu)) { +// +// } +// } + } + else { + var baseUrls = this.getCache('', 'baseUrls', []) + for (var i = 0; i < baseUrls.length; i ++) { + var bu2 = baseUrls[i] + var ats = this.getCache(bu2, 'accounts', []) +// accounts.push({ +// baseUrl: bu2, +// id: 0, +// phone: 0, +// name: '' +// }) + accounts = accounts.concat(ats) + } + } + + if (accounts.length >= 1) { + this.accounts = accounts + } + } + }, + getUrl: function () { + var url = StringUtil.get(this.host) + vUrl.value + return url.replaceAll(' ', '') + }, + //获取基地址 + getBaseUrl: function (url_, fixed) { + var url = StringUtil.trim(url_ != undefined ? url_ : vUrl.value) + var length = this.getBaseUrlLength(url) + if (length <= 0 && url_ == undefined) { + var account = this.getCurrentAccount() + if (account != null) { + return account.baseUrl || '' + } + } + + url = length <= 0 ? '' : url.substring(0, length) + return url == '' ? (fixed != true ? URL_BASE : '') : url + }, + //获取基地址长度,以://后的第一个/分割baseUrl和method + getBaseUrlLength: function (url_) { + var url = StringUtil.trim(url_) + var index = url.indexOf(' ') + if (index >= 0) { + return index + 1 + } + + index = url.indexOf('://') + if (index < 0) { + return 0 + } + + var rest = url.substring(index + 3) + var ind = rest.indexOf('/') + + return ind < 0 ? url.length : index + 3 + ind + }, + //获取操作方法 + getMethod: function (url, noQuery) { + var url = StringUtil.get(url == null ? vUrl.value : url).trim() + var index = this.getBaseUrlLength(url) + url = index <= 0 ? url : url.substring(index) + index = noQuery ? url.indexOf("?") : -1 + if (index >= 0) { + url = url.substring(0, index) + } + return url.startsWith('/') ? url.substring(1) : url + }, + getBranchUrl: function (url) { + var url = StringUtil.get(url == null ? vUrl.value : url).trim() + var index = this.getBaseUrlLength(url) + url = index <= 0 ? url : url.substring(index) + return url.startsWith('/') ? url : '/' + url + }, + //获取请求的tag + getTag: function () { + var req = null; + try { + req = this.getRequest(vInput.value); + } catch (e) { + log('main.getTag', 'try { req = this.getRequest(vInput.value); \n } catch (e) {\n' + e.message) + } + return req == null ? null : req.tag + }, + + getRequest: function (json, defaultValue, isRaw) { // JSON5 兜底,减少修改范围 , isSingle) { + if (JSONResponse.isString(json) != true) { + return json == null ? defaultValue : json + } + var s = isRaw != true && isSingle ? this.switchQuote(json) : json; // this.toDoubleJSON(json, defaultValue); + if (StringUtil.isEmpty(s, true)) { + return defaultValue + } + try { + return jsonlint.parse(s); + } + catch (e) { + log('main.getRequest', 'try { return jsonlint.parse(s); \n } catch (e) {\n' + e.message) + log('main.getRequest', 'return JSON5.parse(s);') + return JSON5.parse(s); // jsonlint.parse(this.removeComment(s)); + } + }, + getExtraComment: function(json) { + var it = json != null ? json : StringUtil.trim(vInput.value); + + var start = it.lastIndexOf('\n/*'); + var end = it.lastIndexOf('\n*/'); + + return start < 0 || end <= start ? null : it.substring(start + '\n/*'.length, end); + }, + + getHeader: function (text) { + var header = {} + var hs = StringUtil.isEmpty(text, true) ? null : StringUtil.split(text, '\n') + + if (hs != null && hs.length > 0) { + var item + for (var i = 0; i < hs.length; i++) { + item = hs[i] || '' + + // 解决整体 trim 后第一行 // 被当成正常的 key 路径而不是注释 + var index = StringUtil.trim(item).startsWith('//') ? 0 : item.lastIndexOf(' //') // 不加空格会导致 http:// 被截断 ('//') //这里只支持单行注释,不用 removeComment 那种带多行的去注释方式 + var item2 = index < 0 ? item : item.substring(0, index) + item2 = item2.trim() + if (item2.length <= 0) { + continue; + } + + index = item2.indexOf(':') + if (index <= 0) { + throw new Error('请求头 Request Header 输入错误!请按照每行 key: value 的格式输入,不要有多余的换行或空格!' + + '\n错误位置: 第 ' + (i + 1) + ' 行' + + '\n错误文本: ' + item) + } + + var val = item2.substring(index + 1, item2.length) + + var ind = val.indexOf('(') //一定要有函数是为了避免里面是一个简短单词和 APIAuto 代码中变量冲突 + if (ind > 0 && val.indexOf(')') > ind) { //不从 0 开始是为了保证是函数,且不是 (1) 这种单纯限制作用域的括号 + try { + val = eval(val) + } + catch (e) { + this.log("getHeader if (hs != null && hs.length > 0) { ... if (ind > 0 && val.indexOf(')') > ind) { ... try { val = eval(val) } catch (e) = " + e.message) + } + } + + header[StringUtil.trim(item2.substring(0, index))] = val + } + } + + return header + }, + + // 分享 APIAuto 特有链接,打开即可还原分享人的 JSON 参数、设置项、搜索关键词、分页数量及页码等配置 + shareLink: function (isRandom) { + var settingStr = null + try { + settingStr = JSON.stringify({ + requestVersion: this.requestVersion, + requestCount: this.requestCount, + isTestCaseShow: this.isTestCaseShow, + // isHeaderShow: this.isHeaderShow, + // isRandomShow: this.isRandomShow, + isRandomListShow: this.isRandomShow ? this.isRandomListShow : undefined, + isRandomSubListShow: this.isRandomListShow ? this.isRandomSubListShow : undefined, + // isRandomEditable: this.isRandomEditable, + isCrossEnabled: this.isCrossEnabled, + isMLEnabled: this.isMLEnabled, + isDelegateEnabled: this.isDelegateEnabled, + isPreviewEnabled: this.isPreviewEnabled, + isStatisticsEnabled: this.isStatisticsEnabled, + isEncodeEnabled: this.isEncodeEnabled, + isEditResponse: this.isEditResponse, + isLocalShow: this.isTestCaseShow ? this.isLocalShow : undefined, + page: this.page, + count: this.count, + testCasePage: this.testCasePage, + testCaseCount: this.testCaseCount, + testRandomCount: this.testRandomCount, + randomPage: this.randomPage, + randomCount: this.randomCount, + randomSubPage: this.randomSubPage, + randomSubCount: this.randomSubCount, + host: StringUtil.isEmpty(this.host, true) ? undefined : encodeURIComponent(this.host), + search: StringUtil.isEmpty(this.search, true) ? undefined : encodeURIComponent(this.search), + testCaseSearch: StringUtil.isEmpty(this.testCaseSearch, true) ? undefined : this.testCaseSearch, + randomSearch: StringUtil.isEmpty(this.randomSearch, true) ? undefined : encodeURIComponent(this.randomSearch), + randomSubSearch: StringUtil.isEmpty(this.randomSubSearch, true) ? undefined : encodeURIComponent(this.randomSubSearch) + }) + } catch (e) { + log(e) + } + + // 实测 561059 长度的 URL 都支持,只是输入框显示长度约为 2000 + window.open(this.getShareLink( + isRandom + , null + , null + , null + , this.isTestCaseShow || StringUtil.isEmpty(vHeader.value, true) ? null : encodeURIComponent(StringUtil.trim(vHeader.value)) + , this.isTestCaseShow || StringUtil.isEmpty(vRandom.value, true) ? null : encodeURIComponent(StringUtil.trim(vRandom.value)) + , settingStr + )) + }, + getShareLink: function (isRandom, json, url, type, header, random, setting) { + var jsonStr = json == null ? null : (typeof json == 'string' ? json : JSON.stringify(json)) + if (this.isTestCaseShow != true && jsonStr == null) { // StringUtil.isEmpty(jsonStr) + try { + jsonStr = JSON.stringify(encode(parseJSON(vInput.value))) + } catch (e) { // 可能包含注释 + log(e) + jsonStr = encode(StringUtil.trim(vInput.value)) + } + } + + var headerStr = header + + var randomStr = random + + // URL 太长导致打不开标签 + var settingStr = setting + + var href = window.location.href || 'http://apijson.cn/api' + var ind = href == null ? -1 : href.indexOf('?') // url 后带参数只能 encodeURIComponent + + return (ind < 0 ? href : href.substring(0, ind)) + + (this.view != 'code' ? "?send=false" : (isRandom ? "?send=random" : "?send=true")) + + "&type=" + StringUtil.trim(type == null ? REQUEST_TYPE_JSON : type) + + "&url=" + encodeURIComponent(StringUtil.trim(url == null ? vUrl.value : url)) + + (jsonStr == null ? '' : "&json=" + jsonStr) + + (headerStr == null ? '' : "&header=" + headerStr) + + (randomStr == null ? '' : "&random=" + randomStr) + + (settingStr == null ? '' : "&setting=" + settingStr) + + }, + + onClickSelectInput: function (item, index) { + isClickSelectInput = true; + this.selectInput(item, index, true); + }, + selectInput: function (item, index, isDone) { // , isValue) { + var target = currentTarget = currentTarget || vInput; // currentTarget = target; + var isValue = isInputValue; // isInputValue = isValue; + + // 失去焦点后拿不到有效值 + // var selectionStart = target.selectionStart; + // var selectionEnd = target.selectionEnd; + + var text = StringUtil.get(target.value); + var before = text.substring(0, selectionStart); + var after = text.substring(selectionEnd); + + var name = item == null ? '' : StringUtil.get(item.name); + target.value = text = before + name + after + if (target == vScript) { // 不这样会自动回滚 + this.scripts[this.scriptType][this.scriptBelongId][this.isPreScript ? 'pre' : 'post'].script = text + } + else if (target == vInput) { + inputted = target.value; + } + + if (isDone) { + this.options = []; + + target.focus(); + try { + selectionStart = target.selectionStart = selectionEnd + (isClickSelectInput ? (name.length - (isValue ? 4 : 0)) : 0) + + (isValue ? (after.startsWith(',') ? 1 : 0) : (target == vInput || target == vScript ? 3 : 2)); + selectionEnd = target.selectionEnd = selectionStart + (isValue ? 0 : 4) + } catch (e) { + console.log(e) + } + isClickSelectInput = false; + // vOption.focusout() + + if (this.isChainShow && this.isTestCaseShow) { + this.addCase2Chain(item.value) + return + } + + if (isInputValue != true) { + this.showOptions(target, text, before + name + (isSingle ? "'" : '"') + ': ', after.substring(3), true); + } + } else { + target.selectionStart = selectionStart; + selectionEnd = target.selectionEnd = selectionStart + name.length; + isClickSelectInput = false; + } + }, + + // 显示保存弹窗 + showSave: function (show) { + if (show) { + if (this.isTestCaseShow) { + alert('请先输入请求内容!') + return + } + + var tag = this.getTag() + this.history.name = (this.urlComment || this.getMethod() + (StringUtil.isEmpty(tag, true) ? '' : ' ' + tag)) + ' ' + this.formatTime() //不自定义名称的都是临时的,不需要时间太详细 + } + this.isSaveShow = show + }, + + // 显示导出弹窗 + showExport: function (show, isRemote, isRandom, isScript) { + if (show) { + // this.isExportCheckShow = isRemote + + if (isRemote) { //共享测试用例 + this.isExportRandom = isRandom + this.isExportScript = isScript + + // if (isRandom != true) { // 分享搜索关键词和分页信息也挺好 } && this.isTestCaseShow != true) { // 没有拿到列表,没用 + // setTimeout(function () { + // App.shareLink(App.isRandomTest) + // }, 1000) + // } + + if (this.isTestCaseShow) { + alert('请先输入请求内容!') + return + } + + if (this.view == 'error') { // this.view != 'code') { + alert('发现错误,请输入正确的内容!') // alert('请先测试请求,确保是正确可用的!') + return + } + if (isRandom) { + this.exTxt.name = '随机配置 ' + this.formatDateTime() + } + else if (isScript) { // 避免 APIJSON 启动报错 '执行脚本 ' + this.formatDateTime() + this.exTxt.name = this.scriptType + (this.isPreScript ? 'Pre' : 'Post') + this.getCurrentScriptBelongId() + } + else { + if (this.isEditResponse) { + this.isExportRemote = isRemote + this.exportTxt() + return + } + + // var tag = this.getTag() + this.exTxt.name = this.urlComment || '' // 避免偷懒不输入名称 this.getMethod() + (StringUtil.isEmpty(tag, true) ? '' : ' ' + tag) + } + } + else { //下载到本地 + if (this.isTestCaseShow) { //文档 + this.exTxt.name = 'APIJSON自动化文档 ' + this.formatDateTime() + } + else if (this.view == 'markdown' || this.view == 'output') { + var suffix + switch (this.language) { + case CodeUtil.LANGUAGE_KOTLIN: + suffix = '.kt'; + break; + case CodeUtil.LANGUAGE_JAVA: + suffix = '.java'; + break; + case CodeUtil.LANGUAGE_C_SHARP: + suffix = '.cs'; + break; + + case CodeUtil.LANGUAGE_SWIFT: + suffix = '.swift'; + break; +// case CodeUtil.LANGUAGE_OBJECTIVE_C: +// suffix = '.h'; +// break; + + case CodeUtil.LANGUAGE_GO: + suffix = '.go'; + break; + case CodeUtil.LANGUAGE_C_PLUS_PLUS: + suffix = '.cpp'; + break; + + case CodeUtil.LANGUAGE_TYPE_SCRIPT: + suffix = '.ts'; + break; + case CodeUtil.LANGUAGE_JAVA_SCRIPT: + suffix = '.js'; + break; + + case CodeUtil.LANGUAGE_PHP: + suffix = '.php'; + break; + case CodeUtil.LANGUAGE_PYTHON: + suffix = '.py'; + break; + default: + suffix = '.java'; + break; + } + + this.exTxt.name = 'User' + suffix + alert('自动生成模型代码,可填类名后缀:\n' + + 'Kotlin.kt, Java.java, Swift.swift, C#.cs, Go.go, TypeScript.ts, ' + + '\nJavaScript.js, PHP.php, Python.py, C++.cpp'); + } + else { + this.exTxt.name = 'APIJSON测试 ' + this.getMethod() + ' ' + this.formatDateTime() + } + } + } + + this.isExportShow = show + this.isExportRemote = isRemote + }, + + // 显示配置弹窗 + showConfig: function (show, index) { + this.isConfigShow = false + if (this.isTestCaseShow) { + if (index == 3 || index == 4 || index == 5 || index == 10 || index == 13 || index == 16) { + this.showTestCase(false, false) + } + } + + if (show) { + this.exTxt.button = index == 8 ? '上传' : '切换' + this.exTxt.index = index + switch (index) { + case 0: + case 1: + case 2: + case 6: + case 16: + case 7: + case 8: + case 15: + this.exTxt.name = index == 0 ? this.database : (index == 1 ? this.schema : (index == 2 ? this.language + : (index == 6 ? this.server : (index == 8 ? this.thirdParty : (index == 15 ? this.otherEnv + : (index == 16 ? (this.methods || []).join() : (this.types || []).join())))))) + this.isConfigShow = true + + if (index == 0) { + alert('可填数据库:\n' + CodeUtil.DATABASE_KEYS.join()) + } + else if (index == 2) { + alert('自动生成代码,可填语言:\nKotlin,Java,Swift,C#,Go,TypeScript,\nJavaScript,PHP,Python,C++') + } + else if (index == 16) { + alert('多个方法用 , 隔开,可填方法: ' + HTTP_METHODS.join()) + } + else if (index == 7) { + alert('多个类型用 , 隔开,可填类型:\nPARAM(GET ?a=1&b=c&key=value),\nJSON(POST application/json),\nFORM(POST x-www-form-urlencoded),\nDATA(POST form-data),\nGRPC(POST application/json 需要 GRPC 服务开启反射)') + } + else if (index == 8) { + this.isHeaderShow = true + + alert('例如:\nSWAGGER http://apijson.cn:8080/v2/api-docs\nSWAGGER /v2/api-docs // 省略 Host\nSWAGGER / // 省略 Host 和 分支 URL\nRAP /repository/joined /repository/get\nYAPI /api/interface/list_menu /api/interface/get\nPOSTMAN https://www.postman.com/collections/cd72b75c6a985f7a9737\nPOSTMAN /cd72b75c6a985f7a9737') + + try { + this.getThirdPartyApiList(this.thirdParty, function (platform, docUrl, listUrl, itemUrl, url_, res, err) { + CodeUtil.thirdParty = platform + var data = err != null ? null : (res || {}).data; + var code = data == null ? null : data.errCode || data.errcode || data.err_code + + if (err != null || (code != null && code != 0)) { + App.isHeaderShow = true + App.isRandomShow = false + alert('请把 YApi/Rap/Swagger/Postman 等网站的有效 Cookie 粘贴到请求头 Request Header 输入框后再试!') + } + + App.onResponse(url_, res, err) + return false + }, function (platform, docUrl, listUrl, itemUrl, url_, res, err) { + var data = (res || {}).data + var apiMap = CodeUtil.thirdPartyApiMap || {} + + if (platform == PLATFORM_POSTMAN) { + var apis = data.item || data.requests + if (apis != null) { + for (var i = 0; i < apis.length; i++) { + var item = apis[i] + var req = item == null ? null : item.request + var urlObj = req.url || {} + var path = urlObj.path + var url = path instanceof Array ? '/' + path.join('/') : (typeof urlObj == 'string' ? urlObj : urlObj.raw) + if (StringUtil.isEmpty(url, true)) { + url = item.url + } + if (url != null && url.startsWith('{{url}}')) { + url = url.substring('{{url}}'.length) + } + url = App.getBranchUrl(url) + + if (StringUtil.isEmpty(url, true)) { + continue + } + + var name = item.name + + apiMap[url] = { + name: name, + request: req, + response: item.response == null || item.response.length <= 0 ? null : item.response[0], + detail: name + } + } + } + + return true + } + else if (platform == PLATFORM_SWAGGER) { + var apis = data == null ? null : data.paths + if (apis != null) { + // var i = 0 + for (var url in apis) { + var item = apis[url] + apiMap[url] = item.post || item.get || item.put || item.delete + } + } + } + else if (platform == PLATFORM_RAP) { + } + else if (platform == PLATFORM_YAPI) { + var api = (data || {}).data + var url = api == null || api.path == null ? null : StringUtil.noBlank(api.path).replace(/\/\//g, '/') + if (StringUtil.isEmpty(url, true)) { + return + } + + var typeAndParam = App.parseYApiTypeAndParam(api) + + var name = StringUtil.trim(api.username) + ': ' + StringUtil.trim(api.title) + apiMap[url] = { + name: name, + request: typeAndParam.param, + response: api.res_body == null ? null : parseJSON(api.res_body), + detail: name + + '\n' + (api.up_time == null ? '' : (typeof api.up_time != 'number' ? api.up_time : new Date(1000*api.up_time).toLocaleString())) + + '\nhttp://apijson.cn/yapi/project/1/interface/api/' + api._id + + '\n\n' + (StringUtil.isEmpty(api.markdown, true) ? StringUtil.trim(api.description) : api.markdown.trim().replace(/\\_/g, '_')) + } + } + else { + alert('第三方平台只支持 Postman, Swagger, Rap, YApi !') + return true + } + + CodeUtil.thirdPartyApiMap = apiMap + App.saveCache(App.thirdParty, 'thirdPartyApiMap', apiMap); + + return true + }) + } catch (e) { + console.log('created try { ' + + '\nthis.User = this.getCache(this.server, User) || {}' + + '\n} catch (e) {\n' + e.message) + } + + } + break + case 3: + this.host = this.getBaseUrl() + this.showUrl(false, StringUtil.get(vUrl.value).substring(this.host.length)) //没必要导致必须重新获取 Response,this.onChange(false) + break + case 4: + this.isHeaderShow = show + this.saveCache('', 'isHeaderShow', show) + break + case 13: + this.isScriptShow = show + this.saveCache('', 'isScriptShow', show) + this.listScript() + break + case 5: + this.isRandomShow = show + this.saveCache('', 'isRandomShow', show) + break + case 9: + this.isDelegateEnabled = show + this.saveCache('', 'isDelegateEnabled', show) + break + case 14: + this.isEnvCompareEnabled = show + this.saveCache('', 'isEnvCompareEnabled', show) + + // this.enableML(false) + break + case 10: + this.isPreviewEnabled = show + this.saveCache('', 'isPreviewEnabled', show) + + this.onChange(false) + break + case 17: + this.isStatisticsEnabled = show + this.saveCache('', 'isStatisticsEnabled', show) + + this.isTestCaseShow = false + // this.resetTestCount(this.currentAccountIndex) + + this.remotes = null + this.reportId = 0 + this.showTestCase(true, false) + break + case 12: + this.isEncodeEnabled = show + this.saveCache('', 'isEncodeEnabled', show) + break + case 11: + var did = ((this.currentRemoteItem || {}).Document || {}).id + if (did == null) { + alert('请先选择一个已上传的用例!') + return + } + + this.isEditResponse = show + // this.saveCache('', 'isEditResponse', show) + + vInput.value = ((this.view != 'code' || StringUtil.isEmpty(this.jsoncon, true) ? null : this.jsoncon) + || (this.currentRemoteItem.TestRecord || {}).response) || '' + + vHeader.value = (this.currentRemoteItem.TestRecord || {}).header || '' + + this.isTestCaseShow = false + this.onChange(false) + break + } + } + else if (index == 3) { + var host = StringUtil.get(this.host) + var branch = StringUtil.get(vUrl.value) + this.host = '' + vUrl.value = host + branch //保证 showUrl 里拿到的 baseUrl = this.host (http://apijson.cn:8080/put /balance) + this.setBaseUrl() //保证自动化测试等拿到的 baseUrl 是最新的 + this.showUrl(false, branch) //没必要导致必须重新获取 Response,this.onChange(false) + } + else if (index == 4) { + this.isHeaderShow = show + this.saveCache('', 'isHeaderShow', show) + } + else if (index == 13) { + this.isScriptShow = show + this.saveCache('', 'isScriptShow', show) + } + else if (index == 5) { + this.isRandomShow = show + this.saveCache('', 'isRandomShow', show) + } + else if (index == 9) { + this.isDelegateEnabled = show + this.saveCache('', 'isDelegateEnabled', show) + } + else if (index == 10) { + this.isPreviewEnabled = show + this.saveCache('', 'isPreviewEnabled', show) + // vRequestMarkdown.innerHTML = '' + } + else if (index == 17) { + this.isStatisticsEnabled = show + this.saveCache('', 'isStatisticsEnabled', show) + } + else if (index == 14) { + this.isEnvCompareEnabled = show + this.saveCache('', 'isEnvCompareEnabled', show) + this.enableML(this.isMLEnabled) + } + else if (index == 12) { + this.isEncodeEnabled = show + this.saveCache('', 'isEncodeEnabled', show) + } + else if (index == 11) { + this.isEditResponse = show + // this.saveCache('', 'isEditResponse', show) + + vInput.value = (this.currentRemoteItem.Document || {}).request || '' + vHeader.value = (this.currentRemoteItem.Document || {}).header || '' + + this.isTestCaseShow = false + this.onChange(false) + } + }, + + // 显示删除弹窗 + showDelete: function (show, item, index, isRandom, isChainGroup) { + this.isDeleteShow = show + this.isDeleteRandom = isRandom + this.isDeleteChainGroup = isChainGroup + this.exTxt.name = '请输入' + (isRandom ? '随机配置' : (isChainGroup ? '分组' : '接口')) + '名来确认' + if (isRandom) { + this.currentRandomItem = Object.assign(item, { + index: index + }) + } + else { + this.currentDocItem = Object.assign(item, { + index: index + }) + } + }, + + // 删除接口文档 + deleteDoc: function () { + var isDeleteRandom = this.isDeleteRandom + var isDeleteChainGroup = this.isDeleteChainGroup + var item = (isDeleteRandom ? this.currentRandomItem : this.currentDocItem) || {} + var doc = (isDeleteRandom ? item.Random : (isDeleteChainGroup ? item.Chain : item.Document)) || {} + + var type = isDeleteRandom ? '随机配置' : (isDeleteChainGroup ? '分组' : '接口') + if ((isDeleteChainGroup && doc.groupId == null) || (isDeleteChainGroup != true && doc.id == null)) { + alert('未选择' + type + '或' + type + '不存在!') + return + } + if ((isDeleteChainGroup && doc.groupName != this.exTxt.name) || (isDeleteChainGroup != true && doc.name != this.exTxt.name)) { + alert('输入的' + type + '名和要删除的' + type + '名不匹配!') + return + } + + this.showDelete(false, {}) + + this.isTestCaseShow = false + this.isRandomListShow = false + var isChainShow = this.isChainShow + + var url = this.server + '/delete' + var req = isDeleteRandom ? { + format: false, + 'Random': { + 'id': doc.id + }, + 'tag': 'Random' + } : (isDeleteChainGroup || isChainShow ? { + format: false, + 'Chain': { + 'id': isDeleteChainGroup ? null : doc.id, + 'groupId': isDeleteChainGroup ? doc.groupId : null + }, + 'tag': isDeleteChainGroup ? 'Chain-group' : 'Chain' + } : { + format: false, + 'Document': { + 'id': doc.id + }, + 'tag': 'Document' + }) + this.adminRequest(url, req, {}, function (url, res, err) { + App.onResponse(url, res, err) + + var data = res.data || {} + + if (isDeleteRandom) { + if (data.Random != null && JSONResponse.isSuccess(data.Random)) { + if (((item.Random || {}).toId || 0) <= 0) { + App.randoms.splice(item.index, 1) + } + else { + App.randomSubs.splice(item.index, 1) + } + // App.showRandomList(true, App.currentRemoteItem) + } + } + else if (isDeleteChainGroup) { + App.chainGroups.splice(item.index, 1) + App.selectChainGroup(App.currentChainGroupIndex, null) + } + else { + if (data.Document != null && JSONResponse.isSuccess(data.Document)) { + App.remotes.splice(item.index, 1) + App.showTestCase(true, App.isLocalShow) + } + } + }) + }, + + // 保存当前的JSON + save: function () { + if (this.history.name.trim() === '') { + Helper.alert('名称不能为空!', 'danger') + return + } + var val = { + name: this.history.name, + detail: this.history.name, + type: this.type, + url: '/' + this.getMethod(), + request: inputted, + response: this.jsoncon, + header: vHeader.value, + random: vRandom.value, + scripts: this.scripts + } + var key = String(Date.now()) + localforage.setItem(key, val, function (err, value) { + Helper.alert('保存成功!', 'success') + App.showSave(false) + val.key = key + App.historys.push(val) + }) + }, + + // 清空本地历史 + clearLocal: function () { + this.locals.splice(0, this.locals.length) //UI无反应 this.locals = [] + this.saveCache('', 'locals', []) + }, + + // 删除已保存的 + remove: function (item, index, isRemote, isRandom, isProject, isChainGroup) { + if (isRemote == null || isRemote == false) { //null != false + if (isProject) { + this.projectHosts.splice(index, 1) + this.saveCache('', 'projectHosts', this.projectHosts) + return + } + + localforage.removeItem(item.key, function () { + App.historys.splice(index, 1) + }) + } else { + if (this.isLocalShow) { + this.locals.splice(index, 1) + this.saveCache('', 'locals', this.locals) + return + } + + if (isRandom && (((item || {}).Random || {}).id || 0) <= 0) { + this.randomSubs.splice(index, 1) + return + } + + this.showDelete(true, item, index, isRandom, isChainGroup) + } + }, + + // 根据参数注入用例恢复数据 + restoreRandom: function (index, item) { + this.currentRandomIndex = index + this.currentRandomItem = item + this.isRandomListShow = false + this.isRandomSubListShow = false + var random = (item || {}).Random || {} + this.randomTestTitle = random.name + this.testRandomCount = random.count + vRandom.value = StringUtil.get(random.config) + + var response = ((item || {}).TestRecord || {}).response + if (StringUtil.isEmpty(response, true) == false) { + this.jsoncon = StringUtil.trim(response) + this.view = 'code' + } + }, + // 根据测试用例/历史记录恢复数据 + restoreRemoteAndTest: function (index, item) { + this.restoreRemote(index, item, true) + }, + // 根据测试用例/历史记录恢复数据 + restoreRemote: function (index, item, test, showRandom) { + this.currentDocIndex = index + this.currentRemoteItem = item + if (showRandom != null) { + this.isRandomShow = showRandom + this.isRandomListShow = showRandom + } + this.restore(item, ((item || {}).TestRecord || {}).response, true, test) + }, + // 根据历史恢复数据 + restore: function (item, response, isRemote, test) { + this.isEditResponse = false + + item = item || {} + var doc = item + var docId = doc.id || 0 + + var scripts = item.scripts + if (isRemote) { + var originItem = item + item.random = (originItem.Random || {}).config + + doc = item.Document || {} + docId = doc.id || 0 + + var pre = Object.assign({ + 'script': '' + }, item['Script:pre'] || {}) + var post = Object.assign({ + 'script': '' + }, item['Script:post'] || {}) + + var preId = pre.id + var postId = post.id + if (docId > 0 && (preId == null || postId == null)) { + // var accountId = this.getCurrentAccountId(); + const cri = this.currentRemoteItem || {} + const chain = cri.Chain || {} + const cId = chain.id || 0 + + this.request(true, REQUEST_TYPE_POST, REQUEST_TYPE_JSON, '/get', { + 'Script:pre': preId != null ? undefined : { + 'ahead': 1, + // 'testAccountId': 0, + 'chainId': cId, + 'documentId': docId, + '@order': 'date-' + }, + 'Script:post': postId != null ? undefined : { + 'ahead': 0, + // 'testAccountId': 0, + 'chainId': cId, + 'documentId': docId, + '@order': 'date-' + } + }, {}, function (url, res, err) { + var data = res.data + if (JSONResponse.isSuccess(data) != true) { + App.log(err != null ? err : (data == null ? '' : data.msg)) + return + } + + // var scripts = item.scripts || {} + var scripts = originItem.scripts || {} + // var ss = scripts.case + // if (ss == null) { + // scripts.case = ss = {} + // } + + // var bs = ss[docId] + // if (bs == null) { + // ss[docId] = bs = {} + // } + + var bs = scripts + + var pre = data['Script:pre'] + if (pre != null && pre.script != null) { + bs.pre = originItem['Script:pre'] = data['Script:pre'] + } + + var post = data['Script:post'] + if (post != null && post.script != null) { + bs.post = originItem['Script:post'] = data['Script:post'] + } + + originItem.scripts = scripts + + App.changeScriptType(App.scriptType) + App.scripts.case[docId] = scripts + }) + } + + if (scripts == null) { + scripts = { + pre: pre, + post: post + } + } + item.scripts = scripts + + item = doc + this.scripts.case[docId] = scripts + } + else { + this.scripts = scripts + } + + // localforage.getItem(item.key || '', function (err, value) { + var branch = StringUtil.get(item.url || '/get') + if (branch.startsWith('/') == false) { + branch = '/' + branch + } + + this.method = item.method; + this.type = item.type; + this.urlComment = item.name; + this.requestVersion = item.version; + this.showUrl(false, branch) + + this.showTestCase(false, this.isLocalShow) + vInput.value = StringUtil.get(item.request) + vHeader.value = StringUtil.get(item.header) + vRandom.value = StringUtil.get(item.random) + this.changeScriptType(this.scriptType) + + this.onChange(false) + + if (isRemote) { + this.randoms = [] + this.showRandomList(this.isRandomListShow, item) + } + + if (test) { + this.send(false) + } + else { + if (StringUtil.isEmpty(response, true) == false) { + setTimeout(function () { + App.jsoncon = StringUtil.trim(response) + App.view = 'code' + }, 500) + } + } + + // }) + }, + + onClickHost: function(index, item) { + this.projectHost = item = item || {} + this.isTestCaseShow = false + + this.host = '' + var bu = this.getBranchUrl() + + vUrl.value = item.host + bu + this.showUrl(false, bu) + + this.saveCache('', 'URL_BASE', baseUrl) + this.saveCache('', 'projectHost', this.projectHost) + }, + + listProjectHost: function() { + var req = { + 'TestRecord[]': { + 'count': 0, + 'TestRecord': { + '@column': 'DISTINCT host,project', + '@from@': { + 'join': '&/Document', + 'TestRecord': { + '@column': 'host,documentId', + '@group': 'host,documentId', + 'host{}': 'length(host)>2' + }, + 'Document': { + 'id@': '/TestRecord/documentId', + '@column': "ifnull(project,''):project", + '@group': 'project', +// 'project{}': 'length(project)>0' + } + } + } + } + } + + this.request(true, REQUEST_TYPE_POST, REQUEST_TYPE_JSON, this.server + '/get', req, {}, function (url, res, err) { + var data = res.data + if (JSONResponse.isSuccess(data) != true) { + App.log(err != null ? err : (data == null ? '' : data.msg)) + return + } + + var projectHosts = App.getCache('', 'projectHosts', []) + var list = data['TestRecord[]'] || [] +// var phs = [] +// for (var i = 0; i < list.length; i ++) { +// var item = list[i] || {} +// var host = (item.TestRecord || {}).host +// if (StringUtil.isEmpty(host, true)) { +// continue +// } +// +// phs.push({ 'host': host, 'project': (item.Document || {}).project }) +// } + + App.projectHosts = projectHosts.concat(list) + }) + }, + + syncProjectHost: function(index, item) { + var rawProject = item == null ? null : item.rawProject + var project = item == null ? null : item.project + + var list = this.testCases + var count = list == null ? 0 : list.length + if (count <= 0) { + alert('没有可操作的用例!请先查询用例,保证有至少一个显示!') + return + } + + var ids = [] + for (var i = 0; i < count; i ++) { + var item = list[i] + var doc = item == null ? null : item.Document + var id = doc == null ? null : doc.id + if (id == null || id <= 0) { + continue + } + + ids.push(id) + } + + var req = { + 'Document': { + 'id{}': ids, + 'project{}': [null, '', rawProject], + 'project': project || '' + }, + 'tag': 'Document-project[]' + } + + this.adminRequest('/put', req, {}, function (url, res, err) { + App.onResponse(url, res, err) + var data = res.data + if (JSONResponse.isSuccess(data)) { + App.listProjectHost() + } + }) + }, + + // 获取所有保存的json + listHistory: function () { + localforage.iterate(function (value, key, iterationNumber) { + if (key[0] !== '#') { + value.key = key + App.historys.push(value) + } + if (key === '#theme') { + // 设置默认主题 + App.checkedTheme = value + } + }) + }, + + // 导出文本 + exportTxt: function (btnIndex) { + if (btnIndex == null) { + btnIndex = 0 + } + + if (btnIndex == 1 && this.isExportRandom != true) { + this.shareLink(this.isRandomTest) + return + } + + this.isExportShow = false + + if (this.isExportRemote == false) { //下载到本地 + + if (this.isTestCaseShow) { //文档 + saveTextAs('# ' + this.exTxt.name + '\n主页: https://github.com/Tencent/APIJSON' + + '\n\nBASE_URL: ' + this.getBaseUrl() + + '\n\n\n## 测试用例(Markdown格式,可用工具预览) \n\n' + this.getDoc4TestCase() + + '\n\n\n\n\n\n\n\n## 文档(Markdown格式,可用工具预览) \n\n' + doc + , this.exTxt.name + '.txt') + } + else if (this.view == 'markdown' || this.view == 'output') { //model + var clazz = StringUtil.trim(this.exTxt.name) + + var txt = '' //配合下面 +=,实现注释判断,一次全生成,方便测试 + if (clazz.endsWith('.java')) { + txt += CodeUtil.parseJavaBean(docObj, clazz.substring(0, clazz.length - 5), this.database) + } + else if (clazz.endsWith('.swift')) { + txt += CodeUtil.parseSwiftStruct(docObj, clazz.substring(0, clazz.length - 6), this.database) + } + else if (clazz.endsWith('.kt')) { + txt += CodeUtil.parseKotlinDataClass(docObj, clazz.substring(0, clazz.length - 3), this.database) + } + else if (clazz.endsWith('.m')) { + txt += CodeUtil.parseObjectiveCEntity(docObj, clazz.substring(0, clazz.length - 2), this.database) + } + else if (clazz.endsWith('.cs')) { + txt += CodeUtil.parseCSharpEntity(docObj, clazz.substring(0, clazz.length - 3), this.database) + } + else if (clazz.endsWith('.php')) { + txt += CodeUtil.parsePHPEntity(docObj, clazz.substring(0, clazz.length - 4), this.database) + } + else if (clazz.endsWith('.go')) { + txt += CodeUtil.parseGoEntity(docObj, clazz.substring(0, clazz.length - 3), this.database) + } + else if (clazz.endsWith('.cpp')) { + txt += CodeUtil.parseCppStruct(docObj, clazz.substring(0, clazz.length - 4), this.database) + } + else if (clazz.endsWith('.js')) { + txt += CodeUtil.parseJavaScriptEntity(docObj, clazz.substring(0, clazz.length - 3), this.database) + } + else if (clazz.endsWith('.ts')) { + txt += CodeUtil.parseTypeScriptEntity(docObj, clazz.substring(0, clazz.length - 3), this.database) + } + else if (clazz.endsWith('.py')) { + txt += CodeUtil.parsePythonEntity(docObj, clazz.substring(0, clazz.length - 3), this.database) + } + else { + alert('请正确输入对应语言的类名后缀!') + } + + if (StringUtil.isEmpty(txt, true)) { + alert('找不到 ' + clazz + ' 对应的表!请检查数据库中是否存在!\n如果不存在,请重新输入存在的表;\n如果存在,请刷新网页后重试。') + return + } + saveTextAs(txt, clazz) + } + else { + var res = parseJSON(this.jsoncon) + res = this.removeDebugInfo(res) + + var s = '' + switch (this.language) { + case CodeUtil.LANGUAGE_KOTLIN: + s += '(Kotlin):\n\n' + CodeUtil.parseKotlinResponse('', res, 0, false, ! isSingle) + break; + case CodeUtil.LANGUAGE_JAVA: + s += '(Java):\n\n' + CodeUtil.parseJavaResponse('', res, 0, false, ! isSingle) + break; + case CodeUtil.LANGUAGE_C_SHARP: + s += '(C#):\n\n' + CodeUtil.parseCSharpResponse('', res, 0) + break; + + case CodeUtil.LANGUAGE_SWIFT: + s += '(Swift):\n\n' + CodeUtil.parseSwiftResponse('', res, 0, isSingle) + break; +// case CodeUtil.LANGUAGE_OBJECTIVE_C: +// s += '(Objective-C):\n\n' + CodeUtil.parseObjectiveCResponse('', res, 0) +// break; + + case CodeUtil.LANGUAGE_GO: + s += '(Go):\n\n' + CodeUtil.parseGoResponse('', res, 0) + break; + case CodeUtil.LANGUAGE_C_PLUS_PLUS: + s += '(C++):\n\n' + CodeUtil.parseCppResponse('', res, 0, isSingle) + break; + + case CodeUtil.LANGUAGE_TYPE_SCRIPT: + s += '(TypeScript):\n\n' + CodeUtil.parseTypeScriptResponse('', res, 0, isSingle) + break; + case CodeUtil.LANGUAGE_JAVA_SCRIPT: + s += '(JavaScript):\n\n' + CodeUtil.parseJavaScriptResponse('', res, 0, isSingle) + break; + + case CodeUtil.LANGUAGE_PHP: + s += '(PHP):\n\n' + CodeUtil.parsePHPResponse('', res, 0, isSingle) + break; + case CodeUtil.LANGUAGE_PYTHON: + var isML = this.isMLEnabled + var tr = (this.currentRemoteItem || {}).TestRecord || {} + var stddObj = isML ? JSONResponse.updateFullStandard(parseJSON(tr.standard), res, isML) : null + var resObj = isML ? stddObj : (res || parseJSON(tr.response)) + s += '(Python):\n\n' + CodeUtil.parsePythonResponse('', resObj, 0, ! isSingle, isML) + break; + default: + s += ':\n没有生成代码,可能生成代码(封装,解析)的语言配置错误。 \n'; + break; + } + + saveTextAs('# ' + this.exTxt.name + '\n主页: https://github.com/Tencent/APIJSON' + + '\n\n\nURL: ' + StringUtil.get(vUrl.value) + + '\n\n\nHeader:\n' + StringUtil.get(vHeader.value) + + '\n\n\nRequest:\n' + StringUtil.get(vInput.value) + + '\n\n\nResponse:\n' + StringUtil.get(this.jsoncon) + + '\n\n\n## 解析 Response 的代码' + s + , this.exTxt.name + '.txt') + } + } + else { //上传到远程服务器 + var id = this.User == null ? null : this.User.id + if (id == null || id <= 0) { + alert('请先登录!') + return + } + + const project = (this.projectHost || {}).project + + const isExportRandom = this.isExportRandom + const isExportScript = this.isExportScript + + const cri = this.currentRemoteItem || {} + const chain = cri.Chain || {} + const currentAccountId = this.getCurrentAccountId() + const doc = cri.Document || {} + const tr = cri.TestRecord || {} + const cgId = chain.groupId || 0 + const cId = chain.id || 0 + const did = isExportRandom && btnIndex == 1 ? null : doc.id + + if (isExportScript) { + const extName = this.exTxt.name; + const scriptType = this.scriptType + const script = ((this.scripts[scriptType] || {})[this.getCurrentScriptBelongId()] || {})[this.isPreScript ? 'pre' : 'post'] || {}; + const sid = script.id + const url = sid == null ? '/post' : '/put' + const req = { + format: false, + 'Script': Object.assign({ + 'id': sid == null ? undefined : sid, + 'simple': 1, + 'ahead': this.isPreScript ? 1 : 0, + 'chainGroupId': cgId, + 'chainId': cId, + 'documentId': did == null || scriptType != 'case' ? 0 : did, + 'testAccountId': scriptType != 'account' ? 0 : currentAccountId, + 'name': extName, + 'script': vScript.value + }, script), + 'tag': 'Script' + } + + this.request(true, REQUEST_TYPE_POST, REQUEST_TYPE_JSON, url, req, {}, function (url, res, err) { + App.onResponse(url, res, err) + + var data = res.data || {} + var isPut = url.indexOf('/put') >= 0 + var ok = JSONResponse.isSuccess(data) + alert((isPut ? '修改' : '上传') + (ok ? '成功' : '失败!\n' + StringUtil.get(err != null ? err.message : data.msg))) + + if (ok && ! isPut) { + script.id = (data.Script || {}).id + } + }) + + return + } + + const isEditResponse = this.isEditResponse + const isReleaseRESTful = isExportRandom && btnIndex == 1 && ! isEditResponse + + const path = this.getMethod(); + const methodInfo = isReleaseRESTful ? (JSONObject.parseUri(path, true) || {}) : {}; + if (isReleaseRESTful) { + var isRestful = methodInfo.isRestful; + var tag = methodInfo.tag; + var table = methodInfo.table; + + if (isRestful) { + alert('请求 URL 格式不是 APIJSON 万能通用接口!必须为 /get/user 这种 /{method}/{tag} 格式!其中 method 只能为 [' + APIJSON_METHODS.join() + '] 中的一个,tag 不能为 Table, Table[] 这种与 APIJSON 简单接口冲突的格式! ') + return + } + if (StringUtil.isEmpty(tag, true)) { + alert('请求 URL 缺少 tag!必须为 /get/user 这种 /{method}/{tag} 格式!其中 method 只能为 [' + APIJSON_METHODS.join() + '] 中的一个,tag 不能为 Table, Table[] 这种与 APIJSON 简单接口冲突的格式! ') + return + } + if (JSONObject.isTableKey(table)) { + alert('请求 URL 中的字符 ' + table + ' 与 APIJSON 简单接口冲突!必须为 /get/user 这种 /{method}/{tag} 格式!其中 method 只能为 [' + APIJSON_METHODS.join() + '] 中的一个,tag 不能为 Table, Table[] 这种与 APIJSON 简单接口冲突的格式! ') + return + } + } + + if ((isExportRandom != true || btnIndex == 1) && StringUtil.isEmpty(this.exTxt.name, true)) { + alert('请输入接口名!') + return + } + + if (isExportRandom && btnIndex <= 0 && did == null) { + alert('请先上传测试用例!') + return + } + + this.isTestCaseShow = false + + const currentResponse = this.view != 'code' || StringUtil.isEmpty(this.jsoncon, true) ? {} : this.removeDebugInfo(parseJSON(this.jsoncon)); + + const after = isSingle ? this.switchQuote(inputted) : inputted; // this.toDoubleJSON(inputted); + const inputObj = this.getRequest(after, {}); + + const rawInputStr = JSON.stringify(inputObj) + + var commentObj = null; + if (isExportRandom != true) { + var commentStddObj = null + try { + commentStddObj = parseJSON(isEditResponse ? tr.standard : doc.standard); + } + catch(e) { + log(e) + } + + var code_ = inputObj.code + if (isEditResponse) { + inputObj.code = null // delete inputObj.code + } + + commentObj = JSONResponse.updateStandard(commentStddObj, inputObj); + CodeUtil.parseComment(after, docObj == null ? null : docObj['[]'], path, this.database, this.language, isEditResponse != true, commentObj, true); + + if (isEditResponse) { + inputObj.code = code_ + } + } + + var rawRspStr = JSON.stringify(currentResponse || {}) + const code = currentResponse.code; + const thrw = currentResponse.throw; + delete currentResponse.code; // currentResponse.code = null; //code必须一致 + delete currentResponse.throw; // currentResponse.throw = null; // throw必须一致 + + const isML = this.isMLEnabled; + const stddObj = isML ? JSONResponse.updateStandard({}, currentResponse) : {}; + stddObj.status = (this.currentHttpResponse || {}).status || 200; + stddObj.code = code || 0; + stddObj.throw = thrw; + currentResponse.code = code; + currentResponse.throw = thrw; + + var config = vRandom.value; + const mapReq = {}; + const mustKeys = []; + const typeObj = {}; + const refuseKeys = []; + + if (isReleaseRESTful) { + var mapReq2 = {} + + var cfgLines = StringUtil.split(config, '\n', true); + var newCfg = ''; + if (cfgLines != null) { + for (var i = 0; i < cfgLines.length; i++) { + var cfgLine = cfgLines[i]; + var ind = cfgLine == null ? -1 : cfgLine.indexOf(': '); + if (ind <= 0) { + continue; + } + + var cInd = cfgLine.indexOf('//'); + if (cInd >= 0 && cInd <= ind) { + continue; + } + + var k = cfgLine.substring(0, ind).replaceAll('/', '.'); // .trim(); + var ks = StringUtil.split(k, '.') + var p = inputObj; + for (var j = 0; j < ks.length - 1; j ++) { + if (p == null) { + break; + } + + var jk = ks[j]; + p = jk == null ? null : p[jk]; + } + + var v = p == null ? null : p[ks[ks.length - 1]]; + mapReq[k] = v; + mapReq2[k] = v; + + // 智能判断 count, @key 等 + if (k.startsWith('@') || k.endsWith('[].count') || k.endsWith('[].query') || ['format', 'version'].indexOf(k) >= 0) { + refuseKeys.push('!' + k); + } + else { + mustKeys.push(k); + } + + var t = JSONResponse.getType(v); + typeObj[k] = t == 'integer' ? 'NUMBER' : (t == 'number' ? 'DECIMAL' : t.toUpperCase()); + + newCfg += (i <= 0 ? '' : '\n') + k + ': ' + cfgLine.substring(ind+2).trim(); + } + + refuseKeys.push('!'); + config = newCfg; + } + + commentObj = JSONResponse.updateStandard({}, mapReq2); + } + + var callback = function (randomName, constConfig, constJson) { + // 用现成的测试过的更好,Response 与 Request 严格对应 + // var mapReq = {}; + // if (isExportRandom && btnIndex == 1) { + // + // var mapReq2 = {} + // var cfgLines = StringUtil.split(constConfig, '\n', true); + // if (cfgLines != null) { + // for (var i = 0; i < cfgLines.length; i++) { + // var cfgLine = cfgLines[i]; + // var ind = cfgLine == null ? -1 : cfgLine.indexOf(': '); + // if (ind <= 0) { + // continue; + // } + // + // var k = cfgLine.substring(0, ind).replaceAll('/', '.'); // .trim(); + // var v = cfgLine.substring(ind + 1).trim(); + // try { + // v = parseJSON(v); + // } + // catch (e) { + // log(e) + // } + // + // mapReq[k] = v + // mapReq2[k] = v + // } + // } + // + // commentObj = JSONResponse.updateStandard({}, mapReq2); + // } + + const userId = App.User.id; + const methods = App.methods; + const method = App.isShowMethod() ? App.method : null; + const extName = App.exTxt.name; + const baseUrl = App.getBaseUrl(); + const url = (isReleaseRESTful ? baseUrl : App.server) + (isExportRandom || isEditResponse || did == null ? '/post' : '/put') + const reqObj = btnIndex <= 0 ? constJson : mapReq + const req = isExportRandom && btnIndex <= 0 ? { + format: false, + 'Random': { + userId: userId, + toId: 0, + chainGroupId: cgId, + chainId: cId, + documentId: did, + count: App.requestCount, + name: App.exTxt.name, + config: config + }, + 'TestRecord': { + 'userId': userId, + 'documentId': documentId, + 'host': StringUtil.isEmpty(baseUrl, true) ? null : baseUrl, + 'chainGroupId': cgId, + 'chainId': cId, + 'response': rawRspStr, + 'standard': isML ? JSON.stringify(stddObj) : null + }, + 'tag': 'Random' + } : { + format: false, + 'Document': isEditResponse ? null : { + 'id': did == null ? undefined : did, + 'userId': userId, + 'project': StringUtil.isEmpty(project, true) ? null : project, +// 'testAccountId': currentAccountId, +// 'chainGroupId': cgId, + 'operation': CodeUtil.getOperation(path, reqObj), + 'name': extName, + 'method': method, + 'type': App.type, + 'url': '/' + path, // 'url': isReleaseRESTful ? ('/' + methodInfo.method + '/' + methodInfo.tag) : ('/' + path), + 'request': JSON.stringify(reqObj, null, ' '), + 'apijson': btnIndex <= 0 ? undefined : JSON.stringify(constJson, null, ' '), + 'standard': commentObj == null ? null : JSON.stringify(commentObj, null, ' '), + 'header': vHeader.value, + 'detail': App.getExtraComment() || ((App.currentRemoteItem || {}).Document || {}).detail, + }, + 'TestRecord': isEditResponse != true && did != null ? null : { + 'userId': userId, +// 'chainGroupId': cgId, + 'documentId': isEditResponse ? did : undefined, + 'randomId': 0, + 'host': baseUrl, +// 'testAccountId': currentAccountId, + 'response': isEditResponse ? rawInputStr : rawRspStr, + 'standard': isML || isEditResponse ? JSON.stringify(isEditResponse ? commentObj : stddObj) : undefined, + // 没必要,直接都在请求中说明,查看也方便 'detail': (isEditResponse ? App.getExtraComment() : null) || ((App.currentRemoteItem || {}).TestRecord || {}).detail, + }, + 'tag': isEditResponse ? 'TestRecord' : 'Document' + } + + App.request(true, REQUEST_TYPE_POST, REQUEST_TYPE_JSON, url, req, {}, function (url, res, err) { + App.onResponse(url, res, err) + + var data = res.data || {} + + if (isExportRandom && btnIndex <= 0) { + if (JSONResponse.isSuccess(data)) { + App.randoms = [] + App.showRandomList(true, (App.currentRemoteItem || {}).Document) + } + } + else { + var isPut = url.indexOf('/put') >= 0 + + if (JSONResponse.isSuccess(data) != true) { + if (isPut) { // 修改失败就转为新增 + App.currentRemoteItem = null; + alert('修改失败,请重试(自动转为新增)!' + StringUtil.trim(data.msg)) + } + } + else { + App.remotes = [] + App.showTestCase(true, false) + + if (isPut) { // 修改失败就转为新增 + alert('修改成功') + return + } + + if (isReleaseRESTful) { + var structure = {"MUST": mustKeys.join(), "TYPE": typeObj, "REFUSE": refuseKeys.join()}; + + var reqObj = { + format: false, + Request: { + method: StringUtil.toUpperCase(methodInfo.method), + tag: methodInfo.tag, + structure: JSON.stringify(structure, null, ' '), + detail: extName + }, + tag: 'Request' + }; + + App.request(true, REQUEST_TYPE_POST, REQUEST_TYPE_JSON, baseUrl + '/post', reqObj, {}, function (url, res, err) { + if (res.data != null && res.data.Request != null && JSONResponse.isSuccess(res.data.Request)) { + alert('已自动生成并上传 Request 表校验规则配置:\n' + JSON.stringify(reqObj.Request, null, ' ')) + } + else { + var reqStr = JSON.stringify(reqObj, null, ' '); + console.log('已自动生成,但上传以下 Request 表校验规则配置失败,可能需要手动加表记录:\nPOST ' + baseUrl + '/post' + '\n' + reqStr) + alert('已自动生成,但上传以下 Request 表校验规则配置失败,可能需要手动加表记录,如未自动复制可在控制台复制:\n' + reqStr) + navigator.clipboard.writeText(reqStr); + } + App.onResponse(url, res, err) + }) + } + + //自动生成随机配置(遍历 JSON,对所有可变值生成配置,排除 @key, key@, key() 等固定值) + + const isGenerate = StringUtil.isEmpty(config, true); + var req = isGenerate != true ? null : (isReleaseRESTful ? mapReq : App.getRequest(vInput.value, {})) + App.newAndUploadRandomConfig(baseUrl, req, (data.Document || {}).id, config, App.requestCount, function (url, res, err) { + + + + if (res.data != null && res.data.Random != null && JSONResponse.isSuccess(res.data.Random)) { + alert('已' + (isGenerate ? '自动生成并' : '') + '上传随机配置:\n' + config) + App.isRandomListShow = true + } + else { + alert((isGenerate ? '已自动生成,但' : '') + '上传以下随机配置失败:\n' + config) + vRandom.value = config + } + App.onResponse(url, res, err) + }, isReleaseRESTful) + } + } + }) + }; + + if (btnIndex == 1) { + // this.parseRandom(inputObj, config, null, true, true, false, callback) + callback(null, null, inputObj) + } + else { + callback(null, null, inputObj) + } + + } + }, + newAndUploadRandomConfig: function(baseUrl, req, documentId, config, count, callback, isReleaseRESTful) { + if (documentId == null) { + return + } + const isGenerate = StringUtil.isEmpty(config, true); + var configs = isGenerate ? [] : [config] + if (isGenerate) { + var config = StringUtil.trim(this.newRandomConfig(null, '', req, false)) + if (StringUtil.isEmpty(config, true)) { + return; + } + + configs.push(config) + config2 = StringUtil.trim(this.newRandomConfig(null, '', req, true)) + if (StringUtil.isNotEmpty(config2, true)) { + configs.push(config2) + } + } + + for (var i = 0; i < configs.length; i ++) { + const config = configs[i] + this.request(true, REQUEST_TYPE_POST, REQUEST_TYPE_JSON, (isReleaseRESTful ? baseUrl : this.server) + '/post', { + format: false, + Random: { + documentId: documentId, + count: count, + name: '默认配置' + (isGenerate ? '(上传测试用例时自动生成)' : ''), + config: config + }, + TestRecord: { + host: baseUrl, + response: '' + }, + tag: 'Random' + }, {}, callback) + } + }, + + onClickAddRandom: function () { + if (this.isRandomListShow || this.isRandomSubListShow) { + this.randomTestTitle = null; + this.isRandomListShow = false; + this.isRandomSubListShow = false; + } else if (StringUtil.isEmpty(vRandom.value, true)) { + var req = this.getRequest(vInput.value, {}) + vRandom.value = StringUtil.trim(this.newRandomConfig(null, '', req, Math.random() >= 0.5, Math.random() >= 0.3, Math.random() >= 0.8)) + } else { + this.showExport(true, true, true) + } + }, + + newRandomConfig: function (path, key, value, isRand, isBad, noDeep, isConst) { + if (key == null) { + return '' + } + if (path == '' && (key == 'tag' || key == 'version' || key == 'format')) { + return '' + } + + var config = '' + var childPath = path == null || path == '' ? key : path + '/' + key + var prefix = childPath + ': ' + + var isPositive = Math.random() >= 0.3 + var offset = (isPositive ? '+' : '') + (isPositive ? 1 : -1)*randomPrimeInt() + + if (value instanceof Array) { + if (isConst) { + config += prefix + '[]' + for (var i = 0; i < value.length; i ++) { + var cfg = this.newRandomConfig(childPath, '' + i, value[i], isRand, isBad, noDeep, isConst) + config += '\n' + (StringUtil.isEmpty(cfg, true) ? 'null' : cfg.trim()) + } + return config + } + if (isBad && noDeep && StringUtil.isNotEmpty(childPath, true)) { + return prefix + (isRand ? 'RANDOM_BAD_ARR' : 'ORDER_BAD_ARR' + offset) + '()' + } + + if (Math.random() >= 7) { + var val + if (value.length <= 0) { + val = '' + } + else { + if (value.length <= 1) { + val = ', ' + JSON.stringify(value) + } + else if (value.length <= 2) { + val = ', ' + JSON.stringify([value[0]]) + ', ' + JSON.stringify([value[1]]) + ', ' + JSON.stringify(value) + } + else { + val = ', ' + JSON.stringify([value[0]]) + ', ' + JSON.stringify([value[value.length - 1]]) + ', ' + JSON.stringify([value[Math.floor(value.length / 2)]]) + ', ' + JSON.stringify(value) + } + } + + config += prefix + (isRand ? 'RANDOM_IN' : 'ORDER_IN') + '(undefined, null, false, true, -1025, 0, [], {}, 1, 3.14, "null", "undefined", Number.MAX_SAFE_INTEGER, "-1025", "0", "" + Number.MAX_SAFE_INTEGER, "[", "]", "{", "}", "1", "3.14", "true", "false"' + val + ')' + } + else { + config += prefix + '[]' + + var l = randomInt(0, 13) + for (var i = 0; i < l; i ++) { + var cfg = this.newRandomConfig(childPath, '' + i, value[i], isRand, isBad, noDeep, isConst) + if (StringUtil.isEmpty(cfg, true)) { + break + } + + config += '\n' + cfg.trim() + } + } + + return config + } + else if (value instanceof Object) { + if (isBad && noDeep && StringUtil.isNotEmpty(childPath, true)) { + return prefix + (isRand ? 'RANDOM_BAD_OBJ' : 'ORDER_BAD_OBJ' + offset) + '()' + } + + for (var k in value) { + var v = value[k] + + var isAPIJSONArray = isConst == false && v instanceof Object && v instanceof Array == false + && k.startsWith('@') == false && (k.endsWith('[]') || k.endsWith('@')) + + if (isAPIJSONArray) { + if (k.endsWith('@')) { + delete v.from + delete v.range + } + + prefix = '\n' + (childPath == null || childPath == '' ? '' : childPath + '/') + k + '/' + if (v.hasOwnProperty('page')) { + config += prefix + 'page: ' + (isRand ? 'RANDOM_INT' : 'ORDER_INT') + '(0, 10)' + delete v.page + } + if (v.hasOwnProperty('count')) { + config += prefix + 'count: ' + (isRand ? 'RANDOM_IN' : 'ORDER_IN') + '(undefined, null, 0, 1, 5, 10, 20' + + ([0, 1, 5, 10, 20].indexOf(v.count) >= 0 ? ')' : ', ' + v.count + ')') + delete v.count + } + if (v.hasOwnProperty('query')) { + config += prefix + 'query: ' + (isRand ? 'RANDOM_IN' : 'ORDER_IN') + '(undefined, null, 0, 1, 2)' + delete v.query + } + } + + var cfg = this.newRandomConfig(childPath, k, v, isRand, isBad, noDeep, isConst) + if (StringUtil.isNotEmpty(cfg, true)) { + config += '\n' + cfg + } + } + + return config + } + else { + if (isConst) { + return prefix + JSON.stringify(value) // 会自动给 String 加 "" + } + //自定义关键词 + if (key.startsWith('@')) { + return config + } + + if (typeof value == 'boolean') { + if (isBad) { + return prefix + (isRand ? 'RANDOM_BAD_BOOL' : 'ORDER_BAD_BOOL' + offset) + '()' + } + + config += prefix + (isRand ? 'RANDOM_IN' : 'ORDER_IN') + '(undefined, null, false, true)' + } + else if (typeof value == 'number') { + if (isBad) { + return prefix + (isRand ? 'RANDOM_BAD_NUM' : 'ORDER_BAD_NUM' + offset) + '()' + } + + var isId = key == 'id' || key.endsWith('Id') || key.endsWith('_id') || key.endsWith('_ID') + if (isId) { + config += prefix + (isRand ? 'RANDOM_IN' : 'ORDER_IN') + '(undefined, null, ' + value + ')' + if (value >= 1000000000) { //PHP 等语言默认精确到秒 1000000000000) { + config += '\n// 可替代上面的 ' + prefix + 'RANDOM_INT(' + Math.round(0.9 * value) + ', ' + Math.round(1.1 * value) + ')' + } + else { + config += '\n// 可替代上面的 ' + prefix + 'RANDOM_INT(1, ' + (10 * value) + ')' + } + } + else { + var valStr = String(value) + var dotIndex = valStr.indexOf('.') + var hasDot = dotIndex >= 0 + var keep = dotIndex < 0 ? 2 : valStr.length - dotIndex - 1 + + if (value < 0) { + config += prefix + (hasDot ? 'RANDOM_NUM' : 'RANDOM_INT') + '(' + (100 * value) + (hasDot ? ', 0, ' + keep + ')' : ', 0)') + } + else if (value > 0 && value < 1) { // 0-1 比例 + config += prefix + 'RANDOM_NUM(0, 1, ' + keep + ')' + } + else if ((hasDot && value > 0 && value <= 100) || (hasDot != true && value > 5 && value <= 100)) { // 10% 百分比 + config += prefix + (hasDot ? 'RANDOM_NUM(0, 100, ' + keep + ')' : 'RANDOM_INT(0, 100)') + } + else { + config += prefix + (dotIndex < 0 && value <= 10 + ? (isRand ? 'RANDOM_INT' : 'ORDER_INT') + '(0, 10)' + : ((hasDot ? 'RANDOM_NUM' : 'RANDOM_INT') + '(0, ' + 100 * value + (hasDot ? ', ' + keep + ')' : ')')) + ) + var hasDot = String(value).indexOf('.') >= 0 + + if (value < 0) { + config += '\n// 可替代上面的 ' + prefix + (hasDot ? 'RANDOM_NUM' : 'RANDOM_INT') + '(' + (100 * value) + ', 0)' + } + else if (value > 0 && value < 1) { // 0-1 比例 + config += '\n// 可替代上面的 ' + prefix + 'RANDOM_NUM(0, 1)' + } + else if (value >= 0 && value <= 100) { // 10% 百分比 + config += '\n// 可替代上面的 ' + prefix + 'RANDOM_INT(0, 100)' + } + else { + config += '\n// 可替代上面的 ' + prefix + (hasDot != true && value < 10 ? (isRand ? 'RANDOM_INT' : 'ORDER_INT') + '(0, 9)' : ((hasDot ? 'RANDOM_NUM' : 'RANDOM_INT') + '(0, ' + 100 * value + ')')) + } + } + } + } + else if (typeof value == 'string') { + if (isBad) { + return prefix + (isRand ? 'RANDOM_BAD_STR' : 'ORDER_BAD_STR' + offset) + '()' + } + + //引用赋值 || 远程函数 || 匹配条件范围 + if (key.endsWith('@') || key.endsWith('()') || key.endsWith('{}')) { + return config + } + + config += prefix + (isRand ? 'RANDOM_IN' : 'ORDER_IN') + '(undefined, null, ""' + (value == '' ? ')' : ', "' + value + '")') + } + else { + if (isBad) { + return prefix + (isRand ? 'RANDOM_BAD' : 'ORDER_BAD' + offset) + '()' + } + + config += prefix + (isRand ? 'RANDOM_IN' : 'ORDER_IN') + '(undefined, null' + (value == null ? ')' : ', ' + JSON.stringify(value) + ')') + } + + } + + return config + }, + + + + // 保存配置 + saveConfig: function () { + this.isConfigShow = this.exTxt.index == 8 + + switch (this.exTxt.index) { + case 0: + this.database = CodeUtil.database = this.exTxt.name + this.saveCache('', 'database', this.database) + + doc = null + var item = this.accounts[this.currentAccountIndex] + item.isLoggedIn = false + this.onClickAccount(this.currentAccountIndex, item) + break + case 1: + this.schema = CodeUtil.schema = this.exTxt.name + this.saveCache('', 'schema', this.schema) + + doc = null + var item = this.accounts[this.currentAccountIndex] + item.isLoggedIn = false + this.onClickAccount(this.currentAccountIndex, item) + break + case 2: + this.language = CodeUtil.language = this.exTxt.name + this.saveCache('', 'language', this.language) + + doc = null + this.onChange(false) + break + case 6: + this.server = this.exTxt.name + this.saveCache('', 'server', this.server) + this.logout(true) + break + case 16: + this.methods = StringUtil.split(this.exTxt.name, ',', true) + this.saveCache('', 'methods', this.methods) + break + case 7: + this.types = StringUtil.split(this.exTxt.name, ',', true) + this.saveCache('', 'types', this.types) + break + case 15: + this.otherEnv = StringUtil.get(this.exTxt.name) + this.saveCache('', 'otherEnv', this.otherEnv) + break + case 8: + var thirdParty = this.exTxt.name + this.getThirdPartyApiList(thirdParty, function (platform, docUrl, listUrl, itemUrl, url_, res, err) { + var jsonData = (res || {}).data + var isJSONData = jsonData instanceof Object + if (isJSONData == false) { //后面是 URL 才存储;是 JSON 数据则不存储 + App.thirdParty = thirdParty + App.saveCache('', 'thirdParty', App.thirdParty) + } + + const header = App.getHeader(vHeader.value) + + if (platform == PLATFORM_SWAGGER) { + var swaggerCallback = function (url_, res, err) { + if (App.isSyncing) { + alert('正在同步,请等待完成') + return + } + App.isSyncing = true + App.onResponse(url_, res, err) + + var apis = (res.data || {}).paths + if (apis == null) { // || apis.length <= 0) { + App.isSyncing = false + alert('没有查到 Swagger 文档!请开启跨域代理,并检查 URL 是否正确!') + return + } + App.exTxt.button = '...' + + App.resetUploading() + + var item + // var i = 0 + for (var url in apis) { + item = apis[url] + //导致 url 全都是一样的 setTimeout(function () { + if (App.uploadSwaggerApi(url, item, 'get') + || App.uploadSwaggerApi(url, item, 'post') + || App.uploadSwaggerApi(url, item, 'put') + || App.uploadSwaggerApi(url, item, 'delete') + ) {} + // }, 100*i) + // i ++ + } + } + + if (isJSONData) { + swaggerCallback(docUrl, { data: jsonData }, null) + } + else { + App.request(false, REQUEST_TYPE_GET, REQUEST_TYPE_PARAM, docUrl, {}, header, swaggerCallback) + } + } + else if (platform == PLATFORM_RAP || platform == PLATFORM_YAPI || platform == PLATFORM_POSTMAN) { + var isRap = platform == PLATFORM_RAP + var isPostman = isRap != true && platform == PLATFORM_POSTMAN + + var itemCallback = function (url, res, err) { + try { + App.onResponse(url, res, err) + } catch (e) {} + + var data = res.data == null ? null : (isPostman ? (res.data.item || res.data.requests) : res.data.data) + if (isRap || isPostman) { + var modules = data == null ? null : (isRap ? data.modules : data) + if (modules != null) { + for (var i = 0; i < modules.length; i++) { + var it = modules[i] || {} + if (isPostman) { + App.uploadPostmanApi(it) + continue + } + + var interfaces = it.interfaces || [] + + for (var j = 0; j < interfaces.length; j++) { + App.uploadRapApi(interfaces[j]) + } + } + } + } + else { + App.uploadYApi(data) + } + } + + if (isJSONData) { + App.resetUploading() + + itemCallback(itemUrl, { data: jsonData }, null) + } + else { + App.request(false, REQUEST_TYPE_GET, REQUEST_TYPE_PARAM, listUrl, {}, header, function (url_, res, err) { + if (App.isSyncing) { + alert('正在同步,请等待完成') + return + } + App.isSyncing = true + App.onResponse(url_, res, err) + + var apis = res.data == null ? null : (isPostman ? res.data.item : res.data.data) + if (apis == null) { // || apis.length <= 0) { + App.isSyncing = false + alert('没有查到 ' + (isRap ? 'Rap' : 'YApi') + ' 文档!请开启跨域代理,并检查 URL 是否正确!') + return + } + App.exTxt.button = '...' + + App.resetUploading() + + if (isPostman) { + itemCallback(itemUrl, { data: res.data }, null) + return + } + + for (var url in apis) { + var item = apis[url] || {} + + var list = (isRap ? [ { _id: item.id } ] : (item == null ? null : item.list)) || [] + for (let i1 = 0; i1 < list.length; i1++) { + var listItem1 = list[i1] + if (listItem1 == null || listItem1._id == null) { + App.log('listItem1 == null || listItem1._id == null >> continue') + continue + } + + App.request(false, REQUEST_TYPE_GET, REQUEST_TYPE_PARAM, itemUrl + '?id=' + listItem1._id, {}, header, itemCallback) + } + + } + }) + + } + + } + else { + alert('第三方平台只支持 Postman, Swagger, Rap, YApi !') + } + + return true + }) + + break + } + }, + resetUploading: function() { + App.uploadTotal = 0 // apis.length || 0 + App.uploadDoneCount = 0 + App.uploadFailCount = 0 + App.uploadRandomCount = 0 + }, + + getThirdPartyApiList: function (thirdParty, listCallback, itemCallback) { + this.parseThirdParty(thirdParty, function (platform, jsonData, docUrl, listUrl, itemUrl) { + var isJSONData = jsonData instanceof Object + + const header = App.getHeader(vHeader.value) + + if (platform == PLATFORM_POSTMAN) { + if (isJSONData) { + listCallback(platform, docUrl, listUrl, itemUrl, itemUrl, { data: jsonData }, null) + } + else { + App.request(false, REQUEST_TYPE_GET, REQUEST_TYPE_PARAM, docUrl, {}, header, function (url_, res, err) { + if (listCallback != null && listCallback(platform, docUrl, listUrl, itemUrl, url_, res, err)) { + return + } + + if (itemCallback != null) { + itemCallback(platform, docUrl, listUrl, itemUrl, itemUrl, res, err) + } + }) + } + } + else if (platform == PLATFORM_SWAGGER) { + if (isJSONData) { + listCallback(platform, docUrl, listUrl, itemUrl, itemUrl, { data: jsonData }, null) + } + else { + App.request(false, REQUEST_TYPE_GET, REQUEST_TYPE_PARAM, docUrl, {}, header, function (url_, res, err) { + if (listCallback != null && listCallback(platform, docUrl, listUrl, itemUrl, url_, res, err)) { + return + } + + if (itemCallback != null) { + itemCallback(platform, docUrl, listUrl, itemUrl, itemUrl, res, err) + } + }) + } + } + else if (platform == PLATFORM_RAP || platform == PLATFORM_YAPI) { + var isRap = platform == PLATFORM_RAP + + if (isJSONData) { + if (listCallback != null && listCallback(platform, docUrl, listUrl, itemUrl, listUrl, {data: [jsonData]}, null)) { + return + } + + if (itemCallback != null) { + itemCallback(platform, docUrl, listUrl, itemUrl, itemUrl, {data: jsonData}, null) + } + } + else { + App.request(false, REQUEST_TYPE_GET, REQUEST_TYPE_PARAM, listUrl, {}, header, function (url_, res, err) { + if (listCallback != null && listCallback(platform, docUrl, listUrl, itemUrl, url_, res, err)) { + return + } + + var apis = (res.data || {}).data + if (apis == null) { // || apis.length <= 0) { + alert('没有查到 ' + (isRap ? 'Rap' : 'YApi') + ' 文档!' + + '\n请开启跨域代理,并检查 URL 是否正确!' + + '\nYApi/Rap/Swagger/Postman 网站的 Cookie 必须粘贴到请求头 Request Header 输入框!') + return + } + + var item + for (var url in apis) { + item = apis[url] || {} + + var list = (isRap ? [ { _id: item.id } ] : (item == null ? null : item.list)) || [] + for (let i1 = 0; i1 < list.length; i1++) { + var listItem1 = list[i1] + if (listItem1 == null || listItem1._id == null) { + App.log('listItem1 == null || listItem1._id == null >> continue') + continue + } + + // var p = listItem1.path == null ? null : StringUtil.noBlank(listItem1.path).replaceAll('//', '/') + // if (p == null) { + // continue + // } + + App.request(false, REQUEST_TYPE_GET, REQUEST_TYPE_PARAM, itemUrl + '?id=' + listItem1._id, {}, header, function (url_, res, err) { + if (itemCallback != null) { + itemCallback(platform, docUrl, listUrl, itemUrl, url_, res, err) + } + }) + } + + } + }) + } + } + else { + alert('第三方平台只支持 Postman, Swagger, Rap, YApi !') + } + }) + + }, + + parseThirdParty: function (thirdParty, callback) { + var tp = StringUtil.trim(thirdParty) + var index = tp.indexOf(' ') + var platform = index < 0 ? PLATFORM_SWAGGER : tp.substring(0, index).toUpperCase() + var docUrl = index <= 0 ? tp.trim() : tp.substring(index + 1).trim() + + var jsonData = null + try { + jsonData = parseJSON(docUrl) + } + catch (e) {} + + var host = this.getBaseUrl() + var listUrl = null + var itemUrl = null + + if (platform == PLATFORM_POSTMAN) { + if (docUrl.startsWith('/') || docUrl.indexOf('://') < 0) { + docUrl = 'https://www.postman.com/' + (docUrl.startsWith('/collections') ? '' : '/collections') + (docUrl.startsWith('/') ? '' : '/') + docUrl + } + listUrl = docUrl + } + else if (platform == PLATFORM_SWAGGER) { + if (docUrl == '/') { + docUrl += 'v2/api-docs' + } + if (docUrl.startsWith('/')) { + docUrl = host + docUrl + } + } + else if (platform == PLATFORM_RAP || platform == PLATFORM_YAPI) { + var isRap = platform == PLATFORM_RAP + index = docUrl.indexOf(' ') + listUrl = index < 0 ? docUrl + (isRap ? '/repository/joined' : '/api/interface/list_menu') : docUrl.substring(0, index).trim() + itemUrl = index < 0 ? docUrl + (isRap ? '/repository/get' : '/api/interface/get') : docUrl.substring(index + 1).trim() + + if (listUrl.startsWith('/')) { + listUrl = host + listUrl + } + if (itemUrl.startsWith('/')) { + itemUrl = host + itemUrl + } + } + + callback(platform, jsonData, docUrl, listUrl, itemUrl) + }, + + /**上传 Postman API + * @param docItem + * @param callback + */ + uploadPostmanApi: function(docItem) { + var api = docItem + if (api == null) { + log('postApi', 'api == null >> return') + this.exTxt.button = 'All:' + this.uploadTotal + '\nDone:' + this.uploadDoneCount + '\nFail:' + this.uploadFailCount + return false + } + + this.uploadTotal ++ + + var request = api.request || {} + var response = api.response || [] + var body = request.body || {} + var json = body.raw || api.rawModeData + var options = body.options || {} + var language = (options.raw || {}).language + + var method = api.method || request.method + var type = REQUEST_TYPE_JSON + switch (method || '') { + case 'GET': + type = REQUEST_TYPE_PARAM + break + case 'POST': + switch (language || '') { + case 'form-data': // FIXME + type = REQUEST_TYPE_DATA + break + case 'form-url-encoded': // FIXME + type = REQUEST_TYPE_FORM + break + // case 'json': //JSON + default: + type = REQUEST_TYPE_JSON + break + } + break + default: + type = REQUEST_TYPE_JSON + break + } + + + var urlObj = request.url || {} + var path = urlObj.path + var url = path instanceof Array ? '/' + path.join('/') : (typeof urlObj == 'string' ? urlObj : urlObj.raw) + if (StringUtil.isEmpty(url, true)) { + url = api.url + } + if (url != null && url.startsWith('{{url}}')) { + url = url.substring('{{url}}'.length) + } + + var parameters = api.queryParams || request.queryParams || (urlObj instanceof Object ? urlObj.query : null) + var parameters2 = [] + if (parameters != null && parameters.length > 0) { + + for (var k = 0; k < parameters.length; k++) { + var paraItem = parameters[k] || {} + var name = paraItem.key || '' + if (StringUtil.isEmpty(name, true)) { + continue + } + + var val = paraItem.value + if (val == '{{' + name + '}}') { + val = null + } + + //转成和 Swagger 一样的字段及格式 + paraItem.name = name + paraItem.type = paraItem.type == 'Number' ? 'integer' : StringUtil.toLowerCase(paraItem.type) + paraItem.default = val + + parameters2.push(paraItem) + } + } + + var header = '' + var headers = request.header || api.headerData || [] + if (headers != null && headers.length > 0) { + for (var k = 0; k < headers.length; k++) { + var paraItem = headers[k] || {} + var name = paraItem.key || '' + if (StringUtil.isEmpty(name, true)) { + continue + } + + var val = paraItem.value + header += (k <= 0 ? '' : '\n') + name + ': ' + (val == null ? '' : val) + + (StringUtil.isEmpty(paraItem.description, true) ? '' : ' // ' + paraItem.description) + } + } + + if (StringUtil.isEmpty(header, true)) { + header = api.headers + } + + return this.uploadThirdPartyApi(method, type, api.name || request.name, url, parameters2, json, header + , api.description || request.description, null, response == null ? null : response[0]) + }, + + /**上传 Swagger API + * @param url + * @param docItem + * @param method + * @param callback + */ + uploadSwaggerApi: function(url, docItem, method) { + method = method || 'get' + var api = docItem == null ? null : docItem[method] + if (api == null) { + log('postApi', 'api == null >> return') + this.exTxt.button = 'All:' + this.uploadTotal + '\nDone:' + this.uploadDoneCount + '\nFail:' + this.uploadFailCount + return false + } + + this.uploadTotal ++ + + var parameters = api.parameters || [] + var parameters2 = [] + if (parameters != null && parameters.length > 0) { + + for (var k = 0; k < parameters.length; k++) { + var paraItem = parameters[k] || {} + var name = paraItem.name || '' + if (name == 'mock') { + continue + } + + parameters2.push(paraItem) + } + } + + return this.uploadThirdPartyApi(method, method == 'get' ? REQUEST_TYPE_PARAM : REQUEST_TYPE_JSON + , api.summary, url, parameters2, null, api.headers, api.description) + }, + + + /**上传 Rap API + * @param docItem + */ + uploadRapApi: function(docItem) { + var api = docItem + if (api == null) { + log('postApi', 'api == null >> return') + this.exTxt.button = 'All:' + this.uploadTotal + '\nDone:' + this.uploadDoneCount + '\nFail:' + this.uploadFailCount + return false + } + + this.uploadTotal ++ + + var method = REQUEST_TYPE_POST + var type + switch ((api.summary || {}).requestParamsType || '') { + case 'QUERY_PARAMS': + method = REQUEST_TYPE_GET + type = REQUEST_TYPE_PARAM + break + case 'BODY_PARAMS': + method = REQUEST_TYPE_POST + switch ((api.summary || {}).bodyOption || '') { + case 'FORM_DATA': + type = REQUEST_TYPE_DATA + break + case 'FORM_URLENCODED': + type = REQUEST_TYPE_FORM + break + // case 'RAW': //JSON + default: + type = REQUEST_TYPE_JSON + break + } + break + default: + method = REQUEST_TYPE_POST + type = REQUEST_TYPE_JSON + break + } + + var header = '' + + var parameters = api.properties + + var parameters2 = [] + if (parameters != null && parameters.length > 0) { + + for (var k = 0; k < parameters.length; k++) { + + var paraItem = parameters[k] || {} + var name = paraItem.name || '' + if (StringUtil.isEmpty(name, true) || paraItem.scope != 'request') { + continue + } + + var val = paraItem.value + + if (paraItem.pos == 1) { //header + header += (k <= 0 ? '' : '\n') + name + ': ' + (val == null ? '' : val) + + (StringUtil.isEmpty(paraItem.description, true) ? '' : ' // ' + paraItem.description) + continue + } + + //转成和 Swagger 一样的字段及格式 + paraItem.type = paraItem.type == 'Number' ? 'integer' : StringUtil.toLowerCase(paraItem.type) + paraItem.default = val + + parameters2.push(paraItem) + } + } + + return this.uploadThirdPartyApi(method, type, api.name, api.url, parameters2, null, header, api.description) + }, + + /**上传 YApi + * @param docItem + */ + uploadYApi: function(docItem) { + var api = docItem + if (api == null) { + log('postApi', 'api == null >> return') + this.exTxt.button = 'All:' + this.uploadTotal + '\nDone:' + this.uploadDoneCount + '\nFail:' + this.uploadFailCount + return false + } + + this.uploadTotal++ + + var headers = api.req_headers || [] + var header = '' + for (var i = 0; i < headers.length; i ++) { + var item = headers[i]; + var name = item == null ? null : item.name + if (name == null) { + continue + } + header += (i <= 0 ? '' : '\n') + name + ': ' + item.value + + (StringUtil.isEmpty(item.description, true) ? '' : ' // ' + item.description) + } + + var typeAndParam = this.parseYApiTypeAndParam(api) + + return this.uploadThirdPartyApi( + typeAndParam.method, typeAndParam.type, api.title, api.path, typeAndParam.param, null, header + , (StringUtil.trim(api.username) + ': ' + StringUtil.trim(api.title) + + '\n' + (api.up_time == null ? '' : (typeof api.up_time != 'number' ? api.up_time : new Date(1000*api.up_time).toLocaleString())) + + '\nhttp://apijson.cn/yapi/project/1/interface/api/' + api._id + + '\n\n' + (StringUtil.isEmpty(api.markdown, true) ? StringUtil.trim(api.description) : api.markdown.trim().replace(/\\_/g, '_'))) + , api.username + ) + }, + + + parseYApiTypeAndParam: function (api) { + if (api == null) { + return {} + } + + var type + var parameters + switch (api.req_body_type || '') { + case 'form': + type = REQUEST_TYPE_FORM + parameters = api.req_body_form + break + case 'data': + type = REQUEST_TYPE_DATA + parameters = api.req_params + break + case 'query': + type = REQUEST_TYPE_PARAM + parameters = api.req_query + break + default: + type = REQUEST_TYPE_JSON + parameters = api.req_body_other == null ? null : parseJSON(api.req_body_other) + + var params = parameters.properties || {} + var required = parameters.required || [] + var newParams = [] + for (var k in params) { //TODO 递归里面的子项 + var item = params[k] + item.name = k + item.required = required.indexOf(k) >= 0 + newParams.push(item) + } + parameters = newParams + break + } + + var parameters2 = [] + if (parameters != null && parameters.length > 0) { + //过滤掉无效的,避免多拼接 , 导致 req 不是合法 JSON + for (var k = 0; k < parameters.length; k++) { + var paraItem = parameters[k] || {} + var name = paraItem.name || '' + if (StringUtil.isEmpty(name, true)) { + continue + } + + //转成和 Swagger 一样的字段及格式 + paraItem.url = paraItem.path + + var val = (paraItem.mock || {}).mock + if (val == null && type == 'array') { + val = [] + var it = paraItem.items || {} + var v = it == null ? null : (it.mock || {}).mock + val.push(v) + } + paraItem.default = val + + parameters2.push(paraItem) + } + } + + return { + type: type, + param: parameters2 + } + }, + + generateValue: function (t, n, isSQL) { + if (t == 'boolean') { + return true + } + if (t == 'integer') { + return n == 'pageSize' ? 10 : 1 + } + if (t == 'number') { + return n == 'pageSize' ? 10 : 1 + } + if (t == 'string') { // TODO + return '' + } + if (t == 'object') { + return {} + } + if (t == 'array') { + return [] + } + var suffix = n != null && n.length >= 3 ? n.substring(n.length - 3).toLowerCase() : null + if (suffix == 'dto') { + return {} + } + + return null + }, + + //上传第三方平台的 API 至 APIAuto + uploadThirdPartyApi: function(method, type, name, url, parameters, json, header, description, creator, data) { + if (typeof json == 'string') { + json = parseJSON(json) + } + const reqObj = json || {} + + var req = '{' + + var isJSONEmpty = json == null || Object.keys(json).length <= 0 + if (parameters != null && parameters.length > 0) { + for (var k = 0; k < parameters.length; k++) { + var paraItem = parameters[k] || {} + var n = paraItem.name || '' //传进来前已过滤,这里只是避免万一为 null 导致后面崩溃 + var val = paraItem.default + var t = paraItem.type || typeof val + + if (val == undefined) { + val = this.generateValue(t, n) + reqObj[n] = val + } + + reqObj[n] = val + + if (typeof val == 'string' && (StringUtil.isEmpty(t, true) || t == 'string')) { + val = isJSONEmpty ? ('"' + val.replace(/"/g, '\\"') + '"') : val + } + else if (val instanceof Object) { + val = JSON.stringify(val, null, ' ') + } + + if (isJSONEmpty) { + req += '\n "' + n + '": ' + val + (k < parameters.length - 1 ? ',' : '') + + ' // ' + (paraItem.required ? '必填。 ' : '') + StringUtil.trim(paraItem.description) + } else { + url += (k <= 0 && url.indexOf('?') < 0 ? '?' : '&') + n + '=' + (val == null ? '' : val) + } + } + + } + + req += '\n}' + + if (isJSONEmpty != true) { + req = JSON.stringify(json, null, ' ') + } + + var commentObj = JSONResponse.updateStandard({}, reqObj); + CodeUtil.parseComment(req, null, url, this.database, this.language, true, commentObj, true) + var standard = this.isMLEnabled ? JSONResponse.updateStandard({}, data) : null + + name = StringUtil.get(name) + if (name.length > 100) { + name = name.substring(0, 60) + ' ... ' + name.substring(70, 100) + } + + var currentAccountId = this.getCurrentAccountId() + const baseUrl = this.getBaseUrl(url) + const path = this.getBranchUrl(url) + var callback = function (url, res, err) { + var data = res.data + var did = data.Document == null ? null : data.Document.id + const isRandom = did != null && did > 0 + var config = isRandom ? StringUtil.trim(App.newRandomConfig(null, '', reqObj, false, null, null, true)) : null + App.request(true, REQUEST_TYPE_POST, REQUEST_TYPE_JSON, App.server + '/post', { + format: false, + 'Document': isRandom ? undefined : { + 'creator': creator, + 'testAccountId': currentAccountId, + 'method': StringUtil.isEmpty(method, true) ? null : method.trim().toUpperCase(), + 'operation': CodeUtil.getOperation(path.substring(1), reqObj), + 'type': type, + 'name': StringUtil.get(name), + 'url': path, + 'request': reqObj == null ? null : JSON.stringify(reqObj, null, ' '), + 'standard': commentObj == null ? null : JSON.stringify(commentObj, null, ' '), + 'header': StringUtil.isEmpty(header, true) ? null : StringUtil.trim(header), + 'detail': StringUtil.trim(description).replaceAll('*/', '* /') + }, + 'Random': isRandom ? { + toId: 0, + documentId: did, + count: 1, + name: '常量取值 ' + App.formatDateTime(), + config: config + } : undefined, + 'TestRecord': { + 'randomId': 0, + 'host': StringUtil.isEmpty(baseUrl, true) ? null : baseUrl, + 'testAccountId': currentAccountId, + 'response': data == null ? '' : JSON.stringify(data, null, ' '), + 'standard': standard == null ? '' : JSON.stringify(standard, null, ' '), + }, + 'tag': isRandom ? 'Random' : 'Document' + }, {}, function (url, res, err) { + //太卡 App.onResponse(url, res, err) + var data = res.data || {} + var tblObj = isRandom ? data.Random : data.Document + if (tblObj.id != null && tblObj.id > 0) { + App.uploadDoneCount ++ + if (isRandom) { + App.uploadRandomCount ++ + } + } else { + App.uploadFailCount ++ + } + + if (isRandom != true) { + App.newAndUploadRandomConfig(baseUrl, reqObj, tblObj.id, null, 5) + } + App.exTxt.button = 'All:' + App.uploadTotal + '\nDone:' + App.uploadDoneCount + '\nFail:' + App.uploadFailCount + if (App.uploadDoneCount + App.uploadFailCount >= App.uploadTotal) { + alert('导入完成,其中 ' + App.uploadRandomCount + ' 个用例已存在,改为生成和上传了参数注入配置') + App.isSyncing = false + App.testCasePage = 0 + App.isRandomShow = true + App.isRandomListShow = true + App.showTestCase(false, false) + App.remotes = [] + App.showTestCase(true, false) + } + }) + } + if (JSONObject.isAPIJSONPath(path)) { + callback(url, {}, null) + } + else { + this.request(true, REQUEST_TYPE_POST, REQUEST_TYPE_JSON, this.server + '/get/Document?format=false&@role=OWNER', { + url: path, + method: StringUtil.isEmpty(method, true) ? null : method.trim().toUpperCase() + }, {}, callback) + } + + return true + }, + + // 切换主题 + switchTheme: function (index) { + this.checkedTheme = index + localforage.setItem('#theme', index) + }, + + + // APIJSON <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< + + //格式化日期 + formatDate: function (date) { + if (date == null) { + date = new Date() + } + return date.getFullYear() + '-' + this.fillZero(date.getMonth() + 1) + '-' + this.fillZero(date.getDate()) + }, + //格式化时间 + formatTime: function (date) { + if (date == null) { + date = new Date() + } + return this.fillZero(date.getHours()) + ':' + this.fillZero(date.getMinutes()) + ':' + this.fillZero(date.getSeconds()) + }, + formatDateTime: function (date) { + if (date == null) { + date = new Date() + } + return this.formatDate(date) + ' ' + this.formatTime(date) + }, + //填充0 + fillZero: function (num, n) { + if (num == null) { + num = 0 + } + if (n == null || n <= 0) { + n = 2 + } + var len = num.toString().length; + while(len < n) { + num = "0" + num; + len++; + } + return num; + }, + + + saveAccounts: function() { + baseUrl = this.getBaseUrl() + this.saveCache(baseUrl, 'currentAccountIndex', this.currentAccountIndex) + + var accounts = this.accounts || [] + var accountMap = {} + for (var i = 0; i < accounts.length; i ++) { + var account = accounts[i] + if (account == null || account.phone == null) { + continue + } + + var bu = account.baseUrl || baseUrl + var list = accountMap[bu] || [] + + var find = false + for (var j = 0; j < list.length; j ++) { + var act = list[j] + if (act == null) { + continue + } + + if (act.baseUrl == bu && act.phone == account.phone) { + find = true + break + } + } + + if (find != true) { + list.push(account) + accountMap[bu] = list + } + } + + for (var k in accountMap) { + this.saveCache(k, 'accounts', accountMap[k]) + } + }, + + onClickAccount: function (index, item, callback) { + var accounts = this.accounts + var num = accounts == null ? 0 : accounts.length + if (index < 0 || index >= num) { + item = this.getCurrentAccount() + if (item != null && item.isLoggedIn) { + //logout FIXME 没法自定义退出,浏览器默认根据url来管理session的 + this.logout(false, function (url, res, err) { + App.onResponse(url, res, err) + + item.isLoggedIn = false + App.saveAccounts() + + App.changeScriptType(App.scriptType) + + if (callback != null) { + callback(false, index, err) + } + }); + } else { + if (callback != null) { + callback(false, index) + } + } + + this.currentAccountIndex = index + this.changeScriptType(App.scriptType) + return + } + + if (this.currentAccountIndex == index) { + if (item == null) { + if (callback != null) { + callback(false, index) + } + } + else { + this.setRememberLogin(item.remember) + this.account = item.phone + this.password = item.password + + if (item.isLoggedIn) { + //logout FIXME 没法自定义退出,浏览器默认根据url来管理session的 + this.logout(false, function (url, res, err) { + App.onResponse(url, res, err) + + item.isLoggedIn = false + App.saveAccounts() + App.changeScriptType(App.scriptType) + + if (callback != null) { + callback(false, index, err) + } + }); + + this.currentAccountIndex = -1 + this.changeScriptType(App.scriptType) + } + else { + //login + this.login(false, function (url, res, err) { + App.onResponse(url, res, err) + + var data = res.data || {} + var user = data.user || data.userObj || data.userObject || data.userRsp || data.userResp || data.userBean || data.userData || data.data || data.User || data.Data + if (user == null) { + if (callback != null) { + callback(false, index, err) + } + } + else { + var headers = res.headers || {} + baseUrl = App.getBaseUrl(vUrl.value) + + item.baseUrl = item.baseUrl || baseUrl + item.id = user.id || user.userId || user.user_id || user.userid // TODO 工具函数直接遍历 key 判断可能的名称 + item.name = user.name || user.nickname || user.nickName || user.user_name || user.username || user.userName +// item.phone = user.phone = user.mobile || user.mobileNo || user.mobileNum || user.mobileNumber || user.phone || user.phoneNo || user.phoneNum || user.phoneNumber || user.mobile_no || user.mobile_num || user.mobile_number || user.phone_no || user.phone_num || user.phone_number + item.remember = data.remember + item.isLoggedIn = true + item.token = headers.token || headers.Token || data.token || data.Token || user.token || user.Token || user.accessToken || user.access_token || data.accessToken || data.access_token + item.cookie = res.cookie || headers.cookie || headers.Cookie || headers['set-cookie'] || headers['Set-Cookie'] + + App.accounts[App.currentAccountIndex] = item + App.saveAccounts() + + App.changeScriptType(App.scriptType) + + if (callback != null) { + callback(true, index, err) + } + } + }); + } + + } + + return; + } + + //退出当前账号 + var c = this.currentAccountIndex + var it = c == null || this.accounts == null ? null : this.accounts[c]; + if (it != null) { //切换 BASE_URL后 it = undefined 导致UI操作无法继续 + it.isLoggedIn = false //异步导致账号错位 this.onClickAccount(c, this.accounts[c]) + } + + //切换到这个tab + this.currentAccountIndex = index + this.changeScriptType(App.scriptType) + + //目前还没做到同一标签页下测试账号切换后,session也跟着切换,所以干脆每次切换tab就重新登录 + if (item != null) { + item.isLoggedIn = false + this.onClickAccount(index, item, callback) + } + else { + if (callback != null) { + callback(false, index) + } + } + }, + + removeAccountTab: function () { + if (this.accounts.length <= 1) { + alert('至少要 1 个测试账号!') + return + } + + this.accounts.splice(this.currentAccountIndex, 1) + if (this.currentAccountIndex >= this.accounts.length) { + this.currentAccountIndex = this.accounts.length - 1 + } + + this.saveAccounts() + }, + addAccountTab: function () { + this.showLogin(true, false) + }, + + showCompare4TestCaseList: function (show) { + var testCases = show ? App.testCases : null + var allCount = testCases == null ? 0 : testCases.length + App.allCount = allCount + if (allCount > 0) { + + var accountIndex = (this.accounts[this.currentAccountIndex] || {}).isLoggedIn ? this.currentAccountIndex : -1 + this.currentAccountIndex = accountIndex //解决 onTestResponse 用 -1 存进去, handleTest 用 currentAccountIndex 取出来为空 +// var account = this.accounts[accountIndex] +// baseUrl = this.getBaseUrl() + + var reportId = this.reportId + if (reportId == null || Number.isNaN(reportId)) { + reportId = null + } + + var tests = this.tests[String(accountIndex)] // FIXME account.phone + '@' + (account.baseUrl || baseUrl)] + if ((reportId != null && reportId >= 0) || (tests != null && JSONObject.isEmpty(tests) != true)) { + for (var i = 0; i < allCount; i++) { + var item = testCases[i] + var d = item == null ? null : item.Document + if (d == null || d.id == null) { + continue + } + + if (reportId != null && reportId >= 0) { + var tr = item.TestRecord || {} + var rsp = parseJSON(tr.response) + tests[d.id] = [rsp] + + var cmp = parseJSON(tr.compare) + if (cmp == null || Object.keys(cmp).length <= 0) { + cmp = JSONResponse.compareWithBefore(null, null) + } + + this.onTestResponse(null, allCount, testCases, i, item, d, item.Random, tr, rsp, cmp, false, accountIndex, true); + continue + } + + this.compareResponse(null, allCount, testCases, i, item, (tests[d.id] || {})[0], false, accountIndex, true) + } + } + } + }, + + onClickChainPath: function (index, path) { + var chainPaths = this.chainPaths + this.chainPaths = chainPaths.slice(0, index) + this.selectChainGroup(0, path) + }, + isChainGroupShow: function () { + return this.chainShowType != 1 && (this.chainGroups.length > 0) // || this.chainPaths.length <= 0) + }, + isChainItemShow: function () { + return this.chainShowType != 2 || (this.chainGroups.length <= 0 && this.chainPaths.length > 0) + }, + selectChainGroup: function (index, group) { + this.currentChainGroupIndex = index + this.currentDocIndex = -1 + this.isCaseGroupEditable = false + + if (group == null) { + if (index == null) { + index = this.chainPaths.length - 1 + group = this.chainPaths[index] + } else { + this.chainPaths = [] + } + } else { + this.chainPaths = [group] // .push(group) + } + + this.casePaths = this.chainPaths + + var groupId = group == null ? 0 : group.groupId + if (groupId != null && groupId > 0) { // group != null && groupId == 0) { +// this.chainGroups = [] + this.remotes = this.testCases = (this.chainGroups[index] || {})['[]'] || [] +// this.showTestCase(true, false, null) + return + } + + var isMLEnabled = this.isMLEnabled + var userId = this.User.id + var project = this.projectHost.project + baseUrl = this.getBaseUrl(vUrl.value, true) + + var key = groupId + '' + var page = this.chainGroupPage = this.chainGroupPages[key] || 0 + var count = this.chainGroupCount = this.chainGroupCounts[key] || 0 + var search = this.chainGroupSearch = this.chainGroupSearches[key] || '' + + search = StringUtil.isEmpty(search, true) ? null : '%' + StringUtil.trim(search).replaceAll('_', '\\_').replaceAll('%', '\\%') + '%' + var req = { + format: false, + '[]': { + 'count': count || 0, + 'page': page || 0, + 'Chain': { + 'userId': userId, + 'toGroupId': groupId, + 'groupName$': search, +// '@raw': '@column', + '@column': "groupId;any_value(groupName):groupName;count(*):count", + '@group': 'groupId', + '@order': 'groupId-', +// 'documentId>': 0 + }, + '[]': { + 'count': 0, //200 条测试直接卡死 0, + 'page': 0, + 'join': '&/Document', + 'Chain': { + // TODO 后续再支持嵌套子组合 'toGroupId': groupId, + 'userId': userId, + 'groupId@': '[]/Chain/groupId', + '@column': "id,groupId,documentId,randomId,rank", + '@order': 'rank+,id+', + 'documentId>': 0 + }, + 'Document': { + 'id@': '/Chain/documentId', + // '@column': 'id,userId,version,date,name,operation,method,type,url,request,apijson,standard', // ;substr(url,' + (StringUtil.length(groupUrl) + 2) + '):substr', + '@order': 'version-,date-', + 'userId': userId, + 'project': StringUtil.isEmpty(project, true) ? null : project, + 'name$': search, + 'operation$': search, + 'url$': search, + // 'group{}': group == null || StringUtil.isNotEmpty(groupUrl) ? null : 'length(group)<=0', + // 'group{}': group == null ? null : (group.groupName == null ? "=null" : [group.groupName]), + '@combine': search == null ? null : 'name$,operation$,url$', +// 'method{}': methods == null || methods.length <= 0 ? null : methods, +// 'type{}': types == null || types.length <= 0 ? null : types, + '@null': 'sqlauto', //'sqlauto{}': '=null' + // '@having': StringUtil.isEmpty(groupUrl) ? null : "substring_index(substr,'/',1)<0" + }, + 'Random': { +// 'id@': '/Chain/randomId', + 'toId': 0, + 'chainId@': '/Chain/id', + 'documentId@': '/Document/id', + 'userId': userId, + '@order': 'date-' + }, + 'TestRecord': { + 'chainId@': '/Chain/id', + 'documentId@': '/Document/id', + 'userId': userId, + 'host': StringUtil.isEmpty(baseUrl, true) ? null : baseUrl, +// 'testAccountId': this.getCurrentAccountId(), + 'randomId': 0, +// 'reportId': reportId <= 0 ? null : reportId, +// 'invalid': reportId == null ? 0 : null, + '@order': 'date-', + '@column': 'id,userId,documentId,testAccountId,reportId,duration,minDuration,maxDuration,response' + (this.isStatisticsEnabled ? ',compare' : '')+ (isMLEnabled ? ',standard' : ''), + 'standard{}': isMLEnabled ? (this.database == 'SQLSERVER' ? 'len(standard)>2' : 'length(standard)>2') : null //用 MySQL 5.6 '@having': this.isMLEnabled ? 'json_length(standard)>0' : null + }, + 'Script:pre': { + 'ahead': 1, + // 'testAccountId': 0, + 'chainId@': '/Chain/id', + 'documentId@': '/Document/id', + '@order': 'date-' + }, + 'Script:post': { + 'ahead': 0, + // 'testAccountId': 0, + 'chainId@': '/Chain/id', + 'documentId@': '/Document/id', + '@order': 'date-' + } + }, + }, + '@role': IS_NODE ? null : 'LOGIN', + key: IS_NODE ? this.key : undefined // 突破常规查询数量限制 + } + + if (IS_BROWSER) { + this.onChange(false) + } + + this.request(true, REQUEST_TYPE_POST, REQUEST_TYPE_JSON, this.server + '/get', req, {}, function (url, res, err) { + App.onResponse(url, res, err) + var data = res.data + if (JSONResponse.isSuccess(data) == false) { + alert('获取场景用例分组失败!\n' + (err != null ? err.message : (data || '').msg)) + if (IS_BROWSER) { // 解决一旦错了,就只能清缓存 + App.chainGroupCount = 50 + App.chainGroupPage = 0 + App.chainGroupSearch = '' + App.chainGroupCounts = {} + App.chainGroupPages = {} + App.chainGroupSearches = {} + + App.saveCache(App.server, 'chainGroupCount', App.chainGroupCount) + App.saveCache(App.server, 'chainGroupPage', App.chainGroupPage) + App.saveCache(App.server, 'chainGroupSearch', App.chainGroupSearch) + App.saveCache(App.server, 'chainGroupCounts', App.chainGroupCounts) + App.saveCache(App.server, 'chainGroupPages', App.chainGroupPages) + App.saveCache(App.server, 'chainGroupSearches', App.chainGroupSearches) + } + return + } + + var chainGroups = App.chainGroups = data['[]'] || [] + var count = chainGroups.length + var index = App.currentChainGroupIndex + if (index == null || index < 0 || index >= count) { + App.currentChainGroupIndex = index = 0 + } + + var item = chainGroups[index] || {} + if (item.Chain != null) { + App.chainPaths.push(item.Chain) + } + App.remotes = App.testCases = item['[]'] || [] + App.isTestCaseShow = true +// App.showTestCase(true, false, null) + }) + }, + + addCase2Chain: function (item) { + var id = item == null ? null : item.id + if (id == null || id <= 0) { + alert('请选择有效的用例!') + return + } + + var group = (this.chainGroups[this.currentChainGroupIndex] || {}).Chain + if (group == null) { + var index = this.chainPaths.length - 1 + group = this.chainPaths[index] + } + + var groupId = group == null ? 0 : group.groupId + if (groupId == null || groupId <= 0) { + alert('请选择有效的场景串联用例分组!') + return + } + + var nextIndex = this.currentDocIndex + var nextChain = nextIndex == null || nextIndex < 0 ? null : (this.testCases[nextIndex] || {}).Chain + var nextRank = nextChain == null ? null : nextChain.rank + + var groupName = group.groupName + var isAdd = true + this.request(true, REQUEST_TYPE_POST, REQUEST_TYPE_JSON, this.server + '/post', { + Chain: { + 'rank': this.formatDateTime(StringUtil.isEmpty(nextRank, true) ? null : new Date(new Date(nextRank).getTime() - 10)), + 'groupName': groupName, + 'groupId': groupId, + 'documentId': item.id + }, + tag: 'Chain' + }, {}, function (url, res, err) { + App.onResponse(url, res, err) + var isOk = JSONResponse.isSuccess(res.data) + + var msg = isOk ? '' : ('\nmsg: ' + StringUtil.get((res.data || {}).msg)) + if (err != null) { + msg += '\nerr: ' + err.msg + } + alert((isAdd ? '新增' : '修改') + (isOk ? '成功' : '失败') + (isAdd ? '! \n' :'!\ngroupId: ' + groupId) + '\ngroupName: ' + groupName + '\n' + msg) + + App.isCaseGroupEditable = ! isOk + if (isOk) { +// App.remotes = App.testCases = [] +// App.showTestCase(true, false, null) + App.selectChainGroup(App.currentChainGroupIndex, null) + } + }) + }, + + onClickPath: function (index, path) { + if (this.isChainShow) { + this.onClickChainPath(index, path) + return + } + + var paths = this.casePaths + this.casePaths = paths.slice(0, index) + this.selectCaseGroup(0, path) + }, + isCaseGroupShow: function () { + if (this.isChainShow) { + return this.isChainGroupShow() + } + + return this.caseShowType != 1 && (this.caseGroups.length > 0 || this.casePaths.length <= 0) + }, + isCaseItemShow: function () { + if (this.isChainShow) { + return this.isChainItemShow() + } + + return this.caseShowType != 2 || (this.caseGroups.length <= 0 && this.casePaths.length > 0) + }, + getCaseGroupShowName: function(index, item) { + if (StringUtil.isNotEmpty(item.groupName, true)) { + return item.groupName + } + if (StringUtil.isEmpty(item.groupUrl, true)) { + return '-' + } + + var prev = index <= 0 ? null : (this.casePaths[index-1] || {}).groupUrl + return item.groupUrl.substring(prev == null ? 1 : prev.length + 1) + }, + selectCaseGroup: function (index, group) { + if (this.isChainShow) { + this.selectChainGroup(index, group) + return + } + + this.isCaseGroupEditable = false + + if (group == null) { + if (index == null) { + index = this.casePaths.length - 1 + group = this.casePaths[index] + } else { + this.casePaths = [] + } + } else { + this.casePaths.push(group) + } + + var groupUrl = group == null ? '' : (group.groupUrl || '') + if (group != null && StringUtil.isEmpty(groupUrl)) { + this.caseGroups = [] + this.remotes = App.testCases = [] + this.showTestCase(true, false, null) + return + } + + var project = (this.projectHost || {}).project + + var page = this.caseGroupPage = this.caseGroupPages[groupUrl] || 0 + var count = this.caseGroupCount = this.caseGroupCounts[groupUrl] || 0 + var search = this.caseGroupSearch = this.caseGroupSearches[groupUrl] || '' + + search = StringUtil.isEmpty(search, true) ? null : '%' + StringUtil.trim(search).replaceAll('_', '\\_').replaceAll('%', '\\%') + '%' + var req = { + format: false, + 'Document[]': { + 'count': count || 0, + 'page': page || 0, + 'Document': { + '@from@': { + 'Document': { + '@raw': '@column', + '@column': "substr(url,1,length(url)-length(substring_index(url,'/',-1))-1):groupUrl;group:groupName", // (CASE WHEN length(`group`) > 0 THEN `group` ELSE '-' END):name", + 'userId': this.User.id, + 'project': StringUtil.isEmpty(project, true) ? null : project, + 'group$': search, + 'url$': search, + // 'url&$': StringUtil.isEmpty(groupUrl) ? null : [groupUrl.replaceAll('_', '\\_').replaceAll('%', '\\%') + '/%'], + '@combine': search == null ? null : 'group$,url$', + '@null': 'sqlauto', //'sqlauto{}': '=null', + 'url{}': 'length(url)>0', + 'url&$': StringUtil.isEmpty(groupUrl) ? null : [groupUrl.replaceAll('_', '\\_').replaceAll('%', '\\%') + '/%'] + // 'group{}': group == null || StringUtil.isNotEmpty(groupUrl) ? null : 'length(group)<=0' // SQL WHERE 条件不用别名 + // '@having': "length(url)>0" // StringUtil.isEmpty(groupUrl) ? "length(url)>0" : "(url = '" + groupUrl.replaceAll("'", "\\'") + "')" + } + }, + 'groupUrl&$': StringUtil.isEmpty(groupUrl) ? null : [groupUrl.replaceAll('_', '\\_').replaceAll('%', '\\%') + '/%'], + 'groupName$': search, + 'groupUrl$': search, + '@combine': search == null ? null : 'groupName$,groupUrl$', + '@column': "groupName,groupUrl;any_value(groupName):rawName;length(groupName):groupNameLen;length(groupUrl):groupUrlLen;count(*):count", + '@group': 'groupName,groupUrl', + '@order': 'groupNameLen+,groupName-,groupUrlLen+,groupUrl+', + } + }, + '@role': IS_NODE ? null : 'LOGIN', + key: IS_NODE ? this.key : undefined // 突破常规查询数量限制 + } + + if (IS_BROWSER) { + this.onChange(false) + } + + this.request(true, REQUEST_TYPE_POST, REQUEST_TYPE_JSON, this.server + '/get', req, {}, function (url, res, err) { + App.onResponse(url, res, err) + var data = res.data + if (JSONResponse.isSuccess(data) == false) { + alert('获取用例分组失败!\n' + (err != null ? err.message : (data || '').msg)) + if (IS_BROWSER) { // 解决一旦错了,就只能清缓存 + App.caseGroupCount = 50 + App.caseGroupPage = 0 + App.caseGroupSearch = '' + App.caseGroupCounts = {} + App.caseGroupPages = {} + App.caseGroupSearches = {} + + App.saveCache(App.server, 'caseGroupCount', App.caseGroupCount) + App.saveCache(App.server, 'caseGroupPage', App.caseGroupPage) + App.saveCache(App.server, 'caseGroupSearch', App.caseGroupSearch) + App.saveCache(App.server, 'caseGroupCounts', App.caseGroupCounts) + App.saveCache(App.server, 'caseGroupPages', App.caseGroupPages) + App.saveCache(App.server, 'caseGroupSearches', App.caseGroupSearches) + } + return + } + + App.caseGroups = data['Document[]'] || [] + App.remotes = App.testCases = [] + App.showTestCase(true, false, null) + }) + }, + + switchCaseShowType: function () { + if (this.isLocalShow) { + alert('只有远程用例才能切换!') + return + } + + if (this.isChainShow) { + this.chainShowType = (this.chainShowType + 1)%3 + if (this.chainShowType != 1 && this.chainPaths.length <= 0 && this.chainGroups.length <= 0) { + this.selectChainGroup(-1, null) + } + return + } + + this.caseShowType = (this.caseShowType + 1)%3 + if (this.caseShowType != 1 && this.casePaths.length <= 0 && this.caseGroups.length <= 0) { + this.selectCaseGroup(-1, null) + } + }, + + onClickPathRoot: function () { + var isChainShow = this.isChainShow +// var paths = isChainShow ? this.chainPaths : this.casePaths + var paths = isChainShow ? [] : this.casePaths + if (paths.length <= 0) { + var type = this.groupShowType = (this.groupShowType + 1)%3 + this.isChainShow = type == 1 + this.isLocalShow = type == 2 + + this.testCases = this.remotes = [] + if (type == 1 && this.chainGroups.length <= 0) { + this.selectChainGroup(-1, null) + } + else if (type == 0 && this.caseGroups.length <= 0) { + this.selectCaseGroup(-1, null) + } + else { + this.showTestCase(true, this.isLocalShow) + } + } + else if (isChainShow) { + this.selectChainGroup(-1, null) + } + else { + this.selectCaseGroup(-1, null) + } + }, + + getCaseCountStr: function() { + var isChainShow = this.isChainShow + var isCaseGroupShow = this.isCaseGroupShow() + var isLocalShow = this.isLocalShow + var caseShowType = this.caseShowType + var caseGroups = (isChainShow ? this.chainGroups : this.caseGroups) || [] + var testCases = this.testCases || [] + + if (isLocalShow) { + return '(' + testCases.length + ')' + } + + return '(' + (isCaseGroupShow ? caseGroups.length : '') + + (caseShowType == 0 && isCaseGroupShow ? '|' : '') + + (caseShowType == 2 && (isCaseGroupShow) ? '' : testCases.length) + ')'; + + // 以下代码不知道为啥结果显示不对 + // var isCaseGroupShow = this.isCaseGroupShow() + // var isCaseItemShow = this.isCaseItemShow() + // var caseGroups = (this.isChainShow ? this.chainGroups : this.caseGroups) || [] + // + // return '(' + (isCaseGroupShow ? caseGroups.length : '') + // + (isCaseGroupShow && isCaseItemShow ? '|' : '') + // + (isCaseItemShow ? '' : testCases.length) + ')'; + }, + + //显示远程的测试用例文档 + showTestCase: function (show, isLocal, callback, group) { + this.isTestCaseShow = show + this.isLocalShow = isLocal + + if (IS_BROWSER) { + vOutput.value = show ? '' : (output || '') + this.showDoc() + } + + if (isLocal) { + this.testCases = this.locals || [] + return + } + + this.testCases = this.remotes || [] + this.getCurrentSummary().summaryType = 'total' // this.onClickSummary('total', true) + + if (show) { + var testCases = this.testCases + var allCount = testCases == null ? 0 : testCases.length + App.allCount = allCount + if (allCount > 0) { + if (! (this.isAllSummaryShow() || this.isCurrentSummaryShow())) { + this.showCompare4TestCaseList(show) + } + return; + } + + var project = (this.projectHost || {}).project + + // this.isTestCaseShow = false + var reportId = this.reportId + + var methods = this.methods + var types = this.types + + var isChainShow = this.isChainShow + var paths = isChainShow ? this.chainPaths : this.casePaths + var index = paths.length - 1 + group = group != null ? group : paths[index] + var groupId = group == null ? 0 : (group.groupId || 0) + var groupUrl = group == null ? '' : (group.groupUrl || '') + var groupKey = isChainShow ? groupId + '' : groupUrl + + var page = this.testCasePage = this.testCasePages[groupKey] || 0 + var count = this.testCaseCount = this.testCaseCounts[groupKey] || 100 + var search = this.testCaseSearch = this.testCaseSearches[groupKey] || '' + + search = StringUtil.isEmpty(search, true) ? null : '%' + StringUtil.trim(search).replaceAll('_', '\\_').replaceAll('%', '\\%') + '%' + + var url = this.server + '/get' + var userId = this.User.id + + this.coverage = {} + this.view = 'markdown' + var req = { + format: false, + '[]': { + 'count': count || 100, //200 条测试直接卡死 0, + 'page': page || 0, + 'join': isChainShow ? '&/Document,@/Random' : '@/TestRecord,@/Script:pre,@/Script:post', + 'Chain': isChainShow ? { + // TODO 后续再支持嵌套子组合 'toGroupId': groupId, + 'groupId': groupId, + '@column': "id,groupId,documentId,randomId,documentName,randomName,rank", // ;unix_timestamp(rank):rank", + '@order': 'rank+,id+', + 'documentId>': 0 + } : null, + 'Document': { + 'id@': isChainShow ? '/Chain/documentId' : null, + // '@column': 'id,userId,version,date,name,operation,method,type,url,request,apijson,standard', // ;substr(url,' + (StringUtil.length(groupUrl) + 2) + '):substr', + '@order': 'version-,date-', + 'userId': userId, + 'project': StringUtil.isEmpty(project, true) ? null : project, + 'name$': search, + 'operation$': search, + 'url$': search, + 'url|$': StringUtil.isEmpty(groupUrl) ? null : [groupUrl, groupUrl.replaceAll('_', '\\_').replaceAll('%', '\\%') + '/%'], + // 'group{}': group == null || StringUtil.isNotEmpty(groupUrl) ? null : 'length(group)<=0', + // 'group{}': group == null ? null : (group.groupName == null ? "=null" : [group.groupName]), + '@combine': search == null ? null : 'name$,operation$,url$', + 'method{}': methods == null || methods.length <= 0 ? null : methods, + 'type{}': types == null || types.length <= 0 ? null : types, + '@null': 'sqlauto', //'sqlauto{}': '=null' + // '@having': StringUtil.isEmpty(groupUrl) ? null : "substring_index(substr,'/',1)<0" + }, + 'Random': isChainShow ? { + 'id@': '/Chain/randomId', + 'toId': 0, // isChainShow ? 0 : null, +// 'chainId@': isChainShow ? '/Chain/id' : null, +// 'documentId@': isChainShow ? null : '/Document/documentId', + 'userId': userId + }: null, + 'TestRecord': { + 'chainId@': isChainShow ? '/Chain/id' : null, + 'chainId': isChainShow ? null : 0, + 'documentId@': '/Document/id', + 'userId': userId, + 'host': StringUtil.isEmpty(baseUrl, true) ? null : baseUrl, +// 'testAccountId': this.getCurrentAccountId(), + 'randomId': 0, + 'reportId': reportId <= 0 ? null : reportId, + 'invalid': reportId == null ? 0 : null, + '@order': 'date-', + '@column': 'id,userId,documentId,testAccountId,reportId,duration,minDuration,maxDuration,response' + (this.isStatisticsEnabled ? ',compare' : '')+ (this.isMLEnabled ? ',standard' : ''), + 'standard{}': this.isMLEnabled ? (this.database == 'SQLSERVER' ? 'len(standard)>2' : 'length(standard)>2') : null //用 MySQL 5.6 '@having': this.isMLEnabled ? 'json_length(standard)>0' : null + }, + 'Script:pre': { + 'ahead': 1, + // 'testAccountId': 0, + 'chainId@': isChainShow ? '/Chain/id' : null, + 'chainId': isChainShow ? null : 0, + 'documentId@': '/Document/id', + '@order': 'date-' + }, + 'Script:post': { + 'ahead': 0, + // 'testAccountId': 0, + 'chainId@': isChainShow ? '/Chain/id' : null, + 'chainId': isChainShow ? null : 0, + 'documentId@': '/Document/id', + '@order': 'date-' + } + }, + '@role': IS_NODE ? null : 'LOGIN', + key: IS_NODE ? this.key : undefined // 突破常规查询数量限制 + } + + if (IS_BROWSER) { + this.onChange(false) + } + + this.request(true, REQUEST_TYPE_POST, REQUEST_TYPE_JSON, url, req, {}, function (url, res, err) { + App.isTestCaseShow = false + if (callback) { + callback(url, res, err) + return + } + + App.onTestCaseListResponse(show, url, res, err) + }) + } else if (callback != null) { + callback(null, {}, null) + } + }, + + lastCaseReqTime: 0, + onTestCaseListResponse: function(show, url, res, err) { + var time = res == null || res.config == null || res.config.metadata == null ? 0 : (res.config.metadata.startTime || 0) + if (time < this.lastCaseReqTime) { + return + } + + this.lastCaseReqTime = time; + + this.onResponse(url, res, err) + + var data = res.data + + if (JSONResponse.isSuccess(data)) { + this.isTestCaseShow = true + this.isLocalShow = false + this.testCases = App.remotes = data['[]'] + this.getCurrentRandomSummary().summaryType = 'total' // App.onClickSummary('total', true) + if (this.isChainShow && this.currentChainGroupIndex >= 0) { + var chain = this.chainGroups[this.currentChainGroupIndex] + if (chain != null) { + chain['[]'] = this.testCases + } + } + + if (IS_BROWSER) { + vOutput.value = show ? '' : (output || '') + this.showDoc() + } + + this.showCompare4TestCaseList(show) + + //this.onChange(false) + } else if (IS_BROWSER) { // 解决一旦错了,就只能清缓存 + this.testCaseCount = 50 + this.testCasePage = 0 + this.testCaseSearch = '' + this.testCasePages = {} + this.testCaseCounts = {} + this.testCaseSearches = {} + + this.saveCache(this.server, 'testCasePage', this.testCasePage) + this.saveCache(this.server, 'testCaseCount', this.testCaseCount) + this.saveCache(this.server, 'testCaseSearch', this.testCaseSearch) + this.saveCache(this.server, 'testCasePages', null) + this.saveCache(this.server, 'testCaseCounts', null) + this.saveCache(this.server, 'testCaseSearches', null) + } + }, + + onClickLogoutSummary: function (color) { + this.onClickSummary(color, false, -1) + }, + onClickAllSummary: function (color) { + this.onClickSummary(color, false, this.accounts.length) // this.currentAccountIndex) + }, + onClickCurrentSummary: function (color) { + this.onClickSummary(color, false, this.currentAccountIndex) + }, + onClickSummary: function (color, isRandom, accountIndex) { + var isCur = this.currentAccountIndex == accountIndex + if (! isCur) { + this.onClickAccount(accountIndex, accountIndex < 0 ? this.logoutSummary : this.accounts[accountIndex]) + } + // this.currentAccountIndex = accountIndex + // this.isTestCaseShow = false + + var isSub = this.isRandomSubListShow + var arr = isRandom ? (isSub ? this.currentRandomItem.subs : this.currentRemoteItem.randoms) : this.remotes; + var list = [] + if (color == null || color == 'total') { + list = arr + if (isCur) { + this.statisticsShowType = (this.statisticsShowType + 1)%3; + } + } else if (arr != null) { + for (var i = 0; i < arr.length; i++) { + var obj = arr[i] + if (obj == null) { + continue + } + + var count = isRandom && obj.Random != null ? obj.Random.count : (isRandom ? null : obj.totalCount) + if (count != null && count > (isRandom ? 1 : 0)) { + var sum = obj[color + 'Count'] + if (sum != null && sum > 0) { + list.push(obj) + } + continue + } + + if (obj.compareColor == color) { + list.push(obj) + } + } + } + + if (isRandom) { + if (isSub) { + this.currentRandomItem.summaryType = color + this.randomSubs = list + } else { + this.currentRemoteItem.summaryType = color + this.randoms = list + } + } else { + var summary = this.getSummary(accountIndex) || {} + summary.summaryType = color + this.testCases = list + this.isTestCaseShow = true + // this.showTestCase(true, false) + } + }, + + showCompare4RandomList: function (show, isSub) { + this.getCurrentRandomSummary().summaryType = 'total' + + var randoms = show ? (isSub ? this.randomSubs : this.randoms) : null + var randomCount = randoms == null ? 0 : randoms.length + if (randomCount > 0) { + var accountIndex = (this.accounts[this.currentAccountIndex] || {}).isLoggedIn ? this.currentAccountIndex : -1 + this.currentAccountIndex = accountIndex //解决 onTestResponse 用 -1 存进去, handleTest 用 currentAccountIndex 取出来为空 + var docId = ((this.currentRemoteItem || {}).Document || {}).id + + var tests = (this.tests[String(accountIndex)] || {})[docId] + if (tests != null && JSONObject.isEmpty(tests) != true) { + // if (! isSub) { + // this.resetCount(this.currentRemoteItem, true, isSub, accountIndex) + // } + + for (var i = 0; i < randomCount; i++) { + var item = randoms[i] + var r = item == null ? null : item.Random + if (r == null || r.id == null) { + continue + } + + this.resetCount(item, true, isSub, accountIndex) + + var subCount = r.count || 0 + if (subCount == 1) { + this.compareResponse(null, randomCount, randoms, i, item, tests[r.id], true, accountIndex, true) + } + else if (subCount > 1) { + var subRandoms = item['[]'] || [] + var subSize = Math.min(subRandoms.length, subCount) + for (var j = 0; j < subSize; j++) { + var subItem = subRandoms[j] + var sr = subItem == null ? null : subItem.Random + if (sr == null || sr.id == null) { + continue + } + + this.compareResponse(null, subSize, subRandoms, j, subItem, tests[sr.id > 0 ? sr.id : (sr.toId + '' + sr.id)], true, accountIndex, true) + } + } + } + } + } + }, + + //显示远程的随机配置文档 + showRandomList: function (show, item, isSub, callback) { + this.isRandomEditable = false + this.isRandomListShow = show && ! isSub + this.isRandomSubListShow = show && isSub + if (! isSub) { + this.randomSubs = [] + } + + if (IS_BROWSER) { + vOutput.value = show ? '' : (output || '') + this.showDoc() + } + + var randoms = [] + if (this.randomPage == 0 && ! isSub) { + randoms = (this.currentRemoteItem || {}).randoms || [] + } + else if (this.randomSubPage == 0 && isSub) { + randoms = (this.currentRandomItem || {}).subs || [] + } + + if (isSub) { + this.randomSubs = randoms + } + else { + this.randoms = randoms + } + + this.getCurrentRandomSummary().summaryType = 'total' // this.onClickSummary('total', true) + if (! this.isRandomSummaryShow()) { + this.showCompare4RandomList(show, isSub) + } + + if (show && this.isRandomShow && randoms.length <= 0 && item != null && item.id != null) { + this.isRandomListShow = false + + var subSearch = StringUtil.isEmpty(this.randomSubSearch, true) + ? null : '%' + StringUtil.trim(this.randomSubSearch) + '%' + var search = isSub ? subSearch : (StringUtil.isEmpty(this.randomSearch, true) + ? null : '%' + StringUtil.trim(this.randomSearch) + '%') + + var url = this.server + '/get' + baseUrl = this.getBaseUrl(vUrl.value, true) + const cri = this.currentRemoteItem || {} + const chain = cri.Chain || {} + const cId = chain.id || 0 + + var req = { + '[]': { + 'count': (isSub ? this.randomSubCount : this.randomCount) || 100, + 'page': (isSub ? this.randomSubPage : this.randomPage) || 0, + 'Random': { + 'toId': isSub ? item.id : 0, + 'chainId': cId, + 'documentId': isSub ? null : item.id, + '@order': "date-", + 'name$': search + }, + 'TestRecord': { + 'randomId@': '/Random/id', +// 'testAccountId': this.getCurrentAccountId(), + 'host': StringUtil.isEmpty(baseUrl, true) ? null : baseUrl, + '@order': 'date-' + }, + '[]': isSub ? null : { + 'count': this.randomSubCount || 100, + 'page': this.randomSubPage || 0, + 'Random': { + 'toId@': '[]/Random/id', + 'chainId': cId, + 'documentId': item.id, + '@order': "date-", + 'name$': subSearch + }, + 'TestRecord': { + 'randomId@': '/Random/id', +// 'testAccountId': this.getCurrentAccountId(), + 'host': StringUtil.isEmpty(baseUrl, true) ? null : baseUrl, + '@order': 'date-' + } + } + }, + key: IS_NODE ? this.key : undefined // 突破常规查询数量限制 + } + + if (IS_BROWSER) { + this.onChange(false) + } + + this.request(true, REQUEST_TYPE_POST, REQUEST_TYPE_JSON, url, req, {}, function (url, res, err) { + if (callback) { + callback(url, res, err) + return + } + App.onRandomListResponse(show, isSub, url, res, err) + }) + } else if (callback) { + callback(null, {}, null) + } + }, + + onRandomListResponse: function (show, isSub, url, res, err) { + res = res || {} + + App.onResponse(url, res, err) + + var data = res.data + + if (JSONResponse.isSuccess(data)) { + App.isRandomListShow = ! isSub + App.isRandomSubListShow = isSub + if (isSub) { + if (App.currentRandomItem == null) { + App.currentRandomItem = {} + } + App.randomSubs = App.currentRandomItem.subs = App.currentRandomItem['[]'] = data['[]'] + } + else { + if (App.currentRemoteItem == null) { + App.currentRemoteItem = {} + } + App.randoms = App.currentRemoteItem.randoms = data['[]'] + } + this.getCurrentRandomSummary().summaryType = 'total' // App.onClickSummary('total', true) + + if (IS_BROWSER) { + vOutput.value = show ? '' : (output || '') + App.showDoc() + } + + // if (! this.isRandomSummaryShow()) { + App.showCompare4RandomList(show, isSub) + // } + + //App.onChange(false) + } + }, + + // 设置文档 + showDoc: function () { + if (this.setDoc(doc) == false) { + this.getDoc(function (d) { + App.setDoc(d); + }); + } + }, + + + saveCache: function (url, key, value) { + var cache = this.getCache(url); + cache[key] = value + localStorage.setItem('APIAuto:' + url, JSON.stringify(cache)) + }, + getCache: function (url, key, defaultValue) { + var cache = localStorage.getItem('APIAuto:' + url) + try { + cache = parseJSON(cache) + } catch(e) { + this.log('login this.send >> try { cache = parseJSON(cache) } catch(e) {\n' + e.message) + } + cache = cache || {} + var val = key == null ? cache : cache[key] + return val == null && defaultValue != null ? defaultValue : val + }, + + getCurrentDocumentId: function() { + var d = (this.currentRemoteItem || {}).Document + return d == null ? null : d.id; + }, + getCurrentRandomId: function() { + var r = (this.currentRandomItem || {}).Random + return r == null ? null : r.id; + }, + getCurrentScriptBelongId: function() { + return this.getScriptBelongId(this.scriptType) + }, + getScriptBelongId: function(scriptType) { + var st = scriptType; + var bid = st == 'global' ? 0 : ((st == 'account' ? this.getCurrentAccountId() : this.getCurrentDocumentId()) || 0) + return bid + }, + listScript: function() { + var req = { + 'Script:pre': { + 'ahead': 1, + 'testAccountId': 0, + 'documentId': 0, + '@order': 'date-' + }, + 'Script:post': { + 'ahead': 0, + 'testAccountId': 0, + 'documentId': 0, + '@order': 'date-' + } + } + + var accounts = this.accounts || [] + for (let i = 0; i < accounts.length; i++) { + var a = accounts[i] + var id = a == null ? null : a.id + if (id == null) { + continue + } + + req['account_' + id] = { // 用数字被居然强制格式化到 JSON 最前 + 'Script:pre': { + 'ahead': 1, + 'testAccountId': id, + 'documentId': 0, + '@order': 'date-' + }, + 'Script:post': { + 'ahead': 0, + 'testAccountId': id, + 'documentId': 0, + '@order': 'date-' + } + } + } + + this.request(true, REQUEST_TYPE_POST, REQUEST_TYPE_JSON, '/get', req, {}, function (url, res, err) { + var data = res.data + if (JSONResponse.isSuccess(data) != true) { + App.log(err != null ? err : (data == null ? '' : data.msg)) + return + } + + var scripts = App.scripts || {} + var ss = scripts.global + if (ss == null) { + scripts.global = ss = {} + } + + var bs = ss['0'] || {} + if (bs == null) { + ss['0'] = bs = {} + } + + var pre = data['Script:pre'] + if (pre != null && pre.script != null) { + bs.pre = data['Script:pre'] + } + var post = data['Script:post'] + if (post != null && post.script != null) { + bs.post = data['Script:post'] + } + + // delete data['Script:pre'] + // delete data['Script:post'] + + var cs = scripts.account + if (cs == null) { + scripts.account = cs = {} + } + + for (let key in data) { + var val = data[key] + var pre = val == null || key.startsWith('account_') != true ? null : val['Script:pre'] + if (pre == null) { + continue + } + + var post = val['Script:post'] + + var bs = cs[key.substring('account_'.length)] + + if (pre != null) { // && pre.script != null) { + bs.pre = pre + } + if (post != null) { // && post.script != null) { + bs.post = post + } + } + + App.scripts = Object.assign(newDefaultScript(), scripts) + }) + }, + + + /**登录确认 + */ + confirm: function () { + switch (this.loginType) { + case 'login': + this.login(this.isAdminOperation) + break + case 'register': + this.register(this.isAdminOperation) + break + case 'forget': + this.resetPassword(this.isAdminOperation) + break + } + }, + + showLogin: function (show, isAdmin) { + this.isLoginShow = show + this.isAdminOperation = isAdmin + + if (show != true) { + return + } + + var user = isAdmin ? this.User : null // add account this.accounts[this.currentAccountIndex] + + // alert("showLogin isAdmin = " + isAdmin + "; user = \n" + JSON.stringify(user, null, ' ')) + + if (user == null || StringUtil.isEmpty(user.phone, true)) { + user = { + phone: '13000082001', + password: '123456' + } + } + + this.setRememberLogin(user.remember) + this.account = user.phone + this.password = user.password + + var schemas = StringUtil.isEmpty(this.schema, true) ? null : StringUtil.split(this.schema) + + const req = { + type: 0, // 登录方式,非必须 0-密码 1-验证码 + // asDBAccount: ! isAdminOperation, // 直接 /execute 接口传 account, password + phone: this.account, + password: this.password, + version: 1, // 全局默认版本号,非必须 + remember: vRemember.checked, + format: false, + defaults: isAdmin ? { + key: IS_NODE ? this.key : undefined // 突破常规查询数量限制 + } : { + '@database': StringUtil.isEmpty(this.database, true) ? undefined : this.database, + '@schema': schemas == null || schemas.length != 1 ? undefined : this.schema + } + } + + this.isHeaderShow = true + this.isRandomShow = true + this.isRandomListShow = false + + if (IS_BROWSER && ! isAdmin) { + this.prevMethod = this.method + this.prevType = this.type + + this.prevUrl = vUrl.value + this.prevUrlComment = vUrlComment.value + this.prevInput = vInput.value + this.prevComment = vComment.value + this.prevWarning = vWarning.value + this.prevRandom = vRandom.value + this.prevHeader = vHeader.value + this.prevScript = vScript.value + + this.method = REQUEST_TYPE_POST + this.type = REQUEST_TYPE_JSON + this.showUrl(isAdmin, '/login') + vInput.value = JSON.stringify(req, null, ' ') + + this.testRandomCount = 1 + vRandom.value = `phone: App.account\npassword: App.password\nremember: vRemember.checked` + } + + this.scripts = newDefaultScript() + this.method = REQUEST_TYPE_POST + this.type = REQUEST_TYPE_JSON + this.showTestCase(false, this.isLocalShow) + if (IS_BROWSER) { + this.onChange(false) + } + }, + + setRememberLogin: function (remember) { + vRemember.checked = remember || false + }, + + getCurrentAccount: function() { + return this.accounts == null ? null : this.accounts[this.currentAccountIndex] + }, + getCurrentAccountId: function() { + var a = this.getCurrentAccount() + return a != null && a.isLoggedIn ? a.id : null + }, + + /**登录 + */ + login: function (isAdminOperation, callback) { + this.isEditResponse = false + var schemas = StringUtil.isEmpty(this.schema, true) ? null : StringUtil.split(this.schema) + + const req = { + type: 0, // 登录方式,非必须 0-密码 1-验证码 + // asDBAccount: ! isAdminOperation, // 直接 /execute 接口传 account, password + phone: this.account, + password: this.password, + version: 1, // 全局默认版本号,非必须 + remember: vRemember.checked, + format: false, + defaults: isAdminOperation ? { + key: IS_NODE ? this.key : undefined // 突破常规查询数量限制 + } : { + '@database': StringUtil.isEmpty(this.database, true) ? undefined : this.database, + '@schema': schemas == null || schemas.length != 1 ? undefined : this.schema + } + } + + if (isAdminOperation) { + this.isLoginShow = false + this.request(isAdminOperation, REQUEST_TYPE_POST, REQUEST_TYPE_JSON, this.server + '/login', req, this.getHeader(vHeader.value), function (url, res, err) { + if (callback) { + callback(url, res, err) + return + } + + App.onLoginResponse(isAdminOperation, req, url, res, err); + }) + } + else { + function recover() { + App.isLoginShow = false + + if (App.prevUrl != null) { + App.method = App.prevMethod || REQUEST_TYPE_POST + App.type = App.prevType || REQUEST_TYPE_JSON + + vUrl.value = App.prevUrl || (URL_BASE + '/get') + vUrlComment.value = App.prevUrlComment || '' + vComment.value = App.prevComment || '' + vWarning.value = App.prevWarning || '' + vInput.value = App.prevInput || '{}' + vRandom.value = App.prevRandom || '' + vHeader.value = App.prevHeader || '' + vScript.value = App.prevScript || '' + + App.prevUrl = null + } + } + + const baseUrl = this.getBaseUrl() + + if (IS_BROWSER && callback == null) { + var item + for (var i in this.accounts) { + item = this.accounts[i] + if (item != null && baseUrl == item.baseUrl && req.phone == item.phone) { + recover() + alert(req.phone + ' 已在测试账号中!') + // this.currentAccountIndex = i + item.remember = vRemember.checked + this.onClickAccount(i, item) + return + } + } + } + + this.scripts = newDefaultScript() + + const isLoginShow = this.isLoginShow + var curUser = this.getCurrentAccount() || {} + const loginMethod = (isLoginShow ? this.method : curUser.loginMethod) || REQUEST_TYPE_POST + const loginType = (isLoginShow ? this.type : curUser.loginType) || REQUEST_TYPE_JSON + const loginUrl = (isLoginShow ? this.getBranchUrl() : curUser.loginUrl) || '/login' + const loginReq = (isLoginShow ? this.getRequest(vInput.value) : curUser.loginReq) || req + const loginRandom = (isLoginShow ? vRandom.value : curUser.loginRandom) || '' + const loginHeader = (isLoginShow ? this.getHeader(vHeader.value) : curUser.loginHeader) || {} + + function loginCallback(url, res, err, random) { + recover() + if (callback) { + callback(url, res, err) + } else { + App.onLoginResponse(isAdminOperation, req, url, res, err, loginMethod, loginType, loginUrl, loginReq, loginRandom, loginHeader) + } + + if (App.prevUrl != null) { + App.method = App.prevMethod || REQUEST_TYPE_POST + App.type = App.prevType || REQUEST_TYPE_JSON + + vUrl.value = App.prevUrl || (URL_BASE + '/get') + vUrlComment.value = App.prevUrlComment || '' + vComment.value = App.prevComment || '' + vWarning.value = App.prevWarning || '' + vInput.value = App.prevInput || '{}' + vRandom.value = App.prevRandom || '' + vHeader.value = App.prevHeader || '' + vScript.value = App.prevScript || '' + + App.prevUrl = null + } + } + + if (isLoginShow) { + this.isLoginShow = false + + this.testRandomWithText(true, loginCallback) + return + } + + this.scripts = newDefaultScript() + + this.parseRandom(loginReq, loginRandom, 0, true, false, false, function(randomName, constConfig, constJson) { + App.request(isAdminOperation, loginMethod, loginType, baseUrl + loginUrl, constJson, loginHeader, function (url, res, err) { + if (App.isEnvCompareEnabled != true) { + loginCallback(url, res, err, null, loginMethod, loginType, loginUrl, constJson, loginHeader) + return + } + + App.request(isAdminOperation, loginMethod, loginType, App.getBaseUrl(App.otherEnv) + loginUrl + , loginReq, loginHeader, function(url_, res_, err_) { + var data = res_.data + var user = data.user || data.userObj || data.userObject || data.userRsp || data.userResp || data.userBean || data.userData || data.data || data.User || data.Data + if (user != null) { + var headers = res.headers || {} + App.otherEnvTokenMap[req.phone + '@' + baseUrl] = headers.token || headers.Token || data.token || data.Token || user.token || user.Token + App.otherEnvCookieMap[req.phone + '@' + baseUrl] = res.cookie || headers.cookie || headers.Cookie || headers['set-cookie'] || headers['Set-Cookie'] + App.saveCache(App.otherEnv, 'otherEnvTokenMap', App.otherEnvTokenMap) + App.saveCache(App.otherEnv, 'otherEnvCookieMap', App.otherEnvCookieMap) + } + + if (callback) { + callback(url, res, err) + return + } + + App.onResponse(url_, res_, err_); + App.onLoginResponse(isAdminOperation, req, url, res, err, loginMethod, loginType, loginUrl, constJson, loginRandom, loginHeader) + }, App.scripts) + }) + }) + } + }, + + onLoginResponse: function(isAdmin, req, url, res, err, loginMethod, loginType, loginUrl, loginReq, loginRandom, loginHeader) { + res = res || {} + if (isAdmin) { + var data = res.data || {} + + var user = data.user || data.userObj || data.userObject || data.userRsp || data.userResp || data.userBean || data.userData || data.data || data.User || data.Data + if (user == null) { + alert('登录失败,请检查网络后重试。\n' + data.msg + '\n详细信息可在浏览器控制台查看。') + App.onResponse(url, res, err) + } + else { + if (user != null) { + var headers = res.headers || {} + user.remember = data.remember + user.phone = req.mobile || req.mobileNo || req.mobileNum || req.mobileNumber || req.phone || req.phoneNo || req.phoneNum || req.phoneNumber || req.mobile_no || req.mobile_num || req.mobile_number || req.phone_no || req.phone_num || req.phone_number + user.password = req.password + user.token = headers.token || headers.Token || data.token || data.Token || user.token || user.Token || user.accessToken || user.access_token || data.accessToken || data.access_token + user.cookie = res.cookie || headers.cookie || headers.Cookie || headers['set-cookie'] || headers['Set-Cookie'] + App.User = user + } + + //保存User到缓存 + App.saveCache(App.server, 'User', user) + + if (App.currentAccountIndex == null || App.currentAccountIndex < 0) { + App.currentAccountIndex = 0 + } + var item = App.accounts[App.currentAccountIndex] + item.isLoggedIn = false + App.onClickAccount(App.currentAccountIndex, item) //自动登录测试账号 + + if (user.id > 0) { + if (App.isChainShow && App.chainShowType != 1 && App.chainPaths.length <= 0 && App.chainGroups.length <= 0) { + App.selectChainGroup(-1, null) + } + if (App.caseShowType != 1 && App.casePaths.length <= 0 && App.caseGroups.length <= 0) { + App.selectCaseGroup(-1, null) + } + App.showTestCase(true, false) + } + } + } else { + App.onResponse(url, res, err) + + //由login按钮触发,不能通过callback回调来实现以下功能 + var data = res.data || {} + if (JSONResponse.isSuccess(data)) { + var headers = res.headers || {} + var user = data.user || data.userObj || data.userObject || data.userRsp || data.userResp || data.userBean || data.userData || data.data || data.User || data.Data + App.accounts.push({ + isLoggedIn: true, + baseUrl: App.getBaseUrl(), + id: user.id, + name: user.name || user.nickname || user.nickName || user.user_name || user.username || user.userName, + phone: req.phone, + password: req.password, + remember: data.remember, + loginMethod: loginMethod, + loginType: loginType, + loginUrl: loginUrl, + loginReq: loginReq, + loginRandom: loginRandom, + loginHeader: loginHeader, + cookie: res.cookie || headers.cookie, + token: headers.token || headers.Token || data.token || data.Token || user.token || user.Token || user.accessToken || user.access_token || data.accessToken || data.access_token + }) + + var lastItem = App.accounts[App.currentAccountIndex] + if (lastItem != null) { + lastItem.isLoggedIn = false + } + + App.currentAccountIndex = App.accounts.length - 1 + App.saveAccounts() + App.listScript() + } + } + }, + + /**注册 + */ + register: function (isAdminOperation) { + this.scripts = newDefaultScript() + this.showUrl(isAdminOperation, '/register') + vInput.value = JSON.stringify( + { + Privacy: { + phone: this.account, + _password: this.password + }, + User: { + name: 'APIJSONUser' + }, + verify: vVerify.value + }, + null, ' ') + this.showTestCase(false, false) + this.onChange(false) + this.send(isAdminOperation, function (url, res, err) { + App.onResponse(url, res, err) + + var data = res.data + + if (JSONResponse.isSuccess(data)) { + alert('注册成功') + + var privacy = data.Privacy || {} + + App.account = privacy.phone + App.loginType = 'login' + } + }, this.scripts) + }, + + /**重置密码 + */ + resetPassword: function (isAdminOperation) { + this.scripts = newDefaultScript() + this.showUrl(isAdminOperation, '/put/password') + vInput.value = JSON.stringify( + { + verify: vVerify.value, + Privacy: { + phone: this.account, + _password: this.password + } + }, + null, ' ') + this.showTestCase(false, this.isLocalShow) + this.onChange(false) + this.send(isAdminOperation, function (url, res, err) { + App.onResponse(url, res, err) + + var data = res.data + + if (JSONResponse.isSuccess(data)) { + alert('重置密码成功') + + var privacy = data.Privacy || {} + + App.account = privacy.phone + App.loginType = 'login' + } + }, this.scripts) + }, + + /**退出 + */ + logout: function (isAdminOperation, callback) { + this.isEditResponse = false + var req = {} + + if (isAdminOperation) { + // alert('logout isAdminOperation this.saveCache(this.server, User, {})') + this.delegateId = null + this.saveCache(this.server, 'delegateId', null) + + this.saveCache(this.server, 'User', {}) + } + + // alert('logout isAdminOperation = ' + isAdminOperation + '; url = ' + url) + if (isAdminOperation) { + this.request(isAdminOperation, REQUEST_TYPE_POST, REQUEST_TYPE_JSON, this.server + '/logout' + , req, this.getHeader(vHeader.value), function (url, res, err) { + if (callback) { + callback(url, res, err) + return + } + + // alert('logout clear admin ') + + App.clearUser() + App.onResponse(url, res, err) + App.showTestCase(false, App.isLocalShow) + }) + } + else { + this.scripts = newDefaultScript() + this.showUrl(isAdminOperation, '/logout') + vInput.value = JSON.stringify(req, null, ' ') + this.method = REQUEST_TYPE_POST + this.type = REQUEST_TYPE_JSON + this.showTestCase(false, this.isLocalShow) + this.onChange(false) + this.send(isAdminOperation, function (url, res, err) { + if (App.isEnvCompareEnabled != true) { + if (callback) { + callback(url, res, err) + } + return + } + + App.request(isAdminOperation, REQUEST_TYPE_POST, REQUEST_TYPE_JSON, App.getBaseUrl(App.otherEnv) + '/logout' + , req, App.getHeader(vHeader.value), function (url_, res_, err_) { + if (callback) { + callback(url, res, err) + return + } + }) + + }, this.scripts) + } + }, + + /**获取验证码 + */ + getVerify: function (isAdminOperation) { + this.scripts = newDefaultScript() + this.showUrl(isAdminOperation, '/post/verify') + var type = this.loginType == 'login' ? 0 : (this.loginType == 'register' ? 1 : 2) + vInput.value = JSON.stringify( + { + type: type, + phone: this.account + }, + null, ' ') + this.showTestCase(false, this.isLocalShow) + this.onChange(false) + this.send(isAdminOperation, function (url, res, err) { + App.onResponse(url, res, err) + + var data = res.data || {} + var obj = JSONResponse.isSuccess(data) ? data.verify : null + var verify = obj == null ? null : obj.verify + if (verify != null) { //FIXME isEmpty校验时居然在verify=null! StringUtil.isEmpty(verify, true) == false) { + vVerify.value = verify + } + }, this.scripts) + }, + + clearUser: function () { + this.User.id = 0 + this.Privacy = {} + this.casePaths = [] + this.caseGroups = [] + this.remotes = [] + // 导致刚登录成功就马上退出 this.delegateId = null + this.saveCache(this.server, 'User', this.User) //应该用lastBaseUrl,baseUrl应随watch输入变化重新获取 + // this.saveCache(this.server, 'delegateId', this.delegateId) //应该用lastBaseUrl,baseUrl应随watch输入变化重新获取 + }, + + /**计时回调 + */ + onHandle: function (before) { + if (IS_NODE) { + return; + } + + this.isDelayShow = false + if (inputted != before) { + clearTimeout(handler); + return; + } + + this.view = 'output'; + vComment.value = ''; + vWarning.value = ''; + // vUrlComment.value = ''; + vOutput.value = 'resolving...'; + + //格式化输入代码 + try { + try { + this.header = this.getHeader(vHeader.value) + } catch (e2) { + this.isHeaderShow = true + vHeader.select() + throw new Error(e2.message) + } + + before = StringUtil.trim(before); + + var afterObj; + var after; + var code = ''; + + if (StringUtil.isEmpty(before)) { + afterObj = {}; + after = ''; + } else { + before = StringUtil.trim(before); // this.toDoubleJSON(StringUtil.trim(before)); + log('onHandle before = \n' + before); + + var json = isSingle ? this.switchQuote(before) : before; + try { + afterObj = jsonlint.parse(json); + after = JSON.stringify(afterObj, null, " "); + before = isSingle ? this.switchQuote(after) : after; + } + catch (e) { + log('main.onHandle', 'try { return jsonlint.parse(before); \n } catch (e) {\n' + e.message) + log('main.onHandle', 'return jsonlint.parse(this.removeComment(before));') + + try { + afterObj = JSON5.parse(json); // jsonlint.parse(this.removeComment(before)); + after = JSON.stringify(afterObj, null, " "); + } catch (e2) { + throw new Error('请求 JSON 格式错误!请检查并编辑请求!\n\n如果JSON中有注释,请 手动删除 或 点击左边的 \'/" 按钮 来去掉。\n\n' + e.message + '\n\n' + e2.message) + } + } + + //关键词let在IE和Safari上不兼容 + if (this.isEditResponse != true) { + try { + code = this.getCode(after); //必须在before还是用 " 时使用,后面用会因为解析 ' 导致失败 + } catch (e) { + code = '\n\n\n建议:\n使用其它浏览器,例如 谷歌Chrome、火狐FireFox 或者 微软Edge, 因为这样能自动生成请求代码.' + + '\nError:\n' + e.message + '\n\n\n'; + } + } + + var selectionStart = vInput.selectionStart + var selectionEnd = vInput.selectionEnd + vInput.value = before + + '\n\n\n ' + + ' \n'; //解决遮挡 + + vInput.selectionStart = selectionStart + vInput.selectionEnd = selectionEnd + vInput.setSelectionRange(selectionStart, selectionEnd) + } + + vSend.disabled = false; + + if (this.isEditResponse != true) { + vOutput.value = output = '登录后点 ↑ 上方左侧最后图标按钮可查看用例列表,点上方右侧中间图标按钮可上传用例并且添加到列表中 ↑ \nOK,请点左上方 [发送请求] 按钮来测试。[点击这里查看视频教程](https://i.youku.com/i/UNTg1NzI1MjQ4MA==/videos?spm=a2hzp.8244740.0.0)' + code; + + this.showDoc() + } + + var docKey = this.isEditResponse ? 'TestRecord' : 'Document'; + var currentItem = (this.currentRemoteItem || {})[docKey] || {} + var detail = currentItem.detail; + var extraComment = this.getExtraComment() + + try { + var standardObj = null; + try { + standardObj = parseJSON(currentItem.standard); + } catch (e3) { + log(e3) + } + + var isAPIJSONRouter = false; + try { + var apijson = parseJSON(currentItem.apijson); + isAPIJSONRouter = JSONResponse.isObject(apijson) + } catch (e3) { + log(e3) + } + + var m = this.getMethod(); + var w = isSingle || this.isEditResponse ? '' : StringUtil.trim(CodeUtil.parseComment(after, docObj == null ? null : docObj['[]'], m, this.database, this.language, this.isEditResponse != true, standardObj, null, true, isAPIJSONRouter)); + var c = isSingle ? '' : StringUtil.trim(CodeUtil.parseComment(after, docObj == null ? null : docObj['[]'], m, this.database, this.language, this.isEditResponse != true, standardObj, null, null, isAPIJSONRouter)); + + //TODO 统计行数,补全到一致 vInput.value.lineNumbers + if (isSingle != true) { + if (afterObj.tag == null) { + m = m == null ? 'GET' : m.toUpperCase() + if (['GETS', 'HEADS', 'POST', 'PUT', 'DELETE'].indexOf(m) >= 0) { + w += ' ! 非开放请求必须设置 tag !例如 "tag": "User"' + c += ' ! 非开放请求必须设置 tag !例如 "tag": "User"' + } + } + + if (StringUtil.isEmpty(detail, true)) { + c += extraComment == null ? '' : ('\n\n/*' + extraComment + '\n*/'); + } else { + c += '\n\n/*' + (extraComment == null ? '' : extraComment + '\n\n') + detail + '\n*/'; + } + } + + + vWarning.value = w + + '\n\n\n ' + + ' \n'; //解决遮挡 + vComment.value = c + + '\n\n\n ' + + ' \n'; //解决遮挡 + + vUrlComment.value = isSingle || StringUtil.isEmpty(this.urlComment, true) + ? '' : CodeUtil.getBlank(StringUtil.length(vUrl.value), 1) + CodeUtil.getComment(this.urlComment, false, ' ') + + ' - ' + (this.requestVersion > 0 ? 'V' + this.requestVersion : 'V*'); + + if (! isSingle) { + var method = this.getMethod(); // m 已经 toUpperCase 了 + var isRestful = ! JSONObject.isAPIJSONPath(method); + if (isRestful != true) { + method = method.toUpperCase(); + } + var apiMap = isRestful ? CodeUtil.thirdPartyApiMap : null; + var api = apiMap == null ? null : apiMap['/' + method]; + var name = api == null ? null : api.name; + if (StringUtil.isEmpty(name, true) == false) { + this.urlComment = name; + vUrlComment.value = CodeUtil.getBlank(StringUtil.length(vUrl.value), 1) + CodeUtil.getComment(this.urlComment, false, ' ') + } + } + + onScrollChanged() + onURLScrollChanged() + } catch (e) { + log('onHandle try { vComment.value = CodeUtil.parseComment >> } catch (e) {\n' + e.message); + } + + if (this.isPreviewEnabled) { + try { + // 去掉前面的 JSON + var raw = StringUtil.trim(isSingle ? vInput.value : vComment.value); + var start = raw.lastIndexOf('\n/*') + var end = raw.lastIndexOf('\n*/') + var ct = start < 0 || end <= start ? '' : StringUtil.trim(raw.substring(start + '\n/*'.length, end)) + + markdownToHTML('```js\n' + (start < 0 || end <= start ? raw : raw.substring(0, start)) + '\n```\n' + + (StringUtil.isEmpty(ct, true) ? '' : ct + '\n\n```js\n' + ct + '\n```\n'), true); + } catch (e3) { + log(e3) + } + } + + if (this.isEditResponse) { + this.view = 'code'; + this.jsoncon = after + } + + } catch(e) { + log(e) + vSend.disabled = true + + this.view = 'error' + this.error = { + msg: e.message + } + } + }, + + + /**输入内容改变 + */ + onChange: function (delay) { + this.setBaseUrl(); + + if (IS_NODE || document.activeElement == vOption || this.options.length > 0) { + return; + } + + inputted = StringUtil.get(vInput.value); + vComment.value = ''; + vWarning.value = ''; + // vUrlComment.value = ''; + + clearTimeout(handler); + + this.isDelayShow = delay; + + if (delay) { + handler = setTimeout(function () { + App.onHandle(inputted); + }, 2000); + } else { + this.onHandle(inputted); + } + }, + + /**单双引号切换 + */ + transfer: function () { + isSingle = ! isSingle; + + vInput.value = this.switchQuote(vInput.value); + + this.isTestCaseShow = false; + + // // 删除注释 <<<<<<<<<<<<<<<<<<<<< + // + // var input = this.removeComment(vInput.value); + // if (vInput.value != input) { + // vInput.value = input + // } + // + // // 删除注释 >>>>>>>>>>>>>>>>>>>>> + + this.onChange(false); + + var list = docObj == null ? null : docObj['[]']; + if (list != null && list.length > 0) { + this.onDocumentListResponse('', {data: docObj}, null, function (d) { + App.setDoc(d); + }); + } + }, + + isShowMethod: function() { + return this.methods == null || this.methods.length != 1 + }, + isShowType: function() { + return this.types == null || this.types.length != 1 + }, + + /**请求类型切换 + */ + changeMethod: function () { + var methods = this.methods + var count = methods == null ? 0 : methods.length + if (count <= 0) { + methods = HTTP_METHODS + count = HTTP_METHODS.length + } + + if (count > 1) { + var index = methods.indexOf(this.method) + 1 + CodeUtil.method = this.method = methods[index % count] + } + this.onChange(false); + }, + + /**获取显示的请求方法名称 + */ + getMethodName: function (method, type) { + method = StringUtil.trim(method) + if (method.length > 0) { + return method + } + + return [REQUEST_TYPE_GET, REQUEST_TYPE_PARAM].indexOf(type) < 0 ? REQUEST_TYPE_POST : REQUEST_TYPE_GET + }, + /**获取显示的请求类型名称 + */ + getTypeName: function (type, method) { + var t = type + if (StringUtil.isEmpty(t, true)) { + if (StringUtil.isEmpty(method, true)) { + t = REQUEST_TYPE_JSON + } + else if (method == REQUEST_TYPE_GET) { + t = REQUEST_TYPE_PARAM + } + else if (method == REQUEST_TYPE_POST) { + t = REQUEST_TYPE_JSON + } + else { + t = REQUEST_TYPE_DATA + } + } + +// var methods = this.methods +// if (this.isShowMethod()) { +// return t +// } +// +// var ts = this.types +// if (ts == null || ts.length <= 1 || (ts.length <= 2 && ts.indexOf(REQUEST_TYPE_PARAM) >= 0 && ts.indexOf(REQUEST_TYPE_GRPC) < 0)) { +// return t == REQUEST_TYPE_PARAM ? 'GET' : 'POST' +// } + return t + }, + /**请求类型切换 + */ + changeType: function () { + var types = this.types + var count = types == null ? 0 : types.length + if (count <= 0) { + types = HTTP_CONTENT_TYPES + count = HTTP_CONTENT_TYPES.length + } + + if (count > 1) { + var index = types.indexOf(this.type) + 1 + this.type = types[index % count] + CodeUtil.type = this.type; + } + + var url = StringUtil.get(vUrl.value) + var index = url.indexOf('?') + if (index >= 0) { + var paramObj = getRequestFromURL(url.substring(index), true) + vUrl.value = url.substring(0, index) + if (paramObj != null && JSONObject.isEmpty(paramObj) == false) { + var originVal = this.getRequest(vInput.value, {}); + var isConflict = false; + + if (JSONObject.isEmpty(originVal) == false) { + for (var k in paramObj) { + if (originVal.hasOwnProperty(k)) { + isConflict = true; + break; + } + } + } + + if (isConflict) { + vInput.value = JSON.stringify(paramObj, null, ' ') + '\n\n// FIXME 从 URL 上的参数转换过来,需要与下面原来的字段合并为一个 JSON:\n\n' + StringUtil.get(vInput.value) + } + else { + vInput.value = JSON.stringify(Object.assign(originVal, paramObj), null, ' ') + } + } + clearTimeout(handler) //解决 vUrl.value 和 vInput.value 变化导致刷新,而且会把 vInput.value 重置,加上下面 onChange 再刷新就卡死了 + } + + this.onChange(false); + }, + + changeScriptType: function (type) { + type = type || 'case' + if (type == 'account') { + var id = this.getCurrentAccountId() + if (id == null || id <= 0) { + type = 'case' + } + } + + this.scriptBelongId = 0 // 解决可能的报错 + this.scriptType = type + var bid = this.getCurrentScriptBelongId() + + var scripts = this.scripts + if (scripts == null) { + scripts = newDefaultScript() + this.scripts = scripts + } + var ss = scripts[type] + if (ss == null) { + ss = { + 0: { + pre: { // 可能有 id + script: '' // index.html 中 v-model 绑定,不能为 null + }, + post: { + script: '' + } + }, + [bid]: { + pre: { // 可能有 id + script: '' // index.html 中 v-model 绑定,不能为 null + }, + post: { + script: '' + } + } + } + scripts[type] = ss + } + + var bs = ss[bid] + if (bs == null) { + bs = { + pre: { // 可能有 id + script: '' // index.html 中 v-model 绑定,不能为 null + }, + post: { + script: '' + } + } + ss[bid] = bs + } + var pre = bs.pre + if (pre == null) { + pre = { + script: '' + } + bs.pre = pre + } + if (pre.script == null) { + pre.script = '' + } + + var post = bs.post + if (post == null) { + post = { + script: '' + } + bs.post = post + } + if (post.script == null) { + post.script = '' + } + + this.scriptBelongId = bid + }, + changeScriptPriority: function (isPre) { + this.isPreScript = isPre == true + this.changeScriptType(this.scriptType) + }, + + /** + * 删除注释 + */ + removeComment: function (json) { + var reg = /("([^\\\"]*(\\.)?)*")|('([^\\\']*(\\.)?)*')|(\/{2,}.*?(\r|\n))|(\/\*(\n|.)*?\*\/)/g // 正则表达式 + try { + return StringUtil.get(json).replace(reg, function(word) { // 去除注释后的文本 + return /^\/{2,}/.test(word) || /^\/\*/.test(word) ? "" : word; + }) + } catch (e) { + log('transfer delete comment in json >> catch \n' + e.message); + } + return json; + }, + + showAndSend: function (branchUrl, req, isAdminOperation, callback) { + this.showUrl(isAdminOperation, branchUrl) + vInput.value = JSON.stringify(req, null, ' ') + this.showTestCase(false, this.isLocalShow) + this.onChange(false) + this.send(isAdminOperation, callback) + }, + + /**发送请求 + */ + send: function(isAdminOperation, callback, caseScript_, accountScript_, globalScript_, ignorePreScript) { + if (this.isTestCaseShow) { + alert('请先输入请求内容!') + return + } + + if (StringUtil.isEmpty(this.host, true)) { + var url = StringUtil.get(vUrl.value) + if (url.startsWith('/') != true && url.startsWith('http://') != true && url.startsWith('https://') != true) { + alert('URL 缺少 http:// 或 https:// 前缀,可能不完整或不合法,\n可能使用同域的 Host,很可能访问出错!') + } + } + else { + if (StringUtil.get(vUrl.value).indexOf('://') >= 0) { + alert('URL Host 已经隐藏(固定) 为 \n' + this.host + ' \n将会自动在前面补全,导致 URL 不合法访问出错!\n如果要改 Host,右上角设置 > 显示(编辑)URL Host') + } + } + + this.onHandle(vInput.value) + + clearTimeout(handler) + + if (this.isEditResponse) { + this.onChange(false) + return + } + + var header + try { + header = this.getHeader(vHeader.value) + } catch (e) { + // alert(e.message) + return + } + + var req = this.getRequest(vInput.value, {}) + + var url = this.getUrl() + + vOutput.value = "requesting... \nURL = " + url + + errHandler = function () { + vOutput.value = "requesting... \nURL = " + url + "\n\n可能" + ERR_MSG + } + setTimeout(errHandler, 5000) + this.view = 'output'; + + var caseScript = (caseScript_ != null ? caseScript_ : ((this.scripts || {}).case || {})[this.getCurrentDocumentId() || 0]) || {} + + var method = this.isShowMethod() ? this.method : null + + this.setBaseUrl() + this.request(isAdminOperation, method, this.type, url, req, isAdminOperation ? {} : header, callback, caseScript, accountScript_, globalScript_, ignorePreScript) + + var baseUrls = this.getCache('', 'baseUrls', []) + var bu = this.getBaseUrl(url) + if (StringUtil.isNotEmpty(bu, true) && baseUrls.indexOf(bu) < 0) { + baseUrls.push(bu) + this.saveCache('', 'baseUrls', baseUrls) + var projectHosts = this.projectHosts || [] + var projectHost = this.projectHost || {} + var find = false + for (var j = 0; j < projectHosts.length; j ++) { + var pjt = projectHosts[j] + if (pjt == null || StringUtil.isEmpty(pjt.host, true)) { + continue + } + + if (pjt.url == projectHost.host) { + find = true + break + } + } + + if (find != true) { + projectHosts.push({host: bu, project: projectHost.project}) + this.projectHosts = projectHosts + this.saveCache('', 'projectHosts', projectHosts) + } + + } + + this.locals = this.locals || [] + if (this.locals.length >= 1000) { //最多1000条,太多会很卡 + this.locals.splice(900, this.locals.length - 900) + } + var path = this.getMethod() + this.locals.unshift({ + 'Document': { + 'userId': this.User.id, + 'project': (this.projectHost || {}).project, + 'name': this.formatDateTime() + ' ' + (this.urlComment || StringUtil.trim(req.tag)), + 'operation': CodeUtil.getOperation(path, req), + 'method': method, + 'type': this.type, + 'url': '/' + path, + 'request': JSON.stringify(req, null, ' '), + 'header': vHeader.value, + 'scripts': this.scripts + } + }) + this.saveCache('', 'locals', this.locals) + }, + + adminRequest: function (url, req, header, callback) { + this.request(true, REQUEST_TYPE_POST, REQUEST_TYPE_JSON, url, req, header, callback) + }, + + //请求 + request: function (isAdminOperation, method, type, url, req, header, callback, caseScript_, accountScript_, globalScript_, ignorePreScript, timeout_, wait_, retry_) { + this.loadingCount ++ + + const isEnvCompare = this.isEnvCompareEnabled + + const scripts = (isAdminOperation || caseScript_ == null ? null : this.scripts) || {} + const globalScript = (isAdminOperation ? null : (globalScript_ != null ? globalScript_ : (scripts.global || {})[0])) || {} + const accountScript = (isAdminOperation ? null : (accountScript_ != null ? accountScript_ : (scripts.account || {})[this.getCurrentAccountId() || 0])) || {} + const caseScript = (isAdminOperation ? null : caseScript_) || {} + const timeout = timeout_ != null ? timeout_ : this.timeout + const wait = wait_ != null ? wait_ : (this.wait || 0) + var retry = retry_ != null ? retry_ : (this.retry || 0) + + var evalPostScript = function () {} + + const onHttpResponse = function (res) { + App.currentHttpResponse = res + clearTimeout(errHandler) + var postEvalResult = evalPostScript(url, res, null) + if (postEvalResult == BREAK_ALL) { + return + } + + App.loadingCount -- + res = res || {} + + if (isDelegate) { + var hs = res.headers || {} + var delegateId = hs['Apijson-Delegate-Id'] || hs['apijson-delegate-id'] + + if (delegateId != null) { + if (isEnvCompare) { + if (delegateId != App.otherEnvDelegateId) { + App.otherEnvDelegateId = delegateId + App.saveCache(App.server, 'otherEnvDelegateId', delegateId) + } + } else { + if (delegateId != App.delegateId) { + App.delegateId = delegateId + App.saveCache(App.server, 'delegateId', delegateId) + } + } + } + } + + //any one of then callback throw error will cause it calls then(null) + // if ((res.config || {}).method == 'options') { + // return + // } + if (DEBUG) { + log('send >> success:\n' + JSON.stringify(res.data, null, ' ')) + } + + //未登录,清空缓存 + if (res.data != null && res.data.code == 407) { + // alert('request res.data != null && res.data.code == 407 >> isAdminOperation = ' + isAdminOperation) + if (isAdminOperation) { + // alert('request App.User = {} App.server = ' + App.server) + + App.clearUser() + } + else { + // alert('request App.accounts[App.currentAccountIndex].isLoggedIn = false ') + var account = App.accounts[App.currentAccountIndex] + if (account != null) { + account.isLoggedIn = false + } + } + } + + if (postEvalResult == BREAK_LAST) { + return + } + + if (callback != null) { + callback(url, res, null) + return + } + App.onResponse(url, res, null) + } + + const onHttpCatch = function (err) { + if (retry != null && retry > 0 && retryReq(err)) { + return; + } + + var errObj = err instanceof Array == false && err instanceof Object ? err : {} + var res = {status: errObj.status || (errObj.response || {}).status, request: {url: url, headers: header, data: req}, data: (errObj.response || {}).data} + App.currentHttpResponse = res + + var postEvalResult = evalPostScript(url, res, err) + if (postEvalResult == BREAK_ALL) { + return + } + + App.loadingCount -- + + log('send >> error:\n' + err) + if (isAdminOperation) { + App.delegateId = null + } + + if (postEvalResult == BREAK_LAST) { + return + } + + if (callback != null) { + callback(url, res, err) + return + } + + if (typeof App.autoTestCallback == 'function') { + App.autoTestCallback('Error when testing: ' + err + '.\nurl: ' + url + ' \nrequest: \n' + JSON.stringify(req, null, ' '), err) + } + + App.onResponse(url, {request: {url: url, headers: header, data: req}}, err) + } + + var retryReq = function (err) { + if (retry == null || retry < 0) { + onHttpCatch(err) + return false + } + + retry -- + try { + setTimeout(function () { + sendRequest(isAdminOperation, method, type, url, req, header, callback) + }, wait < 0 ? 0 : wait) + } catch (e) { + App.log('request retryReq retry = ' + retry + ' >> try {\n' + + ' sendRequest(isAdminOperation, method, type, url, req, header, callback)\n' + + ' } catch (e) = ' + e.message) + return retryReq(err) + } + + return true + } + + var sendRequest = function (isAdminOperation, method, type, url, req, header, callback) { + var hs = "" + if (isDelegate && header != null) { + for (var k in header) { + var v = k == null ? null : header[k] + if (k == null || k.toLowerCase() == 'apijson-delegate-id') { + continue + } + hs += '\n' + k + ': ' + (v instanceof Object ? JSON.stringify(v) : v) + } + } + + var isParam = HTTP_URL_ARG_TYPES.indexOf(type) >= 0 + + if (req != null && JSONResponse.getType(req) == 'object') { // 支持 URL 里有 Path Variable,例如 http://apijson.cn:8080/{method}/{table} + var ind = -1 // 支持 ?id={id} 这种动态参数 url.indexOf('?') + var uri = ind < 0 ? url : url.substring(0, ind) + + var newReq = {} + for (var k in req) { + var v = k == null ? null : req[k] + var kind = uri.indexOf('{' + k + '}') + if (kind >= 0) { + if (v instanceof Array) { + var multiInd = uri.indexOf(':=') + } + + uri = uri.replaceAll('${' + k + '}', v).replaceAll('{{' + k + '}}', v).replaceAll('{' + k + '}', v) + continue + } + + newReq[k] = v + } + + url = uri + (ind < 0 ? '' : url.substring(ind)) + req = newReq + } + + axios.interceptors.request.use(function (config) { + config.metadata = { startTime: new Date().getTime()} + return config; + }, function (error) { + return Promise.reject(error); + }); + axios.interceptors.response.use(function (response) { + response.config.metadata.endTime = new Date().getTime() + response.duration = response.config.metadata.endTime - response.config.metadata.startTime + return response; + }, function (error) { + error.config.metadata.endTime = new Date().getTime(); + error.duration = error.config.metadata.endTime - error.config.metadata.startTime; + return Promise.reject(error); + }); + + // axios.defaults.withcredentials = true + axios({ + method: method != null ? method : (HTTP_METHODS.indexOf(type) >= 0 ? type.toLowerCase() : (type == REQUEST_TYPE_PARAM ? 'get' : 'post')), + url: (isDelegate ? ( + App.server + '/delegate?$_type=' + (type || REQUEST_TYPE_JSON) + + (StringUtil.isEmpty(App.delegateId, true) ? '' : '&$_delegate_id=' + App.delegateId) + + '&$_delegate_url=' + encodeURIComponent(url) + + (StringUtil.isEmpty(hs, true) ? '' : '&$_headers=' + encodeURIComponent(hs.trim())) + ) : ( + App.isEncodeEnabled ? encodeURI(url) : url + ) + ), + params: isParam ? req : null, + data: HTTP_JSON_TYPES.indexOf(type) >= 0 ? req : (HTTP_FORM_DATA_TYPES.indexOf(type) >= 0 ? toFormData(req) : null), + headers: header, //Accept-Encoding(HTTP Header 大小写不敏感,SpringBoot 接收后自动转小写)可能导致 Response 乱码 + withCredentials: true, //Cookie 必须要 type == REQUEST_TYPE_JSON + // crossDomain: true + timeout: timeout + }) + .then(onHttpResponse) + .catch(onHttpCatch) + } + + var evalScript = isAdminOperation || caseScript_ == null ? function () {} : function (isPre, code, res, err) { + var logger = console.log + console.log = function(msg) { + logger(msg) + vOutput.value = StringUtil.get(msg) + } + + App.view = 'output' + vOutput.value = '' + + try { +// var s = `(function () { +// var App = ` + App + `; +// +// var type = ` + type + `; +// var url = ` + url + `; +// var req = ` + (req == null ? null : JSON.stringify(req)) + `; +// var header = ` + (header == null ? null : JSON.stringify(header)) + `; +// +// ` + (isPre ? '' : ` +// // var res = ` + (res == null ? null : JSON.stringify(res)) + `; +// var data = ` + (res == null || res.data == null ? null : JSON.stringify(res.data)) + `; +// var err = ` + (err == null ? null : JSON.stringify(err)) + `; +// +// `) + code + ` +// })()` +// +// eval(s) + + var isTest = false; + var isInject = false; + var data = res == null ? null : res.data + var result = eval(code) + console.log = logger + return result + } + catch (e) { + console.log(e); + console.log = logger + + App.loadingCount -- + + // TODO if (isPre) { + App.view = 'error' + App.error = { + msg: '执行脚本报错:\n' + e.message + } + + if (callback != null) { + callback(url, res, e) + } else { + // catch 中也 evalScript 导致死循环 + // if (isPre != true) { + // throw e + // } + + // TODO 右侧底部新增断言列表 + App.onResponse(url, null, new Error('执行脚本报错:\n' + e.message)) // this.onResponse is not a function + // callback = function (url, res, err) {} // 仅仅为了后续在 then 不执行 onResponse + } + } + + return BREAK_ALL + } + + // const preScript = function () { + // if (isAdminOperation) { + // return + // } + + var preScript = '' + + var globalPreScript = isAdminOperation || ignorePreScript || caseScript_ == null ? null : StringUtil.trim((globalScript.pre || {}).script) + if (StringUtil.isNotEmpty(globalPreScript, true)) { + preScript += globalPreScript + '\n\n' // evalScript(true, globalPreScript) + } + + var accountPreScript = isAdminOperation || ignorePreScript || caseScript_ == null ? null : StringUtil.trim((accountScript.pre || {}).script) + if (StringUtil.isNotEmpty(accountPreScript, true)) { + preScript += accountPreScript + '\n\n' // evalScript(true, accountPreScript) + } + + var casePreScript = isAdminOperation || ignorePreScript || caseScript_ == null ? null : StringUtil.trim((caseScript.pre || {}).script) + if (StringUtil.isNotEmpty(casePreScript, true)) { + preScript += casePreScript + '\n\n' // evalScript(true, casePreScript) + } + + var preEvalResult = null; + if (StringUtil.isNotEmpty(preScript, true)) { + preEvalResult = evalScript(true, preScript) + } + + // } + + evalPostScript = isAdminOperation || caseScript_ == null ? function () {} : function (url, res, err) { + var postScript = '' + + var casePostScript = StringUtil.trim((caseScript.post || {}).script) + if (StringUtil.isNotEmpty(casePostScript, true)) { + postScript += casePostScript + '\n\n' // evalScript(false, casePostScript, res, err) + } + + var accountPostScript = StringUtil.trim((accountScript.post || {}).script) + if (StringUtil.isNotEmpty(accountPostScript, true)) { + postScript += accountPostScript + '\n\n' // evalScript(false, accountPostScript, res, err) + } + + var globalPostScript = StringUtil.trim((globalScript.post || {}).script) + if (StringUtil.isNotEmpty(globalPostScript, true)) { + postScript += globalPostScript + '\n\n' // evalScript(false, globalPostScript, res, err) + } + + if (StringUtil.isNotEmpty(postScript, true)) { + if (StringUtil.isNotEmpty(preScript, true)) { // 如果有副作用代码,则通过判断 if (isPre) {..} 在里面执行 + postScript = preScript + '\n\n// request >>>>>>>>>>>>>>>>>>>>>>>>>> response \n\n' + postScript + } + + return evalScript(false, postScript, res, err) + } + + return null; + } + + if (preEvalResult == BREAK_ALL) { + return + } + + type = type || REQUEST_TYPE_JSON + url = StringUtil.noBlank(url) + if (url.startsWith('/')) { + url = (isAdminOperation ? this.server : this.getBaseUrl()) + url + } + + var isDelegate = (isAdminOperation == false && this.isDelegateEnabled) + || (isAdminOperation && (url.indexOf('://apijson.cn:9090') > 0 || url.indexOf('.devin.ai') > 0)) + + if (header != null && header.Cookie != null) { + if (isDelegate) { + header['Set-Cookie'] = header.Cookie + delete header.Cookie + } + else if (IS_BROWSER) { + document.cookie = header.Cookie + } + } else if (IS_NODE) { + var curUser = isAdminOperation ? this.User : this.getCurrentAccount() + if (curUser != null && curUser[isEnvCompare ? 'phone' : 'cookie'] != null) { + if (header == null) { + header = {} + } + + // Node 环境内通过 headers 设置 Cookie 无效 + header.Cookie = isEnvCompare ? this.otherEnvCookieMap[curUser.phone + '@' + baseUrl] : curUser.cookie + } + } + + var delegateId = isEnvCompare ? this.otherEnvDelegateId : this.delegateId + if (isDelegate && delegateId != null && (header == null || header['Apijson-Delegate-Id'] == null)) { + if (header == null) { + header = {}; + } + header['Apijson-Delegate-Id'] = delegateId + } + + + if (IS_NODE) { + if (DEBUG) { + log('req = ' + JSON.stringify(req, null, ' ')) + } + // 低版本 node 报错 cannot find module 'node:url' ,高版本报错 TypeError: axiosCookieJarSupport is not a function + // const axiosCookieJarSupport = require('axios-cookiejar-support').default; + // const tough = require('tough-cookie'); + // axiosCookieJarSupport(axios); + // const cookieJar = new tough.CookieJar(); + // axios.defaults.jar = cookieJar; + // axios.defaults.withCredentials = true; + + // const {parse, stringify, toJSON, fromJSON} = require('flatted'); + // JSON.stringify = stringify; + // JSON.parse = parse; + + // const CircularJSON = require('circular-json'); + // JSON.stringify = CircularJSON.stringify; + // JSON.parse = CircularJSON.parse; + } + + if (preEvalResult == BREAK_LAST) { + return + } + + retryReq() + }, + + lastReqTime: 0, + /**请求回调 + */ + onResponse: function (url, res, err) { + if (res == null) { + res = {} + } else { + var time = res.config == null || res.config.metadata == null ? null : res.config.metadata.startTime; + if (time != null && time > 0 && time < this.lastReqTime) { + return + } + + this.lastReqTime = time == null || time <= 0 ? 0 : time; + } + + if (DEBUG) { + log('onResponse url = ' + url + '\nerr = ' + err + '\nreq = \n' + + (res.request == null || res.request.data == null ? 'null' : JSON.stringify(res.request.data)) + + '\n\nres = \n' + (res.data == null ? 'null' : JSON.stringify(res.data)) + ) + } + + if (err != null) { + if (IS_BROWSER) { + var errObj = err instanceof Array == false && err instanceof Object ? err : {} + var data = (errObj.response || {}).data + var msg = typeof data == 'string' ? StringUtil.trim(data) : JSON.stringify(data, null, ' ') + msg = "Response:\nurl = " + url + "\nerror = " + err.message + (StringUtil.isEmpty(msg) ? '' : '\n\n' + msg) + '\n\n' + ERR_MSG + // vOutput.value = "Response:\nurl = " + url + "\nerror = " + err.message; + this.view = 'error'; + this.error = { + msg: msg + } + this.output = msg + } + } + else { + if (IS_BROWSER) { + var data = res.data || {} + var isStr = typeof data == 'string' + if (isSingle && (isStr != true) && data instanceof Object && (data instanceof Array == false) && JSONResponse.isSuccess(data)) { //不格式化错误的结果 + data = JSONResponse.formatObject(data); + } + this.jsoncon = isStr ? data : JSON.stringify(data, null, ' ') + this.view = 'code' // isStr ? 'output' : 'code' + + vOutput.value = isStr ? data : '' + } + + // 会导致断言用了这个 + // if (this.currentRemoteItem == null) { + // this.currentRemoteItem = {} + // } + // if (this.currentRemoteItem.TestRecord == null) { + // this.currentRemoteItem.TestRecord = {} + // } + // this.currentRemoteItem.TestRecord.response = data + } + }, + + + /**处理复制事件 + * @param event + */ + doOnCopy: function(event) { + var target = event.target; + var selectionStart = target.selectionStart; + var selectionEnd = target.selectionEnd; + + if (target == vUrl) { + try { + var contentType = CONTENT_TYPE_MAP[this.type]; + var json = this.getRequest(vInput.value) + var header = this.getHeader(vHeader.value); + var headerStr = ''; + if (header != null) { + for (var k in header) { + var v = header[k]; + headerStr += '\n' + k + ': ' + StringUtil.get(v); + } + } + + console.log('复制时自动转换:\n' + + `Request URL: ` + vUrl.value + ` +Request Method: ` + (this.type == REQUEST_TYPE_PARAM ? 'GET' : 'POST') + (StringUtil.isEmpty(contentType, true) ? '' : ` +Content-Type: ` + contentType) + (StringUtil.isEmpty(headerStr, true) ? '' : headerStr) + + '\n\n' + JSON.stringify(json)); + } catch (e) { + log(e) + } + } + else if (target == vHeader || target == vRandom) { // key: value 转 { "key": value } + if (selectionStart < 0 || selectionStart <= selectionEnd) { + try { + var selection = selectionStart < 0 ? target.value : StringUtil.get(target.value).substring(selectionStart, selectionEnd); + var lines = StringUtil.split(selection, '\n'); + var json = {}; + + for (var i = 0; i < lines.length; i ++) { + var l = StringUtil.trim(lines[i]) || ''; + if (l.startsWith('//')) { + continue; + } + + var ind = l.lastIndexOf(' //'); + l = ind < 0 ? l : StringUtil.trim(l.substring(0, ind)); + + ind = l.indexOf(':'); + if (ind >= 0) { + var left = target == vHeader ? StringUtil.trim(l.substring(0, ind)) : l.substring(0, ind); + json[left] = StringUtil.trim(l.substring(ind + 1)); + } + } + + if (Object.keys(json).length > 0) { + var txt = JSON.stringify(json) + console.log('复制时自动转换:\n' + txt) + navigator.clipboard.writeText(selection + '\n\n' + txt); + alert('复制内容最后拼接了,控制台 Console 也打印了:\n' + txt); + } + } catch (e) { + log(e) + } + } + } + + }, + + /**处理粘贴事件 + * @param event + */ + doOnPaste: function(event) { + var paste = (event.clipboardData || window.clipboardData || navigator.clipboard).getData('text'); + var target = event.target; + var selectionStart = target.selectionStart; + var selectionEnd = target.selectionEnd; + + if (StringUtil.isNotEmpty(paste, true) && (StringUtil.isEmpty(target.value, true) + || selectionStart <= 0 && selectionEnd >= StringUtil.get(target.value).length)) { + if (target == vUrl) { // TODO 把 Chrome 或 Charles 等抓到的 Response Header 和 Content 自动粘贴到 vUrl, vHeader + try { + if (paste.trim().indexOf('\n') > 0) { // 解决正常的 URL 都粘贴不了 + var contentStart = 0; + var lines = StringUtil.split(paste, '\n'); + var header = ''; + + for (var i = 0; i < lines.length; i++) { + var l = StringUtil.trim(lines[i]); + var ind = l.indexOf(':'); + var left = ind < 0 ? '' : StringUtil.trim(l.substring(0, ind)); + + if (/^[a-zA-Z0-9\- ]+$/g.test(left)) { + var lowerKey = left.toLowerCase(); + var value = l.substring(ind + 1).trim(); + + if (lowerKey == 'host') { + this.setBaseUrl(value.endsWith(':443') ? 'https://' + value.substring(0, value.length - ':443'.length) : 'http://' + value); + event.preventDefault(); + } + else if (lowerKey == 'request method') { + value = value.toUpperCase(); + this.method = value + this.type = value == 'GET' ? 'PARAM' : (value == 'POST' ? 'JSON' : value); + event.preventDefault(); + } + else if (lowerKey == 'content-type') { + var type = vType.value != 'JSON' ? null : CONTENT_VALUE_TYPE_MAP[value]; + if (StringUtil.isEmpty(type, true) != true) { + this.type = type; + event.preventDefault(); + } + } + else if (lowerKey == 'request url') { + vUrl.value = value; + event.preventDefault(); + } + else if (StringUtil.isEmpty(lowerKey, true) || lowerKey.startsWith('accept-') + || lowerKey.startsWith('access-control-') || IGNORE_HEADERS.indexOf(lowerKey) >= 0) { + // 忽略 + } + else { + header += '\n' + left + ': ' + StringUtil.trim(l.substring(ind + 1)); + } + + contentStart += lines[i].length + 1; + } + else { + if (ind <= 0 || StringUtil.isEmpty(l) || l.startsWith('HTTP/') || l.startsWith('HTTPS/')) { // HTTP/1.1 200 + contentStart += lines[i].length + 1; + continue; + } + + var ind = l.indexOf(' '); + var m = ind < 0 ? '' : StringUtil.trim(l.substring(0, ind)); + if (APIJSON_METHODS.indexOf(m.toLowerCase()) >= 0) { // POST /gets HTTP/1.1 + contentStart += lines[i].length + 1; + var t = m.toUpperCase() + this.method = t + this.type = t == 'GET' ? 'PARAM' : (t == 'POST' ? 'JSON' : t); + + l = l.substring(ind).trim(); + ind = l.indexOf(' '); + var url = ind < 0 ? l : l.substring(0, ind); + if (url.length > 0 && url != '/') { + vUrl.value = this.getBaseUrl() + (url.startsWith('/') ? url : '/' + url); + } + + event.preventDefault(); + continue; + } + + var content = StringUtil.trim(paste.substring(contentStart)); + var json = null; + try { + json = JSON5.parse(content); // { "a":1, "b": "c" } + } + catch (e) { + log(e) + try { + json = getRequestFromURL('?' + content, true); // a=1&b=c + } catch (e2) { + log(e2) + } + } + + vInput.value = json == null ? '' : JSON.stringify(json, null, ' '); + event.preventDefault(); + break; + } + + } + + if (StringUtil.isEmpty(header, true) != true) { + vHeader.value = StringUtil.trim(header); + event.preventDefault(); + } + } + } + catch (e) { + log(e) + } + } + else if (target == vHeader || target == vRandom) { // { "key": value } 转 key: value + try { + var json = JSON5.parse(paste); + var newStr = ''; + for (var k in json) { + var v = json[k]; + if (v instanceof Object || v instanceof Array) { + v = JSON.stringify(v); + } + newStr += '\n' + k + ': ' + (target != vHeader && typeof v == 'string' ? "'" + v.replaceAll("'", "\\'") + "'" : StringUtil.get(v)); + } + target.value = StringUtil.trim(newStr); + event.preventDefault(); + } + catch (e) { + log(e) + } + } + else if (target == vInput) { // key: value 转 { "key": value } + try { + try { + JSON5.parse(paste); // 正常的 JSON 就不用转了 + } + catch (e) { + var lines = StringUtil.split(paste, '\n'); + var json = {}; + + for (var i = 0; i < lines.length; i++) { + var l = StringUtil.trim(lines[i]) || ''; + if (l.startsWith('//')) { + continue; + } + + var ind = l.lastIndexOf(' //'); + l = ind < 0 ? l : StringUtil.trim(l.substring(0, ind)); + + ind = l.indexOf(':'); + if (ind >= 0) { + var left = target == vHeader ? StringUtil.trim(l.substring(0, ind)) : l.substring(0, ind); + if (left.indexOf('=') >= 0 || left.indexOf('&') >= 0) { + try { + json = getRequestFromURL('?' + paste, true); + if (Object.keys(json).length > 0) { + break; + } + } catch (e2) { + log(e) + } + } + + json[left] = StringUtil.trim(l.substring(ind + 1)); + } + } + + if (Object.keys(json).length <= 0) { + json = getRequestFromURL('?' + paste, true); + } + + if (Object.keys(json).length > 0) { + vInput.value = JSON.stringify(json, null, ' '); + event.preventDefault(); + } + } + } + catch (e) { + log(e) + } + } + } + + }, + + /**处理按键事件 + * @param event + */ + doOnKeyUp: function (event, type, isFilter, item) { + var keyCode = event.keyCode ? event.keyCode : (event.which ? event.which : event.charCode); + var isEnter = keyCode == 13 + + if (type == 'ask') { + if (isEnter) { + var isRes = false; + if (StringUtil.isEmpty(vAskAI.value) && StringUtil.isNotEmpty(this.jsoncon, true) ) { + var res = JSON.parse(this.jsoncon); +// res = this.removeDebugInfo(res); + delete res['trace:stack'] + delete res['debug:info|help'] + vAskAI.value = JSON.stringify(res) + isRes = true; + } + + const user_query = StringUtil.trim(vAskAI.value); + if (StringUtil.isEmpty(user_query)) { + return; + } + + function onMessage(item) { + var data2 = item == null ? null : item.data + if (data2 == null || item.type != 'chunk') { + return; + } + + var answer = StringUtil.get(typeof data2 == 'string' ? data2 : (data instanceof Array ? data2.join() : JSON.stringify(data2))) + .replaceAll('/wiki/Tencent/APIJSON#', 'https://deepwiki.com/Tencent/APIJSON/').replaceAll('/wiki/TommyLemon/APIAuto#', 'https://deepwiki.com/TommyLemon/APIAuto/'); + App.view = 'markdown'; + vOutput.value += answer; + markdownToHTML(vOutput.value) + } + + function queryResult() { + App.request(true, REQUEST_TYPE_GET, REQUEST_TYPE_PARAM, 'https://api.devin.ai/ada/query/' + App.uuid, {}, {}, function (url, res, err) { +// App.onResponse(url, res, err) + var data = res.data || {} +// var isOk = JSONResponse.isSuccess(data) + + var msg = ''; // isOk ? '' : ('\nmsg: ' + StringUtil.get(data.msg)) + if (err != null) { + msg += '\nerr: ' + err.message + vOutput.value = err.message + App.view = 'error'; + return + } + + var queries = data.queries || [] + var last = queries[queries.length - 1] || {} + var query = last.user_query || user_query + var response = last.response || [] + var answer = '#### ' + query + '\n
'; + for (var i = 0; i < response.length; i ++) { + // onMessage(response[i]) + var item = response[i]; + var data2 = item == null ? null : item.data; + if (data2 == null || item.type != 'chunk') { + continue; + } + + answer += '\n' + StringUtil.trim(typeof data2 == 'string' ? data2 : (data2 instanceof Array ? data2.join() : JSON.stringify(data2))) + .replaceAll('/wiki/Tencent/APIJSON#', 'https://deepwiki.com/Tencent/APIJSON/').replaceAll('/wiki/TommyLemon/APIAuto#', 'https://deepwiki.com/TommyLemon/APIAuto/'); + } + + answer += '\n
\n'; + + App.view = 'markdown'; + vOutput.value = answer; + markdownToHTML(answer); // vOutput.value) + }) + +// App.uuid = null; // 解决第一次后的都不生效 + vAskAI.value = ''; + } + + function askAI() { + vOutput.value = '#### ' + user_query + '\n
'; + App.loadingCount ++; + const ws = new WebSocket('wss://api.devin.ai/ada/ws/query/' + App.uuid); + + // 连接成功 + ws.onopen = () => { + console.log('WebSocket connected'); + // 这里通常不需要主动发送内容,除非协议需要 + }; + + // 收到消息 + ws.onmessage = (event) => { + try { + const data = JSON.parse(event.data); + console.log('Message:', data); + onMessage(data) + + if (data.type === 'chunk') { + console.log('Chunk Data:', data.data); + } + } catch (err) { + console.error('Failed to parse message:', event.data); + } + }; + + // 连接关闭 + ws.onclose = () => { + console.log('WebSocket closed'); + App.loadingCount --; + queryResult(); + }; + + // 错误处理 + ws.onerror = (err) => { + console.error('WebSocket error:', err); + queryResult(); + }; + + } + + // 可能少调用了 https://api2.amplitude.com/2/httpapi 导致不能同一个会话二次请求 +// if (StringUtil.isEmpty(this.uuid, true)) { + this.uuid = crypto.randomUUID(); +// } + + this.request(true, REQUEST_TYPE_POST, REQUEST_TYPE_JSON, 'https://api.devin.ai/ada/query', { + "engine_id": "multihop", + "user_query": "" + (isRes ? "这是用 HTTP 接口工具 TommyLemon/APIAuto 发请求后的响应结果,分析并" : "") + "用中文回答:" + user_query, + "keywords": [], + "repo_names": [ + "Tencent/APIJSON" + ], + "additional_context": "", + "query_id": this.uuid, + "use_notes": false, + "generate_summary": false + }, {}, function (url, res, err) { + App.onResponse(url, res, err) + var data = res.data || {} +// var isOk = JSONResponse.isSuccess(data) + + var msg = ''; // isOk ? '' : ('\nmsg: ' + StringUtil.get(data.msg)) + if (err != null) { + msg += '\nerr: ' + err.message + vOutput.value = err.message + App.view = 'error'; + return + } + + askAI(); + }) + } + return + } + + if (type == 'option') { + if (isEnter) { + this.selectInput(item); + } + return + } + + if (type == 'project') { + if (isEnter || item.host == (this.projectHost || {}).host) { + this.projectHost = {project: item.project} + this.saveCache('', 'projectHost', this.projectHost) + } + return + } + + var project = (this.projectHost || {}).project + + if (isFilter && (type == 'caseGroup' || type == 'chainGroup')) { + this.isCaseGroupEditable = true +// this.isChainGroupEditable = true + } + + var obj = event.srcElement ? event.srcElement : event.target; + if ($(obj).attr('id') == 'vUrl') { + vUrlComment.value = '' + this.currentDocItem = null + this.currentRemoteItem = null + } + + if (isEnter) { // enter + if (isFilter) { + this.onFilterChange(type) + return + } + + if (type == null) { +// 无效,这时已经换行了 if (event.target == vUrl) { +// event.preventDefault(); +// } + this.send(false); + return + } + + if (type == 'chainGroupAdd' || type == 'chainGroup') { + var isAdd = type == 'chainGroupAdd' + var groupName = item == null ? null : item.groupName + if (StringUtil.isEmpty(groupName)) { + alert('请输入名称!') + return + } + + var groupId = item == null ? null : item.groupId + if (groupId == null || groupId <= 0) { + if (! isAdd) { + alert('请选择有效的列表项!') + return + } + + groupId = new Date().getTime() + } + + //修改 Document + this.request(true, REQUEST_TYPE_POST, REQUEST_TYPE_JSON, this.server + (isAdd ? '/post' : '/put'), { + Chain: { + 'groupName': groupName, + 'groupId': isAdd ? groupId : null, + 'groupId{}': isAdd ? null : [groupId] + }, + tag: 'Chain-group' + }, {}, function (url, res, err) { + App.onResponse(url, res, err) + var isOk = JSONResponse.isSuccess(res.data) + + var msg = isOk ? '' : ('\nmsg: ' + StringUtil.get((res.data || {}).msg)) + if (err != null) { + msg += '\nerr: ' + err.msg + } + alert((isAdd ? '新增' : '修改') + (isOk ? '成功' : '失败') + (isAdd ? '! \n' :'!\ngroupId: ' + groupId) + '\ngroupName: ' + groupName + '\n' + msg) + + App.isCaseGroupEditable = ! isOk + }) + + return + } + + if (type == 'chainAdd') { + var groupName = item == null ? null : item.groupName + if (StringUtil.isEmpty(groupName)) { + alert('请输入名称!') + return + } + + var search = StringUtil.isEmpty(groupName, true) ? null : '%' + StringUtil.trim(groupName).replaceAll('_', '\\_').replaceAll('%', '\\%') + '%' + var methods = this.methods + var types = this.types + + //修改 Document + this.request(true, REQUEST_TYPE_POST, REQUEST_TYPE_JSON, this.server + '/get', { + format: false, + 'Document[]': { + 'count': 100, //200 条测试直接卡死 0, + 'page': 0, + 'Document': { + '@column': 'id,userId,version,date,name,operation,method,type,url,request,apijson', + '@order': 'version-,date-', + 'userId': this.User.id, + 'project': StringUtil.isEmpty(project, true) ? null : project, + 'name$': search, + 'operation$': search, + 'url$': search, + // 'group{}': group == null || StringUtil.isNotEmpty(groupUrl) ? null : 'length(group)<=0', + // 'group{}': group == null ? null : (group.groupName == null ? "=null" : [group.groupName]), + '@combine': search == null ? null : 'name$,operation$,url$', + 'method{}': methods == null || methods.length <= 0 ? null : methods, + 'type{}': types == null || types.length <= 0 ? null : types, + '@null': 'sqlauto', //'sqlauto{}': '=null' + // '@having': StringUtil.isEmpty(groupUrl) ? null : "substring_index(substr,'/',1)<0" + } + } + }, {}, function (url, res, err) { + App.onResponse(url, res, err) + var isOk = JSONResponse.isSuccess(res.data) + + var msg = isOk ? '' : ('\nmsg: ' + StringUtil.get((res.data || {}).msg)) + if (err != null) { + msg += '\nerr: ' + err.msg + } + if (! isOk) { + alert(isOk ? '选择右侧接口来添加' : '查询失败!\ngroupName: ' + groupName + '\n' + msg) + return + } + + var list = res.data['Document[]'] || [] + var options = [] + for (var i = 0; i < list.length; i ++) { + var item = list[i] || {} + options.push({ + name: '[' + item.method + '][' + item.type + ']', // + item.name + ' ' + item.url, + type: item.name, + comment: item.url, + value: item + } + ) + } + + App.options = options + document.activeElement = vOption // vChainAdd.focusout() + currentTarget = vChainAdd + vOption.focus() + // App.showOptions(target, text, before, after); + + }) + + return + } + + if (type == 'caseGroup') { + var groupUrl = item == null ? null : item.groupUrl + var rawName = item == null ? null : item.rawName + // if (StringUtil.isEmpty(url)) { + // alert('请选择有效的选项!item.url == null !') + // return + // } + + //修改 Document + this.request(true, REQUEST_TYPE_POST, REQUEST_TYPE_JSON, this.server + '/put', { + Document: { + 'project': StringUtil.isEmpty(project, true) ? null : project, + 'group': item.groupName, + '@raw': '@key', + '@key':"url:substr(url,1,length(url)-length(substring_index(url,'/',-1))-1)", + 'url{}': [groupUrl], + 'group{}': rawName == null ? "=null" : [rawName] + }, + tag: 'Document-group' + }, {}, function (url, res, err) { + App.onResponse(url, res, err) + var isOk = JSONResponse.isSuccess(res.data) + + var msg = isOk ? '' : ('\nmsg: ' + StringUtil.get((res.data || {}).msg)) + if (err != null) { + msg += '\nerr: ' + err.msg + } + alert('修改' + (isOk ? '成功' : '失败') + '!\ngroupUrl: ' + item.groupUrl + '\ngroupName: ' + item.groupName + '\nrawName: ' + item.rawName + msg) + + App.isCaseGroupEditable = ! isOk + }) + + return + } + + if (type == 'random' || type == 'randomSub') { + var r = item == null ? null : item.Random + if (r == null || r.id == null) { + alert('请选择有效的选项!item.Random.id == null !') + return + } + + //修改 Random 的 count + this.request(true, REQUEST_TYPE_POST, REQUEST_TYPE_JSON, this.server + '/put', { + Random: { + id: r.id, + count: r.count, + name: r.name + }, + tag: 'Random' + }, {}, function (url, res, err) { + App.onResponse(url, res, err) + var isOk = JSONResponse.isSuccess(res.data) + + var msg = isOk ? '' : ('\nmsg: ' + StringUtil.get((res.data || {}).msg)) + if (err != null) { + msg += '\nerr: ' + err.msg + } + alert('修改' + (isOk ? '成功' : '失败') + '!\nurl: ' + item.url + '\nname: ' + r.name + msg) + + App.isRandomEditable = ! isOk + }) + + return + } + + } + else { + if (isFilter) { + return + } + if (type == 'random' || type == 'randomSub') { + this.isRandomEditable = true + return + } + if (type == 'document' || type == 'testCase') { + return + } + + this.urlComment = ''; + this.requestVersion = ''; + this.onChange(true); + } + }, + + pageDown: function(type) { + type = type || '' + var page + switch (type) { + case 'caseGroup': + page = this.caseGroupPage + break + case 'testCase': + page = this.testCasePage + break + case 'random': + page = this.randomPage + break + case 'randomSub': + page = this.randomSubPage + break + default: + page = this.page + break + } + + if (page == null) { + page = 0 + } + + if (page > 0) { + page -- + switch (type) { + case 'caseGroup': + this.caseGroupPage = page + break + case 'testCase': + this.testCasePage = page + break + case 'random': + this.randomPage = page + break + case 'randomSub': + this.randomSubPage = page + break + default: + this.page = page + break + } + + this.onFilterChange(type) + } + }, + pageUp: function(type) { + type = type || '' + switch (type) { + case 'caseGroup': + this.caseGroupPage ++ + break + case 'testCase': + this.testCasePage ++ + break + case 'random': + this.randomPage ++ + break + case 'randomSub': + this.randomSubPage ++ + break + default: + this.page ++ + break + } + this.onFilterChange(type) + }, + onFilterChange: function(type) { + type = type || '' + if (type == 'testCase' || type == 'caseGroup' || type == 'chainGroup') { + var isChainShow = this.isChainShow + var paths = isChainShow ? this.chainPaths : this.casePaths + var index = paths.length - 1 + var group = paths[index] + var groupId = group == null ? 0 : (group.groupId || 0) + var groupUrl = group == null ? '' : (group.groupUrl || '') + var groupKey = isChainShow ? groupId + '' : groupUrl + + if (type == 'chainGroup') { + this.chainGroupPages[groupKey] = this.chainGroupPage + this.chainGroupCounts[groupKey] = this.chainGroupCount + this.chainGroupSearches[groupKey] = this.chainGroupSearch + if (index < 0) { + this.saveCache(this.server, 'chainGroupPage', this.chainGroupPage) + this.saveCache(this.server, 'chainGroupCount', this.chainGroupCount) + // this.saveCache(this.server, 'chainGroupSearch', this.chainGroupSearch) + } + this.saveCache(this.server, 'chainGroupPages', this.chainGroupPages) + this.saveCache(this.server, 'chainGroupCounts', this.chainGroupCounts) + // this.saveCache(this.server, 'chainGroupSearches', this.chainGroupSearches) + + this.selectChainGroup(this.currentChainGroupIndex, null) + } + else if (type == 'caseGroup') { + this.caseGroupPages[groupKey] = this.caseGroupPage + this.caseGroupCounts[groupKey] = this.caseGroupCount + this.caseGroupSearches[groupKey] = this.caseGroupSearch + if (index < 0) { + this.saveCache(this.server, 'caseGroupPage', this.caseGroupPage) + this.saveCache(this.server, 'caseGroupCount', this.caseGroupCount) + // this.saveCache(this.server, 'caseGroupSearch', this.caseGroupSearch) + } + this.saveCache(this.server, 'caseGroupPages', this.caseGroupPages) + this.saveCache(this.server, 'caseGroupCounts', this.caseGroupCounts) + // this.saveCache(this.server, 'caseGroupSearches', this.caseGroupSearches) + + this.selectCaseGroup() + } + else { + this.testCasePages[groupKey] = this.testCasePage + this.testCaseCounts[groupKey] = this.testCaseCount + this.testCaseSearches[groupKey] = this.testCaseSearch + + if (index < 0) { + this.saveCache(this.server, 'testCasePage', this.testCasePage) + this.saveCache(this.server, 'testCaseCount', this.testCaseCount) + } + this.saveCache(this.server, 'testCasePages', this.testCasePages) + this.saveCache(this.server, 'testCaseCounts', this.testCaseCounts) + // this.saveCache(this.server, 'testCaseSearches', this.testCaseSearches) + + this.resetTestCount(this.currentAccountIndex) + + this.isStatisticsEnabled = false + this.remotes = null + this.showTestCase(true, false) + } + } + else if (type == 'random') { + this.saveCache(this.server, 'randomPage', this.randomPage) + this.saveCache(this.server, 'randomCount', this.randomCount) + + this.resetTestCount(this.currentAccountIndex, true) + + var cri = this.currentRemoteItem || {} + cri.randoms = null + this.randoms = null + this.showRandomList(true, cri.Document, false) + } + else if (type == 'randomSub') { + this.saveCache(this.server, 'randomSubPage', this.randomSubPage) + this.saveCache(this.server, 'randomSubCount', this.randomSubCount) + + this.resetTestCount(this.currentAccountIndex, true, true) + + var cri = this.currentRandomItem || {} + this.randomSubs = null + this.showRandomList(true, cri.Random, true) + } + else { + docObj = null + doc = null + this.saveCache(this.server, 'page', this.page) + this.saveCache(this.server, 'count', this.count) + // this.saveCache(this.server, 'docObj', null) + // this.saveCache(this.server, 'doc', null) + + this.onChange(false) + + //虽然性能更好,但长时间没反应,用户会觉得未生效 + // this.getDoc(function (d) { + // // vOutput.value = 'resolving...'; + // App.setDoc(d) + // App.onChange(false) + // }); + } + }, + + /**转为请求代码 + * @param rq + */ + getCode: function (rq) { + var s = '\n\n\n### 请求代码(自动生成) \n'; + switch (this.language) { + case CodeUtil.LANGUAGE_KOTLIN: + s += '\n#### <= Android-Kotlin: 空对象用 HashMap<String, Any>(),空数组用 ArrayList<Any>()\n' + + '```kotlin \n' + + CodeUtil.parseKotlinRequest(null, parseJSON(rq), 0, isSingle, false, false, this.type, this.getBaseUrl(), '/' + this.getMethod(), this.urlComment) + + '\n ``` \n注:对象 {} 用 mapOf("key": value),数组 [] 用 listOf(value0, value1)\n'; + break; + case CodeUtil.LANGUAGE_JAVA: + s += '\n#### <= Android-Java: 同名变量需要重命名' + + ' \n ```java \n' + + StringUtil.trim(CodeUtil.parseJavaRequest(null, parseJSON(rq), 0, isSingle, false, false, this.type, '/' + this.getMethod(), this.urlComment)) + + '\n ``` \n注:' + (isSingle ? '用了 APIJSON 的 JSONRequest, JSONResponse 类,也可使用其它类封装,只要 JSON 有序就行\n' : 'LinkedHashMap<>() 可替换为 fastjson 的 JSONObject(true) 等有序JSON构造方法\n'); + + var serverCode = CodeUtil.parseJavaServer(this.type, '/' + this.getMethod(), this.database, this.schema, parseJSON(rq), isSingle); + if (StringUtil.isEmpty(serverCode, true) != true) { + s += '\n#### <= Server-Java: RESTful 等非 APIJSON 规范的 API' + + ' \n ```java \n' + + serverCode + + '\n ``` \n注:' + (isSingle ? '分页和排序用了 Mybatis-PageHelper,如不需要可在生成代码基础上修改\n' : '使用 SSM(Spring + SpringMVC + Mybatis) 框架 \n'); + } + break; + case CodeUtil.LANGUAGE_C_SHARP: + s += '\n#### <= Unity3D-C\\#: 键值对用 {"key", value}' + + '\n ```csharp \n' + + CodeUtil.parseCSharpRequest(null, parseJSON(rq), 0) + + '\n ``` \n注:对象 {} 用 new JObject{{"key", value}},数组 [] 用 new JArray{value0, value1}\n'; + break; + + case CodeUtil.LANGUAGE_SWIFT: + s += '\n#### <= iOS-Swift: 空对象用 [ : ]' + + '\n ```swift \n' + + CodeUtil.parseSwiftRequest(null, parseJSON(rq), 0) + + '\n ``` \n注:对象 {} 用 ["key": value],数组 [] 用 [value0, value1]\n'; + break; +// case CodeUtil.LANGUAGE_OBJECTIVE_C: +// s += '\n#### <= iOS-Objective-C \n ```objective-c \n' +// + CodeUtil.parseObjectiveCRequest(null, parseJSON(rq)) +// + '\n ``` \n'; +// break; + + case CodeUtil.LANGUAGE_GO: + s += '\n#### <= Web-Go: 对象 key: value 会被强制排序,每个 key: value 最后都要加逗号 ","' + + ' \n ```go \n' + + CodeUtil.parseGoRequest(null, parseJSON(rq), 0) + + '\n ``` \n注:对象 {} 用 map[string]interface{} {"key": value},数组 [] 用 []interface{} {value0, value1}\n'; + break; + case CodeUtil.LANGUAGE_C_PLUS_PLUS: + s += '\n#### <= Web-C++: 使用 RapidJSON' + + ' \n ```cpp \n' + + StringUtil.trim(CodeUtil.parseCppRequest(null, parseJSON(rq), 0, isSingle)) + + '\n ``` \n注:std::string 类型值需要判断 RAPIDJSON_HAS_STDSTRING\n'; + break; + + case CodeUtil.LANGUAGE_PHP: + s += '\n#### <= Web-PHP: 空对象用 (object) ' + (isSingle ? '[]' : 'array()') + + ' \n ```php \n' + + CodeUtil.parsePHPRequest(null, parseJSON(rq), 0, isSingle) + + '\n ``` \n注:对象 {} 用 ' + (isSingle ? '[\'key\' => value]' : 'array("key" => value)') + ',数组 [] 用 ' + (isSingle ? '[value0, value1]\n' : 'array(value0, value1)\n'); + break; + + case CodeUtil.LANGUAGE_PYTHON: + s += '\n#### <= Web-Python: 注释符用 \'\#\'' + + ' \n ```python \n' + + CodeUtil.parsePythonRequest(null, parseJSON(rq), 0, isSingle, vInput.value) + + '\n ``` \n注:关键词转换 null: None, false: False, true: True'; + break; + + //以下都不需要解析,直接用左侧的 JSON + case CodeUtil.LANGUAGE_TYPE_SCRIPT: + case CodeUtil.LANGUAGE_JAVA_SCRIPT: + //case CodeUtil.LANGUAGE_PYTHON: + s += '\n#### <= Web-JavaScript/TypeScript: 和左边的请求 JSON 一样 \n'; + break; + default: + s += '\n没有生成代码,可能生成代码(封装,解析)的语言配置错误。\n'; + break; + } + + if (((this.User || {}).id || 0) > 0) { + s += '\n\n#### 开放源码 ' + + '\nAPIJSON 接口测试: https://github.com/TommyLemon/APIAuto ' + + '\nAPIJSON 单元测试: https://github.com/TommyLemon/UnitAuto ' + + '\nAPIJSON 中文文档: https://github.com/vincentCheng/apijson-doc ' + + '\nAPIJSON 英文文档: https://github.com/ruoranw/APIJSONdocs ' + + '\nAPIJSON 官方网站: https://github.com/APIJSON/apijson.cn ' + + '\nAPIJSON -Java版: https://github.com/Tencent/APIJSON ' + + '\nAPIJSON - C# 版: https://github.com/liaozb/APIJSON.NET ' + + '\nAPIJSON - Go 版: https://github.com/glennliao/apijson-go ' + + '\nAPIJSON - PHP版: https://github.com/kvnZero/hyperf-APIJSON ' + + '\nAPIJSON -Node版: https://github.com/kevinaskin/apijson-node ' + + '\nAPIJSON -Python: https://github.com/zhangchunlin/uliweb-apijson ' + + '\n感谢热心的作者们的贡献,GitHub 右上角点 ⭐Star 支持下他们吧 ^_^'; + } + + return s; + }, + + + /**显示文档 + * @param d + **/ + setDoc: function (d) { + if (d == null) { //解决死循环 || d == '') { + return false; + } + doc = d; + var url = StringUtil.trim((this.coverage || {}).url) + if (url != null && url.startsWith('/')) { + url = this.getBaseUrl() + url + this.view = 'html' + vHtml.innerHTML = '
' + return true + } + var html = null // (this.coverage || {}).html + if (StringUtil.isEmpty(html) != true) { + this.view = 'html' + vHtml.innerHTML = html + return true + } + vOutput.value = (StringUtil.isEmpty(url, true) ? (StringUtil.isEmpty(html, true) ? '' : StringUtil.trim(html) + '
') : '
') + + (this.isTestCaseShow ? '' : output) + ( + '\n\n\n## 文档 \n\n 通用文档见 [APIJSON通用文档](https://github.com/Tencent/APIJSON/blob/master/Document.md#3.2) \n### 数据字典\n自动查数据库表和字段属性来生成 \n\n' + d + + '

关于

' + + '

APIAuto-机器学习 HTTP 接口工具' + + '
机器学习零代码测试、生成代码与静态检查、生成文档与光标悬浮注释' + + '
APIAuto(前端网页工具), APIJSON(后端接口服务) 等提供技术支持' + + '
遵循 Apache-2.0 开源协议' + + '
Copyright © 2017-' + new Date().getFullYear() + ' Tommy Lemon' + + '
粤ICP备18005508号-1' + + '



' + ); + + this.view = 'markdown'; + markdownToHTML(vOutput.value); + return true; + }, + + + /** + * 获取文档 + */ + getDoc: function (callback) { + + var isTSQL = ['ORACLE', 'DAMENG'].indexOf(this.database) >= 0 + var isNotTSQL = ! isTSQL + + var count = this.count || 100 //超过就太卡了 + var page = this.page || 0 + + var schemas = StringUtil.isEmpty(this.schema, true) ? null : StringUtil.split(this.schema) + + var search = StringUtil.isEmpty(this.search, true) ? null : '%' + StringUtil.trim(this.search) + '%' + this.request(false, REQUEST_TYPE_POST, REQUEST_TYPE_JSON, this.getBaseUrl() + '/get', { + format: false, + '@database': StringUtil.isEmpty(this.database, true) ? undefined : this.database, + // '@schema': StringUtil.isEmpty(this.schema, true) ? undefined : this.schema, + 'sql@': { + 'from': 'Access', + 'Access': { + '@column': 'name' + } + }, + 'Access[]': { + 'count': count, + 'page': page, + 'Access': { + '@column': 'name,alias,post,put,delete,get,gets,head,heads', + '@order': 'date-,name+', + 'name$': search, + 'alias$': search, + '@combine': search == null ? null : 'name$,alias$', + } + }, + '[]': { + 'count': count, + 'page': page, + 'Table': isTSQL || this.database == 'SQLSERVER' ? null : { + 'table_schema{}': schemas, + 'table_type': 'BASE TABLE', + // 'table_name!$': ['\\_%', 'sys\\_%', 'system\\_%'], + 'table_name$': search, + 'table_comment$': this.database == 'POSTGRESQL' ? null : search, + '@combine': search == null || this.database == 'POSTGRESQL' ? null : 'table_name$,table_comment$', + 'table_name{}@': 'sql', + '@order': 'table_name+', //MySQL 8 SELECT `table_name` 返回的仍然是大写的 TABLE_NAME,需要 AS 一下 + '@column': (schemas != null && schemas.length == 1 ? '' : 'table_schema:table_schema,') + (this.database == 'POSTGRESQL' ? 'table_name' : 'table_name:table_name,table_comment:table_comment') + }, + 'PgClass': this.database != 'POSTGRESQL' ? null : { + 'relname@': '/Table/table_name', + //FIXME 多个 schema 有同名表时数据总是取前面的 不属于 pg_class 表 'nspname': this.schema, + '@column': 'oid;obj_description(oid):table_comment' + }, + 'SysTable': this.database != 'SQLSERVER' ? null : { + 'name!$': [ + '\\_%', + 'sys\\_%', + 'system\\_%' + ], + '@order': 'name+', + '@column': 'name:table_name,object_id' + }, + 'ExtendedProperty': this.database != 'SQLSERVER' ? null : { + '@order': 'name+', + 'major_id@': '/SysTable/object_id', + '@column': 'value:table_comment' + }, + "join": isNotTSQL ? null : { + "&/AllTableComment": { + 'table_name$': search, + 'table_comment$': search, + '@combine': search == null ? null : 'table_name$,table_comment$', + } + }, + "AllTable": isNotTSQL ? null : { + "@order": "TABLE_NAME+", + "@column": "TABLE_NAME:table_name", + 'TABLE_NAME{}@': 'sql' + }, + "AllTableComment": isNotTSQL ? null : { + "TABLE_TYPE": "TABLE", + "TABLE_NAME@": "/AllTable/TABLE_NAME", + "@column": "COMMENTS:table_comment" + }, + '[]': { + 'count': 0, + 'Column': isTSQL ? null : { + 'table_schema{}': schemas, + 'table_schema@': schemas != null && schemas.length == 1 ? null : '[]/Table/table_schema', + 'table_name@': this.database != 'SQLSERVER' ? '[]/Table/table_name' : "[]/SysTable/table_name", + "@order": this.database != 'SQLSERVER' ? null : "table_name+", + '@column': this.database == 'POSTGRESQL' || this.database == 'SQLSERVER' //MySQL 8 SELECT `column_name` 返回的仍然是大写的 COLUMN_NAME,需要 AS 一下 + ? 'column_name;data_type;numeric_precision,numeric_scale,character_maximum_length' + : 'column_name:column_name,column_type:column_type,is_nullable:is_nullable,column_default:column_default,column_comment:column_comment' + }, + 'PgAttribute': this.database != 'POSTGRESQL' ? null : { + 'attrelid@': '[]/PgClass/oid', + 'attname@': '/Column/column_name', + 'attnum>': 0, + '@column': 'col_description(attrelid,attnum):column_comment' + }, + 'SysColumn': this.database != 'SQLSERVER' ? null : { + 'object_id@': '[]/SysTable/object_id', + 'name@': '/Column/column_name', + '@order': 'object_id+', + '@column': 'object_id,column_id' + }, + 'ExtendedProperty': this.database != 'SQLSERVER' ? null : { + '@order': 'major_id+', + 'major_id@': '/SysColumn/object_id', + 'minor_id@': '/SysColumn/column_id', + '@column': 'value:column_comment' + }, + "AllColumn": isNotTSQL ? null : { + "TABLE_NAME@": "[]/AllTable/table_name", + "@column": "COLUMN_NAME:column_name,DATA_TYPE:column_type" + }, + "AllColumnComment": isNotTSQL ? null : { + "TABLE_NAME@": "[]/AllTable/table_name", + "COLUMN_NAME@": "/AllColumn/column_name", + "@column": "COMMENTS:column_comment" + } + } + }, + 'Function[]': { + 'count': count, + 'page': page, + 'Function': { + '@order': 'date-,name+', + '@column': 'name,arguments,returnType,demo,detail,detail:rawDetail', + 'demo()': 'getFunctionDemo()', + 'detail()': 'getFunctionDetail()', + 'name$': search, + 'detail$': search, + '@combine': search == null ? null : 'name$,detail$', + } + }, + 'Request[]': { + 'count': count, + 'page': page, + 'Request': { + '@order': 'version-,method-', + '@json': 'structure', + 'tag$': search, + // 界面又不显示这个字段,搜出来莫名其妙 'detail$': search, + // '@combine': search == null ? null : 'tag$,detail$', + } + } + }, {}, function (url, res, err) { + App.onDocumentListResponse(url, res, err, callback) + }) + }, + + lastDocReqTime: 0, + onDocumentListResponse: function(url, res, err, callback) { + if (err != null || res == null || res.data == null) { + log('getDoc err != null || res == null || res.data == null >> return;'); + if (callback != null) { + callback('') + } + return; + } + + var time = res.config == null || res.config.metadata == null ? 0 : (res.config.metadata.startTime || 0) + if (time < this.lastDocReqTime) { + return + } + + this.lastDocReqTime = time; + +// log('getDoc docRq.responseText = \n' + docRq.responseText); + docObj = res.data || {}; //避免后面又调用 onChange ,onChange 又调用 getDoc 导致死循环 + + var map = {}; + + //Access[] <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< + var ad = '' + var list = docObj == null ? null : docObj['Access[]']; + CodeUtil.accessList = list; + if (list != null) { + if (DEBUG) { + log('getDoc Access[] = \n' + format(JSON.stringify(list))); + } + + ad += '\n\n\n\n\n\n\n\n\n### 访问权限\n自动查 Access 表写入的数据来生成\n' + + ' \n 表名 | 允许 POST
的角色 | 允许 PUT
的角色 | 允许 DELETE
的角色 | 允许 GET
的角色 | 允许 GETS
的角色 | 允许 HEAD
的角色 | 允许 HEADS
的角色 | 表名' + + ' \n -------- | --------- | --------- | --------- | --------- | --------- | --------- | --------- | -------- '; + + for (var i = 0; i < list.length; i++) { + var item = list[i]; + if (item == null) { + continue; + } + if (DEBUG) { + log('getDoc Access[] for i=' + i + ': item = \n' + format(JSON.stringify(item))); + } + + var name = StringUtil.isEmpty(item.alias, true) ? StringUtil.firstCase(item.name, true) : item.alias + map[StringUtil.toLowerCase(item.schema) + '.' + StringUtil.toLowerCase(item.name)] = item + + function getShowString(method, lineItemCount) { + var roles = item[method] == null ? null : parseJSON(item[method]) + var rs = [] + if (roles != null) { + var schemaStr = StringUtil.isEmpty(item.schema) ? 'null' : "'" + item.schema + "'" + for (var j = 0; j < roles.length; j++) { + var r = roles[j] || '' + rs.push('' + r + '') + } + } + return JSONResponse.getShowString(rs, lineItemCount) + } + + ad += '\n' + (name) //右上角设置指定了 Schema + '(' + item.schema + ')') + + ' | ' + getShowString('post', 1) + + ' | ' + getShowString('put', 1) + + ' | ' + getShowString('delete', 1) + + ' | ' + getShowString('get', 2) + + ' | ' + getShowString('gets', 2) + + ' | ' + getShowString('head', 2) + + ' | ' + getShowString('heads', 2) + + ' | ' + (name); //右上角设置指定了 Schema + '(' + item.schema + ')'); + + if (i % 5 == 4) { + ad += ' \n **表名** | **允许 POST**
**的角色** | **允许 PUT**
**的角色** | **允许 DELETE**
**的角色** | **允许 GET**
**的角色** | **允许 GETS**
**的角色** | **允许 HEAD**
**的角色** | **允许 HEADS**
**的角色** | 表名' + } + } + + // ad += ' \n 表名 | 允许 post
的角色 | 允许 put
的角色 | 允许 delete
的角色 | 允许 get
的角色 | 允许 gets
的角色 | 允许 head
的角色 | 允许 heads
的角色 | 表名' + + ad += '\n' //避免没数据时表格显示没有网格 + } + var accessMap = CodeUtil.accessMap = map; + //Access[] >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> + + + //转为文档格式 + var doc = ''; + + //[] <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< + list = docObj == null ? null : docObj['[]']; + map = {}; + CodeUtil.tableList = list; + if (list != null) { + if (DEBUG) { + log('getDoc [] = \n' + format(JSON.stringify(list))); + } + + for (var i = 0; i < list.length; i++) { + var item = list[i]; + + //Table + var table = item == null ? null : (App.database != 'SQLSERVER' ? item.Table : item.SysTable); + if (table == null) { + continue; + } + if (DEBUG) { + log('getDoc [] for i=' + i + ': table = \n' + format(JSON.stringify(table))); + } + + var table_comment = App.database == 'POSTGRESQL' + ? (item.PgClass || {}).table_comment + : (App.database == 'SQLSERVER' + ? (item.ExtendedProperty || {}).table_comment + : table.table_comment + ); + // item.Table.table_name = table.table_name + // item.Table.table_comment = table_comment + + var schema = table.table_schema + var modelName = App.getModelName(i) + map[StringUtil.toLowerCase(schema) + '.' + StringUtil.toLowerCase(modelName)] = table + + // TODO 对 isAPIJSON 和 isRESTful 生成不一样的 + doc += '\n### ' + (i + 1) + '. ' + modelName + + (StringUtil.isEmpty(schema, true) ? '' : ': { @schema: ' + schema + ' }') + + ' - POST' + + ' PUT' + + ' DELETE' + + ' GET' + + ' GETS' + + ' HEAD' + + ' HEADS' + + '\n' + App.toMD(table_comment); + + //Column[] + doc += '\n\n 名称 | 类型 | 最大长度 | 详细说明' + + ' \n -------- | ------------ | ------------ | ------------ '; + + var columnList = item['[]']; + if (columnList == null) { + continue; + } + if (DEBUG) { + log('getDoc [] for ' + i + ': columnList = \n' + format(JSON.stringify(columnList))); + } + + for (var j = 0; j < columnList.length; j++) { + var column = (columnList[j] || {})[App.database != 'SQLSERVER' ? 'Column' : 'SysColumn']; + var name = column == null ? null : column.column_name; + if (name == null) { + continue; + } + + column.column_type = CodeUtil.getColumnType(column, App.database); + var type = CodeUtil.getType4Language(App.language, column.column_type, false); + var length = CodeUtil.getMaxLength(column.column_type); + + if (DEBUG) { + log('getDoc [] for j=' + j + ': column = \n' + format(JSON.stringify(column))); + } + + var o = App.database == 'POSTGRESQL' + ? (columnList[j] || {}).PgAttribute + : (App.database == 'SQLSERVER' + ? (columnList[j] || {}).ExtendedProperty + : column + ); + var column_comment = (o || {}).column_comment + var column_default = column.column_default + + // column.column_comment = column_comment + doc += '\n' + ' ' + name + '' + + ' | ' + type.replace(//g, '>') + ' | ' + length + ' | ' + App.toMD(column_comment); + + } + + doc += '\n\n\n'; + + } + + } + CodeUtil.tableMap = map; + //[] >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> + + doc += ad; + + //Function[] <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< + list = docObj == null ? null : docObj['Function[]']; + CodeUtil.functionList = list; + if (list != null) { + if (DEBUG) { + log('getDoc Function[] = \n' + format(JSON.stringify(list))); + } + + doc += '\n\n\n\n\n\n\n\n\n### 远程函数\n自动查 Function 表写入的数据来生成\n' + + ' \n 说明 | 示例' + + ' \n -------- | -------------- '; + + for (var i = 0; i < list.length; i++) { + var item = list[i]; + var name = item == null ? null : item.name; + if (StringUtil.isEmpty(name, true)) { + continue; + } + if (DEBUG) { + log('getDoc Function[] for i=' + i + ': item = \n' + format(JSON.stringify(item))); + } + + map[name] = item + + var demoStr = JSON.stringify(item.demo) + + // doc += '\n' + item.detail + ' | ' + ' ' + demoStr + ''; + doc += '\n' + name + '(' + StringUtil.get(item.arguments) + '): ' + + CodeUtil.getType4Language(App.language, item.returnType) + ', ' + (item.rawDetail || item.detail) + + ' | ' + ' ' + demoStr + ''; + } + + doc += '\n' //避免没数据时表格显示没有网格 + } + CodeUtil.functionMap = map; + //Function[] >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> + + + //Request[] <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< + list = docObj == null ? null : docObj['Request[]']; + map = {}; + CodeUtil.requestList = list; + if (list != null) { + if (DEBUG) { + log('getDoc Request[] = \n' + format(JSON.stringify(list))); + } + + doc += '\n\n\n\n\n\n\n\n\n### 非开放请求\n自动查 Request 表写入的数据来生成\n' + + ' \n 版本 | 方法 | 请求标识 | 数据和结构' + + ' \n -------- | ------------ | ------------ | ------------ | ------------ '; + + for (var i = 0; i < list.length; i++) { + var item = list[i]; + if (item == null) { + continue; + } + if (DEBUG) { + log('getDoc Request[] for i=' + i + ': item = \n' + format(JSON.stringify(item))); + } + + map[item.version + '.' + item.method + '.' + item.tag] = item + + var jsonStr = JSON.stringify(App.getStructure(false, null, item.structure, item.method, item.tag, item.version)) + + doc += '\n' + item.version + ' | ' + item.method + ' | ' + item.tag + + ' | ' + ' ' + jsonStr + '' + } + + doc += '\n注: \n1.GET,HEAD方法不受限,可传任何 数据、结构。\n2.可在最外层传版本version来指定使用的版本,不传或 version <= 0 则使用最新版。\n\n\n\n\n\n\n'; + } + CodeUtil.requestMap = map; + + + //Request[] >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> + + App.onChange(false); + + if (callback != null) { + callback(doc); + } + +// log('getDoc callback(doc); = \n' + doc); + }, + + getTableKey: function(database) { + database = database || this.database + return this.database == 'SQLSERVER' ? 'SysTable' : (['ORALCE', 'DAMENG'].indexOf(database) >= 0 ? 'AllTable' : 'Table') + }, + getColumnKey: function(database) { + database = database || this.database + return this.database == 'SQLSERVER' ? 'SysColumn' : (['ORALCE', 'DAMENG'].indexOf(database) >= 0 ? 'AllColumn' : 'Column') + }, + getTableObj: function(tableIndex) { + var list = docObj == null ? null : docObj['[]'] + var item = list == null ? null : list[tableIndex] + return item == null ? null : item[this.getTableKey()]; + }, + getColumnList: function(tableIndex) { + var list = docObj == null ? null : docObj['[]'] + var item = list == null ? null : list[tableIndex] + return item == null ? null : item['[]'] + }, + getColumnListWithModelName: function(modelName, schemaName) { + var list = docObj == null ? null : docObj['[]'] + if (list != null) { + for (var i = 0; i < list.length; i++) { + var table = this.getTableObj(i) + if (table != null && this.getModelName(i) == modelName + && (schemaName == null || table.table_schema == schemaName)) { + return list[i]['[]'] + } + } + } + return null + }, + getTableByName: function(tableName, schemaName) { + var list = docObj == null ? null : docObj['[]'] + if (list != null) { + for (var i = 0; i < list.length; i++) { + var table = this.getTableObj(i) + if (table != null && table.table_name == tableName + && (schemaName == null || table.table_schema == schemaName)) { + return table + } + } + } + + return null + }, + getTableByModelName: function(modelName, schemaName) { + var list = docObj == null ? null : docObj['[]'] + if (list != null) { + for (var i = 0; i < list.length; i++) { + var table = this.getTableObj(i) + if (table != null && this.getModelName(i) == modelName + && (schemaName == null || table.table_schema == schemaName)) { + return table + } + } + } + return null + }, + getColumnTypeWithModelName: function(columnName, modelName, schemaName) { + var columnList = this.getColumnListWithModelName(modelName, schemaName) + if (columnList != null) { + for (var j = 0; j < columnList.length; j++) { + var column = this.getColumnObj(columnList, j) + if (column != null && column.column_name == columnName) { + return column + } + } + } + return null + }, + getColumnObj: function(columnList, columnIndex) { + return columnList == null ? null : (columnList[columnIndex] || {})[this.getColumnKey()]; + }, + getAccessObj: function(index) { + var list = docObj == null ? null : docObj['Access[]'] + return list == null ? null : list[index]; + }, + getFunctionObj: function(index) { + var list = docObj == null ? null : docObj['Function[]'] + return list == null ? null : list[index]; + }, + getFunctionByName: function(functionName) { + var list = docObj == null ? null : docObj['Function[]'] + if (list != null) { + for (var i = 0; i < list.length; i++) { + var fun = this.getFunctionObj(i) + if (fun != null && fun.name == functionName) { + return fun + } + } + } + return null + }, + getRequestObj: function(index) { + var list = docObj == null ? null : docObj['Request[]'] + return list == null ? null : list[index]; + }, + getRequestBy: function(method, tag, version) { + var list = docObj == null ? null : docObj['Request[]'] + if (list != null) { + for (var i = 0; i < list.length; i++) { + var req = this.getRequestObj(i) + if (req != null && req.method == method && req.tag == tag && ( + version == null || req.version == version)) { + return req + } + } + } + return null + }, + getSchemaName: function(tableIndex) { + var table = this.getTableObj(tableIndex) + var sch = table == null ? null : table.table_shema + if (StringUtil.isNotEmpty(sch)) { + return sch + } + + var schemas = StringUtil.isEmpty(this.schema, true) ? null : StringUtil.split(this.schema) + return schemas == null || schemas.length != 1 ? null : this.schema + }, + getTableName: function(tableIndex) { + var table = this.getTableObj(tableIndex) + return table == null ? '' : table.table_name + }, + getColumnName: function(columnList, columnIndex) { + var column = this.getColumnObj(columnList, columnIndex) + return column == null ? '' : column.column_name + }, + getModelName: function(tableIndex) { + var table = this.getTableObj(tableIndex) + var table_name = table == null ? null : table.table_name + + var accessMap = table_name == null ? null : CodeUtil.accessMap + var access = accessMap == null ? null : accessMap[StringUtil.toLowerCase(table.table_schema) + '.' + StringUtil.toLowerCase(table_name)] + var alias = access == null ? null : access.alias + + return StringUtil.isEmpty(alias, true) ? StringUtil.firstCase(table_name, true) : alias + }, + getModelNameByTableName: function(tableName, schemaName) { + var table = this.getTableByName(tableName, schemaName) + var table_name = table == null ? null : table.table_name + + var accessMap = table_name == null ? null : CodeUtil.accessMap + var access = accessMap == null ? null : accessMap[StringUtil.toLowerCase(table.table_schema) + '.' + StringUtil.toLowerCase(table_name)] + var alias = access == null ? null : access.alias + + return StringUtil.isEmpty(alias, true) ? StringUtil.firstCase(table_name, true) : alias + }, + + onClickPost: function (tableIndex, modelName, schemaName, role) { + this.handleClickPost(this.getColumnList(tableIndex), modelName || this.getModelName(tableIndex), schemaName, role) + }, + handleClickPost: function (columnList, modelName, schemaName, role) { + if (columnList == null) { + columnList = this.getColumnListWithModelName(modelName, schemaName) + } + + var tbl = {} + + if (columnList != null && columnList.length > 0) { + for (var j = 0; j < columnList.length; j++) { + var column = this.getColumnObj(columnList, j) + var name = column == null ? null : column.column_name; + if (name == null || name.toLowerCase() == "id") { + continue; + } + + var val = column.column_default + if (val == null) { + var column_type = CodeUtil.getColumnType(column, this.database); + var type = CodeUtil.getType4Language(CodeUtil.LANGUAGE_JAVA_SCRIPT, column_type, false); + val = this.generateValue(type, name) + } + + tbl[name] = val + } + } + + if (StringUtil.isNotEmpty(schemaName, true)) { + tbl['@schema'] = schemaName + } + if (StringUtil.isNotEmpty(role, true)) { + tbl['@role'] = role + } + + var json = isSingle ? tbl : {} + if (! isSingle) { + json[modelName] = tbl + json.tag = modelName + json['@explain'] = true + } + + var s = JSON.stringify(json, null, ' ') + this.showCRUD('/post' + (isSingle ? '/' + modelName : ''), isSingle ? this.switchQuote(s) : s) + }, + + onClickGet: function (tableIndex, modelName, schemaName, role) { + this.handleClickGet(this.getColumnList(tableIndex), modelName || this.getModelName(tableIndex), schemaName, role) + }, + handleClickGet: function (columnList, modelName, schemaName, role) { + if (columnList == null) { + columnList = this.getColumnListWithModelName(modelName, schemaName) + } + + var idName = 'id' + var userIdName = 'userId' + var dateName = 'date' + var s = '' + if (columnList != null && columnList.length > 0) { + for (var j = 0; j < columnList.length; j++) { + var column = this.getColumnObj(columnList, j) + var name = column == null ? null : column.column_name; + if (name == null) { + continue; + } + + var ln = name.replaceAll('_', '').toLowerCase() + if (name.toLowerCase() == "id") { + idName = name + } + else if (ln == "userid") { + userIdName = name + } + else if (("date", "time", "createtime", "createat", "createat", "createdat").indexOf(ln) >= 0) { + dateName = name + } + + s += (j <= 0 ? '' : ',') + name + } + } + + var arrName = modelName + '[]' + + this.showCRUD('/get' + (isSingle ? '/' + arrName + '?total@=' + arrName + '/total' + '&info@=' + arrName + '/info' : ''), + isSingle ? `{ + '` + modelName + `': {` + (StringUtil.isEmpty(role, true) ? '' : ` + '@role': '` + role + "',") + (StringUtil.isEmpty(schemaName, true) ? '' : ` + '@schema': '` + schemaName + "',") + ` + '@column': '` + s + `', + '@order': '` + idName + `-', // '@group': '` + userIdName + `', + '` + idName + `>': 10, // '@column': '` + userIdName + `;avg(` + idName + `)', + '` + dateName + `{}': '!=null' // '@having': 'avg(` + idName + `)>10' + }, + 'count': 10, + 'page': 0, + 'query': 2 +}` : `{ + "` + modelName + `[]": { + "` + modelName + `": {` + (StringUtil.isEmpty(role, true) ? '' : ` + "@role": "` + role + '",') + (StringUtil.isEmpty(schemaName, true) ? '' : ` + "@schema": "` + schemaName + '",') + ` + "@column": "` + s + `", + "@order": "` + idName + `-", // "@group": "` + userIdName + `", + "` + idName + `>": 10, // "@column": "` + userIdName + `;avg(` + idName + `)", + "` + dateName + `{}": "!=null" // "@having": "avg(` + idName + `)>10" + }, + "count": 10, + "page": 0, + "query": 2 + }, + "total@": "` + modelName + `[]/total", + "info@": "` + modelName + `[]/info", + "@explain": true +}`) + }, + + onClickPut: function (tableIndex, modelName, schemaName, role) { + this.handleClickPut(this.getColumnList(tableIndex), modelName || this.getModelName(tableIndex), schemaName, role) + }, + handleClickPut: function (columnList, modelName, schemaName, role) { + if (columnList == null) { + columnList = this.getColumnListWithModelName(modelName, schemaName) + } + + var tbl = { + "id{}": [ + 1, + 2, + 4, + 12, + 470, + 82011, + 82012 + ] + } + + if (columnList != null && columnList.length > 0) { + for (var j = 0; j < columnList.length; j++) { + var column = this.getColumnObj(columnList, j) + var name = column == null ? null : column.column_name; + if (name == null) { + continue; + } + + var val = column.column_default + if (val == null) { + var column_type = CodeUtil.getColumnType(column, this.database); + var type = CodeUtil.getType4Language(CodeUtil.LANGUAGE_JAVA_SCRIPT, column_type, false); + val = this.generateValue(type, name) + } + + tbl[name] = val + } + } + + if (StringUtil.isNotEmpty(schemaName, true)) { + tbl['@schema'] = schemaName + } + if (StringUtil.isNotEmpty(role, true)) { + tbl['@role'] = role + } + + var json = isSingle ? tbl : {} + if (! isSingle) { + json[modelName] = tbl + json.tag = modelName + json['@explain'] = true + } + + var s = JSON.stringify(json, null, ' ') + this.showCRUD('/put' + (isSingle ? '/' + modelName + '[]' : ''), isSingle ? this.switchQuote(s) : s) + }, + + onClickDelete: function (tableIndex, modelName, schemaName, role) { + this.handleClickDelete(this.getColumnList(tableIndex), modelName || this.getModelName(tableIndex), schemaName, role) + }, + handleClickDelete: function (columnList, modelName, schemaName, role) { + this.handleClickGetsOrDelete(false, columnList, modelName, schemaName, role) + }, + handleClickGetsOrDelete: function (isGets, columnList, modelName, schemaName, role) { + if (columnList == null) { + columnList = this.getColumnListWithModelName(modelName, schemaName) + } + + var isSchemaEmpty = StringUtil.isEmpty(schemaName, true) + var isRoleEmpty = StringUtil.isEmpty(role, true) + + this.showCRUD((isGets ? '/gets' : '/delete') + (isSingle ? '/' + modelName : ''), + isSingle ? `{ + 'id': 1` + (StringUtil.isEmpty(schemaName, true) ? '' : `, + '@schema': '` + schemaName + "'") + (StringUtil.isEmpty(role, true) ? '' : `, + '@role': '` + role + "'") + ` +}` : `{ + "` + modelName + `": { + "id": 1` + (StringUtil.isEmpty(schemaName, true) ? '' : `, + "@schema": "` + schemaName + '"') + (StringUtil.isEmpty(role, true) ? '' : `, + "@role": "` + role + '"') + ` + }, + "tag": "` + modelName + `", + "@explain": true +}`) + }, + + onClickGets: function (tableIndex, modelName, schemaName, role) { + this.handleClickGets(this.getColumnList(tableIndex), modelName || this.getModelName(tableIndex), schemaName, role) + }, + handleClickGets: function (columnList, modelName, schemaName, role) { + this.handleClickGetsOrDelete(true, columnList, modelName, schemaName, role) + }, + + onClickHead: function (tableIndex, modelName, schemaName, role) { + this.handleClickHead(this.getColumnList(tableIndex), modelName || this.getModelName(tableIndex), schemaName, role) + }, + handleClickHead: function (columnList, modelName, schemaName, role) { + this.handleClickHeadOrHeads(false, columnList, modelName, schemaName, role) + }, + onClickHeads: function (tableIndex, modelName, schemaName, role) { + this.handleClickHeads(this.getColumnList(tableIndex), modelName || this.getModelName(tableIndex), schemaName, role) + }, + handleClickHeads: function (columnList, modelName, schemaName, role) { + this.handleClickHeadOrHeads(true, columnList, modelName, schemaName, role) + }, + handleClickHeadOrHeads: function (isHeads, columnList, modelName, schemaName, role) { + if (columnList == null) { + columnList = this.getColumnListWithModelName(modelName, schemaName) + } + + this.showCRUD((isHeads ? '/heads' : '/head') + (isSingle ? '/' + modelName : ''), + isSingle ? `{ + 'userId': 82001` + (StringUtil.isEmpty(schemaName, true) ? '' : `, + '@schema': '` + schemaName + "'") + (StringUtil.isEmpty(role, true) ? '' : `, + '@role': '` + role + "'") + ` +}` : `{ + "` + modelName + `": { + "userId": 82001` + (StringUtil.isEmpty(schemaName, true) ? '' : `, + "@schema": "` + schemaName + '"') + (StringUtil.isEmpty(role, true) ? '' : `, + "@role": "` + role + '"') + ` + },` + (isHeads ? ` + "tag": "` + modelName + `",` : '') + ` + "@explain": true +}`) + }, + + onClickColumn: function (tableIndex, modelName, columnIndex, columnName) { + modelName = modelName || this.getModelName(tableIndex) + if (StringUtil.isEmpty(columnName, true)) { + var columnList = this.getColumnList(tableIndex) + columnName = columnName || this.getColumnName(columnList, columnIndex) + } + + var arrName = modelName + '[]' + + this.showCRUD('/get' + (isSingle ? '/' + arrName + '?total@=' + arrName + '/total' + '&info@=' + arrName + '/info' : ''), + isSingle ? `{ + '` + modelName + `': { + '@column': 'DISTINCT ` + columnName + `', + '@order': '` + columnName + `+', // '@order': 'id-' + }, + 'count': 0, + 'page': 0, + 'query': 2 +}` : `{ + "` + modelName + '-' + columnName + `[]": { + "` + modelName + `": { + "@column": "DISTINCT ` + columnName + `", + "@order": "` + columnName + `+", // "@order": "id-" + }, + "count": 0, + "page": 0, + "query": 2 + }, + "total@": "` + modelName + '-' + columnName + `[]/total", + "info@": "` + modelName + '-' + columnName + `[]/info", + "@explain": true +}`) + }, + + onClickAccess: function (index, model, schema, method, role) { + if (StringUtil.isEmpty(model, true) || StringUtil.isEmpty(schema, true) || StringUtil.isEmpty(method, true) || StringUtil.isEmpty(role, true)) { + // var access = this.getAccessObj(index) + // model = this.getModelNameByTableName() + } + + method = StringUtil.toLowerCase(method) + switch (method) { + case 'get': + this.handleClickGet(null, model, schema, role) + break + case 'gets': + this.handleClickGets(null, model, schema, role) + break + case 'head': + this.handleClickHead(null, model, schema, role) + break + case 'heads': + this.handleClickHeads(null, model, schema, role) + break + case 'post': + this.handleClickPost(null, model, schema, role) + break + case 'put': + this.handleClickPut(null, model, schema, role) + break + case 'delete': + this.handleClickDelete(null, model, schema, role) + break + } + }, + + onClickFunction: function (index, demo) { + if (StringUtil.isEmpty(demo, true)) { + var fun = this.getFunctionObj(index) + demo = JSON.stringify(fun.demo, null, ' ') // this.getFunctionDemo(fun) + } + + this.showCRUD('/get', isSingle ? this.switchQuote(demo) : demo) + }, + + onClickRequest: function (index, method, tag, version, jsonStr) { + if (StringUtil.isEmpty(method, true) || StringUtil.isEmpty(tag, true) || StringUtil.isEmpty(jsonStr, true)) { + var fun = this.getRequestObj(index) + method = fun.method || 'get' + tag = fun.tag + version = fun.version + if (StringUtil.isEmpty(jsonStr, true)) { + var json = this.getStructure(true, null, fun.structure, method, tag, version, isSingle, true) + jsonStr = json == null ? '' : JSON.stringify(json, null, ' ') + } + } + + vInput.value = '' + this.showCRUD( + '/' + StringUtil.toLowerCase(method) + (isSingle ? '/' + tag + (version == null ? '' : '?version=' + version) : '') + , isSingle ? this.switchQuote(jsonStr) : jsonStr + ) + }, + + showCRUD: function (url, json) { + if (url == this.getBranchUrl()) { + var origin = this.getRequest(vInput.value) + if (origin != null && Object.keys(origin).length > 0) { + json = this.getRequest(json) + if (json == null || Object.keys(json).length <= 0 + || (json instanceof Array != true && json instanceof Object)) { + json = Object.assign(origin, json) + json = JSON.stringify(json, null, ' ') + } + } + } + + this.method = REQUEST_TYPE_POST + this.type = REQUEST_TYPE_JSON + this.showUrl(false, url) + this.urlComment = '' + vInput.value = StringUtil.trim(json) + this.showTestCase(false, this.isLocalShow) + this.onChange(false) + }, + + getTotalAndCoverageString: function(typeName, count, total) { + count = count || 0 + total = total || 0 + if (count > total) { + count = total + } + return '共 ' + total + ' 个' + (typeName || '子项') + ',覆盖 ' + count + ' 个' + + (Number.isInteger(total) != true || total <= 0 ? '' : ',覆盖率 ' + (100*count/total).toFixed(2) + '%'); + }, + getRealClassTotal: function(data, packageName) { + if (data == null) { + return 0; + } + var packageList = data.packageList + var len = packageList == null ? 0 : packageList.length + if (StringUtil.isEmpty(packageName, true)) { + return Math.max(data.classTotal || 0, len); + } + if (len <= 0) { + return 0; + } + for (var i in packageList) { + var pkgObj = packageList[i] + if (pkgObj != null && pkgObj['package'] == packageName) { + return Math.max(pkgObj.classTotal || 0, (pkgObj.classList || []).length || 0); + } + } + return 0; + }, + getRealMethodTotal: function(data, packageName, className) { + if (data == null) { + return 0; + } + var packageList = data.packageList + var len = packageList == null ? 0 : packageList.length + if (StringUtil.isEmpty(packageName, true)) { + if (StringUtil.isEmpty(className, true)) { + return Math.max(data.classTotal || 0, len); + } + return 0; + } + if (len <= 0) { + return 0; + } + for (var i in packageList) { + var pkgObj = packageList[i] + if (pkgObj != null && pkgObj['package'] == packageName) { + var classList = pkgObj.classList + var len2 = classList == null ? 0 : classList.length + if (StringUtil.isEmpty(className, true)) { + return Math.max(data.methodTotal || 0, len2); + } + if (len2 <= 0) { + return 0; + } + for (var j in classList) { + var clsObj = classList[j] + if (clsObj != null && clsObj['class'] == className) { + return Math.max(clsObj.methodTotal || 0, (clsObj.methodList || []).length || 0); + } + } + } + } + return 0; + }, + toDoubleJSON: function (json, defaultValue) { + if (StringUtil.isEmpty(json)) { + return defaultValue == null ? '{}' : JSON.stringify(defaultValue) + } + else if (json.indexOf("'") >= 0) { + json = json.replace(/'/g, '"'); + } + return json; + }, + + switchQuote: function (before) { + if (before == null) { + return before; + } + + var newBefore = ''; + for (var i = 0; i < before.length; i++) { + var chr = before.substring(i, i + 1); // .charAt(i); + if (chr == '"') { + newBefore += "'"; // chr = "'"; + } + else if (chr == "'") { + newBefore += '"'; // chr = '"'; + } + else { + newBefore += chr; + } + } + return newBefore; + }, + + /**转为Markdown格式 + * @param s + * @return {*} + */ + toMD: function (s) { + if (s == null) { + return '' + } + + if (s instanceof Object) { + s = JSON.stringify(s) + } + + if (typeof s != 'string') { + return StringUtil.get(s) + } + + //无效 + s = s.replace(/\|/g, '\|'); + s = s.replace(/\n/g, '
'); + // s = s.replace(/ /g, ' '); + return s; + }, + + /**处理请求结构 + */ + getStructure: function (isDemo, name, obj, method, tag, version, unwrap) { + if (obj == null) { + return null; + } + + if (DEBUG) { + log('getStructure tag = ' + tag + '; version = ' + version + '; isDemo = ' + isDemo + '; obj = \n' + format(obj)); + } + + method = method == null ? 'GET' : method.trim().toUpperCase() + + var isArrayKey = tag != null && tag.endsWith('[]'); + var isMultiArrayKey = isArrayKey && tag.endsWith(":[]") + var isTableKey = false + var tableName = tag + if (tag != null) { //补全省略的Table + var key = isArrayKey ? tag.substring(0, tag.length - (tag.endsWith(':[]') ? 3 : 2)) : tag; + if (this.isTableKey(key)) { + isTableKey = true + tableName = key + // name = key + } + } + + var newObj = {} + + if (obj instanceof Array) { + for (var i = 0; i < obj.length; i++) { + newObj[i] = this.getStructure(isDemo, i + '', obj[i], method); + } + } + else if (obj instanceof Object) { + var refuseKeys = null + + for (var k in obj) { + if (k == null || k == '' || k == 'INSERT' || k == 'REMOVE' || k == 'REPLACE' || k == 'UPDATE') { + continue; + } + + var v = obj[k]; + if (v == null) { + continue; + } + + var nk = k; + + if (isDemo) { + nk = null + if (k == 'REFUSE') { + refuseKeys = StringUtil.isEmpty(v, true) ? null : StringUtil.split(v) + } + else if (k == 'MUST' || k == 'UNIQUE') { + var ks = StringUtil.isEmpty(v, true) ? null : StringUtil.split(v) + if (ks != null) { + for (var j = 0; j < ks.length; j++) { + var kj = ks[j] + newObj[kj] = this.generateValue(CodeUtil.getType4Language(CodeUtil.LANGUAGE_JAVA_SCRIPT + , CodeUtil.getColumnType(this.getColumnTypeWithModelName(kj, tableName), this.database)), kj) + } + } + } + else if (k == 'VERIFY') { // 后续会用数据字典填空 + nk = null + // for (var kj in v) { // 还得把功能符去掉 {} $ > <= ... + // newObj[kj] = this.generateValue(CodeUtil.getType4Language(CodeUtil.LANGUAGE_JAVA_SCRIPT + // , CodeUtil.getColumnType(this.getColumnTypeWithModelName(kj, tableName), this.database)), kj) + // } + } + else if (k == 'TYPE') { + for (var kj in v) { + newObj[kj] = this.generateValue(CodeUtil.getType4Language(CodeUtil.LANGUAGE_JAVA_SCRIPT, v[kj]), kj) + } + } + else { + nk = k + } + } + else { + if (k == 'REFUSE') { + nk = '不能传'; + } else if (k == 'MUST') { + nk = '必须传'; + } else if (k == 'UNIQUE') { + nk = '不重复'; + } else if (k == 'VERIFY') { + nk = '满足条件'; + } else if (k == 'TYPE') { + nk = '满足类型'; + } else { + nk = k; + } + } + + if (nk != null) { + if (v instanceof Object && (v instanceof Array == false)) { + v = this.getStructure(isDemo, nk, v, method); + } + else if (v === '!') { + v = '非必须传的字段'; + } + + if (v != null) { + newObj[nk] = v; + } + } + } + + var isPutOrDel = method == 'PUT' || method == 'DELETE' + var isPostOrPutMulti = isMultiArrayKey && (method == 'POST' || method == 'PUT') + var isGetOrGetsMulti = isArrayKey && (method == 'GET' || method == 'GETS') + var mustHasKey = tableName + (isPostOrPutMulti || isGetOrGetsMulti ? '[]' : '') + var isFulfill = name == null && isTableKey && (isPostOrPutMulti || newObj[mustHasKey] == null) + if (isFulfill && (isPostOrPutMulti || ! isArrayKey)) { + name = tableName + } + + if (isDemo && this.isTableKey(name) && (refuseKeys == null || refuseKeys.indexOf('!') < 0)) { + var columnList = this.getColumnListWithModelName(name) + if (columnList != null) { + var s = '' + for (var i = 0; i < columnList.length; i++) { + var column = this.getColumnObj(columnList, i) + var cn = column == null ? null : column.column_name + + if (cn == null || cn.startsWith('_') || (method == 'POST' && cn.toLowerCase() == 'id') || (refuseKeys != null && refuseKeys.indexOf(cn) >= 0)) { + continue + } + + if (method == 'GET' || method == 'GETS') { + s += (i <= 0 ? '' : ',') + cn + } else { + var nv = this.generateValue(CodeUtil.getType4Language(CodeUtil.LANGUAGE_JAVA_SCRIPT, CodeUtil.getColumnType(column, this.database)), cn) + if (nv != null || newObj[cn] == null) { + newObj[cn] = nv + } + } + } + } + + if (method == 'GET' || method == 'GETS') { + newObj['@column'] = s + } + } + + if (isFulfill) { //补全省略的Table + var realObj = {}; + if (isArrayKey) { //自动为 tag = Comment:[] 的 { ... } 新增键值对 "Comment[]":[] 为 { "Comment[]":[], ... } + if (isPostOrPutMulti) { + var reqObj = isDemo ? this.getRequestBy(method, tableName) : null + var childStruct = reqObj == null ? null : reqObj.structure + if (childStruct != null && childStruct[tableName] != null) { + childStruct = childStruct[tableName] + } + + newObj = childStruct == null ? newObj : Object.assign( + this.getStructure(isDemo, null, childStruct, reqObj.method, null, null, true), newObj + ) + + delete newObj[mustHasKey] + realObj[mustHasKey] = isDemo ? [newObj, newObj] : [newObj]; + } + else if (unwrap || isPutOrDel) { + if (isDemo && isPutOrDel && newObj['id{}'] == null) { + newObj['id{}'] = [ + 1, + 2, + 4, + 12, + 470, + 82011, + 82012 + ] + } + realObj[mustHasKey] = newObj; + } + else { + realObj[mustHasKey] = newObj + } + newObj = realObj + } + else if (unwrap != true) { //自动为 tag = Comment 的 { ... } 包一层为 { "Comment": { ... } } + if (isPutOrDel) { + if (isDemo && newObj.id == null) { + newObj.id = 1 + } + } + + realObj[mustHasKey] = newObj; + newObj = realObj; + } + } + + } + + if (tag != null && unwrap != true) { + newObj.tag = tag; //补全tag + } + if (version != null && unwrap != true) { + newObj.version = version; + } + + if (DEBUG) { + log('getStructure return newObj = \n' + format(newObj)); + } + + return newObj; + }, + + /**判断key是否为表名,用CodeUtil里的同名函数会在Safari上报undefined + * @param key + * @return + */ + isTableKey: function (key) { + log('isTableKey typeof key = ' + (typeof key)); + if (key == null) { + return false; + } + return /^[A-Z][A-Za-z0-9_]*$/.test(key); + }, + + log: function (msg) { + // this.log('Main. ' + msg) + }, + + getDoc4TestCase: function () { + var list = this.remotes || [] + var doc = '' + var item + for (var i = 0; i < list.length; i ++) { + item = list[i] == null ? null : list[i].Document + if (item == null || item.name == null) { + continue + } + doc += '\n\n#### ' + (item.version > 0 ? 'V' + item.version : 'V*') + ' ' + item.name + ' ' + item.url + doc += '\n```json\n' + item.request + '\n```\n' + } + return doc + }, + + enableCross: function (enable) { + this.isCrossEnabled = enable + this.crossProcess = enable ? '交叉账号:已开启' : '交叉账号:已关闭' + this.saveCache(this.server, 'isCrossEnabled', enable) + }, + + enableML: function (enable) { + this.isMLEnabled = enable + this.testProcess = enable ? '机器学习:已开启' : '机器学习:已关闭' + this.saveCache(this.server, 'isMLEnabled', enable) + // if (enable) { + // this.isEnvCompareEnabled = false + // this.saveCache(this.server, 'isEnvCompareEnabled', this.isEnvCompareEnabled) + // } + + this.resetTestCount(this.currentAccountIndex) + + this.remotes = null + this.showTestCase(true, false) + }, + + resetTestCount: function (accountIndex, isRandom, isSub) { + if (isRandom) { + this.resetCount(isSub ? this.currentRandomItem : this.currentRemoteItem, isRandom, isSub, accountIndex) + return + } + + if (accountIndex == -1) { + this.logoutSummary = this.resetCount(this.logoutSummary, false, false, accountIndex) + } + else if (accountIndex >= 0 && accountIndex < (this.accounts || []).length) { + var accountItem = this.resetCount(this.getSummary(accountIndex), false, false, accountIndex) + this.accounts[accountIndex] = accountItem + } + }, + + onClickTestScript: function () { + var logger = console.log + console.log = function(msg) { + logger(msg) + vOutput.value = StringUtil.get(msg) + } + + this.view = 'output' + vOutput.value = '' + + try { + var isTest = true + var isPre = this.isPreScript + + var isAdminOperation = false + var type = this.type + var url = this.getUrl() + var req = this.getRequest(vInput.value, {}) + var header = this.getHeader(vHeader.value) + var callback = null + + var data = isPre ? undefined : (this.jsoncon == null ? null : parseJSON(this.jsoncon)) + var res = isPre ? undefined : { + data: data + } + var err = isPre ? undefined : null + + var sendRequest = function (isAdminOperation, method, type, url, req, header, callback) { + App.request(isAdminOperation, method, type, url, req, header, callback) + } + + eval(vScript.value); + } + catch(e) { + console.log(e); + console.log = logger + + this.view = 'error' + this.error = { + msg: '执行脚本报错:\n' + e.message + } + } + + console.log = logger + }, + + /**参数注入,动态替换键值对 + * @param show + */ + onClickTestRandom: function (isCross, callback) { + this.isRandomTest = true + this.isStatisticsEnabled = true + this.testRandom(! this.isRandomListShow && ! this.isRandomSubListShow, this.isRandomListShow, this.isRandomSubListShow, null, isCross, true, callback) + }, + testRandom: function (show, testList, testSubList, limit, isCross, isManual, callback) { + this.isRandomEditable = false + if (testList != true && testSubList != true) { + this.testRandomProcess = '' + this.testRandomWithText(show, callback) + } + else { + var baseUrl = StringUtil.trim(this.getBaseUrl()) + if (baseUrl == '') { + if (callback) { + callback(true, 0, '请先输入有效的URL!') + } else { + alert('请先输入有效的URL!') + } + return + } + //开放测试 + // if (baseUrl.indexOf('/apijson.cn') >= 0 || baseUrl.indexOf('/39.108.143.172') >= 0) { + // alert('请把URL改成你自己的!\n例如 http://localhost:8080') + // return + // } + // if (baseUrl.indexOf('/apijson.org') >= 0) { + // alert('请把URL改成 http://apijson.cn:8080 或 你自己的!\n例如 http://localhost:8080') + // return + // } + + const list = (testSubList ? App.randomSubs : App.randoms) || [] + var allCount = 0 // list.length + for (let i = 0; i < list.length; i++) { + const item = list[i] + const random = item == null ? null : item.Random + allCount += (random == null || random.count == null ? 0 : random.count) + } + + App.randomAllCount = allCount + App.randomDoneCount = 0 + + if (allCount <= 0) { + if (callback) { + callback(true, 0, '请先获取随机配置\n点击[查看列表]按钮') + } else { + alert('请先获取随机配置\n点击[查看列表]按钮') + } + return + } + + this.testRandomProcess = '正在测试: ' + 0 + '/' + allCount + var summaryItem = (testSubList ? this.currentRandomItem : this.currentRemoteItem) || {} + if (isManual) { + this.resetCount(summaryItem, true, testSubList, this.currentAccountIndex) + } else { + summaryItem.whiteCount = 0 + summaryItem.greenCount = 0 + summaryItem.blueCount = 0 + summaryItem.orangeCount = 0 + summaryItem.redCount = 0 + } + summaryItem.totalCount = allCount + + var methods = this.methods + var method = this.isShowMethod() ? this.method : null + var type = this.type + var json = this.getRequest(vInput.value, {}) + var url = this.getUrl() + var header = this.getHeader(vHeader.value) + + ORDER_MAP = {} //重置 + + for (var i = 0; i < (limit != null ? limit : list.length); i ++) { //limit限制子项测试个数 + const item = list[i] + const random = item == null ? null : item.Random + if (random == null || random.name == null) { + App.randomDoneCount ++ + continue + } + if (DEBUG) { + this.log('test random = ' + JSON.stringify(random, null, ' ')) + } + + const index = i + + const itemAllCount = random.count || 0 + // allCount += (itemAllCount - 1) // 为什么减 1?因为初始化时 var allCount = list.length + + // UI 往上顶出屏幕 + // try { + // document.getElementById((testSubList ? 'randomSubItem' : 'randomItem') + index).scrollIntoView() + // } catch (e) { + // console.log(e) + // } + + App[testSubList ? 'currentRandomSubIndex' : 'currentRandomIndex'] = index + try { + this.testRandomSingle(show, false, itemAllCount > 1 && ! testSubList, item, method, type, url, json, header, isCross, isManual, function (url, res, err) { + var data = null + if (res instanceof Object) { // 可能通过 onTestResponse 返回的是 callback(true, 18, null) + data = res.data + try { + App.onResponse(url, res, err) + if (DEBUG) { + App.log('test App.request >> res.data = ' + (data == null ? 'null' : JSON.stringify(data, null, ' '))) + } + } catch (e) { + App.log('test App.request >> } catch (e) {\n' + e.message) + } + } + + App.compareResponse(res, allCount, list, index, item, data, true, App.currentAccountIndex, false, err, null, isCross, callback) + return true + }) + } + catch (e) { + this.compareResponse(null, allCount, list, index, item, data, true, this.currentAccountIndex, false, e, null, isCross, callback) + } + } + } + }, + /**参数注入,动态替换键值对 + * @param show + * @param callback + */ + testRandomSingle: function (show, testList, testSubList, item, method, type, url, json, header, isCross, isManual, callback) { + item = item || {} + + // 保证能调用自定义函数等 <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< + const scripts = this.scripts || {} + const globalScript = (scripts.global || {})[0] || {} + const accountScript = (scripts.account || {})[this.getCurrentAccountId() || 0] || {} + const caseScript = (scripts.case || {})[this.getCurrentDocumentId() || 0] || {} + + var preScript = '' + + var globalPreScript = StringUtil.trim((globalScript.pre || {}).script) + if (StringUtil.isNotEmpty(globalPreScript, true)) { + preScript += globalPreScript + '\n\n' + } + + var accountPreScript = StringUtil.trim((accountScript.pre || {}).script) + if (StringUtil.isNotEmpty(accountPreScript, true)) { + preScript += accountPreScript + '\n\n' + } + + var casePreScript = StringUtil.trim((caseScript.pre || {}).script) + if (StringUtil.isNotEmpty(casePreScript, true)) { + preScript += casePreScript + '\n\n' + } + // 保证能调用自定义函数等 >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> + + + var random = item.Random = item.Random || {} + var subs = item['[]'] || [] + var existCount = subs.length + subs = existCount <= 0 ? subs : parseJSON(JSON.stringify(subs)) + + var count = random.count || 0 + var respCount = 0; + + for (var i = 0; i < count; i ++) { + // var constConfig = i < existCount ? ((subs[i] || {}).Random || {}).config : this.getRandomConstConfig(random.config, random.id) //第1遍,把 key : expression 改为 key : value + // var constJson = this.getRandomJSON(parseJSON(JSON.stringify(json)), constConfig, random.id) //第2遍,用新的 random config 来修改原 json + + const which = i; + var rawConfig = testSubList && i < existCount ? ((subs[i] || {}).Random || {}).config : random.config + + var cb = function (url, res, err) { + if (callback != null) { + callback(url, res, err, random) + } + else { + App.onResponse(url, res, err) + } + }; + + try { + this.parseRandom( + parseJSON(JSON.stringify(json)), rawConfig, random.id + , ! testSubList, testSubList && i >= existCount, testSubList && i >= existCount + , function (randomName, constConfig, constJson) { + + respCount ++; + + if (testSubList) { //在原来已上传的基础上,生成新的 + if (which >= existCount) { + //异步导致顺序错乱 subs.push({ + subs[which] = { + Random: { + id: -i - 1, //表示未上传 + toId: random.id == null ? 1 : random.id, // 1 为了没选择测试用例时避免用 toId 判断子项错误 + userId: random.userId, + documentId: random.documentId, + count: 1, + name: randomName || 'Temp ' + i, + config: constConfig + }, + //不再需要,因为子项里前面一部分就是已上传的,而且这样更准确,交互更直观 + // TestRecord: { //解决子项始终没有对比标准 + // id: 0, //不允许子项撤回 tr.id, //表示未上传 + // userId: random.userId, + // documentId: random.documentId, + // testAccountId: tr.testAccountId, + // randomId: -i - 1, + // response: tr.response, + // standard: tr.standard, + // date: tr.date, + // compare: tr.compare + // } + // }) + }; + } + } + else { + if (show == true) { + vInput.value = JSON.stringify(constJson, null, ' '); + App.send(false, cb, caseScript); + } + else { + App.request(false, method, type, url, constJson, header, cb, caseScript); + } + } + + if (testSubList && respCount >= count) { // && which >= count - 1) { + if (App.currentRandomItem == null) { + App.currentRandomItem = {} + } + App.randomSubs = App.currentRandomItem.subs = subs + App.getCurrentRandomSummary().summaryType = 'total' // App.onClickSummary('total', true) + if (App.isRandomListShow == true) { + App.resetCount(item, true, false, App.currentAccountIndex) + item.subs = subs + } + if (respCount == count) { + App.testRandom(false, false, true, count, isCross, isManual, callback) + } + } + + }, + preScript + ); + } + catch (e) { + cb(url, {}, e) + } + + } // for + + }, + + resetParentCount: function (item, cri, isRandom, accountIndex) { + if (cri == null || item == null) { + return + } + + if (item.whiteCount == null || item.whiteCount < 0) { + item.whiteCount = 0 + } + if (item.greenCount == null || item.greenCount < 0) { + item.greenCount = 0 + } + if (item.blueCount == null || item.blueCount < 0) { + item.blueCount = 0 + } + if (item.orangeCount == null || item.orangeCount < 0) { + item.orangeCount = 0 + } + if (item.redCount == null || item.redCount < 0) { + item.redCount = 0 + } + + if (cri.whiteCount == null) { + cri.whiteCount = 0 + } + if (cri.greenCount == null) { + cri.greenCount = 0 + } + if (cri.blueCount == null) { + cri.blueCount = 0 + } + if (cri.orangeCount == null) { + cri.orangeCount = 0 + } + if (cri.redCount == null) { + cri.redCount = 0 + } + + cri.whiteCount -= item.whiteCount + cri.greenCount -= item.greenCount + cri.blueCount -= item.blueCount + cri.orangeCount -= item.orangeCount + cri.redCount -= item.redCount + // cri.totalCount -= item.totalCount + + // var isTestCase = isRandom != true && item.Document != null && accountIndex < (this.accounts || []).length + + if (cri.whiteCount < 0) { + cri.whiteCount = 0 // isTestCase ? item.whiteCount : 0 + } + if (cri.greenCount < 0) { + cri.greenCount = 0 // isTestCase ? item.greenCount : 0 + } + if (cri.blueCount < 0) { + cri.blueCount = 0 // isTestCase ? item.blueCount : 0 + } + if (cri.orangeCount < 0) { + cri.orangeCount = 0 // isTestCase ? item.orangeCount : 0 + } + if (cri.redCount < 0) { + cri.redCount = 0 // isTestCase ? item.redCount : 0 + } + // if (cri.totalCount < 0) { + // cri.totalCount = 0 + // } + + cri.totalCount = cri.whiteCount + cri.greenCount + cri.blueCount + cri.orangeCount + cri.redCount + }, + resetCount: function (item, isRandom, isSub, accountIndex) { + if (item == null) { + this.log('resetCount randomItem == null >> return') + return item + } + + if (isRandom) { + var cri = isSub ? this.currentRandomItem : this.currentRemoteItem + if (cri != null && (cri != item || cri.id != item.id)) { + this.resetParentCount(item, cri, isRandom, accountIndex) + } + + cri = this.currentRandomItem + if (isSub && cri != null && (cri != item || cri.id != item.id)) { + this.resetParentCount(item, cri, isRandom, accountIndex) + } + } + + var accounts = this.accounts + var num = accounts == null ? 0 : accounts.length + if (accountIndex < num) { // && accountIndex != this.currentAccountIndex) { + var cs = this.getSummary(accountIndex) + if (cs != null && (cs != item || cs.id != item.id)) { + this.resetParentCount(item, cs, false, accountIndex) + } + } + + if (accountIndex < num) { + var als = this.getAllSummary() + // 不知道为啥总是不对 + // if (als != null && (als != item || als.id != item.id)) { + // this.resetParentCount(item, als, false, accountIndex) + // } + + // 改用以下方式 + var whiteCount = 0 + var greenCount = 0 + var blueCount = 0 + var orangeCount = 0 + var redCount = 0 + // var totalCount = 0 + for (var i = -1; i < num; i++) { + var cs = this.getSummary(i) + if (cs == null) { + continue + } + + if (cs.whiteCount == null || cs.whiteCount < 0) { + cs.whiteCount = 0 + } + if (cs.greenCount == null || cs.greenCount < 0) { + cs.greenCount = 0 + } + if (cs.blueCount == null || cs.blueCount < 0) { + cs.blueCount = 0 + } + if (cs.orangeCount == null || cs.orangeCount < 0) { + cs.orangeCount = 0 + } + if (cs.redCount == null || cs.redCount < 0) { + cs.redCount = 0 + } + if (cs.totalCount == null || cs.totalCount < 0) { + cs.totalCount = cs.whiteCount + cs.greenCount + cs.blueCount + cs.orangeCount + cs.redCount + } + + whiteCount += cs.whiteCount + greenCount += cs.greenCount + blueCount += cs.blueCount + orangeCount += cs.orangeCount + redCount += cs.redCount + // totalCount += cs.totalCount + } + + als.whiteCount = whiteCount + als.greenCount = greenCount + als.blueCount = blueCount + als.orangeCount = orangeCount + als.redCount = redCount + als.totalCount = whiteCount + greenCount + blueCount + orangeCount + redCount // totalCount + } + + // var isTop = isRandom != true && item.Document == null && item.Random == null && accountIndex < (this.accounts || []).length + + item.whiteCount = 0 + item.greenCount = 0 + item.blueCount = 0 + item.orangeCount = 0 + item.redCount = 0 + item.totalCount = 0 + return item + }, + + /**参数注入,动态替换键值对 + * @param show + * @param callback + */ + testRandomWithText: function (show, callback) { + try { + var count = this.testRandomCount || 0; + this.isRandomSubListShow = count > 1; + this.currentRandomItem = { + Random: { + toId: 0, // ((this.currentRandomItem || {}).Random || {}).id || 0, + userId: (this.User || {}).id, + count: count, + name: this.randomTestTitle, + config: vRandom.value + }, + totalCount: count + } + + this.testRandomSingle(show, false, this.isRandomSubListShow, this.currentRandomItem, + this.isShowMethod() ? this.method : null, this.type, this.getUrl() + , this.getRequest(vInput.value, {}), this.getHeader(vHeader.value), false, false, callback + ) + } + catch (e) { + log(e) + vSend.disabled = true + + this.view = 'error' + this.error = { + msg: e.message + } + + this.isRandomShow = true + vRandom.select() + } + }, + + /** + * 与 getRandomJSON 合并,返回一个 + * { + * name: 'long 1, long 2', // 自动按 type0 value0, type1, value1 格式 + * config: {}, //const config + * json: {} //const json + * } + */ + /**参数注入,动态替换键值对 + * @param show + * @param callback + */ + parseRandom: function (json, config, randomId, generateJSON, generateConfig, generateName, callback, preScript, ctx) { + var lines = config == null ? null : config.trim().split('\n') + if (lines == null || lines.length <= 0) { + // return null; + callback('', '', json); + return + } + json = json || {}; + + baseUrl = this.getBaseUrl(); + + var reqCount = lines.length; //有无效的行 lines.length; //等待次数 + var respCount = 0; + + randomId = randomId || 0; + var randomNameKeys = [] + var constConfigLines = [] //TODO 改为 [{ "rawPath": "User/id", "replacePath": "User/id@", "replaceValue": "RANDOM_INT(1, 10)", "isExpression": true }] ? + + // alert('< json = ' + JSON.stringify(json, null, ' ')) + + for (let i = 0; i < reqCount; i ++) { + const which = i; + const lineItem = lines[i] || ''; + + // remove comment // 解决整体 trim 后第一行 // 被当成正常的 key 路径而不是注释 + const commentIndex = StringUtil.trim(lineItem).startsWith('//') ? 0 : lineItem.lastIndexOf(' //'); // -1; // eval 本身支持注释 eval('1 // test') = 1 lineItem.indexOf(' //'); + const line = commentIndex < 0 ? lineItem : lineItem.substring(0, commentIndex).trim(); + + if (line.length <= 0) { + respCount ++; + if (i >= lines.length - 1 && respCount >= reqCount) { + var cn = randomNameKeys.join(', ') + if (cn.length > 50) { + cn = cn.substring(0, 30) + ' ..' + randomNameKeys.length + '.. ' + cn.substring(cn.length - 12) + } + callback(cn, constConfigLines.join('\n'), json); + } + continue; + } + + // path User/id key id@ + const index = line.indexOf(': '); //APIJSON Table:alias 前面不会有空格 //致后面就接 { 'a': 1} 报错 Unexpected token ':' lastIndexOf(': '); // indexOf(': '); 可能会有 Comment:to + const p_k = line.substring(0, index); + const bi = -1; //没必要支持,用 before: undefined, after: .. 同样支持替换,反而这样导致不兼容包含空格的 key p_k.indexOf(' '); + const path = decodeURI(bi < 0 ? p_k : p_k.substring(0, bi)); // User/id + + const pathKeys = path.split('/') + if (pathKeys == null || pathKeys.length <= 0) { + throw new Error('参数注入 第 ' + (i + 1) + ' 行格式错误!\n字符 ' + path + ' 不符合 JSON 路径的格式 key0/key1/../targetKey !' + + '\n每个随机变量配置都必须按照\n key0/key1/../targetKey replaceKey: value // 注释\n的格式!' + + '\n注意冒号 ": " 左边 0 空格,右边 1 空格!其中 replaceKey 可省略。' + + '\nkey: {} 中最外层常量对象 {} 必须用括号包裹为 ({}),也就是 key: ({}) 这种格式!' + + '\nkey: 多行代码 必须用 function f() { var a = 1; return a; } f() 这种一行代码格式!'); + } + + const lastKeyInPath = pathKeys[pathKeys.length - 1] + const customizeKey = bi > 0; + const key = customizeKey ? p_k.substring(bi + 1) : lastKeyInPath; + if (key == null || key.trim().length <= 0) { + throw new Error('参数注入 第 ' + (i + 1) + ' 行格式错误!\n字符 ' + key + ' 不是合法的 JSON key!' + + '\n每个随机变量配置都必须按照\n key0/key1/../targetKey replaceKey: value // 注释\n的格式!' + + '\n注意冒号 ": " 左边 0 空格,右边 1 空格!其中 replaceKey 可省略。' + + '\nkey: {} 中最外层常量对象 {} 必须用括号包裹为 ({}),也就是 key: ({}) 这种格式!' + + '\nkey: 多行代码 必须用 function f() { var a = 1; return a; } f() 这种一行代码格式!'); + } + + // value RANDOM_DB + const value = line.substring(index + ': '.length); + + var invoke = function (val, which, p_k, pathKeys, key, lastKeyInPath) { + try { + if (generateConfig) { + var configVal; + if (val instanceof Object) { + configVal = JSON.stringify(val); + } + else if (typeof val == 'string') { + configVal = '"' + val + '"'; + } + else { + configVal = val; + } + constConfigLines[which] = p_k + ': ' + configVal; + } + + if (generateName) { + var s = val == undefined ? 'undefined' : (typeof val == 'string' && val != '' ? val : JSON.stringify(val)); // null 可以正常转为字符串 + if (val instanceof Array) { + valStr = val.length <= 1 ? s : '[' + val.length + ' .. ' + s.substring(1, s.length - 1) + ']'; + } + else if (val instanceof Object) { + var kl = Object.keys(val).length; + valStr = kl <= 1 ? s : '{' + kl + ' .. ' + s.substring(1, s.length - 1) + '}'; + } + else { + valStr = s; + } + + if (valStr.length > 13) { + valStr = valStr.substring(0, 5) + '..'; + } + randomNameKeys[which] = valStr; + } + + if (generateJSON) { + //先按照单行简单实现 + //替换 JSON 里的键值对 key: value + var parent = json; + var current = null; + for (var j = 0; j < pathKeys.length - 1; j ++) { + current = parent[pathKeys[j]] + if (current == null) { + current = parent[pathKeys[j]] = {} + } + if (parent instanceof Object == false) { + throw new Error('参数注入 第 ' + (i + 1) + ' 行格式错误!路径 ' + path + ' 中' + + ' pathKeys[' + j + '] = ' + pathKeys[j] + ' 在实际请求 JSON 内对应的值不是对象 {} 或 数组 [] !'); + } + parent = current; + } + + if (current == null) { + current = json; + } + // alert('< current = ' + JSON.stringify(current, null, ' ')) + + if (key != lastKeyInPath || current.hasOwnProperty(key) == false) { + delete current[lastKeyInPath]; + } + + current[key] = val; + } + + } + catch (e) { + throw new Error('第 ' + (which + 1) + ' 行随机配置 key: value 后的 value 不合法! \nerr: ' + e.message) + } + + respCount ++; + if (respCount >= reqCount) { + var cn = randomNameKeys.join(', ') + if (cn.length > 50) { + cn = cn.substring(0, 30) + ' ..' + randomNameKeys.length + '.. ' + cn.substring(cn.length - 12) + } + callback(cn, constConfigLines.join('\n'), json); + } + }; + + + const start = value.indexOf('('); + const end = value.lastIndexOf(')'); + + var request4Db = function(tableName, which, p_k, pathKeys, key, lastKeyInPath, isRandom, isDesc, step) { + // const tableName = JSONResponse.getTableName(pathKeys[pathKeys.length - 2]); + vOutput.value = 'requesting value for ' + tableName + '/' + key + ' from database...'; + + const args = StringUtil.split(value.substring(start + 1, end)) || []; + var min = StringUtil.trim(args[0]); + var max = StringUtil.trim(args[1]); + var table = StringUtil.trim(args[2]) || ''; + var column = StringUtil.trim(args[3]) || ''; + + min = min == '' || min == 'null' || min == 'undefined' ? null : +min; + max = max == '' || max == 'null' || max == 'undefined' ? null : +max; + + if ((table.startsWith('"') && table.endsWith('"')) || (table.startsWith("'") && table.endsWith("'"))) { + table = table.substring(1, table.length - 1); + } + if ((column.startsWith('"') && column.endsWith('"')) || (column.startsWith("'") && column.endsWith("'"))) { + column = column.substring(1, column.length - 1); + } + + const finalTableName = StringUtil.isEmpty(table, true) ? tableName : table; + const finalColumnName = StringUtil.isEmpty(column, true) ? lastKeyInPath : column; + + const tableReq = { + '@column': isRandom ? finalColumnName : ('DISTINCT ' + finalColumnName), + '@order': isRandom ? 'rand()' : (finalColumnName + (isDesc ? '-' : '+')) + }; + tableReq[finalColumnName + '>='] = min; + tableReq[finalColumnName + '<='] = max; + + const req = {}; + const listName = isRandom ? null : finalTableName + '-' + finalColumnName + '[]'; + const orderIndex = isRandom ? null : getOrderIndex(randomId, line, null) + + if (isRandom) { + req[finalTableName] = tableReq; + } + else { + // 从数据库获取时不考虑边界,不会在越界后自动循环 + var listReq = { + count: 1, // count <= 100 ? count : 0, + page: (step*orderIndex) % 100 //暂时先这样,APIJSON 应该改为 count*page <= 10000 //FIXME 上限 100 怎么破,lastKeyInPath 未必是 id + }; + listReq[finalTableName] = tableReq; + req[listName] = listReq; + } + + // reqCount ++; + App.request(true, REQUEST_TYPE_POST, REQUEST_TYPE_JSON, baseUrl + '/get', req, {}, function (url, res, err) { + // respCount ++; + try { + App.onResponse(url, res, err) + } catch (e) {} + + var data = (res || {}).data || {} + if (JSONResponse.isSuccess(data) != true) { + respCount = -reqCount; + vOutput.value = '参数注入 为第 ' + (which + 1) + ' 行\n ' + p_k + ' \n获取数据库数据 异常:\n' + data.msg; + alert(StringUtil.get(vOutput.value)); + return + // throw new Error('参数注入 为\n ' + tableName + '/' + key + ' \n获取数据库数据 异常:\n' + data.msg) + } + + if (isRandom) { + invoke((data[finalTableName] || {})[finalColumnName], which, p_k, pathKeys, key, lastKeyInPath); + } + else { + var val = (data[listName] || [])[0]; + //越界,重新获取 + if (val == null && orderIndex > 0 && ORDER_MAP[randomId] != null && ORDER_MAP[randomId][line] != null) { + ORDER_MAP[randomId][line] = null; //重置,避免还是在原来基础上叠加 + request4Db(JSONResponse.getTableName(pathKeys[pathKeys.length - 2]), which, p_k, pathKeys, key, lastKeyInPath, false, isDesc, step); + } + else { + invoke(val, which, p_k, pathKeys, key, lastKeyInPath); + } + } + + // var list = data[listName] || []; + //代码变化会导致缓存失效,而且不好判断,数据量大会导致页面很卡 ORDER_MAP[randomId][line].list = list; + // + // if (step == null) { + // invoke('randomIn(' + list.join() + ')'); + // } + // else { + // invoke('orderIn(' + isDesc + ', ' + step*getOrderIndex(randomId, line, list.length) + list.join() + ')'); + // } + + }) + }; + + + + //支持 1, "a" 这种原始值 + // if (start < 0 || end <= start) { //(1) 表示原始值 start*end <= 0 || start >= end) { + // throw new Error('参数注入 第 ' + (i + 1) + ' 行格式错误!字符 ' + value + ' 不是合法的随机函数!'); + // } + + var toEval = value; + if (start > 0 && end > start) { + + var funWithOrder = value.substring(0, start); + var splitIndex = funWithOrder.indexOf('+'); + + var isDesc = false; + if (splitIndex < 0) { // -(1+2) 这种是表达式,不能作为函数 splitIndex <= 0) { + splitIndex = funWithOrder.indexOf('-'); + isDesc = splitIndex > 0; + } + + var fun = splitIndex < 0 ? funWithOrder : funWithOrder.substring(0, splitIndex); + + if (fun.startsWith('ORDER_') && /^[_A-Z]+$/g.test(fun)) { // [ORDER_DB, ORDER_IN, ORDER_INT].indexOf(fun) >= 0) { //顺序函数 + var stepStr = splitIndex < 0 ? null : funWithOrder.substring(splitIndex + 1, funWithOrder.length); + var step = stepStr == null || stepStr.length <= 0 ? 1 : +stepStr; //都会自动忽略空格 Number(stepStr); //Number.parseInt(stepStr); //+stepStr; + + if (Number.isSafeInteger(step) != true || step <= 0 + || (StringUtil.isEmpty(stepStr, false) != true && StringUtil.isNumber(stepStr) != true) + ) { + throw new Error('参数注入 第 ' + (i + 1) + ' 行格式错误!路径 ' + path + ' 中字符 ' + stepStr + ' 不符合跨步 step 格式!' + + '\n顺序整数 和 顺序取值 可以通过以下格式配置 升降序 和 跨步:' + + '\n ORDER_DB+step(arg0, arg1...)\n ORDER_DB-step(arg0, arg1...)' + + '\n ORDER_INT+step(arg0, arg1...)\n ORDER_INT-step(arg0, arg1...)' + + '\n ORDER_IN+step(start, end)\n ORDER_IN-step(start, end)' + + '\n其中:\n + 为升序,后面没有 step 时可省略;\n - 为降序,不可省略;' + '\n step 为跨步值,类型为 正整数,默认为 1,可省略。' + + '\n+,-,step 前后都不能有空格等其它字符!'); + } + + if (fun == ORDER_DB) { + request4Db(JSONResponse.getTableName(pathKeys[pathKeys.length - 2]), which, p_k, pathKeys, key, lastKeyInPath, false, isDesc, step); //request4Db(key + (isDesc ? '-' : '+'), step); + continue; + } + + var args = StringUtil.split(value.substring(start + 1, end)) + + toEval = (fun == ORDER_IN ? 'orderIn' : (fun == ORDER_INT ? 'orderInt' : (fun == ORDER_BAD_BOOL ? 'orderBadBool' : (fun == ORDER_BAD_NUM + ? 'orderBadNum' : (fun == ORDER_BAD_STR ? 'orderBadStr' : (fun == ORDER_BAD_ARR ? 'orderBadArr' : (fun == ORDER_BAD_OBJ ? 'orderBadObj' : 'orderBad'))))))) + + '(' + (fun == ORDER_BAD ? 'BADS, ' : '') + isDesc + ', ' + getOrderIndex( + randomId, line + , (fun == ORDER_INT || args == null ? 0 : args.length) + + (fun == ORDER_BAD_BOOL ? BAD_BOOLS.length : (fun == ORDER_BAD_NUM ? BAD_NUMS.length : (fun == ORDER_BAD_STR + ? BAD_STRS.length : (fun == ORDER_BAD_ARR ? BAD_ARRS.length : (fun == ORDER_BAD_OBJ ? BAD_OBJS.length : (fun == ORDER_BAD ? BADS.length : 0)))))) + , step + ) + ', ' + value.substring(start + 1); + } + else { //随机函数 + if (fun == PRE_REQ) { + toEval = 'get4Path(((ctx || {}).pre || {}).req, ' + (value == 'PRE_REQ()' ? JSON.stringify(path) : '') + value.substring(start + 1); + } + else if (fun == PRE_ARG) { + toEval = 'get4Path(((ctx || {}).pre || {}).arg, ' + (value == 'PRE_ARG()' ? JSON.stringify(path) : '') + value.substring(start + 1); + } + else if (fun == PRE_RES) { + toEval = 'get4Path(((ctx || {}).pre || {}).res, ' + (value == 'PRE_RES()' ? JSON.stringify(path) : '') + value.substring(start + 1); + } + else if (fun == PRE_DATA) { + toEval = 'get4Path(((ctx || {}).pre || {}).data, ' + (value == 'PRE_DATA()' ? JSON.stringify(path) : '') + value.substring(start + 1); + } + else if (fun == CTX_GET) { + toEval = 'get4Path((ctx || {}).ctx, ' + (value == 'CTX_GET()' ? JSON.stringify(path) : '') + value.substring(start + 1); + } + else if (fun == CTX_PUT) { + var as = StringUtil.split(value.substring(start + 1, end), ', ') || [] + if (as.length >= 2) { + as[1] = 'get4Path(((ctx || {}).pre || {}).data, ' + StringUtil.trim(as[1]) + ')' + } + toEval = 'put4Path((ctx || {}).ctx, ' + (value == 'CTX_PUT()' ? JSON.stringify(path) : '') + as.join(', ') + value.substring(end); + } + else if (fun == CUR_REQ) { + toEval = 'get4Path(((ctx || {}).cur || {}).req, ' + (value == 'CUR_REQ()' ? JSON.stringify(path) : '') + value.substring(start + 1); + } + else if (fun == CUR_ARG) { + toEval = 'get4Path(((ctx || {}).cur || {}).arg, ' + (value == 'CUR_ARG()' ? JSON.stringify(path) : '') + value.substring(start + 1); + } + else if (fun == CUR_RES) { + toEval = 'get4Path(((ctx || {}).cur || {}).res, ' + (value == 'CUR_RES()' ? JSON.stringify(path) : '') + value.substring(start + 1); + } + else if (fun == CUR_DATA) { + toEval = 'get4Path(((ctx || {}).cur || {}).data, ' + (value == 'CUR_DATA()' ? JSON.stringify(path) : '') + value.substring(start + 1); + } + else { + fun = funWithOrder; //还原,其它函数不支持 升降序和跨步! + + if (fun == RANDOM_DB) { + request4Db(JSONResponse.getTableName(pathKeys[pathKeys.length - 2]), which, p_k, pathKeys, key, lastKeyInPath, true); //'random()'); + continue; + } + + if (fun == RANDOM_IN) { + toEval = 'randomIn' + value.substring(start); + } + else if (fun == RANDOM_INT) { + toEval = 'randomInt' + value.substring(start); + } + else if (fun == RANDOM_NUM) { + toEval = 'randomNum' + value.substring(start); + } + else if (fun == RANDOM_STR) { + toEval = 'randomStr' + value.substring(start); + } + else if (fun == RANDOM_BAD) { + toEval = 'randomBad' + value.substring(start); + } + else if (fun == RANDOM_BAD_BOOL) { + toEval = 'randomBadBool' + value.substring(start); + } + else if (fun == RANDOM_BAD_NUM) { + toEval = 'randomBadNum' + value.substring(start); + } + else if (fun == RANDOM_BAD_STR) { + toEval = 'randomBadStr' + value.substring(start); + } + else if (fun == RANDOM_BAD_ARR) { + toEval = 'randomBadArr' + value.substring(start); + } + else if (fun == RANDOM_BAD_OBJ) { + toEval = 'randomBadObj' + value.substring(start); + } + } + + } + + } + + var isInject = true; + var isPre = false; // 避免执行副作用代码 true; + var isTest = false; + var method = null; + var type = null; + var url = null; + var req = null; + var header = null; + var res = {}; + var data = res.data; + var err = null; + invoke(eval(StringUtil.trim(preScript) + '\n;\n(' + toEval + ')'), which, p_k, pathKeys, key, lastKeyInPath); + + // alert('> current = ' + JSON.stringify(current, null, ' ')) + } + + }, + + onClickSend: function () { + this.isRandomTest = false + this.send(false) + }, + onClickTest: function (callback) { + this.isRandomTest = false + this.isStatisticsEnabled = true + this.reportId = new Date().getTime() + this.caseShowType = 1 + + // 自动往右移动,避免断言结果遮挡太多接口名称、URL + var split_obj = IS_BROWSER ? $('.splitx') : null + var split_obj_left = split_obj == null ? 0 : parseInt(split_obj.css('left')) + var width = split_obj_left <= 0 ? 0 : (window.innerWidth || 1280) + if (width > 0 && Math.abs(split_obj_left - 0.4*width) <= 5) { + // 构造事件比较麻烦,即便用 JQuery 也是 + // split_obj[0].dispatchEvent(new TouchEvent('', new class implements TouchEventInit{ + // clientX: 0.6*width + // }())) + + var left_ele = $('.side-left') + var right_ele = $('.side-right') + + // var left_width = left_ele.width() + // var right_width = right_ele.width() + // + // var right_left = parseInt(right_ele.css('left')) + + split_obj.css('left', 0.55*width) + left_ele.width(0.55*width); + right_ele.width(0.45*width).css('left', 0.55*width); + // right_ele.width(right_width - left_ele.width() + left_width).css('left', right_left + left_ele.width() - left_width); + } + + var isCross = this.isCrossEnabled + var accountIndex = isCross ? -1 : this.currentAccountIndex + + var accounts = this.accounts + var num = accounts == null ? 0 : accounts.length + + var cases = this.isChainShow ? this.chainGroups : this.remotes + var total = cases == null ? 0 : cases.length + + var als = this.getAllSummary() + als = this.resetCount(als, false, false, num) + als.totalCount = isCross ? (num + 1)*total : total + + if (isCross) { + for (var i = -1; i < num; i++) { + var cs = this.getSummary(i) + cs = this.resetCount(cs, false, false, i) + cs.totalCount = total + } + } else { + var cs = this.getSummary(accountIndex) + cs = this.resetCount(cs, false, false, accountIndex) + cs.totalCount = total + } + + this.coverage = {} + this.request(false, REQUEST_TYPE_POST, REQUEST_TYPE_JSON, this.getBaseUrl() + '/coverage/start', {}, {}, function (url, res, err) { + try { + App.onResponse(url, res, err) + if (DEBUG) { + App.log('test App.request >> res.data = ' + JSON.stringify(res.data, null, ' ')) + } + } catch (e) { + App.log('test App.request >> } catch (e) {\n' + e.message) + } + App.test(false, accountIndex, isCross, callback) + }) + }, + /**回归测试 + * 原理: + 1.遍历所有上传过的测试用例(URL+请求JSON) + 2.逐个发送请求 + 3.对比同一用例的先后两次请求结果,如果不一致,就在列表中标记对应的用例(× 蓝黄红色下载(点击下载两个文件) √)。 + 4.如果这次请求结果正确,就把请求结果保存到和公司开发环境服务器的APIJSON Server,并取消标记 + + compare: 新的请求与上次请求的对比结果 + 0-相同,无颜色; + 1-对象新增字段或数组新增值,绿色; + 2-值改变,蓝色; + 3-对象缺少字段/整数变小数,黄色; + 4-code/值类型 改变,红色; + */ + test: function (isRandom, accountIndex, isCross, callback) { + var accounts = this.accounts || [] + // alert('test accountIndex = ' + accountIndex) + if (accountIndex == null) { + accountIndex = -1 //isCross ? -1 : 0 + } + + if (isCross) { + var isCrossDone = accountIndex >= accounts.length + this.crossProcess = isCrossDone ? '交叉账号:已开启' : ('交叉账号: ' + (accountIndex + 1) + '/' + accounts.length) + if (isCrossDone) { + this.testProcess = (this.isMLEnabled ? '机器学习:已开启' : '机器学习:已关闭') + this.testRandomProcess = '' + if (accountIndex == accounts.length) { + this.currentAccountIndex = accounts.length - 1 // -1 导致最后右侧显示空对象 + + if (callback) { + callback('已完成账号交叉测试: 退出登录状态 和 每个账号登录状态') + } else { + alert('已完成账号交叉测试: 退出登录状态 和 每个账号登录状态') + } + + if (callback != this.autoTestCallback && typeof this.autoTestCallback == 'function') { + this.autoTestCallback('已完成账号交叉测试: 退出登录状态 和 每个账号登录状态') + } + } + return + } + + if (callback != this.autoTestCallback && typeof this.autoTestCallback == 'function') { + this.autoTestCallback('正在账号交叉测试 ') + } + } + + var baseUrl = StringUtil.trim(this.getBaseUrl()) + if (baseUrl == '') { + if (callback) { + callback('请先输入有效的URL!') + } else { + alert('请先输入有效的URL!') + } + return + } + //开放测试 + // if (baseUrl.indexOf('/apijson.cn') >= 0 || baseUrl.indexOf('/39.108.143.172') >= 0) { + // alert('请把URL改成你自己的!\n例如 http://localhost:8080') + // return + // } + // if (baseUrl.indexOf('/apijson.org') >= 0) { + // alert('请把URL改成 http://apijson.cn:8080 或 你自己的!\n例如 http://localhost:8080') + // return + // } + + const list = (isRandom ? this.randoms : (this.isChainShow ? ( + this.isChainGroupShow() ? this.chainGroups : [this.chainGroups[this.currentChainGroupIndex]] + ) : this.remotes) + ) || [] + + var allCount = list.length + App.doneCount = 0 + App.deepAllCount = 0 + App.randomDoneCount = 0 + if (isRandom != true) { + App.allCount = allCount + } + + // var cs = this.getSummary(accountIndex) + // if (isCross && cs.totalCount <= 0) { + // cs = this.resetCount(cs) + // cs.totalCount = allCount + // } + // + // var als = this.getAllSummary() + // if (als.totalCount <= 0) { + // als = this.resetCount(als) + // als.totalCount = allCount + // } + + if (allCount <= 0) { + if (callback) { + callback('请先获取测试用例文档\n点击[查看用例列表]图标按钮') + } else { + alert('请先获取测试用例文档\n点击[查看用例列表]图标按钮') + } + return + } + + if (this.isChainShow) { + for (var i = 0; i < list.length; i++) { + var item = list[i] + var stepList = item['[]'] || [] + var stepCount = stepList == null ? 0 : stepList.length + allCount += (stepCount - 1) + } + } + + if (isCross) { + if (accountIndex < 0 && accounts[this.currentAccountIndex] != null) { //退出登录已登录的账号 + accounts[this.currentAccountIndex].isLoggedIn = true + } + var index = accountIndex < 0 ? this.currentAccountIndex : accountIndex + this.onClickAccount(index, accounts[index], function (isLoggedIn, index, err) { + // if (index >= 0 && isLoggedIn == false) { + // alert('第 ' + index + ' 个账号登录失败!' + (err == null ? '' : err.message)) + // App.test(isRandom, accountIndex + 1) + // return + // } + App.showTestCase(true, false) + App.startTest(list, allCount, isRandom, accountIndex, isCross, callback) + }) + } + else { + this.startTest(list, allCount, isRandom, accountIndex, isCross, callback) + } + }, + + startTestChain: function (list, allCount, index, item, prev, ctx, isRandom, accountIndex, isCross, callback) { + if (list == null || index == null || index >= list.length) { + return + } + + var cur = item = item || {} + cur.index = index +// item.pre = pre // list[index - 1] + + var doc = item.Document || {} + var method = cur.method = doc.method + var type = cur.type = doc.type + var url = cur.url = doc.url + var json = cur.arg = this.getRequest(doc.request, {}) + var req = cur.req = { + method: method, + url: url, + header: doc.header, + data: json + } + var header = cur.header = doc.header +// +// this.parseRandom(json, rawConfig, random.id, true, false, function (randomName, constConfig, constJson) { + this.startTestSingle(list, allCount, index, item, isRandom, accountIndex, isCross, callback + , function(res, allCount, list, index, response, cmp, isRandom, accountIndex, justRecoverTest, isCross) { + + res = res || {} + var config = res.config || {} + cur.arg = App.getRequest(config.data || config.params, {}) + cur.req = { + method: method, + url: config.url, + header: config.header, + arg: cur.arg + } + cur.status = res.status + cur.statusText = res.statusText + cur.res = res + cur.data = res.data + App.startTestChain(list, allCount, index + 1, list[index + 1], item, ctx, isRandom, accountIndex, isCross, callback) + }, ctx) + return true +// }) + }, + + isFullAssert: true, + toTestDocIndexes: [], + + startTest: function (list, allCount, isRandom, accountIndex, isCross, callback) { + this.testProcess = '正在测试: ' + 0 + '/' + allCount + this.toTestDocIndexes = [] + + if (callback != this.autoTestCallback && typeof this.autoTestCallback == 'function') { + this.autoTestCallback(this.testProcess) + } + + var isChainShow = this.isChainShow + baseUrl = StringUtil.trim(this.getBaseUrl()) + + for (var i = 0; i < list.length; i++) { + const item = list[i] + if (isChainShow) { +// this.showTestCase(true, false, function(url, res, err) { + var stepList = item['[]'] // res.data['[]'] + var stepCount = stepList == null ? 0 : stepList.length + this.startTestChain(stepList, stepCount, 0, stepCount <= 0 ? null : stepList[0], null, { Chain: item }, isRandom, accountIndex, isCross, callback) +// }, item) + continue + } + + this.startTestSingle(list, allCount, i, item, isRandom, accountIndex, isCross, callback) + } + }, + + startTestSingle: function (list, allCount, index, item, isRandom, accountIndex, isCross, callback, singleCallback, ctx) { + this.isFullAssert = false + try { + const isMLEnabled = this.isMLEnabled + const standardKey = isMLEnabled != true ? 'response' : 'standard' + + const otherEnv = this.otherEnv; + const otherBaseUrl = this.isEnvCompareEnabled && StringUtil.isNotEmpty(otherEnv, true) ? this.getBaseUrl(otherEnv) : null + const isEnvCompare = StringUtil.isNotEmpty(otherBaseUrl, true) // 对比自己也行,看看前后两次是否幂等 && otherBaseUrl != baseUrl + + const document = item == null ? null : item.Document + if (document == null || document.name == null) { + if (isRandom) { + App.randomDoneCount ++ + } else { + App.doneCount ++ + } + + return + } + if (document.url == '/login' || document.url == '/logout') { //login会导致登录用户改变为默认的但UI上还显示原来的,单独测试OWNER权限时能通过很困惑 + this.log('startTestSingle document.url == "/login" || document.url == "/logout" >> continue') + if (isRandom) { + App.randomDoneCount ++ + } else { + App.doneCount ++ + } + + return + } + + if (DEBUG) { + this.log('startTestSingle document = ' + JSON.stringify(document, null, ' ')) + } + + var hdr = null + try { + hdr = this.getHeader(document.header) + } catch (e) { + this.log('startTestSingle try { header = this.getHeader(document.header) } catch (e) { \n' + e.message) + } + const header = hdr + + const caseScript = { + pre: (item['Script:pre'] || {}), + post: (item['Script:post'] || {}) + } + + const method = document.method + const type = document.type + const req = this.getRequest(document.request, null, true) + const otherEnvUrl = isEnvCompare ? (otherBaseUrl + document.url) : null + const curEnvUrl = baseUrl + document.url + const cur = item + const pre = list[index - 1] || {} // item.pre = item.pre || list[index - 1] || {} +// const ctx = item.ctx = item.ctx || {} + + var random = item.Random || {} + this.parseRandom(req, random.config, random.id, true, false, false, function(randomName, constConfig, constJson) { + App.request(false, method, type, isEnvCompare ? otherEnvUrl : curEnvUrl, constJson, header, function (url, res, err) { + try { + App.onResponse(url, res, err) + if (DEBUG) { + App.log('startTestSingle App.request >> res.data = ' + JSON.stringify(res.data, null, ' ')) + } + } catch (e) { + App.log('startTestSingle App.request >> } catch (e) {\n' + e.message) + } + + if (isEnvCompare != true) { + App.compareResponse(res, allCount, list, index, item, res.data, isRandom, accountIndex, false, err, null, isCross, callback, singleCallback) + return + } + + const otherErr = err + const rsp = App.removeDebugInfo(res.data) + const rspStr = JSON.stringify(rsp) + const tr = item.TestRecord || {} + if (isMLEnabled) { + tr.response = rspStr + } + tr[standardKey] = isMLEnabled ? JSON.stringify(JSONResponse.updateFullStandard({}, rsp, isMLEnabled)) : rspStr // res.data + item.TestRecord = tr + + App.request(false, method, type, curEnvUrl, constJson, header, function (url, res, err) { + try { + App.onResponse(url, res, err) + if (DEBUG) { + App.log('startTestSingle App.request >> res.data = ' + JSON.stringify(res.data, null, ' ')) + } + } catch (e) { + App.log('startTestSingle App.request >> } catch (e) {\n' + e.message) + } + + App.compareResponse(res, allCount, list, index, item, res.data, isRandom, accountIndex, false, err || otherErr, null, isCross, callback, singleCallback) + }, caseScript) + + }, caseScript) + }, (caseScript.pre || {}).script, { + list: list, + allCount: allCount, + index: index, + cur: cur, + pre: pre, + ctx: ctx + }) + } + catch(e) { + this.compareResponse(null, allCount, list, index, item, null, isRandom, accountIndex, false, e, null, isCross, callback, singleCallback) + } + + }, + + compareResponse: function (res, allCount, list, index, item, response, isRandom, accountIndex, justRecoverTest, err, ignoreTrend, isCross, callback, singleCallback) { + var it = item || {} //请求异步 + var cri = this.currentRemoteItem || {} //请求异步 + var d = (isRandom ? cri.Document : it.Document) || {} //请求异步 + var r = isRandom ? it.Random : null //请求异步 + var tr = it.TestRecord || {} //请求异步 + + var bdt = tr.duration || 0 + it.durationBeforeShowStr = bdt <= 0 ? '' : (bdt < 1000 ? bdt + 'ms' : (bdt < 1000*60 ? (bdt/1000).toFixed(1) + 's' : (bdt <= 1000*60*60 ? (bdt/1000/60).toFixed(1) + 'm' : '>1h'))) + try { + var durationInfo = response == null ? null : response['time:start|duration|end|parse|sql'] + it.durationInfo = durationInfo + if (durationInfo == null) { + throw new Error("response['time:start|duration|end|parse|sql'] is null!"); + } + + var di = durationInfo.substring(durationInfo.indexOf('|') + 1) + it.duration = di.substring(0, di.indexOf('|') || di.length) || 0 + var dt = + it.duration + it.duration = dt + it.durationShowStr = dt <= 0 ? '' : (dt < 1000 ? dt + 'ms' : (dt < 1000*60 ? (dt/1000).toFixed(1) + 's' : (dt <= 1000*60*60 ? (dt/1000/60).toFixed(1) + 'm' : '>1h'))) + var min = tr.minDuration == null || tr.minDuration <= 0 ? 20 : tr.minDuration + var max = tr.maxDuration == null || tr.maxDuration <= 0 ? 200 : tr.maxDuration + it.durationColor = dt < min ? 'green' : (dt > 2*max ? 'red' : (dt > max + min ? 'orange' : (dt > max ? 'blue' : 'black'))) + it.durationHint = dt < min ? '很快:比以往 [' + min + 'ms, ' + max + 'ms] 最快还更快' : (dt > 2*max ? '非常慢:比以往 [' + min + 'ms, ' + max + 'ms] 最慢的两倍还更慢' + : (dt > max + min ? '比较慢:比以往 [' + min + 'ms, ' + max + 'ms] 最快与最慢之和(平均值两倍)还更慢' + : (dt > max ? '有点慢:比以往 [' + min + 'ms, ' + max + 'ms] 最慢还更慢' : '正常:在以往 [' + min + 'ms, ' + max + 'ms] 最快和最慢之间'))) + } + catch (e) { + // log(e) + it.durationShowStr = it.durationShowStr || it.duration + it.durationHint = it.durationHint || '最外层缺少字段 "time:start|duration|end|parse|sql",无法对比耗时' + } + + if (err != null) { + var status = res == null ? null : res.status + var rsp = (err.response || {}).data || {} + tr.compare = { + code: JSONResponse.COMPARE_ERROR, //请求出错 + msg: StringUtil.trim(StringUtil.trim(rsp.code) + ' ' + StringUtil.trim(rsp.message)) || err.message || '请求出错!', + path: (status != null && status != 200 ? status + ' ' : '') + err.message + } + } + else { + var isML = this.isMLEnabled + var standardKey = isML ? 'standard' : 'response' + var stdd = tr[standardKey] + if (isRandom) { + stdd = stdd || ((this.currentRemoteItem || {}).TestRecord || {})[standardKey] + } + + var standard = typeof stdd != 'string' ? stdd : (StringUtil.isEmpty(stdd, true) ? null : parseJSON(stdd)) + tr.compare = JSONResponse.compareResponse(res, standard, this.removeDebugInfo(response) || {}, '', isML, null, null, ignoreTrend) || {} + tr.compare.duration = it.durationHint + } + + this.onTestResponse(res, allCount, list, index, it, d, r, tr, response, tr.compare || {}, isRandom, accountIndex, justRecoverTest, isCross, callback, singleCallback); + }, + + onTestResponse: function(res, allCount, list, index, it, d, r, tr, response, cmp, isRandom, accountIndex, justRecoverTest, isCross, callback, singleCallback) { + this.isFullAssert = false + tr = tr || {} + cmp = cmp || {} + tr.compare = cmp + var status = res == null ? null : res.status + + it = it || {} + var p = cmp.path + it.compareType = cmp.code; + it.compareMessage = (StringUtil.isEmpty(p, true) ? '' : p + ' ') + (cmp.msg || '查看结果') + switch (it.compareType) { + case JSONResponse.COMPARE_ERROR: + it.compareColor = 'red' + it.hintMessage = (status != null && status != 200 ? status + ' ' : '') + '请求出错!' + break; + case JSONResponse.COMPARE_NO_STANDARD: + it.compareColor = 'green' + it.hintMessage = '确认正确后点击[对的,纠正]' + break; + case JSONResponse.COMPARE_KEY_MORE: + case JSONResponse.COMPARE_VALUE_MORE: + case JSONResponse.COMPARE_EQUAL_EXCEPTION: + it.compareColor = 'green' + it.hintMessage = '新增字段/新增值 等' + break; + case JSONResponse.COMPARE_LENGTH_CHANGE: + case JSONResponse.COMPARE_VALUE_CHANGE: + it.compareColor = 'blue' + it.hintMessage = '值改变 等' + break; + case JSONResponse.COMPARE_VALUE_EMPTY: + case JSONResponse.COMPARE_KEY_LESS: + it.compareColor = 'orange' + it.hintMessage = '缺少字段/整数变小数 等' + break; + case JSONResponse.COMPARE_FORMAT_CHANGE: + case JSONResponse.COMPARE_NUMBER_TYPE_CHANGE: + case JSONResponse.COMPARE_TYPE_CHANGE: + case JSONResponse.COMPARE_CODE_CHANGE: + case JSONResponse.COMPARE_THROW_CHANGE: + var code = response == null ? null : response[JSONResponse.KEY_CODE] + it.compareColor = 'red' + it.hintMessage = (code != null && code != JSONResponse.CODE_SUCCESS + ? code + ' ' : (status != null && status != 200 ? status + ' ' : '')) + '状态码/异常/值类型 改变等' + break; + default: + it.compareColor = 'white' + it.hintMessage = '结果正确' + break; + } + + if (isRandom) { + r = r || {} + it.Random = r + + this.updateToRandomSummary(it, 1, accountIndex) + } + else { + d = d || {} + it.Document = d + + this.updateToSummary(it, 1, accountIndex) + } + it.TestRecord = tr + + Vue.set(list, index, it) + + if (justRecoverTest) { + // callback(isRandom, allCount) + return + } + + if (isRandom) { + App.randomDoneCount ++ + } else { + App.doneCount ++ + } + + var doneCount = isRandom ? App.randomDoneCount : App.doneCount + if (isRandom) { + this.testRandomProcess = doneCount >= allCount ? '' : ('正在测试: ' + doneCount + '/' + allCount) + } else { + this.testProcess = doneCount >= allCount ? (this.isMLEnabled ? '机器学习:已开启' : '机器学习:已关闭') : '正在测试: ' + doneCount + '/' + allCount + } + + if (doneCount < allCount && callback != this.autoTestCallback && typeof this.autoTestCallback == 'function') { + this.autoTestCallback('正在测试') + } + + this.log('doneCount = ' + doneCount + '; d.name = ' + (isRandom ? r.name : d.name) + '; it.compareType = ' + it.compareType) + + var documentId = isRandom ? r.documentId : d.id + if (this.tests == null) { + this.tests = {} + } + + var accountIndexStr = String(accountIndex) + if (this.tests[accountIndexStr] == null) { + this.tests[accountIndexStr] = {} + } + + var tests = this.tests[accountIndexStr] || {} + var t = tests[documentId] + if (t == null) { + t = tests[documentId] = {} + } + t[isRandom ? (r.id > 0 ? r.id : (r.toId + '' + r.id)) : 0] = response + + if (isRandom != true && it.compareColor != 'red') { + if (this.toTestDocIndexes == null) { + this.toTestDocIndexes = [] + } + this.toTestDocIndexes.push(index); + } + + this.tests[accountIndexStr] = tests + if (DEBUG) { + this.log('tests = ' + JSON.stringify(tests, null, ' ')) + } + // this.showTestCase(true) + + if (singleCallback != null && singleCallback(res, allCount, list, index, response, cmp, isRandom, accountIndex, justRecoverTest, isCross)) { + return + } + + if (doneCount >= allCount) { // 导致不继续测试 App.doneCount == allCount) { + if (callback != null && callback(isRandom, allCount)) { + return + } + + // alert('onTestResponse accountIndex = ' + accountIndex) + + const deepAllCount = this.toTestDocIndexes == null ? 0 : this.toTestDocIndexes.length + App.deepAllCount = deepAllCount + if (isRandom != true && deepAllCount > 0 && ! this.isChainShow) { // 自动给非 红色 报错的接口跑参数注入 + App.deepDoneCount = 0; + this.startRandomTest4Doc(list, this.toTestDocIndexes, 0, deepAllCount, accountIndex, isCross) + } else if (isCross && doneCount == allCount && accountIndex <= this.accounts.length) { + this.test(false, accountIndex + 1, isCross) + } + } + }, + + startRandomTest4Doc: function (list, indexes, position, deepAllCount, accountIndex, isCross) { + const accInd = accountIndex + var callback = function (isRandom, allCount, msg) { + log("startRandomTest4Doc callback isRandom = " + isRandom + "; allCount = " + allCount + "; msg = " + msg) + if (App.randomDoneCount < App.randomAllCount) { + return true + } + + App.randomDoneCount = App.randomAllCount + + App.deepDoneCount ++ + const deepDoneCount = App.deepDoneCount + const autoTestCallback = App.autoTestCallback + + App.testProcess = deepDoneCount < deepAllCount ? ('正在深度测试: ' + deepDoneCount + '/' + deepAllCount) : (App.isMLEnabled ? '机器学习:已开启' : '机器学习:已关闭') + App.testRandomProcess = App.randomDoneCount >= App.randomAllCount ? '' : ('正在测试: ' + App.randomDoneCount + '/' + App.randomAllCount) + + setTimeout(function () { + App.isTestCaseShow = true + + if (typeof autoTestCallback == 'function') { + autoTestCallback('正在深度测试') + } + + if (deepDoneCount < deepAllCount) { + setTimeout(function () { + App.startRandomTest4Doc(list, indexes, position + 1, deepAllCount, accInd, isCross) + }, IS_NODE ? 200 : 1000) + } else { + App.testRandomProcess = '' + if (isCross) { + if (deepDoneCount == deepAllCount) { + App.test(false, accInd + 1, isCross) + } + } else { + if (deepDoneCount == deepAllCount) { + alert('已完成回归测试') + if (typeof autoTestCallback == 'function') { + autoTestCallback('已完成回归测试') + } + + App.request(false, REQUEST_TYPE_POST, REQUEST_TYPE_JSON, App.getBaseUrl() + '/coverage/report', {}, {}, function (url, res, err) { + try { + App.onResponse(url, res, err) + if (DEBUG) { + App.log('test App.request >> res.data = ' + JSON.stringify(res.data, null, ' ')) + } + } catch (e) { + App.log('test App.request >> } catch (e) {\n' + e.message) + } + + App.coverage = res.data + if (IS_BROWSER) { + setTimeout(function () { + var url = StringUtil.trim((App.coverage || {}).url) + if (StringUtil.isEmpty(url, false)) { + url = App.getBaseUrl() + "/htmlcov/index.html" + } + if (url.startsWith('/')) { + url = App.getBaseUrl() + url + } + window.open(url) + }, 2000) + } + }) + } + } + } + }, IS_NODE ? 200 : 1000) + + return true + } + + try { + var index = indexes[position] + var it = list[index] || {} + + if (IS_BROWSER) { + try { + document.getElementById('docItem' + index).scrollIntoView() + } catch (e) { + console.log(e) + } + } + + this.restoreRemote(index, it, false) + + this.randoms = [] + this.isRandomShow = true + this.isRandomEditable = true + this.isRandomListShow = false + this.isRandomSubListShow = false + this.showRandomList(true, it.Document, false, function (url, res, err) { + try { + App.onRandomListResponse(true, false, url, res, err) + } catch (e) { + log(e) + } + + App.testRandom(false, true, false, null, isCross, false, callback) + }) + } catch (e2) { + log(e2) + callback(true, deepAllCount) + } + }, + + getSummary: function (accountIndex) { + if (accountIndex == -1) { + return this.getLogoutSummary() + } + var accounts = this.accounts || [] + if (accountIndex == accounts.length) { + return this.getAllSummary() + } + + if (accountIndex < 0 || accountIndex >= accounts.length) { + return {} + } + + var ci = this.accounts[accountIndex] + return ci || {} + }, + + getLogoutSummary: function () { + return this.logoutSummary || {} + }, + getCurrentSummary: function () { + return this.getSummary(this.currentAccountIndex) || {} + }, + getAllSummary: function () { + return this.allSummary || {} + }, + + getCurrentRandomSummary: function () { + return (this.isRandomSubListShow ? this.currentRandomItem : this.currentRemoteItem) || {} + }, + + getStatisticsShowStr: function (count, total) { + if (count == null) { + count = 0; + } + if (total == null) { + total = 0; + } + + var showType = this.statisticsShowType; + if (showType == 0) { + return '' + count; + } + return Math.round(total <= 0 ? 0 : count*1000/total)/10 + '%' + (showType == 1 ? '' : ' ' + count) + }, + + getLogoutSummaryTotalText: function () { + return this.getStatisticsShowStr(this.getLogoutSummary().totalCount, this.getAllSummary().totalCount) + }, + + getLogoutSummaryWhiteText: function () { + var summary = this.getLogoutSummary() + return this.getStatisticsShowStr(summary.whiteCount, summary.totalCount) + }, + getLogoutSummaryGreenText: function () { + var summary = this.getLogoutSummary() + return this.getStatisticsShowStr(summary.greenCount, summary.totalCount) + }, + getLogoutSummaryBlueText: function () { + var summary = this.getLogoutSummary() + return this.getStatisticsShowStr(summary.blueCount, summary.totalCount) + }, + getLogoutSummaryOrangeText: function () { + var summary = this.getLogoutSummary() + return this.getStatisticsShowStr(summary.orangeCount, summary.totalCount) + }, + getLogoutSummaryRedText: function () { + var summary = this.getLogoutSummary() + return this.getStatisticsShowStr(summary.redCount, summary.totalCount) + }, + + getSummaryTotalText: function (index) { + return this.getStatisticsShowStr(this.getSummary(index).totalCount, this.getAllSummary().totalCount) + }, + getSummaryWhiteText: function (index) { + var summary = this.getSummary(index) + return this.getStatisticsShowStr(summary.whiteCount, summary.totalCount) + }, + getSummaryGreenText: function (index) { + var summary = this.getSummary(index) + return this.getStatisticsShowStr(summary.greenCount, summary.totalCount) + }, + getSummaryBlueText: function (index) { + var summary = this.getSummary(index) + return this.getStatisticsShowStr(summary.blueCount, summary.totalCount) + }, + getSummaryOrangeText: function (index) { + var summary = this.getSummary(index) + return this.getStatisticsShowStr(summary.orangeCount, summary.totalCount) + }, + getSummaryRedText: function (index) { + var summary = this.getSummary(index) + return this.getStatisticsShowStr(summary.redCount, summary.totalCount) + }, + + getCurrentSummaryTotalText: function () { + return this.getStatisticsShowStr(this.getCurrentSummary().totalCount, this.getAllSummary().totalCount) + }, + getCurrentSummaryWhiteText: function () { + var summary = this.getCurrentSummary() + return this.getStatisticsShowStr(summary.whiteCount, summary.totalCount) + }, + getCurrentSummaryGreenText: function () { + var summary = this.getCurrentSummary() + return this.getStatisticsShowStr(summary.greenCount, summary.totalCount) + }, + getCurrentSummaryBlueText: function () { + var summary = this.getCurrentSummary() + return this.getStatisticsShowStr(summary.blueCount, summary.totalCount) + }, + getCurrentSummaryOrangeText: function () { + var summary = this.getCurrentSummary() + return this.getStatisticsShowStr(summary.orangeCount, summary.totalCount) + }, + getCurrentSummaryRedText: function () { + var summary = this.getCurrentSummary() + return this.getStatisticsShowStr(summary.redCount, summary.totalCount) + }, + + getAllSummaryTotalText: function () { + return this.getStatisticsShowStr(this.getAllSummary().totalCount, this.getAllSummary().totalCount) + }, + getAllSummaryWhiteText: function () { + var summary = this.getAllSummary() + return this.getStatisticsShowStr(summary.whiteCount, summary.totalCount) + }, + getAllSummaryGreenText: function () { + var summary = this.getAllSummary() + return this.getStatisticsShowStr(summary.greenCount, summary.totalCount) + }, + getAllSummaryBlueText: function () { + var summary = this.getAllSummary() + return this.getStatisticsShowStr(summary.blueCount, summary.totalCount) + }, + getAllSummaryOrangeText: function () { + var summary = this.getAllSummary() + return this.getStatisticsShowStr(summary.orangeCount, summary.totalCount) + }, + getAllSummaryRedText: function () { + var summary = this.getAllSummary() + return this.getStatisticsShowStr(summary.redCount, summary.totalCount) + }, + + isSummaryShow: function (accountIndex) { + if (accountIndex == -1) { + return this.isLogoutSummaryShow() + } + var accounts = this.accounts + if (accountIndex == accounts.length) { + return this.isAllSummaryShow() + } + + if (accountIndex < 0 || accountIndex >= accounts.length) { + return false + } + + // var ci = this.isTestCaseShow ? this.accounts[accountIndex] : null + var ci = this.accounts[accountIndex] + return ci != null && ci.totalCount != null && ci.totalCount > 0 + }, + isLogoutSummaryShow: function () { + // var ci = this.isCrossEnabled != true ? null : this.logoutSummary + var ci = this.logoutSummary + return ci != null && ci.totalCount != null && ci.totalCount > 0 + }, + isCurrentSummaryShow: function () { + return this.isSummaryShow(this.currentAccountIndex) + }, + isAllSummaryShow: function () { + // var ci = this.isCrossEnabled != true ? null : this.allSummary + var ci = this.allSummary + return ci != null && ci.totalCount != null && ci.totalCount > 0 + }, + isRandomSummaryShow: function () { + var ci = this.isRandomListShow || this.isRandomSubListShow ? this.getCurrentRandomSummary() : null + return ci != null && ci.totalCount != null && ci.totalCount > 0 + }, + + updateSummary: function (item, change, key) { + if (change == null || key == null) { + return item + } + + if (item == null) { + item = {} + } + + var count = item[key] + if (count == null || count < 0) { + count = 0 + } + count += change + + item[key] = count + + // 对于 Random 进入子项再退出后有时显示居然不准 + // if (cri.totalCount == null) { + // cri.totalCount = 0 + // } + // cri.totalCount += change + // if (cri.totalCount < 0) { + // cri.totalCount = 0 + // } + item.totalCount = item.whiteCount + item.greenCount + item.blueCount + item.orangeCount + item.redCount + + return item + }, + + //更新父级总览数据 + updateToSummary: function (item, change, accountIndex) { + if (item == null || change == null) { + return + } + + var key = item.compareColor + 'Count' + this.allSummary = this.updateSummary(this.allSummary, change, key) + + if (accountIndex == -1) { + this.logoutSummary = this.updateSummary(this.logoutSummary, change, key) + } + else if (accountIndex >= 0 && accountIndex < this.accounts.length) { + var accountItem = this.updateSummary(this.getSummary(accountIndex), change, key) + this.accounts[accountIndex] = accountItem + } + }, + updateToRandomSummary: function (item, change, accountIndex) { + var random = item == null || change == null ? null : item.Random + if (random == null) { + return + } + + if (random.count == 1 || (random.id != null && random.id < 0)) { + var key = item.compareColor + 'Count' + this.updateToSummary(item, change, accountIndex) + + var curRandom = this.isRandomListShow || this.currentRandomItem == null ? null : this.currentRandomItem.Random + var isTemp = curRandom != null && (curRandom.id == null || curRandom.id < 0) + var cri = this.updateSummary(isTemp ? this.currentRandomItem : this.currentRemoteItem, change, key) // this.getCurrentRandomSummary()) + + if (isTemp) { + this.currentRandomItem = cri + this.updateSummary(this.currentRemoteItem, change, key) + } else { + this.currentRemoteItem = cri + Vue.set(this.testCases, this.currentDocIndex, cri) + } + + var toId = random.toId + if (toId != null && toId > 0) { + for (var i in this.randoms) { + var toIt = this.randoms[i] + if (toIt != null && toIt.Random != null && toIt.Random.id == toId) { + + var toRandom = toIt.Random + var id = toRandom == null ? 0 : toRandom.id + var count = id == null || id <= 0 ? 0 : toRandom.count + if (count != null && count > 1) { + toIt = this.updateSummary(toIt, change, key) + Vue.set(this.randoms, i, toIt) + } + + break + } + } + } + } + }, + + /**移除调试字段 + * @param obj + */ + removeDebugInfo: function (obj) { + if (obj != null) { + delete obj["trace"] + // 保留 delete obj["sql:generate|cache|execute|maxExecute"] + // 保留 delete obj["depth:count|max"] + delete obj["time"] + delete obj["timestamp"] + delete obj["time:start|duration|end"] + delete obj["time:start|duration|end|parse|sql"] + // 保留 delete obj["throw"] + // 保留 delete obj["trace:throw"] + delete obj["trace:stack"] + delete obj["stack"] + delete obj["debug:info|help"] + } + return obj + }, + + /** + * @param index + * @param item + */ + downloadTest: function (index, item, isRandom) { + item = item || {} + var document; + if (isRandom) { + document = this.currentRemoteItem || {} + } + else { + document = item.Document = item.Document || {} + } + var random = isRandom ? item.Random : null + var testRecord = item.TestRecord = item.TestRecord || {} + + saveTextAs( + '# APIJSON自动化回归测试-前\n主页: https://github.com/Tencent/APIJSON' + + '\n\n接口名称: \n' + (document.version > 0 ? 'V' + document.version : 'V*') + ' ' + document.name + + '\n返回结果: \n' + JSON.stringify(parseJSON(testRecord.response || '{}'), null, ' ') + , '测试:' + document.name + '-前.txt' + ) + + /** + * 浏览器不允许连续下载,saveTextAs也没有回调。 + * 在第一个文本里加上第二个文本的信息? + * beyond compare会把第一个文件的后面一段与第二个文件匹配, + * 导致必须先删除第一个文件内的后面与第二个文件重复的一段,再重新对比。 + */ + setTimeout(function () { + var tests = App.tests[String(App.currentAccountIndex)] || {} + saveTextAs( + '# APIJSON自动化回归测试-后\n主页: https://github.com/Tencent/APIJSON' + + '\n\n接口名称: \n' + (document.version > 0 ? 'V' + document.version : 'V*') + ' ' + document.name + + '\n返回结果: \n' + JSON.stringify(tests[document.id][isRandom ? random.id : 0] || {}, null, ' ') + , '测试:' + document.name + '-后.txt' + ) + + + if (StringUtil.isEmpty(testRecord.standard, true) == false) { + setTimeout(function () { + saveTextAs( + '# APIJSON自动化回归测试-标准\n主页: https://github.com/Tencent/APIJSON' + + '\n\n接口名称: \n' + (document.version > 0 ? 'V' + document.version : 'V*') + ' ' + document.name + + '\n测试结果: \n' + JSON.stringify(testRecord.compare || '{}', null, ' ') + + '\n测试标准: \n' + JSON.stringify(parseJSON(testRecord.standard || '{}'), null, ' ') + , '测试:' + document.name + '-标准.txt' + ) + }, 5000) + } + + }, 5000) + + }, + + /** + * @param index + * @param item + */ + handleTest: function (right, index, item, path, isRandom, isDuration, isCross) { + item = item || {} + var random = item.Random = item.Random || {} + var document; + if (isRandom) { + if ((random.count || 0) > 1) { + this.currentRandomIndex = index + // this.currentRandomSubIndex = -1 + this.restoreRandom(index, item) + this.randomSubs = (item.subs || item['[]']) || [] + this.isRandomSubListShow = true + this.getCurrentRandomSummary().summaryType = 'total' + return + } + + this.currentRandomSubIndex = index + document = this.currentRemoteItem || {} + } + else { + this.currentDocIndex = index + this.currentRemoteItem = item + // this.currentRandomIndex = -1 + // this.currentRandomSubIndex = -1 + document = item.Document = item.Document || {} + } + + this.isFullAssert = true + + var testRecord = item.TestRecord = item.TestRecord || {} + var pathKeys = StringUtil.split(path, '/') || []; + var pathNames = pathKeys.slice(0, pathKeys.length - 1); + var lastKey = pathKeys[pathKeys.length - 1]; + + var tests = this.tests[String(this.currentAccountIndex)] || {} + var currentResponse = (tests[isRandom ? random.documentId : document.id] || {})[ + isRandom ? (random.id > 0 ? random.id : (random.toId + '' + random.id)) : 0 + ] + + if (pathKeys.length > 0) { + var curRsp = StringUtil.isEmpty(testRecord.response) ? {} : parseJSON(testRecord.response); + JSONResponse.setValByPath(curRsp, pathKeys, JSONResponse.getValByPath(currentResponse, pathKeys)); + currentResponse = curRsp; + } + + const rawRspStr = currentResponse == null ? null : JSON.stringify(currentResponse) + + const list = isRandom ? (random.toId == null || random.toId <= 0 ? this.randoms : this.randomSubs) : this.testCases + + var isBefore = item.showType == 'before' + if (right != true) { + item.showType = isBefore ? 'after' : 'before' + Vue.set(list, index, item); + + var res = isBefore ? rawRspStr : testRecord.response + if (isRandom && ! isBefore) { + res = res || ((this.currentRemoteItem || {}).TestRecord || {}).response + } + + this.view = 'code' + this.jsoncon = res || '' + } + else { + var url; + + if (isBefore) { //撤回原来错误提交的校验标准 + if (isDuration) { + alert('撤回上次的耗时需要删除上次的对比标准,请点左边 [错的,撤回] 按钮') + return + } + + url = this.server + '/delete' + const req = { + TestRecord: { + id: testRecord.id, //TODO 权限问题? item.userId, + }, + tag: 'TestRecord' + } + + this.request(true, REQUEST_TYPE_POST, REQUEST_TYPE_JSON, url, req, {}, function (url, res, err) { + App.onResponse(url, res, err) + + var data = res.data || {} + if (JSONResponse.isSuccess(data) != true && testRecord!= null && testRecord.id != null) { + alert('撤回最新的校验标准 异常:\n' + data.msg) + return + } + + if (isRandom) { + App.updateToRandomSummary(item, -1, App.currentAccountIndex) + } else { + App.updateToSummary(item, -1, App.currentAccountIndex) + } + + if (isDuration) { + item.durationColor = 'black' + item.durationHint = '正常:在以往最快和最慢之间' + } + else { + item.compareType = JSONResponse.COMPARE_NO_STANDARD + item.compareMessage = '查看结果' + item.compareColor = 'white' + item.hintMessage = '没有校验标准!' + item.TestRecord = null + } + + App.updateTestRecord(0, list, index, item, rawRspStr == null ? null : parseJSON(rawRspStr), isRandom, true, App.currentAccountIndex, isCross) + }) + } + else { //上传新的校验标准 + // if (isRandom && random.id <= 0) { + // alert('请先上传这个配置!') + // App.currentRandomItem = random + // App.showExport(true, false, true) + // return + // } + var isML = this.isMLEnabled; // 异常分支不合并内容,只记录 code, throw, msg 等关键信息 + + var standard; + var stddObj; + + var minDuration = testRecord.minDuration + var maxDuration = testRecord.maxDuration + if (isDuration) { + if (item.duration == null) { // 没有获取到 + alert('最外层缺少字段 "time:start|duration|end|parse|sql",无法对比耗时!') + return + } + else if (maxDuration == null && minDuration == null) { + maxDuration = item.duration + minDuration = Math.round(maxDuration*0.8) + } + else if (maxDuration == null && minDuration != null) { + maxDuration = Math.max(minDuration, item.duration) + testRecord.minDuration = Math.min(minDuration, item.duration) + } + else if (minDuration == null && maxDuration != null) { + minDuration = Math.min(maxDuration, item.duration) + testRecord.maxDuration = Math.max(maxDuration, item.duration) + } + else if (maxDuration > 0 && maxDuration < item.duration) { + maxDuration = item.duration + } + else if (minDuration > 0 && minDuration > item.duration) { + minDuration = item.duration + } + else { // 已经在正常范围中,不需要纠错 + alert('耗时已经在正常范围中,不需要纠错!') + return + } + } + else { + standard = (StringUtil.isEmpty(testRecord.standard, true) ? null : parseJSON(testRecord.standard)) || {} + if (pathKeys.length <= 0) { + stddObj = JSONResponse.updateFullStandard(standard, rawRspStr == null ? null : parseJSON(rawRspStr), isML) + } else if (isML) { + stddObj = JSONResponse.updateStandardByPath(standard, pathNames, lastKey, rawRspStr == null ? null : parseJSON(rawRspStr)) + } + } + + const isNewRandom = isRandom && random.id <= 0 + const baseUrl = this.getBaseUrl() + const userId = this.User.id + const cri = this.currentRemoteItem || {} + const chain = cri.Chain || {} + const cgId = chain.groupId || 0 + const cId = chain.id || 0 + + //TODO 先检查是否有重复名称的!让用户确认! + // if (isML != true) { + url = this.server + '/post' + const req = { + Random: isNewRandom != true ? null : { + toId: random.toId, + userId: userId, + chainGroupId: cgId, + chainId: cId, + documentId: random.documentId, + name: random.name, + count: random.count, + config: random.config + }, + TestRecord: isDuration ? Object.assign(testRecord, { + id: undefined, + reportId: this.reportId, + host: baseUrl, + userId: userId, + testAccountId: this.getCurrentAccountId(), + chainGroupId: cgId, + chainId: cId, + duration: item.duration, + minDuration: minDuration, + maxDuration: maxDuration, + compare: JSON.stringify(testRecord.compare || {}), + }) : { + userId: userId, + chainGroupId: cgId, + chainId: cId, + documentId: isNewRandom ? null : (isRandom ? random.documentId : document.id), + randomId: isRandom && ! isNewRandom ? random.id : null, + reportId: this.reportId, + host: baseUrl, + testAccountId: this.getCurrentAccountId(), + compare: JSON.stringify(testRecord.compare || {}), + response: rawRspStr, + standard: isML ? JSON.stringify(stddObj) : null + }, + tag: isNewRandom ? 'Random' : 'TestRecord' + } + // } + // else { + // url = this.server + '/post/testrecord/ml' + // req = { + // documentId: document.id + // } + // } + + this.request(true, REQUEST_TYPE_POST, REQUEST_TYPE_JSON, url, req, {}, function (url, res, err) { + App.onResponse(url, res, err) + + var data = res.data || {} + if (JSONResponse.isSuccess(data) != true) { + if (isML) { + alert('机器学习更新标准 异常:\n' + data.msg) + } + } + else { + if (isRandom) { + App.updateToRandomSummary(item, -1, App.currentAccountIndex) + } else { + App.updateToSummary(item, -1, App.currentAccountIndex) + } + + var testRecord = item.TestRecord || {} + if (isDuration) { + item.durationColor = 'black' + item.durationHint = '正常:在以往最快和最慢之间' + } + else { + item.compareType = JSONResponse.COMPARE_EQUAL + item.compareMessage = '查看结果' + item.compareColor = 'white' + item.hintMessage = '结果正确' + + testRecord.compare = { + code: 0, + msg: '结果正确' + } + testRecord.response = rawRspStr + // testRecord.standard = stdd + } + + if (isRandom) { + var r = req == null ? null : req.Random + if (r != null && (data.Random || {}).id != null) { + r.id = data.Random.id + item.Random = r + } + if ((data.TestRecord || {}).id != null) { + testRecord.id = data.TestRecord.id + if (r != null) { + testRecord.randomId = r.id + } + } + } + item.TestRecord = testRecord + + + // if (! isNewRandom) { + // if (isRandom) { + // App.showRandomList(true, App.currentRemoteItem) + // } + // else { + // App.showTestCase(true, false) + // } + // } + + App.updateTestRecord(0, list, index, item, rawRspStr == null ? null : parseJSON(rawRspStr), isRandom, true, App.currentAccountIndex, isCross) + } + + }) + + } + } + }, + + updateTestRecord: function (allCount, list, index, item, response, isRandom, ignoreTrend, accountIndex, isCross) { + item = item || {} + var doc = (isRandom ? item.Random : item.Document) || {} + + this.request(true, REQUEST_TYPE_POST, REQUEST_TYPE_JSON, this.server + '/get', { + TestRecord: { + documentId: isRandom ? doc.documentId : doc.id, + randomId: isRandom ? doc.id : null, + testAccountId: this.getCurrentAccountId(), + 'invalid': 0, + 'host': this.getBaseUrl(), + '@order': 'date-', + '@column': 'id,userId,testAccountId,documentId,randomId,reportId,duration,minDuration,maxDuration,response' + (this.isMLEnabled ? ',standard' : ''), + 'standard{}': this.isMLEnabled ? (this.database == 'SQLSERVER' ? 'len(standard)>2' : 'length(standard)>2') : null // '@having': this.isMLEnabled ? 'json_length(standard)>0' : null + } + }, {}, function (url, res, err) { + App.onResponse(url, res, err) + + var data = (res || {}).data || {} + if (JSONResponse.isSuccess(data) != true) { + alert('获取最新的校验标准 异常:\n' + data.msg) + return + } + + item.TestRecord = data.TestRecord + App.compareResponse(res, allCount, list, index, item, response, isRandom, accountIndex, true, err, ignoreTrend, isCross); + }) + }, + + //显示详细信息, :data-hint :data, :hint 都报错,只能这样 + setRequestHint: function(index, item, isRandom) { + item = item || {} + var d = isRandom ? item.Random : item.Document; + // var r = d == null ? null : (isRandom ? d.config : d.request); + // this.$refs[isRandom ? 'randomTexts' : 'testCaseTexts'][index].setAttribute('data-hint', r == null ? '' : (isRandom ? r : JSON.stringify(this.getRequest(r), null, ' '))); + + if (isRandom) { + var toId = (d == null ? null : d.toId) || 0 + this.$refs[toId <= 0 ? 'randomTexts' : 'randomSubTexts'][index].setAttribute('data-hint', (d || {}).config == null ? '' : d.config); + } + else { + this.$refs['testCaseTexts'][index].setAttribute('data-hint', StringUtil.isEmpty(d.request, true) ? '' : JSON.stringify(this.getRequest(d.request, {}, true), null, ' ')); + } + }, + + //显示详细信息, :data-hint :data, :hint 都报错,只能这样 + setTestHint: function(index, item, isRandom, isDuration, isHandle) { + item = item || {}; + var toId = isRandom ? ((item.Random || {}).toId || 0) : 0; + var h = isDuration ? item.durationHint : (isHandle ? item.compareMessage : item.hintMessage); + this.$refs['test' + (isRandom ? (toId <= 0 ? 'Random' : 'RandomSub') : '') + (isHandle ? 'Handle' : 'Result') + + (isDuration ? 'Duration' : '') + 'Buttons'][index].setAttribute('data-hint', h || ''); + }, + + handleTestArg: function(hasTestArg, rawReq, delayTime, callback) { + if (hasTestArg && IS_BROWSER) { + vUrlComment.value = "" + vComment.value = "" + vWarning.value = "" + } + + if (IS_BROWSER) { + App.onChange(false) + } + + if (hasTestArg && rawReq.send != "false" && rawReq.send != "null") { + setTimeout(function () { + if (rawReq.send == 'random') { + App.onClickTestRandom(App.isCrossEnabled, callback) + } else if (App.isTestCaseShow) { + App.onClickTest(callback) + } else { + App.send(false, callback) + } + + var url = vUrl.value || '' + if (IS_BROWSER && (rawReq.jump == "true" || rawReq.jump == "null" + || (rawReq.jump != "false" && App.isTestCaseShow != true && rawReq.send != 'random' + && (url.endsWith("/get") || url.endsWith("/head")) + ) + )) { + setTimeout(function () { + window.open(vUrl.value + "/" + encodeURIComponent(JSON.stringify(encode(parseJSON(vInput.value))))) + }, 2000) + } + }, Math.max(2000, delayTime)) + } + }, + + autoTest: function(callback, delayTime, isTest, rawReq, setting) { + this.autoTestCallback = callback + + if (delayTime == null) { + delayTime = 0 + } + + if (isTest == null) { + isTest = true + } + + if (rawReq == null) { + rawReq = { + send: true, + type: REQUEST_TYPE_JSON, + url: 'http://localhost:8080/get' + } + } + + if (setting == null) { + setting = StringUtil.isEmpty(rawReq.setting, true) ? null : parseJSON(StringUtil.trim(rawReq.setting, true)) + } + + if (setting == null) { + setting = { + isLocalShow: false, + isTestCaseShow: true, + isRandomShow: true, + isRandomListShow: false, + isRandomSubListShow: false, + isMLEnabled: true, + isCrossEnabled: true, + // testCaseCount: 100, + testCasePage: 0, + // randomCount: 100, + randomPage: 0, + } + } + + rawReq.setting = setting + + this.isLocalShow = setting.isLocalShow + this.isTestCaseShow = setting.isTestCaseShow + this.isRandomShow = setting.isRandomShow + this.isRandomListShow = setting.isRandomListShow + this.isRandomSubListShow = setting.isRandomSubListShow + this.isMLEnabled = setting.isMLEnabled + this.isCrossEnabled = setting.isCrossEnabled + // this.testCaseCount = setting.testCaseCount + this.testCasePage = setting.testCasePage + // this.randomCount = setting.randomCount + this.randomPage = setting.randomPage + this.server = 'http://localhost:8080/' // this.getBaseUrl() + + // if (this.isCrossEnabled) { + // this.currentAccountIndex = -1 + // } + + this.login(true, function (url, res, err) { + if (setting.isRandomShow && setting.isRandomListShow) { + delayTime += Math.min(5000, (App.isMLEnabled ? 50 : 20) * (setting.randomCount || App.randomCount) + 1000) + App.isRandomShow = true + App.isRandomEditable = true + App.isRandomListShow = false + App.isRandomSubListShow = false + // App.showRandomList(false, setting.isRandomSubListShow ? App.currentRandomItem : null, setting.isRandomSubListShow) + App.showRandomList(true, setting.isRandomSubListShow ? (App.currentRandomItem || {}).Random : (App.currentRemoteItem || {}).Document, setting.isRandomSubListShow, function (url, res, err) { + App.onRandomListResponse(true, setting.isRandomSubListShow, url, res, err) + App.handleTestArg(isTest, rawReq, delayTime, callback) + }) + } + else { // if (setting.isTestCaseShow) { + delayTime += Math.min(5000, (App.isMLEnabled ? 30 : 10) * (setting.testCaseCount || App.testCaseCount) + 1000) + + // App.login(true) + App.onLoginResponse(true, { + type: 0, // 登录方式,非必须 0-密码 1-验证码 + phone: App.account, + password: App.password, + version: 1, // 全局默认版本号,非必须 + remember: vRemember.checked, + format: false + }, url, res, err) + + App.showTestCase(true, setting.isLocalShow, function (url, res, err) { + App.onTestCaseListResponse(IS_BROWSER, url, res, err) + App.isTestCaseShow = true + App.handleTestArg(isTest, rawReq, delayTime, callback) + }) + } + + }) + }, + + toPathValuePairMap: function (json, path, map) { + if (map == null) { + map = {} + } + if (json == null) { + return map + } + + if (json instanceof Array) { + for (var i = 0; i < json.length; i++) { + var p = StringUtil.isEmpty(path) ? '' + i : path + '/' + i + map = this.toPathValuePairMap(json[i], p, map) + } + } + else if (json instanceof Object) { + for (var k in json) { + var p = StringUtil.isEmpty(path) ? k : path + '/' + k + map = this.toPathValuePairMap(json[k], p, map) + } + } + else { + map[path == null ? '' : path] = json + } + + return map + }, + + showOptions: function(target, text, before, after, isValue, filter) { + currentTarget = target; + isInputValue = isValue; + selectionStart = target.selectionStart; + selectionEnd = target.selectionEnd; + App.selectIndex = -1; + + clearTimeout(handler); + + // var posX = 0, posY = 0; + + // var event = window.event; + // if (event.pageX || event.pageY) { + // posX = event.pageX; + // posY = event.pageY; + // } + // else if (event.clientX || event.clientY) { + // posX = event.clientX + document.documentElement.scrollLeft + document.body.scrollLeft; + // posY = event.clientY + document.documentElement.scrollTop + document.body.scrollTop; + // } + // else if (target.offsetHeight || target.offsetWidth) { + // // posX = target.offsetHeight; + // // posY = target.offsetWidth; + // } + // + // vOption.style.left = posX + 'px'; + // vOption.style.top = posY + 'px'; + + + var options = App.options; + if (options.length > 0 && StringUtil.isNotEmpty(filter, true)) { + var newOptions = []; + for (var i = 0; i < options.length; i++) { + var opt = options[i]; + var name = opt == null ? null : opt.name; + if (name != null && name.indexOf(filter) >= 0) { + newOptions.push(opt); + } + } + + // App.options = []; + App.options = newOptions; + } + else { + App.options = []; + + var stringType = CodeUtil.getType4Language(App.language, "string") + var objectType = CodeUtil.getType4Language(App.language, "object") + var arrayType = CodeUtil.getType4Language(App.language, "array") + var varcharType = CodeUtil.getType4Language(App.language, "varchar") + var intType = CodeUtil.getType4Language(App.language, "int") + var booleanType = CodeUtil.getType4Language(App.language, "boolean") + var isReq = App.isEditResponse != true + + if (target == vHeader) { + if (isValue != true) { + App.options = [ + { + name: "Cookie", + type: stringType, + comment: "指定 Cookie" + }, { + name: "Set-Cookie", + type: stringType, + comment: "设置 Cookie" + }, { + name: "Add-Cookie", + type: stringType, + comment: "添加 Cookie" + }, { + name: "Token", + type: stringType, + comment: "指定 Token" + }, { + name: "Authorization", + type: stringType, + comment: "授权" + }, { + name: "Authentication", + type: stringType, + comment: "鉴权" + }, { + name: "Content-Type", + type: stringType, + comment: "数据类型" + }, { + name: "Accept", + type: stringType, + comment: "接收格式" + }, { + name: "Accept-Encoding", + type: stringType, + comment: "接收编码" + }, { + name: "Accept-Language", + type: stringType, + comment: "接收语言" + }, { + name: "Cache-Control", + type: stringType, + comment: "缓存控制" + }, { + name: "Connection", + type: stringType, + comment: "连接控制" + }, { + name: "Keep-Alive", + type: stringType, + comment: "保持连接" + }]; + } + } + else if (target == vRandom || target == vScript) { + if (target == vScript) { + App.options = [ + { + name: "type", + type: stringType, + comment: '请求格式类型:PARAM, JSON, FORM, DATA' + },{ + name: "url", + type: stringType, + comment: '请求地址,例如 http://localhost:8080/get ' + },{ + name: "req", + type: objectType, + comment: '请求参数,例如 { format: true, "User": { "id": 82001 } } ' + },{ + name: "header", + type: objectType, + comment: '请求头,例如 Cookie: abc123 ' + }]; + + if (isValue) { + App.options.push({ + name: "callback(url, res, err)", + type: objectType, + comment: '回调函数' + }) + App.options.push({ + name: "sendRequest(isAdminOperation, method, type, url, req, header, callback)", + type: objectType, + comment: '真正发送请求函数' + }) + App.options.push({ + name: "App.request(isAdminOperation, method, type, url, req, header, callback)", + type: objectType, + comment: '包装发送请求函数' + }) + App.options.push({ + name: "if () {\n \n} else if () {\n \n} else {\n \n}", + type: objectType, + comment: '包装发送请求函数' + }) + App.options.push({ + name: "switch () {\n case 1:\n \n break\n case 2:\n \n break\n default:\n \n break\n}", + type: objectType, + comment: '包装发送请求函数' + }) + App.options.push({ + name: "try {\n \n} catch(e) {\n console.log(e)\n}", + type: objectType, + comment: '包装发送请求函数' + }) + App.options.push({ + name: "{}", type: objectType, comment: '对象' + }) + App.options.push({ + name: "[]", type: arrayType, comment: '数组' + }) + App.options.push({ + name: "undefined", name: "undefined", comment: '未定义' + }) + } + else { + App.options.push({ + name: "callback", + type: objectType, + comment: '回调函数 function(url, res, err) {} ' + }) + } + } + + if (isValue != true) { + var standardObj = null; + try { + var currentItem = App.isTestCaseShow ? App.remotes[App.currentDocIndex] : App.currentRemoteItem; + standardObj = parseJSON(((currentItem || {})[isReq ? 'Document' : 'TestRecord'] || {}).standard); + } catch (e3) { + log(e3) + } + if (standardObj == null) { + standardObj = JSONResponse.updateStandard({}, + isReq ? App.getRequest(vInput.value) : App.jsoncon == null ? null : parseJSON(App.jsoncon) + ) + } + + var method = App.isTestCaseShow ? ((App.currentRemoteItem || {}).Document || {}).url : App.getMethod(); + var isRestful = ! JSONObject.isAPIJSONPath(method); + var ind = method == null ? -1 : method.lastIndexOf('/'); + var ind2 = ind < 0 ? -1 : method.substring(0, ind).lastIndexOf('/'); + var table = method == null ? null : (ind < 0 ? method : (isRestful + ? StringUtil.firstCase(method.substring(ind2+1, ind), true) : method.substring(ind+1)) + ); + + var tableList = docObj == null ? null : docObj['[]'] + var isAPIJSONRouter = false // TODO + + var json = App.getRequest(vInput.value) + var map = App.toPathValuePairMap(json) || {} + for (var path in map) { + if (StringUtil.isEmpty(path)) { + continue + } + + var ks = StringUtil.split(path, '/') + var tbl = ks.length < 2 ? table : ks[ks.length - 2] + + var v = map[path] + var t = v == null ? null : CodeUtil.getType4Request(v) + var k = ks[ks.length - 1] + + App.options.push({ + name: target != vScript ? path : JSONResponse.formatKey(ks.join('_'), true, true, true, true, true, true), + type: t == null ? null : (t == 'string' ? stringType : (t == 'integer' ? intType : CodeUtil.getType4Language(App.language, t))), + comment: CodeUtil.getComment4Request(tableList, tbl, k, v, method, false, App.database, App.language + , isReq, ks, isRestful, standardObj, false, isAPIJSONRouter) + }) + } + } + else if (target == vRandom) { + App.options = [ + { + name: "ORDER_DB(-10, 100000, 'Comment', 'id')", + type: stringType, + comment: "从数据库顺序取值 function(min:Integer, max:Integer, table:String, column:String) 可使用 ORDER_DB+2(0, 100) 间隔 step = 2 位来升序取值" + }, { + name: "ORDER_IN(true, 1, 'a')", + type: stringType, + comment: "从选项内顺序取值 function(val0:Any, val1:Any ...) 可使用 ORDER_INT-3(0, 100) 间隔 step = -3 位来降序取值" + }, { + name: "ORDER_INT(-10, 100)", + type: stringType, + comment: "从范围内顺序取值 function(min:Integer, max:Integer) 可使用 ORDER_IN+(0, 100) 间隔 step = 1 位来升序取值" + }, { + name: "RANDOM_DB(-10, 100000, 'Comment', 'id')", + type: stringType, + comment: "从数据库随机取值 function(min:Integer, max:Integer, table:String, column:String)" + }, { + name: "RANDOM_IN(true, 1, 'a')", + type: stringType, + comment: "从选项内随机取值 function(val0:Any, val1:Any ...)" + }, { + name: "RANDOM_INT(-10, 100)", + type: stringType, + comment: "从范围内随机取整数 function(min:Integer, max:Integer)" + }, { + name: "RANDOM_NUM(-9.9, 99.99)", + type: stringType, + comment: "从范围内随机取小数 function(min:Number, max:Number, precision:Integer)" + }, { + name: "RANDOM_STR()", + type: stringType, + comment: "从长度范围内随机取字符串 function(minLength:Integer, maxLength:Integer, regexp:String)" + }, { + name: "undefined", type: "undefined", comment: '未定义' + }, { + name: "Math.round(100*Math.random())", type: stringType, comment: '自定义代码' + } + ] + + if (App.isChainShow) { + App.options = [ + { + name: "PRE_DATA('[]/0/User/id')", + type: stringType, + comment: "从上个请求的返回结果中取值 function(path:String?, defaultVal:Any?, msg:String?)" + }, { + name: "PRE_ARG('[]/page')", + type: stringType, + comment: "从上个请求的参数中取值 function(path:String?, defaultVal:Any?, msg:String?)" + }, { + name: "CTX_GET('userId')", + type: stringType, + comment: "在上下文存放键值对 function(key:String?, defaultVal:Any?, msg:String?)" + }, { + name: "CTX_PUT('userId', 'User/id', 'CUR_DATA')", + type: stringType, + comment: "在上下文存放键值对 function(key:String, val:Any, from:String?, msg:String?)" + }, { + name: "PRE_RES('[]/page')", + type: stringType, + comment: "从上个请求的 Response 对象中取值 function(path:String, defaultVal:Any?, msg:String?)" + }, { + name: "PRE_REQ('isMale')", + type: stringType, + comment: "从上个请求 Request 对象中取值 function(path:String, defaultVal:Any?, msg:String?)" + }, { + name: "CUR_ARG('[]/count')", + type: stringType, + comment: "从当前请求的参数中取值 function(path:String, defaultVal:Any?, msg:String?)" + }, { + name: "CUR_REQ('[]/count')", + type: stringType, + comment: "从当前请求的 Request 对象中取值 function(path:String, defaultVal:Any?, msg:String?)" + }, { + name: "CUR_DATA('[]/count')", + type: stringType, + comment: "从当前请求的返回结果中取值 function(path:String?, defaultVal:Any?, msg:String?)" + }, { + name: "CUR_RES('[]/count')", + type: stringType, + comment: "从当前请求的 Response 对象中取值 function(path:String, defaultVal:Any?, msg:String?)" + } + ].concat(App.options || []) + } + } + else { + App.options.push({ + name: isSingle ? "res.key" : "res['key']", type: stringType, comment: '从上个请求的结果中取值' + }) + } + } + else if (target == vInput) { + var quote = isSingle ? "'" : '"'; + + var table = null; + var isArrayKey = false; + var isSubqueryKey = false; + + var prev = before; + while (prev != null && prev.length > 0) { + var lastIndex = prev.lastIndexOf('{'); + prev = prev.substring(0, lastIndex).trimRight(); + + if (prev.endsWith(':')) { + prev = prev.substring(0, prev.length - 1).trimRight(); + var endsWithDoubleQuote = prev.endsWith('"') + + if (endsWithDoubleQuote || prev.endsWith("'")) { + prev = prev.substring(0, prev.length - 1); + lastIndex = prev.lastIndexOf('\n'); + + var lastLine = prev.substring(lastIndex + 1, prev.length); + var ind = lastLine.lastIndexOf(endsWithDoubleQuote ? '"' : "'"); + table = ind < 0 ? null : lastLine.substring(ind + 1, lastLine.length); + + if (App.isTableKey(table)) { + break; + } + if (table != null && table.endsWith('[]')) { + isArrayKey = true; + break; + } + if (table != null && table.endsWith('@')) { + isSubqueryKey = true; + break; + } + + prev = lastIndex <= 0 ? '' : prev.substring(0, lastIndex); + } + } + } + + if (isValue) { + var lastIndex = before.lastIndexOf('\n'); + var lastLine = before.substring(lastIndex + 1, before.length); + lastIndex = lastLine.lastIndexOf(':'); + lastLine = lastIndex < 0 ? '' : lastLine.substring(0, lastIndex).trim(); + + var endsWithDoubleQuote = lastLine.endsWith('"') + if (endsWithDoubleQuote || lastLine.endsWith("'")) { + lastLine = lastLine.substring(0, lastLine.length - 1); + } + var ind = lastLine.lastIndexOf(endsWithDoubleQuote ? '"' : "'"); + var key = ind < 0 ? null : lastLine.substring(ind + 1, lastLine.length); + + var isArrayKey = JSONObject.isArrayKey(key) + if (isArrayKey || App.isTableKey(key)) { + table = key; + if (isArrayKey) { + ind = key.indexOf('-'); + if (ind < 0) { + ind = key.indexOf(':'); + } + table = key.substring(0, ind < 0 ? key.length - 2 : ind); + } + + App.options = [{ + name: "{}", + type: objectType, + comment: (isArrayKey ? '数组 < ' + table + ': ' : '') + StringUtil.trim((App.getTableByModelName(table) || {}).table_comment) + }] + } else { + switch (key) { + case '@from@': + App.options = [{ + name: "{}", + type: objectType, + comment: '数据来源' + }]; + break; + case '@combine': + case '@raw': + var isRaw = key == '@raw'; + + var end = before.lastIndexOf('{'); + var start = after.indexOf('}'); + var s = (end < 0 ? before : before.substring(end)) + after.substring(0, start + 1); + var json = App.getRequest(s, {}); + + var ks = ''; + var first = true; + for (var k in json) { + if (StringUtil.isNotEmpty(k, true) && (isRaw || (k.startsWith('@') != true && key.indexOf('()') < 0))) { + if (isRaw != true) { + if (k.endsWith('@')) { + k = k.substring(0, k.length - 1); + } + + var lk = k.toLowerCase() + if (lk == 'id' || lk.replaceAll('_', '') == 'userid') { + continue; + } + } + + App.options.push({ + name: quote + k + quote, + type: stringType, + comment: isRaw ? '原始SQL片段' : '条件组合' + }); + + ks += (first ? '' : (isRaw ? ',' : ' | ')) + k; + first = false; + } + } + ; + + if (StringUtil.isNotEmpty(ks, true)) { + App.options.push({ + name: quote + ks + quote, + type: stringType, + comment: isRaw ? '原始SQL片段' : '条件组合' + }); + } + break; + case '@schema': + var schemas = StringUtil.split(App.schema); + if (schemas != null) { + for (var i = 0; i < schemas.length; i++) { + var sch = schemas[i]; + if (StringUtil.isNotEmpty(sch, true)) { + App.options.push({ + name: quote + sch + quote, + type: stringType, + comment: '集合空间(数据库名/模式)' + }); + } + } + } + break; + case '@database': + App.options = [{ + name: isSingle ? "'MYSQL'" : '"MYSQL"', + type: stringType, + comment: 'MySQL' + }, { + name: isSingle ? "'POSTGRESQL'" : '"POSTGRESQL"', + type: stringType, + comment: 'PostgreSQL' + }, { + name: isSingle ? "'SQLSERVER'" : '"SQLSERVER"', + type: stringType, + comment: 'SQLServer' + }, { + name: isSingle ? "'ORACLE'" : '"ORACLE"', + type: stringType, + comment: 'Oracle' + }, { + name: isSingle ? "'DB2'" : '"DB2"', + type: stringType, + comment: 'DB2' + }, { + name: isSingle ? "'DAMENG'" : '"DAMENG"', + type: stringType, + comment: '达梦数据库' + }, { + name: isSingle ? "'CLICKHOUSE'" : '"CLICKHOUSE"', + type: stringType, + comment: 'ClickHouse' + }, { + name: isSingle ? "'SQLITE'" : '"SQLITE"', + type: stringType, + comment: 'SQLite' + }, { + name: isSingle ? "'TDENGINE'" : '"TDENGINE"', + type: stringType, + comment: 'TDengine' + }]; + break; + case '@role': + App.options = [{ + name: isSingle ? "'UNKNOWN'" : '"UNKNOWN"', + type: stringType, + comment: '来访角色: 未登录' + }, { + name: isSingle ? "'LOGIN'" : '"LOGIN"', + type: stringType, + comment: '来访角色: 已登录' + }, { + name: isSingle ? "'CIRCLE'" : '"CIRCLE"', + type: stringType, + comment: '来访角色: 圈子成员' + }, { + name: isSingle ? "'CONTACT'" : '"CONTACT"', + type: stringType, + comment: '来访角色: 联系人' + }, { + name: isSingle ? "'OWNER'" : '"OWNER"', + type: stringType, + comment: '来访角色: 拥有者' + }, { + name: isSingle ? "'ADMIN'" : '"ADMIN"', + type: stringType, + comment: '来访角色: 管理员' + }]; + break; + case '@cache': + App.options = [{ + name: "0", + type: intType, + comment: '缓存方式: 全部' + }, { + name: "1", + type: intType, + comment: '缓存方式: 磁盘' + }, { + name: "2", + type: intType, + comment: '缓存方式: 内存' + }]; + break; + case 'count': + case 'page': + var isPage = key == 'page'; + for (var i = 0; i < 100; i++) { + App.options.push({ + name: StringUtil.get(i), // 直接用数字导致重复生成 JSON + type: intType, + comment: isPage ? '分页页码' : '每页数量' + }); + } + break; + case 'tag': + case 'version': + var isVersion = key == 'version'; + var requestList = docObj == null ? null : docObj['Request[]']; + if (requestList != null) { + for (var i = 0; i < requestList.length; i++) { + var item = requestList[i]; + if (item == null) { + continue; + } + + App.options.push({ + name: isVersion ? item.version : item.tag, + type: intType, + comment: isVersion ? '请求版本' : '请求标识' + }); + } + } + break; + case 'query': + App.options = [{ + name: "0", + type: intType, + comment: '查询内容: 数据' + }, { + name: "1", + type: intType, + comment: '查询内容: 数量' + }, { + name: "2", + type: intType, + comment: '查询内容: 全部' + }]; + break; + case 'range': + App.options = [{ + name: quote + "ANY" + quote, + type: stringType, + comment: '比较范围: 任意' + }, { + name: quote + "ALL" + quote, + type: stringType, + comment: '比较范围: 全部' + }]; + break; + case 'compat': + App.options = [{ + name: "true", + type: booleanType, + comment: '兼容统计: 开启' + }, { + name: "false", + type: booleanType, + comment: '兼容统计: 关闭' + }]; + break; + case '@explain': + App.options = [{ + name: "true", + type: booleanType, + comment: '性能分析: 开启' + }, { + name: "false", + type: booleanType, + comment: '性能分析: 关闭' + }]; + break; + case '': + App.options = [{ + name: "true", + type: booleanType, + comment: '性能分析: 开启' + }, { + name: "false", + type: booleanType, + comment: '性能分析: 关闭' + }]; + break; + default: + if (key.endsWith('()')) { + if (key.startsWith('@')) { + App.options = [{ + name: quote + 'fun(arg0,arg1)' + quote, + type: stringType, + comment: '存储过程' + }]; + } else { + var functionList = docObj == null ? null : docObj['Function[]']; + if (functionList != null) { + for (var i = 0; i < functionList.length; i++) { + var item = functionList[i]; + var name = item == null ? null : item.name; + if (StringUtil.isEmpty(name, true)) { + continue; + } + + App.options.push({ + name: quote + name + '(' + StringUtil.trim(item.arguments) + ')' + quote, + type: CodeUtil.getType4Language(App.language, item.returnType), + comment: item.rawDetail || item.detail + }); + } + } + } + } + break; + } + + var columnList = App.getColumnListWithModelName(table); + if (columnList != null) { + var isHaving = key == '@having'; + var arr = ['max', 'min', 'sum', 'avg', 'length', 'len', 'json_length']; + + var ks = ''; + var first = true; + for (var j = 0; j < columnList.length; j++) { + var column = App.getColumnObj(columnList, j) + var name = column == null ? null : column.column_name; + if (StringUtil.isEmpty(name, true)) { + continue; + } + + var k = name; + switch (key) { + case '@having': + var which = Math.floor(arr.length * Math.random()); + k = arr[which] + '(' + name + ')' + (Math.random() < 0.2 ? '<82010' : (Math.random() < 0.5 ? '>3' : '%2=0')); + break; + case '@order': + k = name + (Math.random() < 0.2 ? '' : (Math.random() < 0.5 ? '-' : '+')); + break; + case '@cast': + var t = column.column_type; + var ind = t == null ? -1 : t.indexOf('('); + k = name + ':' + StringUtil.toUpperCase(ind < 0 ? t : t.substring(0, ind)); + break; + // case '@column': + // case '@group': + // case '@json': + // case '@null': + default: + k = name; + break; + } + + App.options.push({ + name: quote + k + quote, + type: CodeUtil.getType4Language(App.language, column.column_type), + comment: column.column_comment + }) + + ks += (first ? '' : (isHaving ? ';' : ',')) + k; + first = false; + } + + App.options.push({ + name: quote + ks + quote, + type: stringType, + comment: '所有字段组合' + }) + } + } + } else { + App.options = [ + { + name: "@column", + type: varcharType, + comment: "返回字段" + }, + {name: "@from@", type: objectType, comment: "数据来源"}, + { + name: "@group", + type: varcharType, + comment: "分组方式" + }, { + name: "@having", + type: varcharType, + comment: "聚合函数" + }, { + name: "@order", + type: varcharType, + comment: "排序方式" + }, { + name: "@combine", + type: varcharType, + comment: "条件组合" + }, { + name: "@raw", + type: varcharType, + comment: "原始SQL片段" + }, { + name: "@json", + type: varcharType, + comment: "转为JSON" + }, { + name: "@null", + type: varcharType, + comment: "NULL值字段" + }, + {name: "@cast", type: varcharType, comment: "类型转换"}, + { + name: "@schema", + type: varcharType, + comment: "集合空间(数据库名/模式)" + }, { + name: "@database", + type: varcharType, + comment: "数据库类型" + }, { + name: "@datasource", + type: varcharType, + comment: "跨数据源" + }, + {name: "@role", type: varcharType, comment: "来访角色"}, + { + name: "@cache", + type: varcharType, + comment: "缓存方式" + }, { + name: "@explain", + type: varcharType, + comment: "性能分析" + }, { + name: "key-()", + type: varcharType, + comment: "远程函数: 优先执行" + }, { + name: "key()", + type: varcharType, + comment: "远程函数" + }, { + name: "key+()", + type: varcharType, + comment: "远程函数: 延后执行" + }, { + name: "@key-()", + type: varcharType, + comment: "存储过程: 优先执行" + }, { + name: "@key()", + type: varcharType, + comment: "存储过程" + }, { + name: "@key+()", + type: varcharType, + comment: "存储过程: 延后执行" + }, + ]; + + if (isArrayKey) { + App.options = [ + {name: "count", type: intType, comment: "每页数量"}, + {name: "page", type: intType, comment: "分页页码"}, + {name: "query", type: intType, comment: "查询内容"}, + {name: "compat", type: booleanType, comment: "兼容统计"}, + {name: "join", type: varcharType, comment: "联表查询"}, + {name: "[]", type: arrayType, comment: "数组对象"}, + ]; + } else if (isSubqueryKey) { + App.options = [ + { + name: "from", + type: varcharType, + comment: "主表名称" + }, + {name: "count", type: intType, comment: "每页数量"}, + {name: "page", type: intType, comment: "分页页码"}, + { + name: "range", + type: varcharType, + comment: "比较范围" + }, { + name: "join", + type: varcharType, + comment: "联表查询" + }, + ]; + } else if (App.isTableKey(table)) { + var columnList = App.getColumnListWithModelName(table); + if (columnList != null) { + for (var j = 0; j < columnList.length; j++) { + var column = App.getColumnObj(columnList, j) + var name = column == null ? null : column.column_name; + if (StringUtil.isEmpty(name, true)) { + continue; + } + + App.options.push({ + name: name, + type: CodeUtil.getType4Language(App.language, column.column_type), + comment: column.column_comment + }) + } + + var arr = ['{}', '$', '~', '<>', '>', '<', '<=', '>=', '!', '}{', '%', '&$', '|{}', '!~', '+', '-']; + for (var j = 0; j < columnList.length; j++) { + var column = App.getColumnObj(columnList, j) + var name = column == null ? null : column.column_name; + if (StringUtil.isEmpty(name, true)) { + continue; + } + + var which = Math.floor(arr.length * Math.random()); + App.options.push({ + name: name + arr[which], + type: CodeUtil.getType4Language(App.language, column.column_type), + comment: column.column_comment + }) + } + } + } else { + App.options.push([ + {name: "format", type: varcharType, comment: "格式化"}, + {name: "tag", type: varcharType, comment: "请求标识"}, + {name: "version", type: varcharType, comment: "请求版本"}, + {name: "[]", type: arrayType, comment: "数组对象"}, + ]) + } + + if (App.isTableKey(table) != true) { + var tableList = docObj['[]'] + if (tableList != null) { + for (var j = 0; j < tableList.length; j++) { + var tableObj = App.getTableObj(j); + var name = tableObj == null ? null : App.getModelNameByTableName(tableObj.table_name); + if (StringUtil.isEmpty(name, true)) { + continue; + } + + App.options.push({ + name: name, + type: objectType, + comment: tableObj.table_comment + }) + } + } + } + + } + } + } + + if (App.options.length > 0) { + vOption.focus(); + } + else { + target.focus(); + } + } + }, + watch: { + jsoncon: function () { + this.showJsonView() + } + }, + computed: { + theme: function () { + var th = this.themes[this.checkedTheme] + var result = {} + var index = 0; + ['key', 'String', 'Number', 'Boolean', 'Null', 'link-link'].forEach(function(key) { + result[key] = th[index] + index++ + }) + return result + } + }, + created: function () { + try { //可能URL_BASE是const类型,不允许改,这里是初始化,不能出错 + var url = this.getCache('', 'URL_BASE') + if (StringUtil.isEmpty(url, true) == false) { + URL_BASE = url + } + var database = this.getCache('', 'database') + if (StringUtil.isEmpty(database, true) == false) { + this.database = CodeUtil.database = database + } + var schema = this.getCache('', 'schema') + if (StringUtil.isEmpty(schema, true) == false) { + this.schema = CodeUtil.schema = schema + } + var language = this.getCache('', 'language') + if (StringUtil.isEmpty(language, true) == false) { + this.language = CodeUtil.language = language + } + + var methods = this.getCache('', 'methods') + this.methods = methods instanceof Array ? methods : StringUtil.split(methods, ',', true) + var types = this.getCache('', 'types') + this.types = types instanceof Array ? types : StringUtil.split(types, ',', true) + + var otherEnv = this.getCache('', 'otherEnv') + if (StringUtil.isEmpty(otherEnv, true) == false) { + this.otherEnv = otherEnv + } + var server = this.getCache('', 'server') + if (StringUtil.isEmpty(server, true) == false) { + this.server = server + } + var thirdParty = this.getCache('', 'thirdParty') + if (StringUtil.isEmpty(thirdParty, true) == false) { + this.thirdParty = thirdParty + } + + this.locals = this.getCache('', 'locals', []) + + this.isDelegateEnabled = this.getCache('', 'isDelegateEnabled', this.isDelegateEnabled) + this.isEncodeEnabled = this.getCache('', 'isEncodeEnabled', this.isEncodeEnabled) + this.isEnvCompareEnabled = this.getCache('', 'isEnvCompareEnabled', this.isEnvCompareEnabled) + //预览了就不能编辑了,点开看会懵 this.isPreviewEnabled = this.getCache('', 'isPreviewEnabled', this.isPreviewEnabled) + this.isStatisticsEnabled = false // 解决每次都查不到有效 Response this.getCache('', 'isStatisticsEnabled', this.isStatisticsEnabled) + this.isHeaderShow = this.getCache('', 'isHeaderShow', this.isHeaderShow) + this.isRandomShow = this.getCache('', 'isRandomShow', this.isRandomShow) + } catch (e) { + console.log('created try { ' + + '\nvar url = this.getCache(, url) ...' + + '\n} catch (e) {\n' + e.message) + } + try { //这里是初始化,不能出错 + var projectHost = this.getCache('', 'projectHost') + if (projectHost != null) { + this.projectHost = projectHost + } + + var projectHosts = this.getCache('', 'projectHosts') + if (projectHosts != null && projectHosts.length >= 1) { + this.projectHosts = projectHosts + } + } catch (e) { + console.log('created try { ' + + '\nvar projectHosts = this.getCache("", projectHosts)' + + '\n} catch (e) {\n' + e.message) + } + try { //这里是初始化,不能出错 + var accounts = this.getCache(URL_BASE, 'accounts') + if (accounts != null && accounts.length >= 1) { + this.accounts = accounts + this.currentAccountIndex = this.getCache(URL_BASE, 'currentAccountIndex') + } + } catch (e) { + console.log('created try { ' + + '\nvar accounts = this.getCache(URL_BASE, accounts)' + + '\n} catch (e) {\n' + e.message) + } + try { //这里是初始化,不能出错 + var otherEnvTokenMap = this.getCache(this.otherEnv, 'otherEnvTokenMap') + if (otherEnvTokenMap != null) { + this.otherEnvTokenMap = otherEnvTokenMap + } + } catch (e) { + console.log('created try { ' + + '\nvar otherEnvTokenMap = this.getCache(this.otherEnv, otherEnvTokenMap)' + + '\n} catch (e) {\n' + e.message) + } + try { //这里是初始化,不能出错 + var otherEnvCookieMap = this.getCache(this.otherEnv, 'otherEnvCookieMap') + if (otherEnvCookieMap != null) { + this.otherEnvCookieMap = otherEnvCookieMap + } + } catch (e) { + console.log('created try { ' + + '\nvar otherEnvCookieMap = this.getCache(this.otherEnv, otherEnvCookieMap)' + + '\n} catch (e) {\n' + e.message) + } + + try { //可能URL_BASE是const类型,不允许改,这里是初始化,不能出错 + this.User = this.getCache(this.server, 'User', {}) + this.isCrossEnabled = this.getCache(this.server, 'isCrossEnabled', this.isCrossEnabled) + this.isMLEnabled = this.getCache(this.server, 'isMLEnabled', this.isMLEnabled) + this.crossProcess = this.isCrossEnabled ? '交叉账号:已开启' : '交叉账号:已关闭' + this.testProcess = this.isMLEnabled ? '机器学习:已开启' : '机器学习:已关闭' + // this.host = this.getBaseUrl() + + this.page = this.getCache(this.server, 'page', this.page) + this.count = this.getCache(this.server, 'count', this.count) + + this.caseGroupPage = this.getCache(this.server, 'caseGroupPage', this.caseGroupPage) + this.caseGroupCount = this.getCache(this.server, 'caseGroupCount', this.caseGroupCount) + this.caseGroupSearch = this.getCache(this.server, 'caseGroupSearch', this.caseGroupSearch) + this.caseGroupPages = this.getCache(this.server, 'caseGroupPages', this.caseGroupPages) + this.caseGroupCounts = this.getCache(this.server, 'caseGroupCounts', this.caseGroupCounts) + this.caseGroupSearches = this.getCache(this.server, 'caseGroupSearches', this.caseGroupSearches) + + this.testCasePage = this.getCache(this.server, 'testCasePage', this.testCasePage) + this.testCaseCount = this.getCache(this.server, 'testCaseCount', this.testCaseCount) + this.testCasePages = this.getCache(this.server, 'testCasePages', this.testCasePages) + this.testCaseCounts = this.getCache(this.server, 'testCaseCounts', this.testCaseCounts) + this.testCaseSearches = this.getCache(this.server, 'testCaseSearches', this.testCaseSearches) + + this.randomPage = this.getCache(this.server, 'randomPage', this.randomPage) + this.randomCount = this.getCache(this.server, 'randomCount', this.randomCount) + this.randomSubPage = this.getCache(this.server, 'randomSubPage', this.randomSubPage) + this.randomSubCount = this.getCache(this.server, 'randomSubCount', this.randomSubCount) + + this.delegateId = this.getCache(this.server, 'delegateId', this.delegateId) + this.otherEnvDelegateId = this.getCache(this.server, 'otherEnvDelegateId', this.otherEnvDelegateId) + + CodeUtil.thirdPartyApiMap = this.getCache(this.thirdParty, 'thirdPartyApiMap') + } catch (e) { + console.log('created try { ' + + '\nthis.User = this.getCache(this.server, User, {})' + + '\n} catch (e) {\n' + e.message) + } + + try { + var accounts = this.accounts + var num = accounts == null ? 0 : accounts.length + for (var i = -1; i <= num; i++) { + this.resetCount(this.getSummary(i), false, false, i) + } + } catch (e) { + console.log('created try { ' + + '\nthis.User = this.getCache(this.server, User, {})' + + '\n} catch (e) {\n' + e.message) + } + + //无效,只能在index里设置 vUrl.value = this.getCache('', 'URL_BASE') + + this.listHistory() +// if (this.isScriptShow) { + this.changeScriptType() + this.listScript() +// } + + var isLoggedIn = this.User != null && this.User.id != null && this.User.id > 0 + if (isLoggedIn) { + this.listProjectHost() + + if (this.caseShowType != 1 && this.casePaths.length <= 0 && this.caseGroups.length <= 0) { + this.selectCaseGroup(-1, null) + } + } + + var rawReq = getRequestFromURL() + if (rawReq == null || (StringUtil.isEmpty(rawReq.type, true) && StringUtil.isEmpty(rawReq.reportId, true))) { + this.transfer() + + if (isLoggedIn) { + setTimeout(function () { + App.showTestCase(true, false) // 本地历史仍然要求登录 this.User == null || this.User.id == null) + }, 1000) + } + } + else { + setTimeout(function () { + isSingle = ! isSingle + + var hasTestArg = false // 避免 http://localhost:63342/APIAuto/index.html?_ijt=fh8di51h7qip2d1s3r3bqn73nt 这种无意义参数 + if (StringUtil.isNotEmpty(rawReq.method, true)) { + hasTestArg = true + App.method = StringUtil.toUpperCase(rawReq.method, true) + if (App.methods == null) { + App.methods = [App.method] + } + else if (App.methods.indexOf(App.method) < 0) { + App.methods.push(App.method) + } + } + if (StringUtil.isNotEmpty(rawReq.type, true)) { + hasTestArg = true + App.type = StringUtil.toUpperCase(rawReq.type, true) + if (App.types == null) { + App.types = [App.type] + } + else if (App.types.indexOf(App.type) < 0) { + App.types.push(App.type) + } + } + + if (StringUtil.isNotEmpty(rawReq.url, true)) { + hasTestArg = true + vUrl.value = StringUtil.trim(rawReq.url) + } + + var decode = false + if (StringUtil.isNotEmpty(rawReq.decode, true)) { + hasTestArg = true + decode = rawReq.decode == 'true' + } + + if (StringUtil.isNotEmpty(rawReq.json, true)) { + hasTestArg = true + vInput.value = StringUtil.trim(decode ? rawReq.json.replaceAll('\\\\', '') : rawReq.json) + } + + if (StringUtil.isNotEmpty(rawReq.header, true)) { + hasTestArg = true + vHeader.value = StringUtil.trim(rawReq.header, true) + App.isHeaderShow = true + } + + if (StringUtil.isNotEmpty(rawReq.random, true)) { + hasTestArg = true + vRandom.value = StringUtil.trim(rawReq.random, true) + App.isRandomShow = true + App.isRandomListShow = false + } + + if (StringUtil.isNotEmpty(rawReq.reportId, true)) { + try { + App.reportId = + StringUtil.trim(rawReq.reportId, true) + if (Number.isNaN(App.reportId)) { + throw new Error('URL query 中 reportId= 的值必须是 0 以上整数!') + } + App.isStatisticsEnabled = true + App.isRandomShow = true + App.isRandomListShow = false + App.showTestCase(true, false) + } catch (e) { + App.onResponse(null, {}, e) + alert(e) + } + } + + var delayTime = 0 + + // URL 太长导致截断和乱码 + if (StringUtil.isNotEmpty(rawReq.setting, true)) { + var save = rawReq.save == 'true' + try { + var setting = parseJSON(StringUtil.trim(rawReq.setting, true)) || {} + + if ((setting.count != null && setting.count != App.count) + || (setting.page != null && setting.page != App.page) + || (setting.search != null && setting.search != App.search)) { + delayTime += Math.min(5000, 30*(setting.count) + 1000) + App.setDoc(""); + App.getDoc(function (d) { + App.setDoc(d); + }) + } + + for (var k in setting) { + var v = k == null ? null : setting[k] + if (v == null) { + continue + } + App[k] = v // App.$data[k] = app[k] + + if (save) { + App.saveCache('', k, v) + } + } + + if (setting.isTestCaseShow || (setting.isRandomShow && setting.isRandomListShow)) { + var isTest = hasTestArg + hasTestArg = false + + App.autoTest(null, delayTime, isTest, rawReq, setting) + } + } catch (e) { + log(e) + } + } + + App.handleTestArg(hasTestArg, rawReq, delayTime) + }, 2000) + + } + + + // 快捷键 CTRL + I 格式化 JSON + document.addEventListener('keydown', function(event) { + // alert(event.key) 小写字母 i 而不是 KeyI + + var target = event.target; + if (target == vAskAI || target == vSearch || target == vTestCaseSearch || target == vCaseGroupSearch + || target == vChainGroupSearch || target == vChainGroupAdd || target == vChainAdd) { + return + } + + var keyCode = event.keyCode; + var isEnter = keyCode === 13; + if (isEnter && target == vUrl) { + App.send(false); + event.preventDefault(); + return + } + var isDel = keyCode === 8 || keyCode === 46; // backspace 和 del + var isChar = (keyCode >= 48 && keyCode <= 90) || (keyCode >= 106 && keyCode <= 111) || (keyCode >= 186 && keyCode <= 222); + + currentTarget = target; + + if (keyCode === 27) { // ESC + if (document.activeElement == vOption || App.options.length > 0) { + App.options = []; + if (target != null) { + target.focus(); + } + return; + } + } + else if (keyCode === 40 || keyCode === 38) { // 方向键 上 和 下 + if (document.activeElement == vOption || App.options.length > 0) { + // currentTarget = target; + if (keyCode === 38) { + if (App.selectIndex >= 0) { + App.selectIndex -- + App.selectInput(App.selectIndex < 0 ? null : App.options[App.selectIndex], App.selectIndex) + } + } else if (App.selectIndex < App.options.length) { + App.selectIndex ++ + App.selectInput(App.selectIndex >= App.options.length ? null : App.options[App.selectIndex], App.selectIndex) + } + + // var options = document.activeElement == vOption || App.options.length > 0 ? App.options : null; // vOption.options : null; + // if (options != null) { + // for (var i = 0; i < options.length; i++) { + // var opt = options[i] + // if (opt != null && (opt.selected || i == App.selectIndex)) { + // if (keyCode === 38) { + // if (i > 0) { + // opt.selected = false + // options[i - 1].selected = true + // App.selectInput(App.options[i - 1], i - 1) + // } + // } else { + // if (i < options.length - 1) { + // opt.selected = false + // options[i + 1].selected = true + // App.selectInput(App.options[i + 1], i + 1) + // } + // } + // + // break + // } + // } + + event.preventDefault(); + return; + // } + } + } + else if (isEnter || isDel) { // enter || delete + if (document.activeElement == vOption || App.options.length > 0) { // hasFocus is undefined vOption.hasFocus()) { + + var options = vOption.options || App.options + if (options != null && options.length > 0) { + if (isDel) { + var selectionStart = target.selectionStart; + var selectionEnd = target.selectionEnd; + + var text = StringUtil.get(target.value); + var before = text.substring(0, selectionStart); + var after = text.substring(selectionEnd); + + App.showOptions(target, text, before, after); + } + else { + event.preventDefault(); + + for (var i = 0; i < options.length; i++) { + var opt = options[i] + if (opt != null && (opt.selected || i == App.selectIndex)) { + // currentTarget = target; + App.selectInput(App.options[i], i, true); + return; + } + } + + App.selectIndex - 1; + App.options = []; + } + } + + return; + } + + if (target == vUrl) { + } + else if (target != null && target != vOption) { + var selectionStart = target.selectionStart; + var selectionEnd = target.selectionEnd; + + var text = StringUtil.get(target.value); + var before = text.substring(0, selectionStart); + var after = text.substring(selectionEnd); + + var firstIndex = isEnter ? after.indexOf('\n') : -1; + var firstLine = firstIndex <= 0 ? '' : after.substring(0, firstIndex); + var tfl = firstLine.trimLeft(); + + var hasRight = tfl.length > 0; + if (isEnter && hasRight != true) { + var aft = after.substring(firstIndex + 1); + var fi = aft.indexOf('\n'); + tfl = fi < 0 ? aft : aft.substring(0, fi); + } + + // var lastLineStart = isEnter && tfl.length > 0 ? -1 : before.lastIndexOf('\n') + 1; + var lastLineStart = before.lastIndexOf('\n') + 1; + var lastLine = lastLineStart < 0 ? '' : before.substring(lastLineStart); + + var prefixEnd = 0; + for (var i = 0; i < lastLine.length; i++) { + if (lastLine.charAt(i).trim().length > 0) { + if (isDel) { + prefixEnd = 0; + } + break; + } + + prefixEnd += 1; + } + + + var prefix = prefixEnd <= 0 ? '' : lastLine.substring(0, prefixEnd); + + var isStart = false; + var isEnd = false; + var hasPadding = false; + var hasComma = false; + var isVar = false; + var isJSON = false; + var hasNewKey = null; + if (isEnter) { + isEnd = tfl.startsWith(']') || tfl.startsWith('}') + var tll = lastLine.trimRight(); + if (target != vInput) { + hasNewKey = ! hasRight; + } + else if (isEnd || hasRight || tll.endsWith('[')) { + hasNewKey = false; + } + // else if (tll.indexOf('":') > 1 || tll.indexOf("':") > 1) { + else if (tll.indexOf(':') > 1) { // (target == vInput ? 1 : 0) || (target == vScript && tll.indexOf('=') > 0)) { + hasNewKey = true; + } + // else { + // var ind = tll.indexOf(':') + // if (ind > 0) { + // var ind2 = tll.indexOf('"') + // } + // } + + var lastInd = tfl.lastIndexOf('('); + isVar = target == vScript && (before.indexOf('{') < 0 || (lastInd > 0 && tfl.endsWith('{') && tfl.lastIndexOf(')') > lastInd)); + isJSON = target == vInput || (isVar != true && target == vScript); + + isStart = tll.endsWith('{') || tll.endsWith('['); + hasPadding = hasRight != true && isStart; + + tll = before.trimRight(); + hasComma = isJSON && isStart != true && isEnd != true && hasRight != true && tll.endsWith(',') != true; + if (hasComma) { + for (var i = before.length; i >= 0; i--) { + if (before.charAt(i).trim().length > 0) { + break; + } + + selectionStart -= 1; + } + + before = tll + ','; + selectionStart += 1; + } + + if (hasNewKey == null) { + hasNewKey = tll.endsWith('{'); + } + + } + + if (prefix.length > 0 || (isEnter && target != vInput)) { + if (isEnter) { + // if (target == vScript) { + // hasNewKey = false // TODO 把全局定义的 function, variable 等放到 options。 var value = fun() + // target.value = before + '\n' + prefix + (hasPadding ? ' ' : '') + // + (isEnd ? after : (hasRight ? (hasPadding ? tfl : tfl) : '') + '\n' + after.substring(firstIndex + 1) + // ); + // target.selectionEnd = target.selectionStart = selectionStart + prefix.length + 1 + (hasPadding ? 4 : 0); + // } + // else { + var newText = before + '\n' + prefix + (hasPadding ? ' ' : '') + + (hasNewKey ? (isJSON != true ? (isVar ? 'var ' : '') : (isSingle ? "''" : '""')) + + (isVar ? ' = ' : ': ') + (target == vHeader ? '' : 'null') + (hasComma || isEnd || isJSON != true ? '' : ',') : '') + + (isEnd ? after : (hasRight ? (hasPadding ? tfl.trimLeft() : tfl) : '') + '\n' + after.substring(firstIndex + 1) + ); + target.value = newText + if (target == vScript) { // 不这样会自动回滚 + App.scripts[App.scriptType][App.scriptBelongId][App.isPreScript ? 'pre' : 'post'].script = newText + } + + target.selectionEnd = target.selectionStart = selectionStart + prefix.length + (hasComma && isJSON ? 1 : 0) + + (hasNewKey ? 1 : 0) + (hasPadding ? 4 : 0) + (isVar ? 4 : (isJSON ? 1 : 0)); + // } + event.preventDefault(); + + if (hasNewKey) { + App.showOptions(target, text, before, after); + if (target == vInput) { + inputted = target.value; + } + return; + } + } + else if (isDel) { + var newStr = (selectionStart == selectionEnd ? StringUtil.get(before.substring(0, lastLineStart - 1) + ' ') : before) + after; + target.value = newStr; + if (target == vScript) { // 不这样会自动回滚 + App.scripts[App.scriptType][App.scriptBelongId][App.isPreScript ? 'pre' : 'post'].script = newStr + } + + target.selectionEnd = target.selectionStart = selectionStart == selectionEnd ? lastLineStart - 1 : selectionStart; + event.preventDefault(); + } + + if (target == vInput) { + inputted = target.value; + } + } + } + } + else if (target != null && keyCode === 9) { // Tab 加空格 + try { + var selectionStart = target.selectionStart; + var selectionEnd = target.selectionEnd; + + var text = StringUtil.get(target.value); + var before = text.substring(0, selectionStart); + var after = text.substring(selectionEnd); + + var ind = before.lastIndexOf('\n'); + var start = ind < 0 ? 0 : ind + 1; + ind = after.indexOf('\n'); + var end = ind < 0 ? text.length : selectionEnd + ind - 1; + + var selection = text.substring(start, end); + var lines = StringUtil.split(selection, '\n'); + + var newStr = text.substring(0, start); + + var prefix = ' '; + var prefixLen = prefix.length; + for (var i = 0; i < lines.length; i ++) { + var l = lines[i] || ''; + if (i > 0) { + newStr += '\n'; + } + + newStr += prefix + l; + if (i <= 0) { + selectionStart += prefixLen; + } + selectionEnd += prefixLen; + } + + newStr += text.substring(end); + + target.value = newStr; + if (target == vScript) { // 不这样会自动回滚 + App.scripts[App.scriptType][App.scriptBelongId][App.isPreScript ? 'pre' : 'post'].script = newStr + } + + event.preventDefault(); + if (target == vInput) { + inputted = newStr; + } + + target.selectionStart = selectionStart; + target.selectionEnd = selectionEnd; + } catch (e) { + log(e) + } + } + else if ((event.ctrlKey || event.metaKey) && keyCode == 83) { // Ctrl + S 保存 + App.showSave(true) + event.preventDefault() + } + else if (target != null && (event.ctrlKey || event.metaKey) && ([68, 73, 191].indexOf(keyCode) >= 0 || (isChar != true && event.shiftKey != true))) { + var selectionStart = target.selectionStart; + var selectionEnd = target.selectionEnd; + + // 这里拿不到 clipboardData if (keyCode === 86) { + + if (keyCode === 73) { // Ctrl + 'I' 格式化 + try { + if (target == vInput) { + var json = JSON.stringify(JSON5.parse(vInput.value), null, ' '); + vInput.value = inputted = isSingle ? App.switchQuote(json) : json; + } + else { + var lines = StringUtil.split(target.value, '\n'); + var newStr = ''; + + for (var i = 0; i < lines.length; i ++) { + var l = StringUtil.trim(lines[i]) || ''; + if (l.startsWith('//')) { + continue; + } + + var ind = l.lastIndexOf(' //'); + l = ind < 0 ? l : StringUtil.trim(l.substring(0, ind)); + + if (target == vHeader || target == vRandom) { + ind = l.indexOf(':'); + if (ind >= 0) { + var left = target == vHeader ? StringUtil.trim(l.substring(0, ind)) : l.substring(0, ind); + l = left + ': ' + StringUtil.trim(l.substring(ind + 1)); + } + } + + if (l.length > 0) { + newStr += '\n' + l; + } + } + + newStr = StringUtil.trim(newStr); + target.value = newStr; + if (target == vScript) { // 不这样会自动回滚 + App.scripts[App.scriptType][App.scriptBelongId][App.isPreScript ? 'pre' : 'post'].script = newStr + } + } + } catch (e) { + log(e) + } + } + else if (target != null && keyCode === 191) { // Ctrl + '/' 注释与取消注释 + try { + var text = StringUtil.get(target.value); + var before = text.substring(0, selectionStart); + var after = text.substring(selectionEnd); + + var ind = before.lastIndexOf('\n'); + var start = ind < 0 ? 0 : ind + 1; + ind = after.indexOf('\n'); + var end = ind < 0 ? text.length : selectionEnd + ind - 1; + + var selection = text.substring(start, end); + var lines = StringUtil.split(selection, '\n'); + + var newStr = text.substring(0, start); + + var commentSign = '//' + var commentSignLen = commentSign.length + + for (var i = 0; i < lines.length; i ++) { + var l = lines[i] || ''; + if (i > 0) { + newStr += '\n'; + } + + if (StringUtil.trim(l).startsWith(commentSign)) { + var ind = l.indexOf(commentSign); + var suffix = l.substring(ind + commentSignLen); + if (suffix.startsWith(' ')) { + suffix = suffix.substring(1); + if (i <= 0) { + selectionStart -= 1; + } + selectionEnd -= 1; + } + + newStr += StringUtil.get(l.substring(0, ind)) + StringUtil.get(suffix) + if (i <= 0) { + selectionStart -= commentSignLen; + } + selectionEnd -= commentSignLen; + } + else { + newStr += commentSign + ' ' + l; + if (i <= 0) { + selectionStart += commentSignLen + 1; + } + selectionEnd += commentSignLen + 1; + } + } + + newStr += text.substring(end); + + target.value = newStr; + if (target == vScript) { // 不这样会自动回滚 + App.scripts[App.scriptType][App.scriptBelongId][App.isPreScript ? 'pre' : 'post'].script = newStr + } + + if (target == vInput) { + inputted = newStr; + } + } catch (e) { + log(e) + } + } + else if (target != null && keyCode == 68) { // Ctrl + 'D' 删除行 + try { + var text = StringUtil.get(target.value); + var before = text.substring(0, selectionStart); + var after = text.substring(selectionEnd); + + var lastIndex = before.lastIndexOf('\n'); + var firstIndex = after.indexOf('\n'); + + var newStr = (lastIndex < 0 ? '' : before.substring(0, lastIndex)) + '\n' + after.substring(firstIndex + 1); + target.value = newStr; + if (target == vScript) { // 不这样会自动回滚 + App.scripts[App.scriptType][App.scriptBelongId][App.isPreScript ? 'pre' : 'post'].script = newStr + } + + selectionEnd = selectionStart = lastIndex + 1; + event.preventDefault(); + + if (target == vInput) { + inputted = newStr; + } + } catch (e) { + log(e) + } + } + + target.selectionStart = selectionStart; + target.selectionEnd = selectionEnd; + } + else if (target != null && (event.shiftKey || isChar)) { + if (isChar && App.options.length > 0) { + var key = StringUtil.get(event.key); + + var selectionStart = target.selectionStart; + var selectionEnd = target.selectionEnd; + + var text = StringUtil.get(target.value); + var before = text.substring(0, selectionStart); + var after = text.substring(selectionEnd); + var selection = text.substring(selectionStart, selectionEnd); + text = before + (isInputValue && selection == 'null' ? '' : selection) + key + after; + + target.value = text; + if (target == vScript) { // 不这样会自动回滚 + App.scripts[App.scriptType][App.scriptBelongId][App.isPreScript ? 'pre' : 'post'].script = text + } + + target.selectionStart = selectionStart; + target.selectionEnd = (isInputValue && selection == 'null' ? selectionStart : selectionEnd) + key.length; + event.preventDefault(); + + App.showOptions(target, text, before, after, isInputValue, key); + } + + return; + } + + App.selectIndex = -1; + App.options = []; + }) + } + } + + if (IS_BROWSER) { + App = new Vue(App) + window.App = App + } + else { + var methods = App.methods + if (methods instanceof Object && (methods instanceof Array == false)) { + App = Object.assign(App, methods) + } + App.autoTest = App.autoTest || methods.autoTest + + var data = App.data + if (data instanceof Object && (data instanceof Array == false)) { + App = Object.assign(App, data) + } + + module.exports = {getRequestFromURL, App} + } + +})() + +// APIJSON >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> + diff --git a/js/server.js b/js/server.js new file mode 100644 index 0000000..ba2a859 --- /dev/null +++ b/js/server.js @@ -0,0 +1,205 @@ +const Koa = require('koa'); +//const cors = require('koa2-cors'); +const bodyParser = require('koa-bodyparser'); +// const Vue = require('vue'); +const {getRequestFromURL, App} = require('./main'); +// const { createBundleRenderer } = require('vue-server-renderer') + +const JSONResponse = require('../apijson/JSONResponse'); +const StringUtil = require('../apijson/StringUtil'); + +var isCrossEnabled = true; // false; +var isLoading = false; +var startTime = 0; +var endTime = 0; +var message = ''; +var error = null; +var timeMsg = ''; +var progressMsg = ''; + +var progress = 0; +var accountProgress = 0; +var testCaseProgress = 0; +var deepProgress = 0; +var randomProgress = 0; + +function update() { + if (isLoading != true) { + return; + } + + if (error != null) { + isLoading = false; + } + + var curTime = isLoading ? (new Date()).getTime() : null; + if (endTime <= 0 || isLoading) { + endTime = curTime; + } + var duration = endTime - startTime; + var dd = new Date(duration + 60000*(new Date().getTimezoneOffset())); + + timeMsg = '\n\nStart Time: ' + startTime + ' = ' + new Date(startTime).toLocaleString() + + (isLoading ? '; \nCurrent' : '\nEnd') + ' Time: ' + endTime + ' = ' + new Date(endTime).toLocaleString() + + '; \nTime Spent: ' + duration + ' = ' + dd.getHours() + ":" + dd.getMinutes() + ":" + dd.getSeconds() + "." + dd.getMilliseconds(); + + var accountDoneCount = App.currentAccountIndex + 1; + var accountAllCount = App.accounts.length; + + accountProgress = isCrossEnabled != true || accountAllCount <= 0 || accountDoneCount >= accountAllCount ? 1 : (accountDoneCount/accountAllCount).toFixed(2); + testCaseProgress = App.doneCount >= App.allCount ? 1 : (App.doneCount/App.allCount).toFixed(2); + deepProgress = App.deepDoneCount >= App.deepAllCount ? 1 : (App.deepDoneCount/App.deepAllCount).toFixed(2); + randomProgress = App.randomDoneCount >= App.randomAllCount ? 1 : (App.randomDoneCount/App.randomAllCount).toFixed(2); + // progress = accountProgress*testCaseProgress*deepProgress*randomProgress; + progress = accountProgress >= 1 ? 1 : (accountProgress + (accountAllCount <= 0 ? 1 : 1/accountAllCount*(testCaseProgress + + (App.allCount <= 0 ? 1 : 1/App.allCount*(deepProgress + (App.deepAllCount <= 0 ? 1 : 1/App.deepAllCount*randomProgress)))))); + + if (progress >= 1) { + isLoading = false; + } + + progressMsg = '\n\nProgress: ' + (100*progress) + '%' + + '\nTest Account: ' + accountDoneCount + ' / ' + accountAllCount + ' = ' + (100*accountProgress) + '%' + + '\nTest Case: ' + App.doneCount + ' / ' + App.allCount + ' = ' + (100*testCaseProgress) + '%' + + '\nDeep Test Case: ' + App.deepDoneCount + ' / ' + App.deepAllCount + ' = ' + (100*deepProgress) + '%' + + '\nRandom & Order: ' + App.randomDoneCount + ' / ' + App.randomAllCount + ' = ' + (100*randomProgress) + '%'; +}; + +const PORT = 3000; + +var done = false; +const app = new Koa(); +// app.use(bodyParser()); +app.use(async ctx => { + console.log(ctx); + var origin = ctx.get('Origin') || ctx.get('origin'); + console.log('origin = ' + origin); + ctx.set('Access-Control-Max-Age', "1000000"); // "-1"); + ctx.set('Access-Control-Allow-Origin', origin); + ctx.set('Access-Control-Allow-Headers', "*"); + ctx.set('Access-Control-Allow-Credentials', 'true'); + ctx.set('Access-Control-Allow-Methods', 'GET,HEAD,POST,PUT,DELETE,OPTIONS,TRACE'); +// ctx.set('Access-Control-Expose-Headers', "*"); + + if (ctx.method == null || ctx.method.toUpperCase() == 'OPTIONS') { + ctx.status = 200; + return; + } + + if (ctx.path == '/test/start' || (isLoading != true && ctx.path == '/test')) { + if (isLoading && ctx.path == '/test/start') { + ctx.status = 200; + ctx.body = ctx.response.body = 'Already started auto testing in node, please wait for minutes...'; + return; + } + + App.isCrossEnabled = isCrossEnabled; // isCrossEnabled = App.isCrossEnabled; + if (isCrossEnabled) { + App.currentAccountIndex = -1; + } + + isLoading = true; + startTime = (new Date()).getTime(); + endTime = startTime; + message = ''; + error = null; + timeMsg = ''; + progressMsg = ''; + + progress = 0; + accountProgress = 0; + testCaseProgress = 0; + deepProgress = 0; + randomProgress = 0; + + update(); + + App.key = ctx.query.key; + if (StringUtil.isNotEmpty(App.key, true)) { + App.testCaseCount = App.data.testCaseCount = 1000; + App.randomCount = App.data.randomCount = 200; + App.randomSubCount = App.data.randomSubCount = 500; + } + + App.autoTest(function (msg, err) { + message = msg; + error = err; + update(); + console.log('autoTest callback(' + msg + ')' + timeMsg + progressMsg); + return Number.isNaN(progress) != true && progress >= 1; + }); + isLoading = true; + isCrossEnabled = App.isCrossEnabled; + + ctx.status = ctx.response.status = 200; // 302; + ctx.body = ctx.response.body = JSON.stringify({ + 'code': 200, + 'msg': 'Auto testing in node...' + }); + + // setTimeout(function () { // 延迟无效 + ctx.redirect('/test/status'); + // }, 1000) + } + else if (ctx.path == '/test/status' || (isLoading && ctx.path == '/test')) { + update(); + if (isLoading) { + // ctx.response.header['refresh'] = "1"; + // ctx.redirect('/status'); + } + + var server = App.server; + var ind = server == null ? -1 : server.indexOf('?'); + + ctx.status = ctx.response.status = 200; // progress >= 1 ? 200 : 302; + ctx.body = ctx.response.body = JSON.stringify({ + 'code': 200, + 'msg': (message || (progress < 1 || isLoading ? 'Auto testing in node...' : 'Done auto testing in node.')) + timeMsg + progressMsg, + 'progress': progress, + 'reportId': App.reportId, + 'link': server + (ind < 0 ? '?' : '&') + 'reportId=' + App.reportId + }); + } + else if (ctx.path == '/test/compare' || ctx.path == '/test/ml') { + done = false; +// var json = ''; +// ctx.req.addListener('data', (data) => { +// json += data; +// }) +// ctx.req.addListener('end', function() { +// console.log(json); + var body = ctx.body || ctx.req.body || ctx.request.body || {} // || JSON.parse(json) || {}; + console.log(body); + var isML = ctx.path == '/test/ml' || body.isML; + var res = body.response; + var stdd = body.standard; + var response = typeof res != 'string' ? res : (StringUtil.isEmpty(res, true) ? null : JSON.parse(res)); + var standard = typeof stdd != 'string' ? stdd : (StringUtil.isEmpty(stdd, true) ? null : JSON.parse(stdd)); + console.log('\n\nresponse = ' + JSON.stringify(response)); + console.log('\n\nstdd = ' + JSON.stringify(stdd)); + var compare = JSONResponse.compareResponse(null, standard, response || {}, '', isML, null, null, false) || {} + + if (body.newStandard) { + compare.newStandard = JSONResponse.updateFullStandard(standard, response, isML) + } + console.log('\n\ncompare = ' + JSON.stringify(compare)); + + ctx.status = ctx.response.status = 200; + ctx.body = ctx.response.body = compare == null ? '' : JSON.stringify(compare); + done = true; +// }) +// while (true) { +// if (done) { +// break; +// } +// } + } +}); + +app.listen(PORT); + +console.log(`已启动 Node HTTP 服务,可以 +GET http://localhost:${PORT}/test/start +来启动后台回归测试,或者 +GET http://localhost:${PORT}/test/status +来查询测试进度。`);