From e29b18b41a5a8ddbe55b2fe11e6aa522254b0ce8 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Thu, 12 Nov 2020 21:46:57 +0800 Subject: [PATCH 0001/1181] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 4472d0072..efa1344fc 100644 --- a/README.md +++ b/README.md @@ -84,7 +84,7 @@ APIJSON是一种专为API而生的 JSON网络传输协议 以及 基于这套协
### APIJSON接口展示 -使用 APIAuto-机器学习HTTP接口工具来展示基于 APIJSON 协议的 HTTP API:
+使用 APIAuto-机器学习HTTP接口工具 来展示基于 APIJSON 协议的 HTTP API:

多表关联查询、结构自由组合、多个测试账号、一键共享测试用例 From e61c343b84ab7a9b43b490acd0fb0a9236972455 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Thu, 12 Nov 2020 21:47:48 +0800 Subject: [PATCH 0002/1181] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index efa1344fc..582a69f10 100644 --- a/README.md +++ b/README.md @@ -84,7 +84,7 @@ APIJSON是一种专为API而生的 JSON网络传输协议 以及 基于这套协
### APIJSON接口展示 -使用 APIAuto-机器学习HTTP接口工具 来展示基于 APIJSON 协议的 HTTP API:
+以下使用 APIAuto-机器学习HTTP接口工具 来展示基于 APIJSON 协议的 HTTP API (或者 Postman 等其它 HTTP 工具都行):

多表关联查询、结构自由组合、多个测试账号、一键共享测试用例 From 57844b53b75090591cb64b8ae81962bc3002e60b Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Thu, 12 Nov 2020 21:48:25 +0800 Subject: [PATCH 0003/1181] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 582a69f10..861ccabdb 100644 --- a/README.md +++ b/README.md @@ -84,7 +84,7 @@ APIJSON是一种专为API而生的 JSON网络传输协议 以及 基于这套协
### APIJSON接口展示 -以下使用 APIAuto-机器学习HTTP接口工具 来展示基于 APIJSON 协议的 HTTP API (或者 Postman 等其它 HTTP 工具都行):
+以下使用 APIAuto-机器学习HTTP接口工具 来展示基于 APIJSON 协议的 HTTP API (Postman 等其它 HTTP 工具都可以请求):

多表关联查询、结构自由组合、多个测试账号、一键共享测试用例 From 4c2fe45765c1b90c9028dc34ddf9033521187f19 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Thu, 12 Nov 2020 21:48:46 +0800 Subject: [PATCH 0004/1181] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 861ccabdb..f975ccc19 100644 --- a/README.md +++ b/README.md @@ -84,7 +84,7 @@ APIJSON是一种专为API而生的 JSON网络传输协议 以及 基于这套协
### APIJSON接口展示 -以下使用 APIAuto-机器学习HTTP接口工具 来展示基于 APIJSON 协议的 HTTP API (Postman 等其它 HTTP 工具都可以请求):
+以下使用 APIAuto-机器学习HTTP接口工具 来展示基于 APIJSON 协议的 HTTP API (Postman 等其它 HTTP 工具也都可以请求):

多表关联查询、结构自由组合、多个测试账号、一键共享测试用例 From 45018b258035db5b656825c56cfeb9d744796050 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Thu, 12 Nov 2020 21:49:31 +0800 Subject: [PATCH 0005/1181] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index f975ccc19..7b5e17551 100644 --- a/README.md +++ b/README.md @@ -84,7 +84,7 @@ APIJSON是一种专为API而生的 JSON网络传输协议 以及 基于这套协
### APIJSON接口展示 -以下使用 APIAuto-机器学习HTTP接口工具 来展示基于 APIJSON 协议的 HTTP API (Postman 等其它 HTTP 工具也都可以请求):
+以下使用 APIAuto-机器学习HTTP接口工具 来展示基于 APIJSON 协议的 HTTP API (当然 Postman 等也都可以请求):

多表关联查询、结构自由组合、多个测试账号、一键共享测试用例 From bd5fff03694f3cb54d2661518515447a2e9caac3 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Thu, 12 Nov 2020 21:51:30 +0800 Subject: [PATCH 0006/1181] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 7b5e17551..ed56552ba 100644 --- a/README.md +++ b/README.md @@ -123,7 +123,7 @@ APIJSON是一种专为API而生的 JSON网络传输协议 以及 基于这套协

### APIJSON App演示 -使用 APIJSON + ZBLibrary 开发的 Android 客户端 Demo。以下 Gif 图看起来比较卡,实际在手机上 App 运行很流畅: +使用 APIJSON + ZBLibrary 开发的 Android 客户端 Demo。以下 Gif 图看起来比较卡,实际上运行很流畅:
![](https://oscimg.oschina.net/oscnet/up-a3f167e593080e8a3fc09c3d5fc09330c98.gif) ![](https://oscimg.oschina.net/oscnet/up-141abcb5dabc01c890d70c461bd1fdc751f.gif) From c7b3a7d413d6c06d9bf99cdfb4641449f38de1ed Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Thu, 12 Nov 2020 21:53:13 +0800 Subject: [PATCH 0007/1181] Update README.md --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index ed56552ba..54cf00939 100644 --- a/README.md +++ b/README.md @@ -84,7 +84,7 @@ APIJSON是一种专为API而生的 JSON网络传输协议 以及 基于这套协
### APIJSON接口展示 -以下使用 APIAuto-机器学习HTTP接口工具 来展示基于 APIJSON 协议的 HTTP API (当然 Postman 等也都可以请求):
+以下使用 APIAuto-机器学习HTTP接口工具 来展示基于 APIJSON 协议的 HTTP API(当然 Postman 等也都可以请求):

多表关联查询、结构自由组合、多个测试账号、一键共享测试用例 @@ -123,7 +123,7 @@ APIJSON是一种专为API而生的 JSON网络传输协议 以及 基于这套协

### APIJSON App演示 -使用 APIJSON + ZBLibrary 开发的 Android 客户端 Demo。以下 Gif 图看起来比较卡,实际上运行很流畅: +使用 APIJSON + ZBLibrary 开发的 Android 客户端 Demo(以下 Gif 图看起来比较卡,实际上运行很流畅):
![](https://oscimg.oschina.net/oscnet/up-a3f167e593080e8a3fc09c3d5fc09330c98.gif) ![](https://oscimg.oschina.net/oscnet/up-141abcb5dabc01c890d70c461bd1fdc751f.gif) @@ -162,7 +162,7 @@ https://github.com/Tencent/APIJSON/issues?q=is%3Aissue+is%3Aclosed 见  [APIJSON后端部署 - Java](https://github.com/APIJSON/APIJSON-Demo/tree/master/APIJSON-Java-Server)
#### 2.前端部署 -可以跳过这个步骤,直接使用 [APIAuto-自动化接口管理工具](https://github.com/TommyLemon/APIAuto) 或 下载客户端App。
+可以跳过这个步骤,直接使用 [APIAuto-机器学习HTTP接口工具](https://github.com/TommyLemon/APIAuto) 或 下载客户端App。
见  [Android](https://github.com/APIJSON/APIJSON-Demo/tree/master/APIJSON-Android)  或  [iOS](https://github.com/APIJSON/APIJSON-Demo/tree/master/APIJSON-iOS)  或  [JavaScript](https://github.com/APIJSON/APIJSON-Demo/tree/master/APIJSON-JavaScript)
From 0fd54c51dfb545c550d18be1a3d9c17274c0e954 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Thu, 12 Nov 2020 21:54:07 +0800 Subject: [PATCH 0008/1181] Update README.md --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 54cf00939..286b6532f 100644 --- a/README.md +++ b/README.md @@ -84,7 +84,7 @@ APIJSON是一种专为API而生的 JSON网络传输协议 以及 基于这套协
### APIJSON接口展示 -以下使用 APIAuto-机器学习HTTP接口工具 来展示基于 APIJSON 协议的 HTTP API(当然 Postman 等也都可以请求):
+以下使用 APIAuto-机器学习HTTP接口工具 来展示基于 APIJSON 协议的 HTTP API (当然 Postman 等也都可以请求):

多表关联查询、结构自由组合、多个测试账号、一键共享测试用例 @@ -123,7 +123,7 @@ APIJSON是一种专为API而生的 JSON网络传输协议 以及 基于这套协

### APIJSON App演示 -使用 APIJSON + ZBLibrary 开发的 Android 客户端 Demo(以下 Gif 图看起来比较卡,实际上运行很流畅): +使用 APIJSON + ZBLibrary 开发的 Android 客户端 Demo (以下 Gif 图看起来比较卡,实际上运行很流畅):
![](https://oscimg.oschina.net/oscnet/up-a3f167e593080e8a3fc09c3d5fc09330c98.gif) ![](https://oscimg.oschina.net/oscnet/up-141abcb5dabc01c890d70c461bd1fdc751f.gif) From c416514e433e8c98cadfe745b1699cb5b4133d9e Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Sun, 15 Nov 2020 00:35:57 +0800 Subject: [PATCH 0009/1181] =?UTF-8?q?=E5=8D=87=E7=BA=A7=20fastjson=20?= =?UTF-8?q?=E7=89=88=E6=9C=AC=E4=B8=BA=201.2.74=EF=BC=88=E6=B5=8B=E8=AF=95?= =?UTF-8?q?=201.2.75=20=E4=B8=8B=E8=BD=BD=E4=B8=8D=E4=BA=86=20Maven=20?= =?UTF-8?q?=E4=BE=9D=E8=B5=96=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- APIJSONORM/pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/APIJSONORM/pom.xml b/APIJSONORM/pom.xml index 06f79b5bc..9966ad8a5 100755 --- a/APIJSONORM/pom.xml +++ b/APIJSONORM/pom.xml @@ -5,7 +5,7 @@ apijson.orm apijson-orm - 4.2.3 + 4.2.4 jar APIJSONORM @@ -22,7 +22,7 @@ com.alibaba fastjson - 1.2.73 + 1.2.74 From da9c96eb4e37b9beb47a52a7b1afc62618c12ba3 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Wed, 18 Nov 2020 00:00:09 +0800 Subject: [PATCH 0010/1181] Update README.md --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 286b6532f..116a15b41 100644 --- a/README.md +++ b/README.md @@ -258,6 +258,8 @@ QQ 技术群: 734652054(新)、607020115(旧) [APIJSON简单部署和使用](https://blog.csdn.net/m450744192/article/details/108462611) +[学习自动化接口APIJSON](https://www.jianshu.com/p/981a2a630c7b) + [3步创建APIJSON后端新表及配置](https://my.oschina.net/tommylemon/blog/889074) [APIJSON 自动化接口和文档的快速开发神器 (一)](https://blog.csdn.net/qq_41829492/article/details/88670940) From eb69b7a274f8aac2e07b8555afa7826081cb8f06 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Wed, 18 Nov 2020 00:03:59 +0800 Subject: [PATCH 0011/1181] Update README.md --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 116a15b41..1fa5b30db 100644 --- a/README.md +++ b/README.md @@ -256,12 +256,12 @@ QQ 技术群: 734652054(新)、607020115(旧) [后端自动化版本管理,再也不用改URL了!](https://my.oschina.net/tommylemon/blog/1576587) +[3步创建APIJSON后端新表及配置](https://my.oschina.net/tommylemon/blog/889074) + [APIJSON简单部署和使用](https://blog.csdn.net/m450744192/article/details/108462611) [学习自动化接口APIJSON](https://www.jianshu.com/p/981a2a630c7b) -[3步创建APIJSON后端新表及配置](https://my.oschina.net/tommylemon/blog/889074) - [APIJSON 自动化接口和文档的快速开发神器 (一)](https://blog.csdn.net/qq_41829492/article/details/88670940) [APIJSON在mac电脑环境下配置去连接SQL Server](https://juejin.im/post/5e16d21ef265da3e2e4f4956) From 52456d76173ed5ce62679116ee61417011c235f6 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Fri, 20 Nov 2020 11:15:41 +0800 Subject: [PATCH 0012/1181] Update README.md --- README.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 1fa5b30db..c18951b51 100644 --- a/README.md +++ b/README.md @@ -87,35 +87,35 @@ APIJSON是一种专为API而生的 JSON网络传输协议 以及 基于这套协 以下使用 APIAuto-机器学习HTTP接口工具 来展示基于 APIJSON 协议的 HTTP API (当然 Postman 等也都可以请求):

- 多表关联查询、结构自由组合、多个测试账号、一键共享测试用例 + APIJSON 多表关联查询、结构自由组合,APIAuto 多个测试账号、一键共享测试用例

![](https://oscimg.oschina.net/oscnet/up-bbbec4fc5edc472be127c02a4f3cd8f4ec2.JPEG)

- 自动生成封装请求JSON的Android与iOS代码、一键自动生成JavaBean或解析Response的代码 + APIAuto 自动生成前端(客户端)请求代码、后端接口代码、测试用例代码,一键下载

![](https://oscimg.oschina.net/oscnet/up-637193bbd89b41c3264827786319e842aee.JPEG)

- 自动保存请求记录、自动生成接口文档,可添加常用请求、快捷查看一键恢复 + APIAuto 自动保存请求记录、自动生成接口文档,可添加常用请求、快捷查看一键恢复

![](https://oscimg.oschina.net/oscnet/up-7dcb4ae71bd3892a909e4ffa37ba7c1d92a.JPEG)

- 一键自动接口回归测试,不需要写任何代码(注解、注释等全都不要) + APIAuto 一键自动接口回归测试,不需要写任何代码(注解、注释等全都不要)

![](https://oscimg.oschina.net/oscnet/up-c1ba774f8e7fcc5adcdb05cad5bd414d766.JPEG)

- 一图胜千言 - 部分基础功能概览 + 一图胜千言 - APIJSON 部分基础功能概览

![](https://oscimg.oschina.net/oscnet/up-e21240ef3770326ee6015e052226d0da184.JPEG) From 32b81b56f1fa2aeec2fe58c1ad3c250184f7376e Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Fri, 20 Nov 2020 11:23:54 +0800 Subject: [PATCH 0013/1181] Update README.md --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index c18951b51..f7a15bdce 100644 --- a/README.md +++ b/README.md @@ -84,7 +84,8 @@ APIJSON是一种专为API而生的 JSON网络传输协议 以及 基于这套协
### APIJSON接口展示 -以下使用 APIAuto-机器学习HTTP接口工具 来展示基于 APIJSON 协议的 HTTP API (当然 Postman 等也都可以请求):
+以下使用 APIAuto-机器学习接口工具 来展示基于 APIJSON 协议的 HTTP API(界面是 APIAuto, URL+JSON 才是 APIJSON。
+当然 Postman 等也都可以请求,只是没有 静态检查、自动注释、悬浮文档、机器学习测试 等功能):

APIJSON 多表关联查询、结构自由组合,APIAuto 多个测试账号、一键共享测试用例 From 5f87a4e133e2ab8621803cd8d88edf7410e1c457 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Fri, 20 Nov 2020 11:24:26 +0800 Subject: [PATCH 0014/1181] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index f7a15bdce..86fbaac2b 100644 --- a/README.md +++ b/README.md @@ -84,7 +84,7 @@ APIJSON是一种专为API而生的 JSON网络传输协议 以及 基于这套协
### APIJSON接口展示 -以下使用 APIAuto-机器学习接口工具 来展示基于 APIJSON 协议的 HTTP API(界面是 APIAuto, URL+JSON 才是 APIJSON。
+以下使用 APIAuto-机器学习接口工具 来展示基于 APIJSON 协议的 HTTP API (注意界面是 APIAuto, URL+JSON 才是 APIJSON。
当然 Postman 等也都可以请求,只是没有 静态检查、自动注释、悬浮文档、机器学习测试 等功能):

From 6358e39930406ddc539040892f6b360bcc9b7e51 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Fri, 20 Nov 2020 11:25:39 +0800 Subject: [PATCH 0015/1181] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 86fbaac2b..a9e2f9cea 100644 --- a/README.md +++ b/README.md @@ -85,7 +85,7 @@ APIJSON是一种专为API而生的 JSON网络传输协议 以及 基于这套协 ### APIJSON接口展示 以下使用 APIAuto-机器学习接口工具 来展示基于 APIJSON 协议的 HTTP API (注意界面是 APIAuto, URL+JSON 才是 APIJSON。
-当然 Postman 等也都可以请求,只是没有 静态检查、自动注释、悬浮文档、机器学习测试 等功能):
+当然 [Postman 等也都可以请求](https://my.oschina.net/tommylemon/blog/889074),只是没有 APIAuto 的 静态检查、自动注释、悬浮文档、机器学习测试 等功能):

APIJSON 多表关联查询、结构自由组合,APIAuto 多个测试账号、一键共享测试用例 From ef331c2ffc15702b4ab861bf2e72c26e43fca140 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Fri, 20 Nov 2020 11:25:53 +0800 Subject: [PATCH 0016/1181] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index a9e2f9cea..790b49870 100644 --- a/README.md +++ b/README.md @@ -85,7 +85,7 @@ APIJSON是一种专为API而生的 JSON网络传输协议 以及 基于这套协 ### APIJSON接口展示 以下使用 APIAuto-机器学习接口工具 来展示基于 APIJSON 协议的 HTTP API (注意界面是 APIAuto, URL+JSON 才是 APIJSON。
-当然 [Postman 等也都可以请求](https://my.oschina.net/tommylemon/blog/889074),只是没有 APIAuto 的 静态检查、自动注释、悬浮文档、机器学习测试 等功能):
+当然 [Postman 等接口工具也都可以请求](https://my.oschina.net/tommylemon/blog/889074),只是没有 APIAuto 的 静态检查、自动注释、悬浮文档、机器学习测试 等功能):

APIJSON 多表关联查询、结构自由组合,APIAuto 多个测试账号、一键共享测试用例 From 5d7565b2ce26dc60b22138e7a3cbfb53af1b1020 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Fri, 20 Nov 2020 11:30:22 +0800 Subject: [PATCH 0017/1181] Update README.md --- README.md | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 790b49870..85e743221 100644 --- a/README.md +++ b/README.md @@ -84,8 +84,12 @@ APIJSON是一种专为API而生的 JSON网络传输协议 以及 基于这套协
### APIJSON接口展示 -以下使用 APIAuto-机器学习接口工具 来展示基于 APIJSON 协议的 HTTP API (注意界面是 APIAuto, URL+JSON 才是 APIJSON。
-当然 [Postman 等接口工具也都可以请求](https://my.oschina.net/tommylemon/blog/889074),只是没有 APIAuto 的 静态检查、自动注释、悬浮文档、机器学习测试 等功能):
+#### Postman 展示 APIJSON +![](https://static.oschina.net/uploads/img/201711/12230359_f7fQ.jpg) +
+ +#### APIAuto 展示 APIJSON +以下使用 APIAuto-机器学习接口工具 来展示基于 APIJSON 协议的 HTTP API (注意界面是 APIAuto, URL+JSON 才是 APIJSON):

APIJSON 多表关联查询、结构自由组合,APIAuto 多个测试账号、一键共享测试用例 From 0bc7eea98652182468ac358410604e0e2e014ad0 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Fri, 20 Nov 2020 11:40:47 +0800 Subject: [PATCH 0018/1181] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 85e743221..98baeaf2b 100644 --- a/README.md +++ b/README.md @@ -89,7 +89,7 @@ APIJSON是一种专为API而生的 JSON网络传输协议 以及 基于这套协
#### APIAuto 展示 APIJSON -以下使用 APIAuto-机器学习接口工具 来展示基于 APIJSON 协议的 HTTP API (注意界面是 APIAuto, URL+JSON 才是 APIJSON):
+搭配 APIAuto-机器学习接口工具 来管理和测试 APIJSON 可大幅提升联调效率(注意界面是 APIAuto, URL+JSON 才是 APIJSON):

APIJSON 多表关联查询、结构自由组合,APIAuto 多个测试账号、一键共享测试用例 From 62721e4b86b2bd1f38d6ece94df2d61953849648 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Fri, 20 Nov 2020 11:45:50 +0800 Subject: [PATCH 0019/1181] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 98baeaf2b..15e0156af 100644 --- a/README.md +++ b/README.md @@ -328,4 +328,4 @@ https://github.com/Tencent/APIJSON/commits/master https://git.code.tencent.com/Tencent_Open_Source/APIJSON ### 码云主页 -https://gitee.com/TommyLemon/APIJSON +https://gitee.com/Tencent/APIJSON From c67b072fefa976805c2c75bc71b1175abb6c00d2 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Fri, 20 Nov 2020 11:53:51 +0800 Subject: [PATCH 0020/1181] Update README.md --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 15e0156af..738278da5 100644 --- a/README.md +++ b/README.md @@ -89,7 +89,8 @@ APIJSON是一种专为API而生的 JSON网络传输协议 以及 基于这套协
#### APIAuto 展示 APIJSON -搭配 APIAuto-机器学习接口工具 来管理和测试 APIJSON 可大幅提升联调效率(注意界面是 APIAuto, URL+JSON 才是 APIJSON):
+搭配 APIAuto-机器学习接口工具 来管理和测试 APIJSON 可大幅提升联调效率
+(注意网页工具界面是 APIAuto, URL+JSON 才是 APIJSON 的 HTTP API):

APIJSON 多表关联查询、结构自由组合,APIAuto 多个测试账号、一键共享测试用例 From 9e501d43c224e0f98e87270f3a2c4aad949a1417 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Fri, 20 Nov 2020 11:56:18 +0800 Subject: [PATCH 0021/1181] Update README.md --- README.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 738278da5..174c29e6d 100644 --- a/README.md +++ b/README.md @@ -83,14 +83,14 @@ APIJSON是一种专为API而生的 JSON网络传输协议 以及 基于这套协
-### APIJSON接口展示 +### APIJSON 接口展示 #### Postman 展示 APIJSON ![](https://static.oschina.net/uploads/img/201711/12230359_f7fQ.jpg)
#### APIAuto 展示 APIJSON -搭配 APIAuto-机器学习接口工具 来管理和测试 APIJSON 可大幅提升联调效率
-(注意网页工具界面是 APIAuto, URL+JSON 才是 APIJSON 的 HTTP API):
+搭配 APIAuto-机器学习接口工具 来管理和测试 APIJSON 可大幅提升接口联调效率
+(注意网页工具界面是 APIAuto,里面的 URL+JSON 才是 APIJSON 的 HTTP API):

APIJSON 多表关联查询、结构自由组合,APIAuto 多个测试账号、一键共享测试用例 @@ -128,7 +128,7 @@ APIJSON是一种专为API而生的 JSON网络传输协议 以及 基于这套协

-### APIJSON App演示 +### APIJSON App 演示 使用 APIJSON + ZBLibrary 开发的 Android 客户端 Demo (以下 Gif 图看起来比较卡,实际上运行很流畅):
![](https://oscimg.oschina.net/oscnet/up-a3f167e593080e8a3fc09c3d5fc09330c98.gif) @@ -138,7 +138,7 @@ APIJSON是一种专为API而生的 JSON网络传输协议 以及 基于这套协
-### 为什么要用APIJSON? +### 为什么要用 APIJSON? 前后端 关于接口的 开发、文档、联调 等 10 大痛点解析
https://github.com/Tencent/APIJSON/wiki @@ -172,7 +172,7 @@ https://github.com/Tencent/APIJSON/issues?q=is%3Aissue+is%3Aclosed 见  [Android](https://github.com/APIJSON/APIJSON-Demo/tree/master/APIJSON-Android)  或  [iOS](https://github.com/APIJSON/APIJSON-Demo/tree/master/APIJSON-iOS)  或  [JavaScript](https://github.com/APIJSON/APIJSON-Demo/tree/master/APIJSON-JavaScript)
-### 下载客户端App +### 下载客户端 App 仿微信朋友圈动态实战项目
[APIJSONApp.apk](http://files.cnblogs.com/files/tommylemon/APIJSONApp.apk) From c32498b04162d19530c00b1cdf41bc958f3dbdee Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Sun, 22 Nov 2020 02:26:36 +0800 Subject: [PATCH 0022/1181] =?UTF-8?q?=E6=93=8D=E4=BD=9C=E6=96=B9=E6=B3=95?= =?UTF-8?q?=20Operation=20=E6=96=B0=E5=A2=9E=20MUST=20=E5=92=8C=20REFUSE?= =?UTF-8?q?=20=E5=88=86=E5=88=AB=E6=9B=BF=E4=BB=A3=20NECESSARY=20=E5=92=8C?= =?UTF-8?q?=20DISALLOW=EF=BC=9B=E8=A7=A3=E5=86=B3=20Structure.sqlVerify=20?= =?UTF-8?q?=E4=B8=8D=E5=8F=AF=E7=94=A8=E5=8F=8A=E9=A2=84=E9=98=B2=E5=8F=AF?= =?UTF-8?q?=E8=83=BD=E7=9A=84=20SQL=20=E6=B3=A8=E5=85=A5=EF=BC=9B=E8=A7=A3?= =?UTF-8?q?=E5=86=B3=20SQLConfig=20=E8=87=AA=E5=AE=9A=E4=B9=89=E7=9A=84=20?= =?UTF-8?q?idKey=20=E5=92=8C=20userIdKey=20=E5=9C=A8=20Structure=20?= =?UTF-8?q?=E4=B8=AD=E6=9C=AA=E5=90=8C=E6=AD=A5=E5=AF=BC=E8=87=B4=E8=87=AA?= =?UTF-8?q?=E5=AE=9A=E4=B9=89=E5=80=BC=E6=A0=A1=E9=AA=8C=E4=B8=8D=E9=80=9A?= =?UTF-8?q?=E8=BF=87=EF=BC=9B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../main/java/apijson/orm/AbstractParser.java | 21 +- .../java/apijson/orm/AbstractSQLConfig.java | 68 ++++-- .../src/main/java/apijson/orm/Operation.java | 21 +- .../src/main/java/apijson/orm/Structure.java | 203 +++++++++++++++--- .../src/main/java/apijson/orm/model/Test.java | 4 +- 5 files changed, 259 insertions(+), 58 deletions(-) diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java b/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java index 64ebd2992..331b542e5 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java @@ -34,6 +34,7 @@ import apijson.RequestMethod; import apijson.RequestRole; import apijson.StringUtil; +import apijson.orm.AbstractSQLConfig.IdCallback; import apijson.orm.exception.ConditionErrorException; import apijson.orm.exception.ConflictException; import apijson.orm.exception.NotExistException; @@ -43,7 +44,7 @@ /**parser for parsing request to JSONObject * @author Lemon */ -public abstract class AbstractParser implements Parser, ParserCreator, VerifierCreator, SQLCreator { +public abstract class AbstractParser implements Parser, ParserCreator, VerifierCreator, SQLCreator, IdCallback { protected static final String TAG = "AbstractParser"; @@ -490,11 +491,25 @@ public JSONObject parseCorrectRequest(RequestMethod method, String tag, int vers //获取指定的JSON结构 >>>>>>>>>>>>>> + //JSONObject clone 浅拷贝没用,Structure.parse 会导致 structure 里面被清空,第二次从缓存里取到的就是 {} - return Structure.parseRequest(method, name, target, request, maxUpdateCount, creator); + return Structure.parseRequest(method, name, target, request, maxUpdateCount, getGlobleDatabase(), getGlobleSchema(), this, creator); } - + @Override + public String getIdKey(String database, String schema, String table) { + return apijson.JSONObject.KEY_ID; + } + @Override + public String getUserIdKey(String database, String schema, String table) { + return apijson.JSONObject.KEY_USER_ID; + } + @Override + public Object newId(RequestMethod method, String database, String schema, String table) { + return System.currentTimeMillis(); + } + + /**新建带状态内容的JSONObject * @param code * @param msg diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java index c2bdf0933..892e87719 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java @@ -16,8 +16,8 @@ import static apijson.JSONObject.KEY_ID; import static apijson.JSONObject.KEY_JSON; import static apijson.JSONObject.KEY_ORDER; -import static apijson.JSONObject.KEY_ROLE; import static apijson.JSONObject.KEY_RAW; +import static apijson.JSONObject.KEY_ROLE; import static apijson.JSONObject.KEY_SCHEMA; import static apijson.JSONObject.KEY_USER_ID; import static apijson.RequestMethod.DELETE; @@ -48,6 +48,7 @@ import com.alibaba.fastjson.annotation.JSONField; import apijson.JSON; +import apijson.JSONResponse; import apijson.Log; import apijson.NotNull; import apijson.RequestMethod; @@ -55,13 +56,20 @@ import apijson.SQL; import apijson.StringUtil; import apijson.orm.exception.NotExistException; +import apijson.orm.model.Access; import apijson.orm.model.Column; +import apijson.orm.model.Document; import apijson.orm.model.ExtendedProperty; +import apijson.orm.model.Function; import apijson.orm.model.PgAttribute; import apijson.orm.model.PgClass; +import apijson.orm.model.Request; +import apijson.orm.model.Response; import apijson.orm.model.SysColumn; import apijson.orm.model.SysTable; import apijson.orm.model.Table; +import apijson.orm.model.Test; +import apijson.orm.model.TestRecord; /**config sql for JSON Request * @author Lemon @@ -77,6 +85,7 @@ public abstract class AbstractSQLConfig implements SQLConfig { * 表名映射,隐藏真实表名,对安全要求很高的表可以这么做 */ public static final Map TABLE_KEY_MAP; + public static final List CONFIG_TABLE_LIST; public static final List DATABASE_LIST; // 自定义where条件拼接 public static final Map RAW_MAP; @@ -89,6 +98,16 @@ public abstract class AbstractSQLConfig implements SQLConfig { TABLE_KEY_MAP.put(SysTable.class.getSimpleName(), SysTable.TABLE_NAME); TABLE_KEY_MAP.put(SysColumn.class.getSimpleName(), SysColumn.TABLE_NAME); TABLE_KEY_MAP.put(ExtendedProperty.class.getSimpleName(), ExtendedProperty.TABLE_NAME); + + CONFIG_TABLE_LIST = new ArrayList<>(); // Table, Column 等是系统表 AbstractVerifier.SYSTEM_ACCESS_MAP.keySet()); + CONFIG_TABLE_LIST.add(Function.class.getSimpleName()); + CONFIG_TABLE_LIST.add(Request.class.getSimpleName()); + CONFIG_TABLE_LIST.add(Response.class.getSimpleName()); + CONFIG_TABLE_LIST.add(Test.class.getSimpleName()); + CONFIG_TABLE_LIST.add(Access.class.getSimpleName()); + CONFIG_TABLE_LIST.add(Document.class.getSimpleName()); + CONFIG_TABLE_LIST.add(TestRecord.class.getSimpleName()); + DATABASE_LIST = new ArrayList<>(); DATABASE_LIST.add(DATABASE_MYSQL); @@ -1623,8 +1642,18 @@ public String getCompareString(String key, Object value, String type) throws Exc } public String getKey(String key) { + if (isTest()) { + if (key.contains("'")) { // || key.contains("#") || key.contains("--")) { + throw new IllegalArgumentException("参数 " + key + " 不合法!key 中不允许有单引号 ' !"); + } + return getSQLValue(key).toString(); + } + + return getSQLKey(key); + } + public String getSQLKey(String key) { String q = getQuote(); - return (isKeyPrefix() ? getAliasWithQuote() + "." : "") + q + key + q; + return (isKeyPrefix() ? getAliasWithQuote() + "." : "") + q + key + q; } /** @@ -1636,6 +1665,9 @@ private Object getValue(@NotNull Object value) { preparedValueList.add(value); return "?"; } + return getSQLValue(value); + } + public Object getSQLValue(@NotNull Object value) { // return (value instanceof Number || value instanceof Boolean) && DATABASE_POSTGRESQL.equals(getDatabase()) ? value : "'" + value + "'"; return (value instanceof Number || value instanceof Boolean) ? value : "'" + value + "'"; //MySQL 隐式转换用不了索引 } @@ -2230,9 +2262,15 @@ public static String getSQL(AbstractSQLConfig config) throws Exception { case DELETE: return "DELETE FROM " + tablePath + config.getWhereString(true); default: + String explain = (config.isExplain() ? (config.isSQLServer() || config.isOracle() ? "SET STATISTICS PROFILE ON " : "EXPLAIN ") : ""); + if (config.isTest() && RequestMethod.isGetMethod(config.getMethod(), true)) { + String q = config.getQuote(); // 生成 SELECT ( (24 >=0 AND 24 <3) ) AS `code` LIMIT 1 OFFSET 0 + return explain + "SELECT " + config.getWhereString(false) + " AS " + q + JSONResponse.KEY_CODE + q + config.getLimitString(); + } + config.setPreparedValueList(new ArrayList()); String column = config.getColumnString(); - return (config.isExplain() ? (config.isSQLServer() || config.isOracle() ? "SET STATISTICS PROFILE ON " : "EXPLAIN ") : "") + "SELECT " + (config.getCache() == JSONRequest.CACHE_RAM ? "SQL_NO_CACHE " : "") + column + " FROM " + getConditionString(column, tablePath, config); + return explain + "SELECT " + (config.getCache() == JSONRequest.CACHE_RAM ? "SQL_NO_CACHE " : "") + column + " FROM " + getConditionString(column, tablePath, config); } } @@ -2915,16 +2953,7 @@ else if (key.endsWith("-")) {//缩减,PUT查询时处理 } - public static interface Callback { - /**获取 SQLConfig 的实例 - * @param method - * @param database - * @param schema - * @param table - * @return - */ - SQLConfig getSQLConfig(RequestMethod method, String database, String schema, String table); - + public static interface IdCallback { /**为 post 请求新建 id, 只能是 Long 或 String * @param method * @param database @@ -2949,7 +2978,18 @@ public static interface Callback { * @return */ String getUserIdKey(String database, String schema, String table); - + } + + public static interface Callback extends IdCallback { + /**获取 SQLConfig 的实例 + * @param method + * @param database + * @param schema + * @param table + * @return + */ + SQLConfig getSQLConfig(RequestMethod method, String database, String schema, String table); + /**combine 里的 key 在 request 中 value 为 null 或不存在,即 request 中缺少用来作为 combine 条件的 key: value * @param combine * @param key diff --git a/APIJSONORM/src/main/java/apijson/orm/Operation.java b/APIJSONORM/src/main/java/apijson/orm/Operation.java index ec93b0300..a0e2278b1 100755 --- a/APIJSONORM/src/main/java/apijson/orm/Operation.java +++ b/APIJSONORM/src/main/java/apijson/orm/Operation.java @@ -9,21 +9,26 @@ * @author Lemon */ public enum Operation { + /** + * 必须传的字段,结构是 + * "key0,key1,key2..." + */ + MUST, + /** + * @deprecated 用 MUST 代替,最早可能 4.5.0 移除 + */ + NECESSARY, /** * 不允许传的字段,结构是 * "key0,key1,key2..." - * TODO 改成 MUST 减少长度 ? */ - DISALLOW, - + REFUSE, /** - * 必须传的字段,结构是 - * "key0,key1,key2..." - * TODO 改成 REFUSE 减少长度 ? + * @deprecated 用 REFUSE 代替,最早可能 4.5.0 移除 */ - NECESSARY, - + DISALLOW, + /**TODO 是否应该把数组类型写成 BOOLEANS, NUMBERS 等复数单词,以便抽取 enum ?扩展用 VERIFY 或 INSERT/UPDATE 远程函数等 * 验证是否符合预设的类型: diff --git a/APIJSONORM/src/main/java/apijson/orm/Structure.java b/APIJSONORM/src/main/java/apijson/orm/Structure.java index 4b752de08..9f7d622d6 100755 --- a/APIJSONORM/src/main/java/apijson/orm/Structure.java +++ b/APIJSONORM/src/main/java/apijson/orm/Structure.java @@ -5,12 +5,12 @@ package apijson.orm; -import static apijson.JSONObject.KEY_ID; -import static apijson.JSONObject.KEY_USER_ID; import static apijson.orm.Operation.DISALLOW; import static apijson.orm.Operation.EXIST; import static apijson.orm.Operation.INSERT; +import static apijson.orm.Operation.MUST; import static apijson.orm.Operation.NECESSARY; +import static apijson.orm.Operation.REFUSE; import static apijson.orm.Operation.REMOVE; import static apijson.orm.Operation.REPLACE; import static apijson.orm.Operation.TYPE; @@ -45,10 +45,11 @@ import apijson.NotNull; import apijson.RequestMethod; import apijson.StringUtil; +import apijson.orm.AbstractSQLConfig.Callback; +import apijson.orm.AbstractSQLConfig.IdCallback; import apijson.orm.exception.ConflictException; -import apijson.orm.model.Test; -/**结构类 +/**结构类。 TODO 重构为 AbstractContentVerifier implements ContentVerifier 或干脆整合进 AbstractVerifier * 增删改查: OPERATION(ADD,REPLACE,PUT,REMOVE) OPERATION:{key0:value0, key1:value1 ...} * 对值校验: VERIFY:{key0:value0, key1:value1 ...} (key{}:range,key$:"%m%"等) * 对值重复性校验: UNIQUE:"key0:, key1 ..." (UNIQUE:"phone,email" 等) @@ -62,7 +63,7 @@ public class Structure { COMPILE_MAP = new HashMap(); } - + private Structure() {} @@ -91,9 +92,28 @@ public static JSONObject parseRequest(@NotNull final RequestMethod method, final */ public static JSONObject parseRequest(@NotNull final RequestMethod method, final String name , final JSONObject target, final JSONObject request, final int maxUpdateCount, final SQLCreator creator) throws Exception { + return parseRequest(method, name, target, request, maxUpdateCount, null, null, null, creator); + } + /**从request提取target指定的内容 + * @param method + * @param name + * @param target + * @param request + * @param maxUpdateCount + * @param idKey + * @param userIdKey + * @param creator + * @return + * @throws Exception + */ + public static JSONObject parseRequest(@NotNull final RequestMethod method, final String name + , final JSONObject target, final JSONObject request, final int maxUpdateCount + , final String database, final String schema, final IdCallback idCallback, final SQLCreator creator) throws Exception { + Log.i(TAG, "parseRequest method = " + method + "; name = " + name + "; target = \n" + JSON.toJSONString(target) + "\n request = \n" + JSON.toJSONString(request)); + if (target == null || request == null) {// || request.isEmpty()) { Log.i(TAG, "parseRequest target == null || request == null >> return null;"); return null; @@ -105,30 +125,47 @@ public static JSONObject parseRequest(@NotNull final RequestMethod method, final // ":{ " + JSONRequest.KEY_ROLE + ":admin } !"); // } + //解析 - return parse(method, name, target, request, creator, new OnParseCallback() { + return parse(method, name, target, request, database, schema, idCallback, creator, new OnParseCallback() { @Override public JSONObject onParseJSONObject(String key, JSONObject tobj, JSONObject robj) throws Exception { // Log.i(TAG, "parseRequest.parse.onParseJSONObject key = " + key + "; robj = " + robj); + if (robj == null) { if (tobj != null) {//不允许不传Target中指定的Table throw new IllegalArgumentException(method + "请求,请在 " + name + " 内传 " + key + ":{} !"); } } else if (apijson.JSONObject.isTableKey(key)) { + String db = request.getString(apijson.JSONObject.KEY_DATABASE); + String sh = request.getString(apijson.JSONObject.KEY_SCHEMA); + if (StringUtil.isEmpty(db, false)) { + db = database; + } + if (StringUtil.isEmpty(sh, false)) { + sh = schema; + } + + String idKey = idCallback == null ? null : idCallback.getIdKey(db, sh, key); + String finalIdKey = StringUtil.isEmpty(idKey, false) ? apijson.JSONObject.KEY_ID : idKey; + if (method == RequestMethod.POST) { - if (robj.containsKey(KEY_ID)) { - throw new IllegalArgumentException(method + "请求," + name + "/" + key + " 不能传 " + KEY_ID + " !"); + if (robj.containsKey(finalIdKey)) { + throw new IllegalArgumentException(method + "请求," + name + "/" + key + " 不能传 " + finalIdKey + " !"); } } else { if (RequestMethod.isQueryMethod(method) == false) { - verifyId(method.name(), name, key, robj, KEY_ID, maxUpdateCount, true); - verifyId(method.name(), name, key, robj, KEY_USER_ID, maxUpdateCount, false); + verifyId(method.name(), name, key, robj, finalIdKey, maxUpdateCount, true); + + String userIdKey = idCallback == null ? null : idCallback.getUserIdKey(db, sh, key); + String finalUserIdKey = StringUtil.isEmpty(userIdKey, false) ? apijson.JSONObject.KEY_USER_ID : userIdKey; + verifyId(method.name(), name, key, robj, finalUserIdKey, maxUpdateCount, false); } } } - return parseRequest(method, key, tobj, robj, maxUpdateCount, creator); + return parseRequest(method, key, tobj, robj, maxUpdateCount, database, schema, idCallback, creator); } @Override @@ -214,34 +251,70 @@ private static void verifyId(@NotNull String method, @NotNull String name, @NotN */ public static JSONObject parseResponse(@NotNull final RequestMethod method, final String name , final JSONObject target, final JSONObject response, SQLCreator creator, OnParseCallback callback) throws Exception { + return parseResponse(method, name, target, response, null, null, null, creator, callback); + } + /**校验并将response转换为指定的内容和结构 + * @param method + * @param name + * @param target + * @param response + * @param idKey + * @param callback + * @param creator + * @return + * @throws Exception + */ + public static JSONObject parseResponse(@NotNull final RequestMethod method, final String name + , final JSONObject target, final JSONObject response, final String database, final String schema + , final Callback idKeyCallback, SQLCreator creator, OnParseCallback callback) throws Exception { + Log.i(TAG, "parseResponse method = " + method + "; name = " + name + "; target = \n" + JSON.toJSONString(target) + "\n response = \n" + JSON.toJSONString(response)); + if (target == null || response == null) {// || target.isEmpty() { Log.i(TAG, "parseRequest target == null || response == null >> return response;"); return response; } //解析 - return parse(method, name, target, response, creator, callback != null ? callback : new OnParseCallback() {}); + return parse(method, name, target, response, database, schema, idKeyCallback, creator, callback != null ? callback : new OnParseCallback() {}); } /**对request和response不同的解析用callback返回 + * @param method + * @param name * @param target - * @param request + * @param real + * @param creator * @param callback - * @param creator * @return - * @throws Exception + * @throws Exception */ public static JSONObject parse(@NotNull final RequestMethod method, String name, JSONObject target, JSONObject real , SQLCreator creator, @NotNull OnParseCallback callback) throws Exception { + return parse(method, name, target, real, null, null, null, creator, callback); + } + + /**对request和response不同的解析用callback返回 + * @param method + * @param name + * @param target + * @param real + * @param idKey + * @param userIdKey + * @param creator + * @param callback + * @return + * @throws Exception + */ + public static JSONObject parse(@NotNull final RequestMethod method, String name, JSONObject target, JSONObject real + , final String database, final String schema, final IdCallback idCallback, SQLCreator creator, @NotNull OnParseCallback callback) throws Exception { if (target == null) { return null; } - //获取配置<<<<<<<<<<<<<<<<<<<<<<<<<<<< JSONObject type = target.getJSONObject(TYPE.name()); JSONObject verify = target.getJSONObject(VERIFY.name()); @@ -252,6 +325,8 @@ public static JSONObject parse(@NotNull final RequestMethod method, String name, String exist = StringUtil.getNoBlankString(target.getString(EXIST.name())); String unique = StringUtil.getNoBlankString(target.getString(UNIQUE.name())); String remove = StringUtil.getNoBlankString(target.getString(REMOVE.name())); + String must = StringUtil.getNoBlankString(target.getString(MUST.name())); + String refuse = StringUtil.getNoBlankString(target.getString(REFUSE.name())); String necessary = StringUtil.getNoBlankString(target.getString(NECESSARY.name())); String disallow = StringUtil.getNoBlankString(target.getString(DISALLOW.name())); @@ -264,6 +339,8 @@ public static JSONObject parse(@NotNull final RequestMethod method, String name, target.remove(EXIST.name()); target.remove(UNIQUE.name()); target.remove(REMOVE.name()); + target.remove(MUST.name()); + target.remove(REFUSE.name()); target.remove(NECESSARY.name()); target.remove(DISALLOW.name()); @@ -279,6 +356,15 @@ public static JSONObject parse(@NotNull final RequestMethod method, String name, //移除字段>>>>>>>>>>>>>>>>>>> //判断必要字段是否都有<<<<<<<<<<<<<<<<<<< + String[] musts = StringUtil.split(must); + List mustList = musts == null ? new ArrayList() : Arrays.asList(musts); + for (String s : mustList) { + if (real.get(s) == null) {//可能传null进来,这里还会通过 real.containsKey(s) == false) { + throw new IllegalArgumentException(method + "请求," + name + + " 里面不能缺少 " + s + " 等[" + must + "]内的任何字段!"); + } + } + String[] necessarys = StringUtil.split(necessary); List necessaryList = necessarys == null ? new ArrayList() : Arrays.asList(necessarys); for (String s : necessaryList) { @@ -323,7 +409,7 @@ public static JSONObject parse(@NotNull final RequestMethod method, String name, throw new UnsupportedDataTypeException(key + ":value 的value不合法!类型必须是 ARRAY ,结构为 [] !"); } tvalue = callback.onParseJSONArray(key, (JSONArray) tvalue, (JSONArray) rvalue); - + if ((method == RequestMethod.POST || method == RequestMethod.PUT) && JSONRequest.isArrayKey(key)) { objKeySet.add(key); } @@ -345,6 +431,21 @@ public static JSONObject parse(@NotNull final RequestMethod method, String name, Set rkset = real.keySet(); //解析内容并没有改变rkset //解析不允许的字段<<<<<<<<<<<<<<<<<<< + List refuseList = new ArrayList(); + if ("!".equals(refuse)) {//所有非 must,改成 !must 更好 + for (String key : rkset) {//对@key放行,@role,@column,自定义@position等 + if (key != null && key.startsWith("@") == false + && necessaryList.contains(key) == false && objKeySet.contains(key) == false) { + refuseList.add(key); + } + } + } else { + String[] refuses = StringUtil.split(refuse); + if (refuses != null && refuses.length > 0) { + refuseList.addAll(Arrays.asList(refuses)); + } + } + List disallowList = new ArrayList(); if ("!".equals(disallow)) {//所有非necessary,改成 !necessary 更好 for (String key : rkset) {//对@key放行,@role,@column,自定义@position等 @@ -364,6 +465,10 @@ public static JSONObject parse(@NotNull final RequestMethod method, String name, //判断不允许传的key<<<<<<<<<<<<<<<<<<<<<<<<< for (String rk : rkset) { + if (refuseList.contains(rk)) { //不允许的字段 + throw new IllegalArgumentException(method + "请求," + name + + " 里面不允许传 " + rk + " 等" + StringUtil.getString(refuseList) + "内的任何字段!"); + } if (disallowList.contains(rk)) { //不允许的字段 throw new IllegalArgumentException(method + "请求," + name + " 里面不允许传 " + rk + " 等" + StringUtil.getString(disallowList) + "内的任何字段!"); @@ -378,7 +483,7 @@ public static JSONObject parse(@NotNull final RequestMethod method, String name, //不允许传远程函数,只能后端配置 if (rk.endsWith("()") && rv instanceof String) { - throw new UnsupportedOperationException(method + " 请求," +rk + " 不合法!非开放请求不允许传远程函数 key():\"fun()\" !"); + throw new UnsupportedOperationException(method + " 请求," + rk + " 不合法!非开放请求不允许传远程函数 key():\"fun()\" !"); } //不在target内的 key:{} @@ -404,11 +509,23 @@ public static JSONObject parse(@NotNull final RequestMethod method, String name, real = operate(REPLACE, replace, real, creator); //校验与修改Request>>>>>>>>>>>>>>>>> + + String db = real.getString(apijson.JSONObject.KEY_DATABASE); + String sh = real.getString(apijson.JSONObject.KEY_SCHEMA); + if (StringUtil.isEmpty(db, false)) { + db = database; + } + if (StringUtil.isEmpty(sh, false)) { + sh = schema; + } + String idKey = idCallback == null ? null : idCallback.getIdKey(db, sh, name); + String finalIdKey = StringUtil.isEmpty(idKey, false) ? apijson.JSONObject.KEY_ID : idKey; + //TODO放在operate前?考虑性能、operate修改后再验证的值是否和原来一样 //校验存在<<<<<<<<<<<<<<<<<<< TODO 格式改为 id;version,tag 兼容多个字段联合主键 String[] exists = StringUtil.split(exist); if (exists != null && exists.length > 0) { - long exceptId = real.getLongValue(KEY_ID); + long exceptId = real.getLongValue(finalIdKey); for (String e : exists) { verifyExist(name, e, real.get(e), exceptId, creator); } @@ -419,9 +536,9 @@ public static JSONObject parse(@NotNull final RequestMethod method, String name, //校验重复<<<<<<<<<<<<<<<<<<< TODO 格式改为 id;version,tag 兼容多个字段联合主键 String[] uniques = StringUtil.split(unique); if (uniques != null && uniques.length > 0) { - long exceptId = real.getLongValue(KEY_ID); + long exceptId = real.getLongValue(finalIdKey); for (String u : uniques) { - verifyRepeat(name, u, real.get(u), exceptId, creator); + verifyRepeat(name, u, real.get(u), exceptId, finalIdKey, creator); } } //校验重复>>>>>>>>>>>>>>>>>>> @@ -437,6 +554,8 @@ public static JSONObject parse(@NotNull final RequestMethod method, String name, target.put(EXIST.name(), exist); target.put(UNIQUE.name(), unique); target.put(REMOVE.name(), remove); + target.put(MUST.name(), must); + target.put(REFUSE.name(), refuse); target.put(NECESSARY.name(), necessary); target.put(DISALLOW.name(), disallow); //还原 >>>>>>>>>> @@ -768,10 +887,16 @@ private static void sqlVerify(@NotNull String funChar, @NotNull JSONObject real, return; } - SQLConfig config = creator.createSQLConfig().setMethod(RequestMethod.HEAD).setCount(1).setPage(0); - config.setTable(Test.class.getSimpleName()); + if (rv instanceof String && ((String) rv).contains("'")) { // || key.contains("#") || key.contains("--")) { + throw new IllegalArgumentException(rk + ":value 中value不合法!value 中不允许有单引号 ' !"); + } + + SQLConfig config = creator.createSQLConfig().setMethod(RequestMethod.GET).setCount(1).setPage(0); config.setTest(true); - config.putWhere("'" + rv + "'" + logic.getChar() + funChar, tv, false); + // config.setTable(Test.class.getSimpleName()); + // config.setColumn(rv + logic.getChar() + funChar) + config.putWhere(rv + logic.getChar() + funChar, tv, false); // 字符串可能 SQL 注入,目前的解决方式是加 TYPE 校验类型或者干脆不用 sqlVerify,而是通过远程函数来校验 + config.setCount(1); SQLExecutor executor = creator.createSQLExecutor(); JSONObject result = null; @@ -780,12 +905,12 @@ private static void sqlVerify(@NotNull String funChar, @NotNull JSONObject real, } finally { executor.close(); } - if (result != null && JSONResponse.isExist(result.getIntValue(JSONResponse.KEY_COUNT)) == false) { - throw new IllegalArgumentException(rk + ":" + rv + "中value不合法!必须匹配 " + logic.getChar() + tv + " !"); + if (result != null && JSONResponse.isExist(result.getIntValue(JSONResponse.KEY_CODE)) == false) { + throw new IllegalArgumentException(rk + ":value 中value不合法!必须匹配 '" + tk + "': '" + tv + "' !"); } } - + /**验证是否存在 * @param table * @param key @@ -829,7 +954,7 @@ public static void verifyExist(String table, String key, Object value, long exce public static void verifyRepeat(String table, String key, Object value, @NotNull SQLCreator creator) throws Exception { verifyRepeat(table, key, value, 0, creator); } - + /**验证是否重复 * @param table * @param key @@ -838,6 +963,19 @@ public static void verifyRepeat(String table, String key, Object value, @NotNull * @throws Exception */ public static void verifyRepeat(String table, String key, Object value, long exceptId, @NotNull SQLCreator creator) throws Exception { + verifyRepeat(table, key, value, exceptId, null, creator); + } + + /**验证是否重复 + * @param table + * @param key + * @param value + * @param exceptId 不包含id + * @param idKey + * @param creator + * @throws Exception + */ + public static void verifyRepeat(String table, String key, Object value, long exceptId, String idKey, @NotNull SQLCreator creator) throws Exception { if (key == null || value == null) { Log.e(TAG, "verifyRepeat key == null || value == null >> return;"); return; @@ -845,15 +983,16 @@ public static void verifyRepeat(String table, String key, Object value, long exc if (value instanceof JSON) { throw new UnsupportedDataTypeException(key + ":value 中value的类型不能为JSON!"); } - - + + String finalIdKey = StringUtil.isEmpty(idKey, false) ? apijson.JSONObject.KEY_ID : idKey; + SQLConfig config = creator.createSQLConfig().setMethod(RequestMethod.HEAD).setCount(1).setPage(0); config.setTable(table); if (exceptId > 0) {//允许修改自己的属性为该属性原来的值 - config.putWhere(JSONRequest.KEY_ID + "!", exceptId, false); + config.putWhere(finalIdKey + "!", exceptId, false); } config.putWhere(key, value, false); - + SQLExecutor executor = creator.createSQLExecutor(); try { JSONObject result = executor.execute(config, false); diff --git a/APIJSONORM/src/main/java/apijson/orm/model/Test.java b/APIJSONORM/src/main/java/apijson/orm/model/Test.java index 838fa6398..5b887e383 100755 --- a/APIJSONORM/src/main/java/apijson/orm/model/Test.java +++ b/APIJSONORM/src/main/java/apijson/orm/model/Test.java @@ -7,9 +7,11 @@ import apijson.MethodAccess; -/**条件测试 +/**条件测试。最早可能 4.5.0 移除。AbstractSQLConfig 已支持 SELECT 2>1 这种简单条件表达式, + * 相当于是 SELECT 后只有 WHERE 条件表达式,其它全都没有,这样就可以去掉仅用来动态执行校验逻辑 Test 表了。 * @author Lemon */ +@Deprecated @MethodAccess(POST = {}, PUT = {}, DELETE = {}) public class Test { } From e656258c6e1e4158a084227024a79d7d2a7443b4 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Sun, 22 Nov 2020 03:06:44 +0800 Subject: [PATCH 0023/1181] =?UTF-8?q?=E6=96=B0=E5=A2=9E=20APIJSONORM=20?= =?UTF-8?q?=E7=9A=84=E8=BF=9C=E7=A8=8B=E4=BE=9D=E8=B5=96=E6=96=87=E6=A1=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- APIJSONORM/README.md | 51 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 51 insertions(+) create mode 100644 APIJSONORM/README.md diff --git a/APIJSONORM/README.md b/APIJSONORM/README.md new file mode 100644 index 000000000..f99a32f8d --- /dev/null +++ b/APIJSONORM/README.md @@ -0,0 +1,51 @@ +# APIJSONORM [![](https://jitpack.io/v/Tencent/APIJSON.svg)](https://jitpack.io/#Tencent/APIJSON) +腾讯 [APIJSON](https://github.com/Tencent/APIJSON) ORM 库,可通过 Maven, Gradle 等远程依赖。
+Tencent [APIJSON](https://github.com/Tencent/APIJSON) ORM library for remote dependencies with Maven, Gradle, etc. + +### Maven +#### 1. 在 pom.xml 中添加 JitPack 仓库 +#### 1. Add the JitPack repository to pom.xml +```xml + + + jitpack.io + https://jitpack.io + + +``` +
+ +#### 2. 在 pom.xml 中添加 APIJSON 依赖 +#### 2. Add the APIJSON dependency to pom.xml +```xml + + com.github.Tencent + APIJSON + LATEST + +``` + +
+
+
+ +### Gradle +#### 1. 在项目根目录 build.gradle 中最后添加 JitPack 仓库 +#### 1. Add the JitPack repository in your root build.gradle at the end of repositories +```gradle + allprojects { + repositories { + ... + maven { url 'https://jitpack.io/' } + } + } +``` +
+ +#### 2. 在项目某个 module 目录(例如 `app`) build.gradle 中添加 apijson-orm 依赖 +#### 2. Add the APIJSON dependency in one of your modules(such as `app`) +```gradle + dependencies { + implementation 'com.github.Tencent:APIJSON:latest' + } +``` From 076b3fe1f31dca97ef24a517fc2c662a0afc9a26 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Sun, 22 Nov 2020 03:08:19 +0800 Subject: [PATCH 0024/1181] Update README.md --- APIJSONORM/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/APIJSONORM/README.md b/APIJSONORM/README.md index f99a32f8d..7e0850527 100644 --- a/APIJSONORM/README.md +++ b/APIJSONORM/README.md @@ -19,7 +19,7 @@ Tencent [APIJSON](https://github.com/Tencent/APIJSON) ORM library for remote dep #### 2. Add the APIJSON dependency to pom.xml ```xml - com.github.Tencent + com.github.Tencent APIJSON LATEST From b40734c1e77f9a75413e179765a10a4813a00572 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Mon, 23 Nov 2020 23:26:05 +0800 Subject: [PATCH 0025/1181] Update README.md --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 174c29e6d..3c262b187 100644 --- a/README.md +++ b/README.md @@ -163,11 +163,11 @@ https://github.com/Tencent/APIJSON/issues?q=is%3Aissue+is%3Aclosed ### 快速上手 -#### 1.后端部署 +#### 1.后端上手 可以跳过这个步骤,直接用APIJSON服务器IP地址 apijson.cn:8080 来测试接口。
-见  [APIJSON后端部署 - Java](https://github.com/APIJSON/APIJSON-Demo/tree/master/APIJSON-Java-Server)
+见  [APIJSON后端上手 - Java](https://github.com/APIJSON/APIJSON-Demo/tree/master/APIJSON-Java-Server)
-#### 2.前端部署 +#### 2.前端上手 可以跳过这个步骤,直接使用 [APIAuto-机器学习HTTP接口工具](https://github.com/TommyLemon/APIAuto) 或 下载客户端App。
见  [Android](https://github.com/APIJSON/APIJSON-Demo/tree/master/APIJSON-Android)  或  [iOS](https://github.com/APIJSON/APIJSON-Demo/tree/master/APIJSON-iOS)  或  [JavaScript](https://github.com/APIJSON/APIJSON-Demo/tree/master/APIJSON-JavaScript)
From a502f95e22225a86778ff67ec00a2208c0dd72dc Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Tue, 24 Nov 2020 22:20:00 +0800 Subject: [PATCH 0026/1181] Update README.md --- README.md | 7 ------- 1 file changed, 7 deletions(-) diff --git a/README.md b/README.md index 3c262b187..f91b17c4a 100644 --- a/README.md +++ b/README.md @@ -60,13 +60,6 @@ APIJSON是一种专为API而生的 JSON网络传输协议 以及 基于这套协 ### 特点功能 -#### 在线解析 -* 自动生成接口文档,清晰可读永远最新 -* 自动校验与格式化,支持高亮和收展 -* 自动生成各种语言代码,一键下载 -* 自动管理与测试接口用例,一键共享 -* 自动给请求JSON加注释,一键切换 - #### 对于前端 * 不用再向后端催接口、求文档 * 数据和结构完全定制,要啥有啥 From bc135fb78581f7db1399260f7cff79e8cf338872 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Sat, 28 Nov 2020 15:06:02 +0800 Subject: [PATCH 0027/1181] =?UTF-8?q?dbUri=20=E5=AE=8C=E5=85=A8=E4=BA=A4?= =?UTF-8?q?=E7=BB=99=E7=94=A8=E6=88=B7=E6=8E=A7=E5=88=B6=EF=BC=8C=E4=B8=8D?= =?UTF-8?q?=E5=86=8D=E9=92=88=E5=AF=B9=20MySQL=20=E7=89=88=E6=9C=AC?= =?UTF-8?q?=E5=8F=B7=E6=9D=A5=E8=87=AA=E5=8A=A8=E9=85=8D=E7=BD=AE=E5=8F=82?= =?UTF-8?q?=E6=95=B0=EF=BC=9B=E5=8D=87=E7=BA=A7=E7=89=88=E6=9C=AC=E5=8F=B7?= =?UTF-8?q?=E4=B8=BA=204.3.0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- APIJSONORM/pom.xml | 2 +- .../java/apijson/orm/AbstractSQLExecutor.java | 31 +++---------------- 2 files changed, 5 insertions(+), 28 deletions(-) diff --git a/APIJSONORM/pom.xml b/APIJSONORM/pom.xml index 9966ad8a5..af8e3b3a3 100755 --- a/APIJSONORM/pom.xml +++ b/APIJSONORM/pom.xml @@ -5,7 +5,7 @@ apijson.orm apijson-orm - 4.2.4 + 4.3.0 jar APIJSONORM diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java index 038b1121b..ccfe38f8c 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java @@ -666,31 +666,8 @@ public Connection getConnection(@NotNull SQLConfig config) throws Exception { connection = connectionMap.get(config.getDatabase()); if (connection == null || connection.isClosed()) { Log.i(TAG, "select connection " + (connection == null ? " = null" : ("isClosed = " + connection.isClosed()))) ; - - if (config.isMySQL()) { - int v; - try { - String[] vs = config.getDBVersion().split("[.]"); - v = Integer.parseInt(vs[0]); - } - catch (Exception e) { - v = 1; - Log.e(TAG, "getStatement try { String[] vs = config.getDBVersion().split([.]); ... >> } catch (Exception e) {\n" + e.getMessage()); - } - - if (v >= 8) { - connection = DriverManager.getConnection(config.getDBUri() + "?userSSL=false&serverTimezone=GMT%2B8&useUnicode=true&characterEncoding=UTF-8&user=" - + config.getDBAccount() + "&password=" + config.getDBPassword()); - } - else { - connection = DriverManager.getConnection(config.getDBUri() + "?serverTimezone=GMT%2B8&useUnicode=true&characterEncoding=UTF-8&user=" - + config.getDBAccount() + "&password=" + config.getDBPassword()); - } - } - else { //PostgreSQL 不允许 cross-database - connection = DriverManager.getConnection(config.getDBUri(), config.getDBAccount(), config.getDBPassword()); - } - + // PostgreSQL 不允许 cross-database + connection = DriverManager.getConnection(config.getDBUri(), config.getDBAccount(), config.getDBPassword()); connectionMap.put(config.getDatabase(), connection); } @@ -795,14 +772,14 @@ public ResultSet executeQuery(@NotNull SQLConfig config) throws Exception { public int executeUpdate(@NotNull SQLConfig config) throws Exception { PreparedStatement s = getStatement(config); int count = s.executeUpdate(); //PreparedStatement 不用传 SQL - + if (config.getMethod() == RequestMethod.POST && config.getId() == null) { //自增id ResultSet rs = s.getGeneratedKeys(); if (rs != null && rs.next()) { config.setId(rs.getLong(1));//返回插入的主键id } } - + return count; } From b74e6e619d9db603ade1c8b3d3891334fc435a7e Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Sun, 29 Nov 2020 00:54:01 +0800 Subject: [PATCH 0028/1181] Update README.md --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index f91b17c4a..28294aa4a 100644 --- a/README.md +++ b/README.md @@ -186,6 +186,7 @@ https://github.com/Tencent/APIJSON/issues?q=is%3Aissue+is%3Aclosed +
[更多 APIJSON 使用者](https://github.com/Tencent/APIJSON/issues/73) From baafea458b3b59237c6b287bb321a89dbdaaf062 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Sun, 29 Nov 2020 01:39:11 +0800 Subject: [PATCH 0029/1181] Update README.md --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 28294aa4a..9500098bd 100644 --- a/README.md +++ b/README.md @@ -233,6 +233,8 @@ https://github.com/Tencent/APIJSON/issues?q=is%3Aissue+is%3Aclosed 新增功能、强化安全、提高性能、增强稳定、完善文档、丰富周边、推广使用
https://github.com/Tencent/APIJSON/blob/master/Roadmap.md +理论上所有支持 SQL 与 JDBC 的数据库,都可以用本项目对接 CRUD,待测试: +[Hive](https://cwiki.apache.org/confluence/display/Hive/LanguageManual+Select), [ClickHouse](https://clickhouse.tech/docs/zh/sql-reference/syntax/) ### 我要赞赏 如果你喜欢 APIJSON,感觉 APIJSON 帮助到了你,可以点右上角 ⭐Star 支持一下,谢谢 ^_^
From 9d61ecf397ce6b40402deb72367cb9c1fa3c9787 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Sun, 29 Nov 2020 01:56:32 +0800 Subject: [PATCH 0030/1181] Update README.md --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 9500098bd..4dbf0d871 100644 --- a/README.md +++ b/README.md @@ -233,8 +233,8 @@ https://github.com/Tencent/APIJSON/issues?q=is%3Aissue+is%3Aclosed 新增功能、强化安全、提高性能、增强稳定、完善文档、丰富周边、推广使用
https://github.com/Tencent/APIJSON/blob/master/Roadmap.md -理论上所有支持 SQL 与 JDBC 的数据库,都可以用本项目对接 CRUD,待测试: -[Hive](https://cwiki.apache.org/confluence/display/Hive/LanguageManual+Select), [ClickHouse](https://clickhouse.tech/docs/zh/sql-reference/syntax/) +理论上所有支持 SQL 与 JDBC 的数据库,都可以用本项目对接 CRUD,待测试:
+[DB2](https://www.ibm.com/support/knowledgecenter/SSEPGG_11.1.0/com.ibm.db2.luw.sql.ref.doc/doc/r0059224.html), [ClickHouse](https://clickhouse.tech/docs/zh/sql-reference/syntax/), [OceanBase](https://www.oceanbase.com/docs/oceanbase/V2.2.50/ss-sr-select_daur3l), [Spark](http://spark.apache.org/sql/), [Hive](https://cwiki.apache.org/confluence/display/Hive/LanguageManual+Select)(延伸支持 Hadoop, Spark) ### 我要赞赏 如果你喜欢 APIJSON,感觉 APIJSON 帮助到了你,可以点右上角 ⭐Star 支持一下,谢谢 ^_^
From 46c57a839bedfb030b704ec360e382d61a1603f6 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Sun, 29 Nov 2020 02:02:05 +0800 Subject: [PATCH 0031/1181] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 4dbf0d871..b9b5b92ee 100644 --- a/README.md +++ b/README.md @@ -233,7 +233,7 @@ https://github.com/Tencent/APIJSON/issues?q=is%3Aissue+is%3Aclosed 新增功能、强化安全、提高性能、增强稳定、完善文档、丰富周边、推广使用
https://github.com/Tencent/APIJSON/blob/master/Roadmap.md -理论上所有支持 SQL 与 JDBC 的数据库,都可以用本项目对接 CRUD,待测试:
+理论上所有支持 SQL 与 JDBC/ODBC 的数据库,都可以用本项目对接 CRUD,待测试:
[DB2](https://www.ibm.com/support/knowledgecenter/SSEPGG_11.1.0/com.ibm.db2.luw.sql.ref.doc/doc/r0059224.html), [ClickHouse](https://clickhouse.tech/docs/zh/sql-reference/syntax/), [OceanBase](https://www.oceanbase.com/docs/oceanbase/V2.2.50/ss-sr-select_daur3l), [Spark](http://spark.apache.org/sql/), [Hive](https://cwiki.apache.org/confluence/display/Hive/LanguageManual+Select)(延伸支持 Hadoop, Spark) ### 我要赞赏 From bf4e5e4f688025fe7dca956f61a8bcb871214474 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Sun, 29 Nov 2020 02:19:58 +0800 Subject: [PATCH 0032/1181] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index b9b5b92ee..d08effdd4 100644 --- a/README.md +++ b/README.md @@ -233,7 +233,7 @@ https://github.com/Tencent/APIJSON/issues?q=is%3Aissue+is%3Aclosed 新增功能、强化安全、提高性能、增强稳定、完善文档、丰富周边、推广使用
https://github.com/Tencent/APIJSON/blob/master/Roadmap.md -理论上所有支持 SQL 与 JDBC/ODBC 的数据库,都可以用本项目对接 CRUD,待测试:
+理论上所有支持 SQL 与 JDBC/ODBC 的软件,都可以用本项目对接 CRUD,待测试:
[DB2](https://www.ibm.com/support/knowledgecenter/SSEPGG_11.1.0/com.ibm.db2.luw.sql.ref.doc/doc/r0059224.html), [ClickHouse](https://clickhouse.tech/docs/zh/sql-reference/syntax/), [OceanBase](https://www.oceanbase.com/docs/oceanbase/V2.2.50/ss-sr-select_daur3l), [Spark](http://spark.apache.org/sql/), [Hive](https://cwiki.apache.org/confluence/display/Hive/LanguageManual+Select)(延伸支持 Hadoop, Spark) ### 我要赞赏 From 53f13ffab2cc3a7c08957aa57c689e845b9e5a09 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Sun, 29 Nov 2020 02:42:01 +0800 Subject: [PATCH 0033/1181] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index d08effdd4..38db498b5 100644 --- a/README.md +++ b/README.md @@ -234,7 +234,7 @@ https://github.com/Tencent/APIJSON/issues?q=is%3Aissue+is%3Aclosed https://github.com/Tencent/APIJSON/blob/master/Roadmap.md 理论上所有支持 SQL 与 JDBC/ODBC 的软件,都可以用本项目对接 CRUD,待测试:
-[DB2](https://www.ibm.com/support/knowledgecenter/SSEPGG_11.1.0/com.ibm.db2.luw.sql.ref.doc/doc/r0059224.html), [ClickHouse](https://clickhouse.tech/docs/zh/sql-reference/syntax/), [OceanBase](https://www.oceanbase.com/docs/oceanbase/V2.2.50/ss-sr-select_daur3l), [Spark](http://spark.apache.org/sql/), [Hive](https://cwiki.apache.org/confluence/display/Hive/LanguageManual+Select)(延伸支持 Hadoop, Spark) +[DB2](https://www.ibm.com/support/knowledgecenter/SSEPGG_11.1.0/com.ibm.db2.luw.sql.ref.doc/doc/r0059224.html), [ClickHouse](https://clickhouse.tech/docs/zh/sql-reference/syntax/), [OceanBase](https://www.oceanbase.com/docs/oceanbase/V2.2.50/ss-sr-select_daur3l), [Spark](http://spark.apache.org/sql/), [Hive](https://cwiki.apache.org/confluence/display/Hive/LanguageManual+Select)(延伸支持 Hadoop, Spark), [Phoenix](http://phoenix.apache.org/language/index.html#select)(延伸支持 HBase) ### 我要赞赏 如果你喜欢 APIJSON,感觉 APIJSON 帮助到了你,可以点右上角 ⭐Star 支持一下,谢谢 ^_^
From e4240857adde6367e858be6b9800bf78f116e6da Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Sun, 29 Nov 2020 03:46:48 +0800 Subject: [PATCH 0034/1181] =?UTF-8?q?=E6=95=B0=E6=8D=AE=E5=92=8C=E7=BB=93?= =?UTF-8?q?=E6=9E=84=E6=A0=A1=E9=AA=8C=E7=B1=BB=20Structure=20=E6=95=B4?= =?UTF-8?q?=E5=90=88=E5=88=B0=20AbstractVerifier=EF=BC=9BAbstractParser=20?= =?UTF-8?q?=E4=B8=AD=20IdCallback=20=E7=9B=B8=E5=85=B3=E6=96=B9=E6=B3=95?= =?UTF-8?q?=E7=A7=BB=E5=88=B0=20AbstractVerifier=EF=BC=9BAbstractParser=20?= =?UTF-8?q?=E4=BC=98=E5=8C=96=E4=BB=A3=E7=A0=81=E9=81=BF=E5=85=8D=20NPE?= =?UTF-8?q?=EF=BC=9B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../main/java/apijson/orm/AbstractParser.java | 55 +- .../java/apijson/orm/AbstractVerifier.java | 1047 ++++++++++++++++- .../src/main/java/apijson/orm/Structure.java | 788 +------------ .../src/main/java/apijson/orm/Verifier.java | 36 +- .../java/apijson/orm/model/TestRecord.java | 2 +- 5 files changed, 1104 insertions(+), 824 deletions(-) diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java b/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java index 331b542e5..002fd9609 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java @@ -34,7 +34,6 @@ import apijson.RequestMethod; import apijson.RequestRole; import apijson.StringUtil; -import apijson.orm.AbstractSQLConfig.IdCallback; import apijson.orm.exception.ConditionErrorException; import apijson.orm.exception.ConflictException; import apijson.orm.exception.NotExistException; @@ -44,7 +43,7 @@ /**parser for parsing request to JSONObject * @author Lemon */ -public abstract class AbstractParser implements Parser, ParserCreator, VerifierCreator, SQLCreator, IdCallback { +public abstract class AbstractParser implements Parser, ParserCreator, VerifierCreator, SQLCreator { protected static final String TAG = "AbstractParser"; @@ -248,10 +247,16 @@ public AbstractParser setNeedVerifyContent(boolean needVerifyContent) { @Override public SQLExecutor getSQLExecutor() { + if (sqlExecutor == null) { + sqlExecutor = createSQLExecutor(); + } return sqlExecutor; } @Override public Verifier getVerifier() { + if (verifier == null) { + verifier = createVerifier().setVisitor(getVisitor()); + } return verifier; } @@ -377,7 +382,7 @@ public JSONObject parseResponse(JSONObject request) { long duration = endTime - startTime; if (Log.DEBUG) { //用 | 替代 /,避免 APIJSON ORM,APIAuto 等解析路径错误 - requestObject.put("sql:generate|cache|execute|maxExecute", sqlExecutor.getGeneratedSQLCount() + "|" + sqlExecutor.getCachedSQLCount() + "|" + sqlExecutor.getExecutedSQLCount() + "|" + getMaxSQLCount()); + requestObject.put("sql:generate|cache|execute|maxExecute", getSQLExecutor().getGeneratedSQLCount() + "|" + getSQLExecutor().getCachedSQLCount() + "|" + getSQLExecutor().getExecutedSQLCount() + "|" + getMaxSQLCount()); requestObject.put("depth:count|max", queryDepth + "|" + getMaxQueryDepth()); requestObject.put("time:start|duration|end", startTime + "|" + duration + "|" + endTime); } @@ -402,7 +407,7 @@ public JSONObject parseResponse(JSONObject request) { @Override public void onVerifyLogin() throws Exception { - verifier.verifyLogin(); + getVerifier().verifyLogin(); } @Override public void onVerifyContent() throws Exception { @@ -427,7 +432,7 @@ public void onVerifyRole(@NotNull SQLConfig config) throws Exception { config.setRole(getVisitor().getId() == null ? RequestRole.UNKNOWN : RequestRole.LOGIN); } } - verifier.verify(config); + getVerifier().verifyAccess(config); } } @@ -493,20 +498,7 @@ public JSONObject parseCorrectRequest(RequestMethod method, String tag, int vers //JSONObject clone 浅拷贝没用,Structure.parse 会导致 structure 里面被清空,第二次从缓存里取到的就是 {} - return Structure.parseRequest(method, name, target, request, maxUpdateCount, getGlobleDatabase(), getGlobleSchema(), this, creator); - } - - @Override - public String getIdKey(String database, String schema, String table) { - return apijson.JSONObject.KEY_ID; - } - @Override - public String getUserIdKey(String database, String schema, String table) { - return apijson.JSONObject.KEY_USER_ID; - } - @Override - public Object newId(RequestMethod method, String database, String schema, String table) { - return System.currentTimeMillis(); + return getVerifier().verifyRequest(method, name, target, request, maxUpdateCount, getGlobleDatabase(), getGlobleSchema(), creator); } @@ -694,12 +686,8 @@ public JSONObject getStructure(@NotNull String table, String key, String value, config.setOrder(JSONRequest.KEY_VERSION + (version > 0 ? "+" : "-")); config.setCount(1); - if (sqlExecutor == null) { - sqlExecutor = createSQLExecutor(); - } - //too many connections error: 不try-catch,可以让客户端看到是服务器内部异常 - JSONObject result = sqlExecutor.execute(config, false); + JSONObject result = getSQLExecutor().execute(config, false); return getJSONObject(result, "structure");//解决返回值套了一层 "structure":{} } @@ -1405,10 +1393,10 @@ public JSONObject executeSQL(SQLConfig config, boolean isSubquery) throws Except JSONObject result; if (explain) { //如果先执行 explain,则 execute 会死循环,所以只能先执行非 explain config.setExplain(false); //对下面 config.getSQL(false); 生效 - JSONObject res = sqlExecutor.execute(config, false); + JSONObject res = getSQLExecutor().execute(config, false); config.setExplain(explain); - JSONObject explainResult = config.isMain() && config.getPosition() != 0 ? null : sqlExecutor.execute(config, false); + JSONObject explainResult = config.isMain() && config.getPosition() != 0 ? null : getSQLExecutor().execute(config, false); if (explainResult == null) { result = res; @@ -1420,7 +1408,7 @@ public JSONObject executeSQL(SQLConfig config, boolean isSubquery) throws Except } } else { - result = sqlExecutor.execute(config, false); + result = getSQLExecutor().execute(config, false); } return parseCorrectResponse(config.getTable(), result); @@ -1434,7 +1422,7 @@ public JSONObject executeSQL(SQLConfig config, boolean isSubquery) throws Except finally { if (config.getPosition() == 0 && config.limitSQLCount()) { int maxSQLCount = getMaxSQLCount(); - int sqlCount = sqlExecutor.getExecutedSQLCount(); + int sqlCount = getSQLExecutor().getExecutedSQLCount(); Log.d(TAG, "<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< \n\n\n 已执行 " + sqlCount + "/" + maxSQLCount + " 条 SQL \n\n\n >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>"); if (sqlCount > maxSQLCount) { throw new IllegalArgumentException("截至 " + config.getTable() + " 已执行 " + sqlCount + " 条 SQL,数量已超限,必须在 0-" + maxSQLCount + " 内 !"); @@ -1458,27 +1446,27 @@ public void setTransactionIsolation(int transactionIsolation) { @Override public void begin(int transactionIsolation) { Log.d("\n\n" + TAG, "<<<<<<<<<<<<<<<<<<<<<<< begin transactionIsolation = " + transactionIsolation + " >>>>>>>>>>>>>>>>>>>>>>> \n\n"); - sqlExecutor.setTransactionIsolation(transactionIsolation); //不知道 connection 什么时候创建,不能在这里准确控制,sqlExecutor.begin(transactionIsolation); + getSQLExecutor().setTransactionIsolation(transactionIsolation); //不知道 connection 什么时候创建,不能在这里准确控制,getSqlExecutor().begin(transactionIsolation); } @Override public void rollback() throws SQLException { Log.d("\n\n" + TAG, "<<<<<<<<<<<<<<<<<<<<<<< rollback >>>>>>>>>>>>>>>>>>>>>>> \n\n"); - sqlExecutor.rollback(); + getSQLExecutor().rollback(); } @Override public void rollback(Savepoint savepoint) throws SQLException { Log.d("\n\n" + TAG, "<<<<<<<<<<<<<<<<<<<<<<< rollback savepoint " + (savepoint == null ? "" : "!") + "= null >>>>>>>>>>>>>>>>>>>>>>> \n\n"); - sqlExecutor.rollback(savepoint); + getSQLExecutor().rollback(savepoint); } @Override public void commit() throws SQLException { Log.d("\n\n" + TAG, "<<<<<<<<<<<<<<<<<<<<<<< commit >>>>>>>>>>>>>>>>>>>>>>> \n\n"); - sqlExecutor.commit(); + getSQLExecutor().commit(); } @Override public void close() { Log.d("\n\n" + TAG, "<<<<<<<<<<<<<<<<<<<<<<< close >>>>>>>>>>>>>>>>>>>>>>> \n\n"); - sqlExecutor.close(); + getSQLExecutor().close(); } /**开始事务 @@ -1535,6 +1523,7 @@ protected void onClose() { // Log.d(TAG, "onClose >>"); close(); + verifier = null; sqlExecutor = null; queryResultMap.clear(); queryResultMap = null; diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractVerifier.java b/APIJSONORM/src/main/java/apijson/orm/AbstractVerifier.java index 7e21c0b21..ea163a480 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractVerifier.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractVerifier.java @@ -12,12 +12,34 @@ import static apijson.RequestMethod.HEADS; import static apijson.RequestMethod.POST; import static apijson.RequestMethod.PUT; +import static apijson.orm.Operation.DISALLOW; +import static apijson.orm.Operation.EXIST; +import static apijson.orm.Operation.INSERT; +import static apijson.orm.Operation.MUST; +import static apijson.orm.Operation.NECESSARY; +import static apijson.orm.Operation.REFUSE; +import static apijson.orm.Operation.REMOVE; +import static apijson.orm.Operation.REPLACE; +import static apijson.orm.Operation.TYPE; +import static apijson.orm.Operation.UNIQUE; +import static apijson.orm.Operation.UPDATE; +import static apijson.orm.Operation.VERIFY; +import java.net.URL; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collection; import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedHashSet; import java.util.List; import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; +import java.util.regex.Pattern; import javax.activation.UnsupportedDataTypeException; @@ -32,6 +54,7 @@ import apijson.RequestMethod; import apijson.RequestRole; import apijson.StringUtil; +import apijson.orm.AbstractSQLConfig.IdCallback; import apijson.orm.exception.ConflictException; import apijson.orm.exception.NotLoggedInException; import apijson.orm.model.Access; @@ -49,11 +72,12 @@ import apijson.orm.model.Test; import apijson.orm.model.TestRecord; -/**权限验证 +/**校验器(权限、请求参数、返回结果等) + * TODO 合并 Structure 的代码 * @author Lemon * @param id 与 userId 的类型,一般为 Long */ -public abstract class AbstractVerifier implements Verifier { +public abstract class AbstractVerifier implements Verifier, IdCallback { private static final String TAG = "AbstractVerifier"; @@ -61,15 +85,16 @@ public abstract class AbstractVerifier implements Verifier { // > public static final Map> SYSTEM_ACCESS_MAP; public static final Map> ACCESS_MAP; - ; + + public static final Map COMPILE_MAP; static { SYSTEM_ACCESS_MAP = new HashMap>(); + SYSTEM_ACCESS_MAP.put(Access.class.getSimpleName(), getAccessMap(Access.class.getAnnotation(MethodAccess.class))); SYSTEM_ACCESS_MAP.put(Function.class.getSimpleName(), getAccessMap(Function.class.getAnnotation(MethodAccess.class))); SYSTEM_ACCESS_MAP.put(Request.class.getSimpleName(), getAccessMap(Request.class.getAnnotation(MethodAccess.class))); SYSTEM_ACCESS_MAP.put(Response.class.getSimpleName(), getAccessMap(Response.class.getAnnotation(MethodAccess.class))); SYSTEM_ACCESS_MAP.put(Test.class.getSimpleName(), getAccessMap(Test.class.getAnnotation(MethodAccess.class))); - SYSTEM_ACCESS_MAP.put(Access.class.getSimpleName(), getAccessMap(Access.class.getAnnotation(MethodAccess.class))); if (Log.DEBUG) { SYSTEM_ACCESS_MAP.put(Table.class.getSimpleName(), getAccessMap(Table.class.getAnnotation(MethodAccess.class))); @@ -85,6 +110,8 @@ public abstract class AbstractVerifier implements Verifier { } ACCESS_MAP = new HashMap<>(SYSTEM_ACCESS_MAP); + + COMPILE_MAP = new HashMap(); } /**获取权限Map,每种操作都只允许对应的角色 @@ -114,6 +141,19 @@ public String getVisitorIdKey(SQLConfig config) { return config.getUserIdKey(); } + @Override + public String getIdKey(String database, String schema, String table) { + return apijson.JSONObject.KEY_ID; + } + @Override + public String getUserIdKey(String database, String schema, String table) { + return apijson.JSONObject.KEY_USER_ID; + } + @Override + public Object newId(RequestMethod method, String database, String schema, String table) { + return System.currentTimeMillis(); + } + @NotNull @@ -144,7 +184,17 @@ public AbstractVerifier setVisitor(Visitor visitor) { * @return * @throws Exception */ + @Deprecated public boolean verify(SQLConfig config) throws Exception { + return verifyAccess(config); + } + /**验证权限是否通过 + * @param config + * @param visitor + * @return + * @throws Exception + */ + public boolean verifyAccess(SQLConfig config) throws Exception { String table = config == null ? null : config.getTable(); if (table == null) { return true; @@ -332,6 +382,7 @@ public void verifyAdmin() throws Exception { /**验证是否重复 + * FIXME 这个方法实际上没有被使用 * @param table * @param key * @param value @@ -342,6 +393,7 @@ public void verifyRepeat(String table, String key, Object value) throws Exceptio verifyRepeat(table, key, value, 0); } /**验证是否重复 + * FIXME 这个方法实际上没有被使用,而且与 Structure.verifyRepeat 代码重复度比较高,需要简化 * @param table * @param key * @param value @@ -360,18 +412,999 @@ public void verifyRepeat(String table, String key, Object value, long exceptId) JSONRequest request = new JSONRequest(key, value); if (exceptId > 0) {//允许修改自己的属性为该属性原来的值 - request.put(JSONRequest.KEY_ID + "!", exceptId); + request.put(JSONRequest.KEY_ID + "!", exceptId); // FIXME 这里 id 写死了,不支持自定义 } - JSONObject repeat = createParser().setMethod(HEAD).setNeedVerify(true).parseResponse( + JSONObject repeat = createParser().setMethod(GET).setNeedVerify(true).parseResponse( new JSONRequest(table, request) ); repeat = repeat == null ? null : repeat.getJSONObject(table); if (repeat == null) { throw new Exception("服务器内部错误 verifyRepeat repeat == null"); } - if (repeat.getIntValue(JSONResponse.KEY_COUNT) > 0) { + if (repeat.getIntValue(JSONResponse.KEY_CODE) > 0) { throw new ConflictException(key + ": " + value + " 已经存在,不能重复!"); } } + + + /**从request提取target指定的内容 + * @param method + * @param name + * @param target + * @param request + * @param maxUpdateCount + * @param idKey + * @param userIdKey + * @param creator + * @return + * @throws Exception + */ + @Override + public JSONObject verifyRequest(@NotNull final RequestMethod method, final String name + , final JSONObject target, final JSONObject request, final int maxUpdateCount + , final String database, final String schema, final SQLCreator creator) throws Exception { + return verifyRequest(method, name, target, request, maxUpdateCount, database, schema, this, creator); + } + + /**从request提取target指定的内容 + * @param method + * @param name + * @param target + * @param request + * @param creator + * @return + * @throws Exception + */ + public static JSONObject verifyRequest(@NotNull final RequestMethod method, final String name + , final JSONObject target, final JSONObject request, final SQLCreator creator) throws Exception { + return verifyRequest(method, name, target, request, Parser.MAX_UPDATE_COUNT, creator); + } + /**从request提取target指定的内容 + * @param method + * @param name + * @param target + * @param request + * @param maxUpdateCount + * @param creator + * @return + * @throws Exception + */ + public static JSONObject verifyRequest(@NotNull final RequestMethod method, final String name + , final JSONObject target, final JSONObject request, final int maxUpdateCount, final SQLCreator creator) throws Exception { + return verifyRequest(method, name, target, request, maxUpdateCount, null, null, null, creator); + } + + /**从request提取target指定的内容 + * @param method + * @param name + * @param target + * @param request + * @param maxUpdateCount + * @param idKey + * @param userIdKey + * @param creator + * @return + * @throws Exception + */ + public static JSONObject verifyRequest(@NotNull final RequestMethod method, final String name + , final JSONObject target, final JSONObject request, final int maxUpdateCount + , final String database, final String schema, final IdCallback idCallback, final SQLCreator creator) throws Exception { + + Log.i(TAG, "verifyRequest method = " + method + "; name = " + name + + "; target = \n" + JSON.toJSONString(target) + + "\n request = \n" + JSON.toJSONString(request)); + + if (target == null || request == null) {// || request.isEmpty()) { + Log.i(TAG, "verifyRequest target == null || request == null >> return null;"); + return null; + } + + //已在 Verifier 中处理 + // if (RequestRole.get(request.getString(JSONRequest.KEY_ROLE)) == RequestRole.ADMIN) { + // throw new IllegalArgumentException("角色设置错误!不允许在写操作Request中传 " + name + + // ":{ " + JSONRequest.KEY_ROLE + ":admin } !"); + // } + + + //解析 + return parse(method, name, target, request, database, schema, idCallback, creator, new OnParseCallback() { + + @Override + public JSONObject onParseJSONObject(String key, JSONObject tobj, JSONObject robj) throws Exception { + // Log.i(TAG, "verifyRequest.parse.onParseJSONObject key = " + key + "; robj = " + robj); + + if (robj == null) { + if (tobj != null) {//不允许不传Target中指定的Table + throw new IllegalArgumentException(method + "请求,请在 " + name + " 内传 " + key + ":{} !"); + } + } else if (apijson.JSONObject.isTableKey(key)) { + String db = request.getString(apijson.JSONObject.KEY_DATABASE); + String sh = request.getString(apijson.JSONObject.KEY_SCHEMA); + if (StringUtil.isEmpty(db, false)) { + db = database; + } + if (StringUtil.isEmpty(sh, false)) { + sh = schema; + } + + String idKey = idCallback == null ? null : idCallback.getIdKey(db, sh, key); + String finalIdKey = StringUtil.isEmpty(idKey, false) ? apijson.JSONObject.KEY_ID : idKey; + + if (method == RequestMethod.POST) { + if (robj.containsKey(finalIdKey)) { + throw new IllegalArgumentException(method + "请求," + name + "/" + key + " 不能传 " + finalIdKey + " !"); + } + } else { + if (RequestMethod.isQueryMethod(method) == false) { + verifyId(method.name(), name, key, robj, finalIdKey, maxUpdateCount, true); + + String userIdKey = idCallback == null ? null : idCallback.getUserIdKey(db, sh, key); + String finalUserIdKey = StringUtil.isEmpty(userIdKey, false) ? apijson.JSONObject.KEY_USER_ID : userIdKey; + verifyId(method.name(), name, key, robj, finalUserIdKey, maxUpdateCount, false); + } + } + } + + return verifyRequest(method, key, tobj, robj, maxUpdateCount, database, schema, idCallback, creator); + } + + @Override + protected JSONArray onParseJSONArray(String key, JSONArray tarray, JSONArray rarray) throws Exception { + if ((method == RequestMethod.POST || method == RequestMethod.PUT) && JSONRequest.isArrayKey(key)) { + if (rarray == null || rarray.isEmpty()) { + throw new IllegalArgumentException(method + "请求,请在 " + name + " 内传 " + key + ":[{ ... }] " + + ",批量新增 Table[]:value 中 value 必须是包含表对象的非空数组!其中每个子项 { ... } 都是" + + " tag:" + key.substring(0, key.length() - 2) + " 对应单个新增的 structure !"); + } + if (rarray.size() > maxUpdateCount) { + throw new IllegalArgumentException(method + "请求," + name + "/" + key + + " 里面的 " + key + ":[{ ... }] 中 [] 的长度不能超过 " + maxUpdateCount + " !"); + } + } + return super.onParseJSONArray(key, tarray, rarray); + } + }); + + } + + /** + * @param method + * @param name + * @param key + * @param robj + * @param idKey + * @param atLeastOne 至少有一个不为null + */ + private static void verifyId(@NotNull String method, @NotNull String name, @NotNull String key + , @NotNull JSONObject robj, @NotNull String idKey, final int maxUpdateCount, boolean atLeastOne) { + //单个修改或删除 + Object id = robj.get(idKey); //如果必须传 id ,可在Request表中配置NECESSARY + if (id != null && id instanceof Number == false && id instanceof String == false) { + throw new IllegalArgumentException(method + "请求," + name + "/" + key + + " 里面的 " + idKey + ":value 中value的类型只能是 Long 或 String !"); + } + + + //批量修改或删除 + String idInKey = idKey + "{}"; + + JSONArray idIn = null; + try { + idIn = robj.getJSONArray(idInKey); //如果必须传 id{} ,可在Request表中配置NECESSARY + } catch (Exception e) { + throw new IllegalArgumentException(method + "请求," + name + "/" + key + + " 里面的 " + idInKey + ":value 中value的类型只能是 [Long] !"); + } + if (idIn == null) { + if (atLeastOne && id == null) { + throw new IllegalArgumentException(method + "请求," + name + "/" + key + + " 里面 " + idKey + " 和 " + idInKey + " 至少传其中一个!"); + } + } else { + if (idIn.size() > maxUpdateCount) { //不允许一次操作 maxUpdateCount 条以上记录 + throw new IllegalArgumentException(method + "请求," + name + "/" + key + + " 里面的 " + idInKey + ":[] 中[]的长度不能超过 " + maxUpdateCount + " !"); + } + //解决 id{}: ["1' OR 1='1'))--"] 绕过id{}限制 + //new ArrayList(idIn) 不能检查类型,Java泛型擦除问题,居然能把 ["a"] 赋值进去还不报错 + for (int i = 0; i < idIn.size(); i++) { + Object o = idIn.get(i); + if (o != null && o instanceof Number == false && o instanceof String == false) { + throw new IllegalArgumentException(method + "请求," + name + "/" + key + + " 里面的 " + idInKey + ":[] 中所有项的类型都只能是 Long 或 String !"); + } + } + } + } + + + /**校验并将response转换为指定的内容和结构 + * @param method + * @param name + * @param target + * @param response + * @param idKey + * @param callback + * @param creator + * @return + * @throws Exception + */ + @Override + public JSONObject verifyResponse(@NotNull final RequestMethod method, final String name + , final JSONObject target, final JSONObject response, final String database, final String schema + , SQLCreator creator, OnParseCallback callback) throws Exception { + return verifyResponse(method, name, target, response, database, schema, this, creator, callback); + } + + /**校验并将response转换为指定的内容和结构 + * @param method + * @param name + * @param target + * @param response + * @param callback + * @param creator + * @return + * @throws Exception + */ + public static JSONObject verifyResponse(@NotNull final RequestMethod method, final String name + , final JSONObject target, final JSONObject response, SQLCreator creator, OnParseCallback callback) throws Exception { + return verifyResponse(method, name, target, response, null, null, null, creator, callback); + } + /**校验并将response转换为指定的内容和结构 + * @param method + * @param name + * @param target + * @param response + * @param idKey + * @param callback + * @param creator + * @return + * @throws Exception + */ + public static JSONObject verifyResponse(@NotNull final RequestMethod method, final String name + , final JSONObject target, final JSONObject response, final String database, final String schema + , final IdCallback idKeyCallback, SQLCreator creator, OnParseCallback callback) throws Exception { + + Log.i(TAG, "verifyResponse method = " + method + "; name = " + name + + "; target = \n" + JSON.toJSONString(target) + + "\n response = \n" + JSON.toJSONString(response)); + + if (target == null || response == null) {// || target.isEmpty() { + Log.i(TAG, "verifyRequest target == null || response == null >> return response;"); + return response; + } + + //解析 + return parse(method, name, target, response, database, schema, idKeyCallback, creator, callback != null ? callback : new OnParseCallback() {}); + } + + + /**对request和response不同的解析用callback返回 + * @param method + * @param name + * @param target + * @param real + * @param creator + * @param callback + * @return + * @throws Exception + */ + public static JSONObject parse(@NotNull final RequestMethod method, String name, JSONObject target, JSONObject real + , SQLCreator creator, @NotNull OnParseCallback callback) throws Exception { + return parse(method, name, target, real, null, null, null, creator, callback); + } + + /**对request和response不同的解析用callback返回 + * @param method + * @param name + * @param target + * @param real + * @param idKey + * @param userIdKey + * @param creator + * @param callback + * @return + * @throws Exception + */ + public static JSONObject parse(@NotNull final RequestMethod method, String name, JSONObject target, JSONObject real + , final String database, final String schema, final IdCallback idCallback, SQLCreator creator, @NotNull OnParseCallback callback) throws Exception { + if (target == null) { + return null; + } + + //获取配置<<<<<<<<<<<<<<<<<<<<<<<<<<<< + JSONObject type = target.getJSONObject(TYPE.name()); + JSONObject verify = target.getJSONObject(VERIFY.name()); + JSONObject insert = target.getJSONObject(INSERT.name()); + JSONObject update = target.getJSONObject(UPDATE.name()); + JSONObject replace = target.getJSONObject(REPLACE.name()); + + String exist = StringUtil.getNoBlankString(target.getString(EXIST.name())); + String unique = StringUtil.getNoBlankString(target.getString(UNIQUE.name())); + String remove = StringUtil.getNoBlankString(target.getString(REMOVE.name())); + String must = StringUtil.getNoBlankString(target.getString(MUST.name())); + String refuse = StringUtil.getNoBlankString(target.getString(REFUSE.name())); + String necessary = StringUtil.getNoBlankString(target.getString(NECESSARY.name())); + String disallow = StringUtil.getNoBlankString(target.getString(DISALLOW.name())); + + target.remove(TYPE.name()); + target.remove(VERIFY.name()); + target.remove(INSERT.name()); + target.remove(UPDATE.name()); + target.remove(REPLACE.name()); + + target.remove(EXIST.name()); + target.remove(UNIQUE.name()); + target.remove(REMOVE.name()); + target.remove(MUST.name()); + target.remove(REFUSE.name()); + target.remove(NECESSARY.name()); + target.remove(DISALLOW.name()); + + + + //移除字段<<<<<<<<<<<<<<<<<<< + String[] removes = StringUtil.split(remove); + if (removes != null && removes.length > 0) { + for (String r : removes) { + real.remove(r); + } + } + //移除字段>>>>>>>>>>>>>>>>>>> + + //判断必要字段是否都有<<<<<<<<<<<<<<<<<<< + String[] musts = StringUtil.split(must); + List mustList = musts == null ? new ArrayList() : Arrays.asList(musts); + for (String s : mustList) { + if (real.get(s) == null) {//可能传null进来,这里还会通过 real.containsKey(s) == false) { + throw new IllegalArgumentException(method + "请求," + name + + " 里面不能缺少 " + s + " 等[" + must + "]内的任何字段!"); + } + } + + String[] necessarys = StringUtil.split(necessary); + List necessaryList = necessarys == null ? new ArrayList() : Arrays.asList(necessarys); + for (String s : necessaryList) { + if (real.get(s) == null) {//可能传null进来,这里还会通过 real.containsKey(s) == false) { + throw new IllegalArgumentException(method + "请求," + name + + " 里面不能缺少 " + s + " 等[" + necessary + "]内的任何字段!"); + } + } + //判断必要字段是否都有>>>>>>>>>>>>>>>>>>> + + + Set objKeySet = new HashSet(); //不能用tableKeySet,仅判断 Table:{} 会导致 key:{ Table:{} } 绕过判断 + + //解析内容<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< + + Set> set = new LinkedHashSet<>(target.entrySet()); + if (set.isEmpty() == false) { + + String key; + Object tvalue; + Object rvalue; + for (Entry entry : set) { + key = entry == null ? null : entry.getKey(); + if (key == null) { + continue; + } + tvalue = entry.getValue(); + rvalue = real.get(key); + if (callback.onParse(key, tvalue, rvalue) == false) { + continue; + } + + if (tvalue instanceof JSONObject) { //JSONObject,往下一级提取 + if (rvalue != null && rvalue instanceof JSONObject == false) { + throw new UnsupportedDataTypeException(key + ":value 的value不合法!类型必须是 OBJECT ,结构为 {} !"); + } + tvalue = callback.onParseJSONObject(key, (JSONObject) tvalue, (JSONObject) rvalue); + + objKeySet.add(key); + } else if (tvalue instanceof JSONArray) { //JSONArray + if (rvalue != null && rvalue instanceof JSONArray == false) { + throw new UnsupportedDataTypeException(key + ":value 的value不合法!类型必须是 ARRAY ,结构为 [] !"); + } + tvalue = callback.onParseJSONArray(key, (JSONArray) tvalue, (JSONArray) rvalue); + + if ((method == RequestMethod.POST || method == RequestMethod.PUT) && JSONRequest.isArrayKey(key)) { + objKeySet.add(key); + } + } else {//其它Object + tvalue = callback.onParseObject(key, tvalue, rvalue); + } + + if (tvalue != null) {//可以在target中加上一些不需要客户端传的键值对 + real.put(key, tvalue); + } + } + + } + + //解析内容>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> + + + + Set rkset = real.keySet(); //解析内容并没有改变rkset + + //解析不允许的字段<<<<<<<<<<<<<<<<<<< + List refuseList = new ArrayList(); + if ("!".equals(refuse)) {//所有非 must,改成 !must 更好 + for (String key : rkset) {//对@key放行,@role,@column,自定义@position等 + if (key != null && key.startsWith("@") == false + && necessaryList.contains(key) == false && objKeySet.contains(key) == false) { + refuseList.add(key); + } + } + } else { + String[] refuses = StringUtil.split(refuse); + if (refuses != null && refuses.length > 0) { + refuseList.addAll(Arrays.asList(refuses)); + } + } + + List disallowList = new ArrayList(); + if ("!".equals(disallow)) {//所有非necessary,改成 !necessary 更好 + for (String key : rkset) {//对@key放行,@role,@column,自定义@position等 + if (key != null && key.startsWith("@") == false + && necessaryList.contains(key) == false && objKeySet.contains(key) == false) { + disallowList.add(key); + } + } + } else { + String[] disallows = StringUtil.split(disallow); + if (disallows != null && disallows.length > 0) { + disallowList.addAll(Arrays.asList(disallows)); + } + } + //解析不允许的字段>>>>>>>>>>>>>>>>>>> + + + //判断不允许传的key<<<<<<<<<<<<<<<<<<<<<<<<< + for (String rk : rkset) { + if (refuseList.contains(rk)) { //不允许的字段 + throw new IllegalArgumentException(method + "请求," + name + + " 里面不允许传 " + rk + " 等" + StringUtil.getString(refuseList) + "内的任何字段!"); + } + if (disallowList.contains(rk)) { //不允许的字段 + throw new IllegalArgumentException(method + "请求," + name + + " 里面不允许传 " + rk + " 等" + StringUtil.getString(disallowList) + "内的任何字段!"); + } + + if (rk == null) { //无效的key + real.remove(rk); + continue; + } + + Object rv = real.get(rk); + + //不允许传远程函数,只能后端配置 + if (rk.endsWith("()") && rv instanceof String) { + throw new UnsupportedOperationException(method + " 请求," + rk + " 不合法!非开放请求不允许传远程函数 key():\"fun()\" !"); + } + + //不在target内的 key:{} + if (rk.startsWith("@") == false && objKeySet.contains(rk) == false) { + if (rv instanceof JSONObject) { + throw new UnsupportedOperationException(method + " 请求," +name + " 里面不允许传 " + rk + ":{} !"); + } + if ((method == RequestMethod.POST || method == RequestMethod.PUT) && rv instanceof JSONArray && JSONRequest.isArrayKey(rk)) { + throw new UnsupportedOperationException(method + " 请求," + name + " 里面不允许 " + rk + ":[] 等未定义的 Table[]:[{}] 批量操作键值对!"); + } + } + } + //判断不允许传的key>>>>>>>>>>>>>>>>>>>>>>>>> + + + + //校验与修改Request<<<<<<<<<<<<<<<<< + //在tableKeySet校验后操作,避免 导致put/add进去的Table 被当成原Request的内容 + real = operate(TYPE, type, real, creator); + real = operate(VERIFY, verify, real, creator); + real = operate(INSERT, insert, real, creator); + real = operate(UPDATE, update, real, creator); + real = operate(REPLACE, replace, real, creator); + //校验与修改Request>>>>>>>>>>>>>>>>> + + + String db = real.getString(apijson.JSONObject.KEY_DATABASE); + String sh = real.getString(apijson.JSONObject.KEY_SCHEMA); + if (StringUtil.isEmpty(db, false)) { + db = database; + } + if (StringUtil.isEmpty(sh, false)) { + sh = schema; + } + String idKey = idCallback == null ? null : idCallback.getIdKey(db, sh, name); + String finalIdKey = StringUtil.isEmpty(idKey, false) ? apijson.JSONObject.KEY_ID : idKey; + + //TODO放在operate前?考虑性能、operate修改后再验证的值是否和原来一样 + //校验存在<<<<<<<<<<<<<<<<<<< TODO 格式改为 id;version,tag 兼容多个字段联合主键 + String[] exists = StringUtil.split(exist); + if (exists != null && exists.length > 0) { + long exceptId = real.getLongValue(finalIdKey); + for (String e : exists) { + verifyExist(name, e, real.get(e), exceptId, creator); + } + } + //校验存在>>>>>>>>>>>>>>>>>>> + + //TODO放在operate前?考虑性能、operate修改后再验证的值是否和原来一样 + //校验重复<<<<<<<<<<<<<<<<<<< TODO 格式改为 id;version,tag 兼容多个字段联合主键 + String[] uniques = StringUtil.split(unique); + if (uniques != null && uniques.length > 0) { + long exceptId = real.getLongValue(finalIdKey); + for (String u : uniques) { + verifyRepeat(name, u, real.get(u), exceptId, finalIdKey, creator); + } + } + //校验重复>>>>>>>>>>>>>>>>>>> + + + //还原 <<<<<<<<<< + target.put(TYPE.name(), type); + target.put(VERIFY.name(), verify); + target.put(INSERT.name(), insert); + target.put(UPDATE.name(), update); + target.put(REPLACE.name(), replace); + + target.put(EXIST.name(), exist); + target.put(UNIQUE.name(), unique); + target.put(REMOVE.name(), remove); + target.put(MUST.name(), must); + target.put(REFUSE.name(), refuse); + target.put(NECESSARY.name(), necessary); + target.put(DISALLOW.name(), disallow); + //还原 >>>>>>>>>> + + Log.i(TAG, "parse return real = " + JSON.toJSONString(real)); + return real; + } + + + + /**执行操作 + * @param opt + * @param targetChild + * @param real + * @param creator + * @return + * @throws Exception + */ + private static JSONObject operate(Operation opt, JSONObject targetChild, JSONObject real, SQLCreator creator) throws Exception { + if (targetChild == null) { + return real; + } + if (real == null) { + throw new IllegalArgumentException("operate real == null!!!"); + } + + + Set> set = new LinkedHashSet<>(targetChild.entrySet()); + String tk; + Object tv; + + for (Entry e : set) { + tk = e == null ? null : e.getKey(); + if (tk == null) { + continue; + } + tv = e.getValue(); + + if (opt == TYPE) { + verifyType(tk, tv, real); + } + else if (opt == VERIFY) { + verifyValue(tk, tv, real, creator); + } + else if (opt == UPDATE) { + real.put(tk, tv); + } + else { + if (real.containsKey(tk)) { + if (opt == REPLACE) { + real.put(tk, tv); + } + } + else { + if (opt == INSERT) { + real.put(tk, tv); + } + } + } + } + + return real; + } + + + /**验证值类型 + * @param tk + * @param tv {@link Operation} + * @param real + * @throws Exception + */ + public static void verifyType(@NotNull String tk, Object tv, @NotNull JSONObject real) throws UnsupportedDataTypeException { + if (tv instanceof String == false) { + throw new UnsupportedDataTypeException("服务器内部错误," + tk + ":value 的value不合法!" + + "Request表校验规则中 TYPE:{ key:value } 中的value只能是String类型!"); + } + + verifyType(tk, (String) tv, real.get(tk)); + } + /**验证值类型 + * @param tk + * @param tv {@link Operation} + * @param rv + * @throws Exception + */ + public static void verifyType(@NotNull String tk, @NotNull String tv, Object rv) throws UnsupportedDataTypeException { + verifyType(tk, tv, rv, false); + } + /**验证值类型 + * @param tk + * @param tv {@link Operation} + * @param rv + * @param isInArray + * @throws Exception + */ + public static void verifyType(@NotNull String tk, @NotNull String tv, Object rv, boolean isInArray) throws UnsupportedDataTypeException { + if (rv == null) { + return; + } + + if (tv.endsWith("[]")) { + + verifyType(tk, "ARRAY", rv); + + for (Object o : (Collection) rv) { + verifyType(tk, tv.substring(0, tv.length() - 2), o, true); + } + + return; + } + + //这里不抽取 enum,因为 enum 不能满足扩展需求,子类需要可以自定义,而且 URL[] 这种也不符合命名要求,得用 constructor + getter + setter + switch (tv) { + case "BOOLEAN": //Boolean.parseBoolean(real.getString(tk)); 只会判断null和true + if (rv instanceof Boolean == false) { //JSONObject.getBoolean 可转换Number类型 + throw new UnsupportedDataTypeException(tk + ":value 的value不合法!类型必须是 BOOLEAN" + (isInArray ? "[] !" : " !")); + } + break; + case "NUMBER": //整数 + try { + Long.parseLong(rv.toString()); //1.23会转换为1 real.getLong(tk); + } catch (Exception e) { + throw new UnsupportedDataTypeException(tk + ":value 的value不合法!类型必须是 NUMBER" + (isInArray ? "[] !" : " !")); + } + break; + case "DECIMAL": //小数 + try { + Double.parseDouble(rv.toString()); + } catch (Exception e) { + throw new UnsupportedDataTypeException(tk + ":value 的value不合法!类型必须是 DECIMAL" + (isInArray ? "[] !" : " !")); + } + break; + case "STRING": + if (rv instanceof String == false) { //JSONObject.getString 可转换任何类型 + throw new UnsupportedDataTypeException(tk + ":value 的value不合法!类型必须是 STRING" + (isInArray ? "[] !" : " !")); + } + break; + case "URL": //网址,格式为 http://www.apijson.org, https://www.google.com 等 + try { + new URL((String) rv); + } catch (Exception e) { + throw new UnsupportedDataTypeException(tk + ":value 的value不合法!类型必须是 URL" + (isInArray ? "[] !" : " !")); + } + break; + case "DATE": //日期,格式为 YYYY-MM-DD(例如 2020-02-20)的 STRING + try { + LocalDate.parse((String) rv); + } catch (Exception e) { + throw new UnsupportedDataTypeException(tk + ":value 的value不合法!类型必须是格式为 YYYY-MM-DD(例如 2020-02-20)的 DATE" + (isInArray ? "[] !" : " !")); + } + break; + case "TIME": //时间,格式为 HH:mm:ss(例如 12:01:30)的 STRING + try { + LocalTime.parse((String) rv); + } catch (Exception e) { + throw new UnsupportedDataTypeException(tk + ":value 的value不合法!类型必须是格式为 HH:mm:ss(例如 12:01:30)的 TIME" + (isInArray ? "[] !" : " !")); + } + break; + case "DATETIME": //日期+时间,格式为 YYYY-MM-DDTHH:mm:ss(例如 2020-02-20T12:01:30)的 STRING + try { + LocalDateTime.parse((String) rv); + } catch (Exception e) { + throw new UnsupportedDataTypeException(tk + ":value 的value不合法!类型必须是格式为 YYYY-MM-DDTHH:mm:ss(例如 2020-02-20T12:01:30)的 DATETIME" + (isInArray ? "[] !" : " !")); + } + break; + case "OBJECT": + if (rv instanceof Map == false) { //JSONObject.getJSONObject 可转换String类型 + throw new UnsupportedDataTypeException(tk + ":value 的value不合法!类型必须是 OBJECT" + (isInArray ? "[] !" : " !") + " OBJECT 结构为 {} !"); + } + break; + case "ARRAY": + if (rv instanceof Collection == false) { //JSONObject.getJSONArray 可转换String类型 + throw new UnsupportedDataTypeException(tk + ":value 的value不合法!类型必须是 ARRAY" + (isInArray ? "[] !" : " !") + " ARRAY 结构为 [] !"); + } + break; + //目前在业务表中还用不上,单一的类型校验已经够用 + // case "JSON": + // try { + // com.alibaba.fastjson.JSON.parse(rv.toString()); + // } catch (Exception e) { + // throw new UnsupportedDataTypeException(tk + ":value 的value不合法!类型必须是 JSON !" + // + "也就是 {Object}, [Array] 或 它们对应的字符串 '{Object}', '[Array]' 4种中的一个 !"); + // } + // break; + default: + throw new UnsupportedDataTypeException( + "服务器内部错误,类型 " + tv + " 不合法!Request表校验规则中 TYPE:{ key:value } 中的 value 必须是" + + " [ BOOLEAN, NUMBER, DECIMAL, STRING, URL, DATE, TIME, DATETIME, OBJECT, ARRAY ] 或它们的数组" + + " [ BOOLEAN[], NUMBER[], DECIMAL[], STRING[], URL[], DATE[], TIME[], DATETIME[], OBJECT[], ARRAY[] ] 中的一个!"); + } + + } + + + /**验证值 + * @param tk + * @param tv + * @param real + * @param creator + * @throws Exception + */ + private static void verifyValue(@NotNull String tk, @NotNull Object tv, @NotNull JSONObject real, SQLCreator creator) throws Exception { + if (tv == null) { + throw new IllegalArgumentException("operate operate == VERIFY " + tk + ":" + tv + " , >> tv == null!!!"); + } + + String rk; + Object rv; + Logic logic; + if (tk.endsWith("$")) { //搜索 + verifyCondition("$", real, tk, tv, creator); + } + else if (tk.endsWith("~") || tk.endsWith("?")) { //TODO 正则表达式, 以后可能取消支持 ? 作为 正则匹配 的功能符 + logic = new Logic(tk.substring(0, tk.length() - 1)); + rk = logic.getKey(); + rv = real.get(rk); + if (rv == null) { + return; + } + + JSONArray array = AbstractSQLConfig.newJSONArray(tv); + + boolean m; + boolean isOr = false; + Pattern reg; + for (Object r : array) { + if (r instanceof String == false) { + throw new UnsupportedDataTypeException(rk + ":" + rv + " 中value只支持 String 或 [String] 类型!"); + } + reg = COMPILE_MAP.get(r); + if (reg == null) { + reg = Pattern.compile((String) r); + } + m = reg.matcher("" + rv).matches(); + if (m) { + if (logic.isNot()) { + throw new IllegalArgumentException(rk + ":value 中value不合法!必须匹配 " + tk + ":" + tv + " !"); + } + if (logic.isOr()) { + isOr = true; + break; + } + } else { + if (logic.isAnd()) { + throw new IllegalArgumentException(rk + ":value 中value不合法!必须匹配 " + tk + ":" + tv + " !"); + } + } + } + + if (isOr == false && logic.isOr()) { + throw new IllegalArgumentException(rk + ":value 中value不合法!必须匹配 " + tk + ":" + tv + " !"); + } + } + else if (tk.endsWith("{}")) { //rv符合tv条件或在tv内 + if (tv instanceof String) {//TODO >= 0, < 10 + verifyCondition("{}", real, tk, tv, creator); + } + else if (tv instanceof JSONArray) { + logic = new Logic(tk.substring(0, tk.length() - 2)); + rk = logic.getKey(); + rv = real.get(rk); + if (rv == null) { + return; + } + + if (((JSONArray) tv).contains(rv) == logic.isNot()) { + throw new IllegalArgumentException(rk + ":value 中value不合法!必须匹配 " + tk + ":" + tv + " !"); + } + } + else { + throw new UnsupportedDataTypeException("服务器Request表verify配置错误!"); + } + } + else if (tk.endsWith("<>")) { //rv包含tv内的值 + logic = new Logic(tk.substring(0, tk.length() - 2)); + rk = logic.getKey(); + rv = real.get(rk); + if (rv == null) { + return; + } + + if (rv instanceof JSONArray == false) { + throw new UnsupportedDataTypeException("服务器Request表verify配置错误!"); + } + + JSONArray array = AbstractSQLConfig.newJSONArray(tv); + + boolean isOr = false; + for (Object o : array) { + if (((JSONArray) rv).contains(o)) { + if (logic.isNot()) { + throw new IllegalArgumentException(rk + ":value 中value不合法!必须匹配 " + tk + ":" + tv + " !"); + } + if (logic.isOr()) { + isOr = true; + break; + } + } else { + if (logic.isAnd()) { + throw new IllegalArgumentException(rk + ":value 中value不合法!必须匹配 " + tk + ":" + tv + " !"); + } + } + } + + if (isOr == false && logic.isOr()) { + throw new IllegalArgumentException(rk + ":value 中value不合法!必须匹配 " + tk + ":" + tv + " !"); + } + } + else { + throw new IllegalArgumentException("服务器Request表verify配置错误!"); + } + } + + /**通过数据库执行SQL语句来验证条件 + * @param funChar + * @param real + * @param tk + * @param tv + * @param creator + * @throws Exception + */ + private static void verifyCondition(@NotNull String funChar, @NotNull JSONObject real, @NotNull String tk, @NotNull Object tv + , @NotNull SQLCreator creator) throws Exception { + //不能用Parser, 0 这种不符合 StringUtil.isName ! + Logic logic = new Logic(tk.substring(0, tk.length() - funChar.length())); + String rk = logic.getKey(); + Object rv = real.get(rk); + if (rv == null) { + return; + } + + if (rv instanceof String && ((String) rv).contains("'")) { // || key.contains("#") || key.contains("--")) { + throw new IllegalArgumentException(rk + ":value 中value不合法!value 中不允许有单引号 ' !"); + } + + SQLConfig config = creator.createSQLConfig().setMethod(RequestMethod.GET).setCount(1).setPage(0); + config.setTest(true); + // config.setTable(Test.class.getSimpleName()); + // config.setColumn(rv + logic.getChar() + funChar) + config.putWhere(rv + logic.getChar() + funChar, tv, false); // 字符串可能 SQL 注入,目前的解决方式是加 TYPE 校验类型或者干脆不用 sqlVerify,而是通过远程函数来校验 + config.setCount(1); + + SQLExecutor executor = creator.createSQLExecutor(); + JSONObject result = null; + try { + result = executor.execute(config, false); + } finally { + executor.close(); + } + if (result != null && JSONResponse.isExist(result.getIntValue(JSONResponse.KEY_CODE)) == false) { + throw new IllegalArgumentException(rk + ":value 中value不合法!必须匹配 '" + tk + "': '" + tv + "' !"); + } + } + + + /**验证是否存在 + * @param table + * @param key + * @param value + * @throws Exception + */ + public static void verifyExist(String table, String key, Object value, long exceptId, @NotNull SQLCreator creator) throws Exception { + if (key == null || value == null) { + Log.e(TAG, "verifyExist key == null || value == null >> return;"); + return; + } + if (value instanceof JSON) { + throw new UnsupportedDataTypeException(key + ":value 中value的类型不能为JSON!"); + } + + + SQLConfig config = creator.createSQLConfig().setMethod(RequestMethod.HEAD).setCount(1).setPage(0); + config.setTable(table); + config.putWhere(key, value, false); + + SQLExecutor executor = creator.createSQLExecutor(); + try { + JSONObject result = executor.execute(config, false); + if (result == null) { + throw new Exception("服务器内部错误 verifyExist result == null"); + } + if (result.getIntValue(JSONResponse.KEY_COUNT) <= 0) { + throw new ConflictException(key + ": " + value + " 不存在!如果必要请先创建!"); + } + } finally { + executor.close(); + } + } + + /**验证是否重复 + * @param table + * @param key + * @param value + * @throws Exception + */ + public static void verifyRepeat(String table, String key, Object value, @NotNull SQLCreator creator) throws Exception { + verifyRepeat(table, key, value, 0, creator); + } + + /**验证是否重复 + * @param table + * @param key + * @param value + * @param exceptId 不包含id + * @throws Exception + */ + public static void verifyRepeat(String table, String key, Object value, long exceptId, @NotNull SQLCreator creator) throws Exception { + verifyRepeat(table, key, value, exceptId, null, creator); + } + + /**验证是否重复 + * TODO 与 AbstractVerifier.verifyRepeat 代码重复,需要简化 + * @param table + * @param key + * @param value + * @param exceptId 不包含id + * @param idKey + * @param creator + * @throws Exception + */ + public static void verifyRepeat(String table, String key, Object value, long exceptId, String idKey, @NotNull SQLCreator creator) throws Exception { + if (key == null || value == null) { + Log.e(TAG, "verifyRepeat key == null || value == null >> return;"); + return; + } + if (value instanceof JSON) { + throw new UnsupportedDataTypeException(key + ":value 中value的类型不能为JSON!"); + } + + String finalIdKey = StringUtil.isEmpty(idKey, false) ? apijson.JSONObject.KEY_ID : idKey; + + SQLConfig config = creator.createSQLConfig().setMethod(RequestMethod.GET).setCount(1).setPage(0); + config.setTable(table); + if (exceptId > 0) { //允许修改自己的属性为该属性原来的值 + config.putWhere(finalIdKey + "!", exceptId, false); + } + config.putWhere(key, value, false); + + SQLExecutor executor = creator.createSQLExecutor(); + try { + JSONObject result = executor.execute(config, false); + if (result == null) { + throw new Exception("服务器内部错误 verifyRepeat result == null"); + } + if (result.getIntValue(JSONResponse.KEY_CODE) > 0) { + throw new ConflictException(key + ": " + value + " 已经存在,不能重复!"); + } + } finally { + executor.close(); + } + } + + } diff --git a/APIJSONORM/src/main/java/apijson/orm/Structure.java b/APIJSONORM/src/main/java/apijson/orm/Structure.java index 9f7d622d6..b5d146f90 100755 --- a/APIJSONORM/src/main/java/apijson/orm/Structure.java +++ b/APIJSONORM/src/main/java/apijson/orm/Structure.java @@ -5,64 +5,31 @@ package apijson.orm; -import static apijson.orm.Operation.DISALLOW; -import static apijson.orm.Operation.EXIST; -import static apijson.orm.Operation.INSERT; -import static apijson.orm.Operation.MUST; -import static apijson.orm.Operation.NECESSARY; -import static apijson.orm.Operation.REFUSE; -import static apijson.orm.Operation.REMOVE; -import static apijson.orm.Operation.REPLACE; -import static apijson.orm.Operation.TYPE; -import static apijson.orm.Operation.UNIQUE; -import static apijson.orm.Operation.UPDATE; -import static apijson.orm.Operation.VERIFY; - -import java.net.URL; -import java.time.LocalDate; -import java.time.LocalDateTime; -import java.time.LocalTime; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; -import java.util.HashMap; -import java.util.HashSet; -import java.util.LinkedHashSet; -import java.util.List; import java.util.Map; -import java.util.Map.Entry; -import java.util.Set; import java.util.regex.Pattern; import javax.activation.UnsupportedDataTypeException; -import com.alibaba.fastjson.JSONArray; import com.alibaba.fastjson.JSONObject; import apijson.JSON; -import apijson.JSONResponse; import apijson.Log; import apijson.NotNull; import apijson.RequestMethod; -import apijson.StringUtil; import apijson.orm.AbstractSQLConfig.Callback; import apijson.orm.AbstractSQLConfig.IdCallback; -import apijson.orm.exception.ConflictException; -/**结构类。 TODO 重构为 AbstractContentVerifier implements ContentVerifier 或干脆整合进 AbstractVerifier +/**结构类。已整合进 AbstractVerifier,最快 4.5.0 移除 * 增删改查: OPERATION(ADD,REPLACE,PUT,REMOVE) OPERATION:{key0:value0, key1:value1 ...} * 对值校验: VERIFY:{key0:value0, key1:value1 ...} (key{}:range,key$:"%m%"等) * 对值重复性校验: UNIQUE:"key0:, key1 ..." (UNIQUE:"phone,email" 等) * @author Lemon */ +@Deprecated public class Structure { public static final String TAG = "Structure"; - public static final Map COMPILE_MAP; - static { - COMPILE_MAP = new HashMap(); - } - + public static final Map COMPILE_MAP = AbstractVerifier.COMPILE_MAP; private Structure() {} @@ -110,135 +77,9 @@ public static JSONObject parseRequest(@NotNull final RequestMethod method, final , final JSONObject target, final JSONObject request, final int maxUpdateCount , final String database, final String schema, final IdCallback idCallback, final SQLCreator creator) throws Exception { - Log.i(TAG, "parseRequest method = " + method + "; name = " + name - + "; target = \n" + JSON.toJSONString(target) - + "\n request = \n" + JSON.toJSONString(request)); - - if (target == null || request == null) {// || request.isEmpty()) { - Log.i(TAG, "parseRequest target == null || request == null >> return null;"); - return null; - } - - //已在 Verifier 中处理 - // if (RequestRole.get(request.getString(JSONRequest.KEY_ROLE)) == RequestRole.ADMIN) { - // throw new IllegalArgumentException("角色设置错误!不允许在写操作Request中传 " + name + - // ":{ " + JSONRequest.KEY_ROLE + ":admin } !"); - // } - - - //解析 - return parse(method, name, target, request, database, schema, idCallback, creator, new OnParseCallback() { - - @Override - public JSONObject onParseJSONObject(String key, JSONObject tobj, JSONObject robj) throws Exception { - // Log.i(TAG, "parseRequest.parse.onParseJSONObject key = " + key + "; robj = " + robj); - - if (robj == null) { - if (tobj != null) {//不允许不传Target中指定的Table - throw new IllegalArgumentException(method + "请求,请在 " + name + " 内传 " + key + ":{} !"); - } - } else if (apijson.JSONObject.isTableKey(key)) { - String db = request.getString(apijson.JSONObject.KEY_DATABASE); - String sh = request.getString(apijson.JSONObject.KEY_SCHEMA); - if (StringUtil.isEmpty(db, false)) { - db = database; - } - if (StringUtil.isEmpty(sh, false)) { - sh = schema; - } - - String idKey = idCallback == null ? null : idCallback.getIdKey(db, sh, key); - String finalIdKey = StringUtil.isEmpty(idKey, false) ? apijson.JSONObject.KEY_ID : idKey; - - if (method == RequestMethod.POST) { - if (robj.containsKey(finalIdKey)) { - throw new IllegalArgumentException(method + "请求," + name + "/" + key + " 不能传 " + finalIdKey + " !"); - } - } else { - if (RequestMethod.isQueryMethod(method) == false) { - verifyId(method.name(), name, key, robj, finalIdKey, maxUpdateCount, true); - - String userIdKey = idCallback == null ? null : idCallback.getUserIdKey(db, sh, key); - String finalUserIdKey = StringUtil.isEmpty(userIdKey, false) ? apijson.JSONObject.KEY_USER_ID : userIdKey; - verifyId(method.name(), name, key, robj, finalUserIdKey, maxUpdateCount, false); - } - } - } - - return parseRequest(method, key, tobj, robj, maxUpdateCount, database, schema, idCallback, creator); - } - - @Override - protected JSONArray onParseJSONArray(String key, JSONArray tarray, JSONArray rarray) throws Exception { - if ((method == RequestMethod.POST || method == RequestMethod.PUT) && JSONRequest.isArrayKey(key)) { - if (rarray == null || rarray.isEmpty()) { - throw new IllegalArgumentException(method + "请求,请在 " + name + " 内传 " + key + ":[{ ... }] " - + ",批量新增 Table[]:value 中 value 必须是包含表对象的非空数组!其中每个子项 { ... } 都是" - + " tag:" + key.substring(0, key.length() - 2) + " 对应单个新增的 structure !"); - } - if (rarray.size() > maxUpdateCount) { - throw new IllegalArgumentException(method + "请求," + name + "/" + key - + " 里面的 " + key + ":[{ ... }] 中 [] 的长度不能超过 " + maxUpdateCount + " !"); - } - } - return super.onParseJSONArray(key, tarray, rarray); - } - }); - - } - - /** - * @param method - * @param name - * @param key - * @param robj - * @param idKey - * @param atLeastOne 至少有一个不为null - */ - private static void verifyId(@NotNull String method, @NotNull String name, @NotNull String key - , @NotNull JSONObject robj, @NotNull String idKey, final int maxUpdateCount, boolean atLeastOne) { - //单个修改或删除 - Object id = robj.get(idKey); //如果必须传 id ,可在Request表中配置NECESSARY - if (id != null && id instanceof Number == false && id instanceof String == false) { - throw new IllegalArgumentException(method + "请求," + name + "/" + key - + " 里面的 " + idKey + ":value 中value的类型只能是 Long 或 String !"); - } - - - //批量修改或删除 - String idInKey = idKey + "{}"; - - JSONArray idIn = null; - try { - idIn = robj.getJSONArray(idInKey); //如果必须传 id{} ,可在Request表中配置NECESSARY - } catch (Exception e) { - throw new IllegalArgumentException(method + "请求," + name + "/" + key - + " 里面的 " + idInKey + ":value 中value的类型只能是 [Long] !"); - } - if (idIn == null) { - if (atLeastOne && id == null) { - throw new IllegalArgumentException(method + "请求," + name + "/" + key - + " 里面 " + idKey + " 和 " + idInKey + " 至少传其中一个!"); - } - } else { - if (idIn.size() > maxUpdateCount) { //不允许一次操作 maxUpdateCount 条以上记录 - throw new IllegalArgumentException(method + "请求," + name + "/" + key - + " 里面的 " + idInKey + ":[] 中[]的长度不能超过 " + maxUpdateCount + " !"); - } - //解决 id{}: ["1' OR 1='1'))--"] 绕过id{}限制 - //new ArrayList(idIn) 不能检查类型,Java泛型擦除问题,居然能把 ["a"] 赋值进去还不报错 - for (int i = 0; i < idIn.size(); i++) { - Object o = idIn.get(i); - if (o != null && o instanceof Number == false && o instanceof String == false) { - throw new IllegalArgumentException(method + "请求," + name + "/" + key - + " 里面的 " + idInKey + ":[] 中所有项的类型都只能是 Long 或 String !"); - } - } - } + return AbstractVerifier.verifyRequest(method, name, target, request, maxUpdateCount, database, schema, idCallback, creator); } - - /**校验并将response转换为指定的内容和结构 * @param method * @param name @@ -311,316 +152,11 @@ public static JSONObject parse(@NotNull final RequestMethod method, String name, */ public static JSONObject parse(@NotNull final RequestMethod method, String name, JSONObject target, JSONObject real , final String database, final String schema, final IdCallback idCallback, SQLCreator creator, @NotNull OnParseCallback callback) throws Exception { - if (target == null) { - return null; - } - - //获取配置<<<<<<<<<<<<<<<<<<<<<<<<<<<< - JSONObject type = target.getJSONObject(TYPE.name()); - JSONObject verify = target.getJSONObject(VERIFY.name()); - JSONObject insert = target.getJSONObject(INSERT.name()); - JSONObject update = target.getJSONObject(UPDATE.name()); - JSONObject replace = target.getJSONObject(REPLACE.name()); - - String exist = StringUtil.getNoBlankString(target.getString(EXIST.name())); - String unique = StringUtil.getNoBlankString(target.getString(UNIQUE.name())); - String remove = StringUtil.getNoBlankString(target.getString(REMOVE.name())); - String must = StringUtil.getNoBlankString(target.getString(MUST.name())); - String refuse = StringUtil.getNoBlankString(target.getString(REFUSE.name())); - String necessary = StringUtil.getNoBlankString(target.getString(NECESSARY.name())); - String disallow = StringUtil.getNoBlankString(target.getString(DISALLOW.name())); - - target.remove(TYPE.name()); - target.remove(VERIFY.name()); - target.remove(INSERT.name()); - target.remove(UPDATE.name()); - target.remove(REPLACE.name()); - - target.remove(EXIST.name()); - target.remove(UNIQUE.name()); - target.remove(REMOVE.name()); - target.remove(MUST.name()); - target.remove(REFUSE.name()); - target.remove(NECESSARY.name()); - target.remove(DISALLOW.name()); - - - - //移除字段<<<<<<<<<<<<<<<<<<< - String[] removes = StringUtil.split(remove); - if (removes != null && removes.length > 0) { - for (String r : removes) { - real.remove(r); - } - } - //移除字段>>>>>>>>>>>>>>>>>>> - - //判断必要字段是否都有<<<<<<<<<<<<<<<<<<< - String[] musts = StringUtil.split(must); - List mustList = musts == null ? new ArrayList() : Arrays.asList(musts); - for (String s : mustList) { - if (real.get(s) == null) {//可能传null进来,这里还会通过 real.containsKey(s) == false) { - throw new IllegalArgumentException(method + "请求," + name - + " 里面不能缺少 " + s + " 等[" + must + "]内的任何字段!"); - } - } - - String[] necessarys = StringUtil.split(necessary); - List necessaryList = necessarys == null ? new ArrayList() : Arrays.asList(necessarys); - for (String s : necessaryList) { - if (real.get(s) == null) {//可能传null进来,这里还会通过 real.containsKey(s) == false) { - throw new IllegalArgumentException(method + "请求," + name - + " 里面不能缺少 " + s + " 等[" + necessary + "]内的任何字段!"); - } - } - //判断必要字段是否都有>>>>>>>>>>>>>>>>>>> - - - Set objKeySet = new HashSet(); //不能用tableKeySet,仅判断 Table:{} 会导致 key:{ Table:{} } 绕过判断 - - //解析内容<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< - - Set> set = new LinkedHashSet<>(target.entrySet()); - if (set.isEmpty() == false) { - - String key; - Object tvalue; - Object rvalue; - for (Entry entry : set) { - key = entry == null ? null : entry.getKey(); - if (key == null) { - continue; - } - tvalue = entry.getValue(); - rvalue = real.get(key); - if (callback.onParse(key, tvalue, rvalue) == false) { - continue; - } - - if (tvalue instanceof JSONObject) { //JSONObject,往下一级提取 - if (rvalue != null && rvalue instanceof JSONObject == false) { - throw new UnsupportedDataTypeException(key + ":value 的value不合法!类型必须是 OBJECT ,结构为 {} !"); - } - tvalue = callback.onParseJSONObject(key, (JSONObject) tvalue, (JSONObject) rvalue); - - objKeySet.add(key); - } else if (tvalue instanceof JSONArray) { //JSONArray - if (rvalue != null && rvalue instanceof JSONArray == false) { - throw new UnsupportedDataTypeException(key + ":value 的value不合法!类型必须是 ARRAY ,结构为 [] !"); - } - tvalue = callback.onParseJSONArray(key, (JSONArray) tvalue, (JSONArray) rvalue); - - if ((method == RequestMethod.POST || method == RequestMethod.PUT) && JSONRequest.isArrayKey(key)) { - objKeySet.add(key); - } - } else {//其它Object - tvalue = callback.onParseObject(key, tvalue, rvalue); - } - - if (tvalue != null) {//可以在target中加上一些不需要客户端传的键值对 - real.put(key, tvalue); - } - } - - } - - //解析内容>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> - - - - Set rkset = real.keySet(); //解析内容并没有改变rkset - - //解析不允许的字段<<<<<<<<<<<<<<<<<<< - List refuseList = new ArrayList(); - if ("!".equals(refuse)) {//所有非 must,改成 !must 更好 - for (String key : rkset) {//对@key放行,@role,@column,自定义@position等 - if (key != null && key.startsWith("@") == false - && necessaryList.contains(key) == false && objKeySet.contains(key) == false) { - refuseList.add(key); - } - } - } else { - String[] refuses = StringUtil.split(refuse); - if (refuses != null && refuses.length > 0) { - refuseList.addAll(Arrays.asList(refuses)); - } - } - - List disallowList = new ArrayList(); - if ("!".equals(disallow)) {//所有非necessary,改成 !necessary 更好 - for (String key : rkset) {//对@key放行,@role,@column,自定义@position等 - if (key != null && key.startsWith("@") == false - && necessaryList.contains(key) == false && objKeySet.contains(key) == false) { - disallowList.add(key); - } - } - } else { - String[] disallows = StringUtil.split(disallow); - if (disallows != null && disallows.length > 0) { - disallowList.addAll(Arrays.asList(disallows)); - } - } - //解析不允许的字段>>>>>>>>>>>>>>>>>>> - - - //判断不允许传的key<<<<<<<<<<<<<<<<<<<<<<<<< - for (String rk : rkset) { - if (refuseList.contains(rk)) { //不允许的字段 - throw new IllegalArgumentException(method + "请求," + name - + " 里面不允许传 " + rk + " 等" + StringUtil.getString(refuseList) + "内的任何字段!"); - } - if (disallowList.contains(rk)) { //不允许的字段 - throw new IllegalArgumentException(method + "请求," + name - + " 里面不允许传 " + rk + " 等" + StringUtil.getString(disallowList) + "内的任何字段!"); - } - - if (rk == null) { //无效的key - real.remove(rk); - continue; - } - - Object rv = real.get(rk); - - //不允许传远程函数,只能后端配置 - if (rk.endsWith("()") && rv instanceof String) { - throw new UnsupportedOperationException(method + " 请求," + rk + " 不合法!非开放请求不允许传远程函数 key():\"fun()\" !"); - } - - //不在target内的 key:{} - if (rk.startsWith("@") == false && objKeySet.contains(rk) == false) { - if (rv instanceof JSONObject) { - throw new UnsupportedOperationException(method + " 请求," +name + " 里面不允许传 " + rk + ":{} !"); - } - if ((method == RequestMethod.POST || method == RequestMethod.PUT) && rv instanceof JSONArray && JSONRequest.isArrayKey(rk)) { - throw new UnsupportedOperationException(method + " 请求," + name + " 里面不允许 " + rk + ":[] 等未定义的 Table[]:[{}] 批量操作键值对!"); - } - } - } - //判断不允许传的key>>>>>>>>>>>>>>>>>>>>>>>>> - - - - //校验与修改Request<<<<<<<<<<<<<<<<< - //在tableKeySet校验后操作,避免 导致put/add进去的Table 被当成原Request的内容 - real = operate(TYPE, type, real, creator); - real = operate(VERIFY, verify, real, creator); - real = operate(INSERT, insert, real, creator); - real = operate(UPDATE, update, real, creator); - real = operate(REPLACE, replace, real, creator); - //校验与修改Request>>>>>>>>>>>>>>>>> - - - String db = real.getString(apijson.JSONObject.KEY_DATABASE); - String sh = real.getString(apijson.JSONObject.KEY_SCHEMA); - if (StringUtil.isEmpty(db, false)) { - db = database; - } - if (StringUtil.isEmpty(sh, false)) { - sh = schema; - } - String idKey = idCallback == null ? null : idCallback.getIdKey(db, sh, name); - String finalIdKey = StringUtil.isEmpty(idKey, false) ? apijson.JSONObject.KEY_ID : idKey; - - //TODO放在operate前?考虑性能、operate修改后再验证的值是否和原来一样 - //校验存在<<<<<<<<<<<<<<<<<<< TODO 格式改为 id;version,tag 兼容多个字段联合主键 - String[] exists = StringUtil.split(exist); - if (exists != null && exists.length > 0) { - long exceptId = real.getLongValue(finalIdKey); - for (String e : exists) { - verifyExist(name, e, real.get(e), exceptId, creator); - } - } - //校验存在>>>>>>>>>>>>>>>>>>> - - //TODO放在operate前?考虑性能、operate修改后再验证的值是否和原来一样 - //校验重复<<<<<<<<<<<<<<<<<<< TODO 格式改为 id;version,tag 兼容多个字段联合主键 - String[] uniques = StringUtil.split(unique); - if (uniques != null && uniques.length > 0) { - long exceptId = real.getLongValue(finalIdKey); - for (String u : uniques) { - verifyRepeat(name, u, real.get(u), exceptId, finalIdKey, creator); - } - } - //校验重复>>>>>>>>>>>>>>>>>>> - - - //还原 <<<<<<<<<< - target.put(TYPE.name(), type); - target.put(VERIFY.name(), verify); - target.put(INSERT.name(), insert); - target.put(UPDATE.name(), update); - target.put(REPLACE.name(), replace); - - target.put(EXIST.name(), exist); - target.put(UNIQUE.name(), unique); - target.put(REMOVE.name(), remove); - target.put(MUST.name(), must); - target.put(REFUSE.name(), refuse); - target.put(NECESSARY.name(), necessary); - target.put(DISALLOW.name(), disallow); - //还原 >>>>>>>>>> - - Log.i(TAG, "parse return real = " + JSON.toJSONString(real)); - return real; + return AbstractVerifier.parse(method, name, target, real, database, schema, idCallback, creator, callback); } - /**执行操作 - * @param opt - * @param targetChild - * @param real - * @param creator - * @return - * @throws Exception - */ - private static JSONObject operate(Operation opt, JSONObject targetChild, JSONObject real, SQLCreator creator) throws Exception { - if (targetChild == null) { - return real; - } - if (real == null) { - throw new IllegalArgumentException("operate real == null!!!"); - } - - - Set> set = new LinkedHashSet<>(targetChild.entrySet()); - String tk; - Object tv; - - for (Entry e : set) { - tk = e == null ? null : e.getKey(); - if (tk == null) { - continue; - } - tv = e.getValue(); - - if (opt == TYPE) { - type(tk, tv, real); - } - else if (opt == VERIFY) { - verify(tk, tv, real, creator); - } - else if (opt == UPDATE) { - real.put(tk, tv); - } - else { - if (real.containsKey(tk)) { - if (opt == REPLACE) { - real.put(tk, tv); - } - } - else { - if (opt == INSERT) { - real.put(tk, tv); - } - } - } - } - - return real; - } - - /**验证值类型 * @param tk * @param tv {@link Operation} @@ -652,262 +188,7 @@ public static void type(@NotNull String tk, @NotNull String tv, Object rv) throw * @throws Exception */ public static void type(@NotNull String tk, @NotNull String tv, Object rv, boolean isInArray) throws UnsupportedDataTypeException { - if (rv == null) { - return; - } - - if (tv.endsWith("[]")) { - - type(tk, "ARRAY", rv); - - for (Object o : (Collection) rv) { - type(tk, tv.substring(0, tv.length() - 2), o, true); - } - - return; - } - - //这里不抽取 enum,因为 enum 不能满足扩展需求,子类需要可以自定义,而且 URL[] 这种也不符合命名要求,得用 constructor + getter + setter - switch (tv) { - case "BOOLEAN": //Boolean.parseBoolean(real.getString(tk)); 只会判断null和true - if (rv instanceof Boolean == false) { //JSONObject.getBoolean 可转换Number类型 - throw new UnsupportedDataTypeException(tk + ":value 的value不合法!类型必须是 BOOLEAN" + (isInArray ? "[] !" : " !")); - } - break; - case "NUMBER": //整数 - try { - Long.parseLong(rv.toString()); //1.23会转换为1 real.getLong(tk); - } catch (Exception e) { - throw new UnsupportedDataTypeException(tk + ":value 的value不合法!类型必须是 NUMBER" + (isInArray ? "[] !" : " !")); - } - break; - case "DECIMAL": //小数 - try { - Double.parseDouble(rv.toString()); - } catch (Exception e) { - throw new UnsupportedDataTypeException(tk + ":value 的value不合法!类型必须是 DECIMAL" + (isInArray ? "[] !" : " !")); - } - break; - case "STRING": - if (rv instanceof String == false) { //JSONObject.getString 可转换任何类型 - throw new UnsupportedDataTypeException(tk + ":value 的value不合法!类型必须是 STRING" + (isInArray ? "[] !" : " !")); - } - break; - case "URL": //网址,格式为 http://www.apijson.org, https://www.google.com 等 - try { - new URL((String) rv); - } catch (Exception e) { - throw new UnsupportedDataTypeException(tk + ":value 的value不合法!类型必须是 URL" + (isInArray ? "[] !" : " !")); - } - break; - case "DATE": //日期,格式为 YYYY-MM-DD(例如 2020-02-20)的 STRING - try { - LocalDate.parse((String) rv); - } catch (Exception e) { - throw new UnsupportedDataTypeException(tk + ":value 的value不合法!类型必须是格式为 YYYY-MM-DD(例如 2020-02-20)的 DATE" + (isInArray ? "[] !" : " !")); - } - break; - case "TIME": //时间,格式为 HH:mm:ss(例如 12:01:30)的 STRING - try { - LocalTime.parse((String) rv); - } catch (Exception e) { - throw new UnsupportedDataTypeException(tk + ":value 的value不合法!类型必须是格式为 HH:mm:ss(例如 12:01:30)的 TIME" + (isInArray ? "[] !" : " !")); - } - break; - case "DATETIME": //日期+时间,格式为 YYYY-MM-DDTHH:mm:ss(例如 2020-02-20T12:01:30)的 STRING - try { - LocalDateTime.parse((String) rv); - } catch (Exception e) { - throw new UnsupportedDataTypeException(tk + ":value 的value不合法!类型必须是格式为 YYYY-MM-DDTHH:mm:ss(例如 2020-02-20T12:01:30)的 DATETIME" + (isInArray ? "[] !" : " !")); - } - break; - case "OBJECT": - if (rv instanceof Map == false) { //JSONObject.getJSONObject 可转换String类型 - throw new UnsupportedDataTypeException(tk + ":value 的value不合法!类型必须是 OBJECT" + (isInArray ? "[] !" : " !") + " OBJECT 结构为 {} !"); - } - break; - case "ARRAY": - if (rv instanceof Collection == false) { //JSONObject.getJSONArray 可转换String类型 - throw new UnsupportedDataTypeException(tk + ":value 的value不合法!类型必须是 ARRAY" + (isInArray ? "[] !" : " !") + " ARRAY 结构为 [] !"); - } - break; - //目前在业务表中还用不上,单一的类型校验已经够用 - // case "JSON": - // try { - // com.alibaba.fastjson.JSON.parse(rv.toString()); - // } catch (Exception e) { - // throw new UnsupportedDataTypeException(tk + ":value 的value不合法!类型必须是 JSON !" - // + "也就是 {Object}, [Array] 或 它们对应的字符串 '{Object}', '[Array]' 4种中的一个 !"); - // } - // break; - default: - throw new UnsupportedDataTypeException( - "服务器内部错误,类型 " + tv + " 不合法!Request表校验规则中 TYPE:{ key:value } 中的 value 必须是" - + " [ BOOLEAN, NUMBER, DECIMAL, STRING, URL, DATE, TIME, DATETIME, OBJECT, ARRAY ] 或它们的数组" - + " [ BOOLEAN[], NUMBER[], DECIMAL[], STRING[], URL[], DATE[], TIME[], DATETIME[], OBJECT[], ARRAY[] ] 中的一个!"); - } - - } - - - /**验证值 - * @param tk - * @param tv - * @param real - * @param creator - * @throws Exception - */ - private static void verify(@NotNull String tk, @NotNull Object tv, @NotNull JSONObject real, SQLCreator creator) throws Exception { - if (tv == null) { - throw new IllegalArgumentException("operate operate == VERIFY " + tk + ":" + tv + " , >> tv == null!!!"); - } - - String rk; - Object rv; - Logic logic; - if (tk.endsWith("$")) { //搜索 - sqlVerify("$", real, tk, tv, creator); - } - else if (tk.endsWith("~") || tk.endsWith("?")) { //TODO 正则表达式, 以后可能取消支持 ? 作为 正则匹配 的功能符 - logic = new Logic(tk.substring(0, tk.length() - 1)); - rk = logic.getKey(); - rv = real.get(rk); - if (rv == null) { - return; - } - - JSONArray array = AbstractSQLConfig.newJSONArray(tv); - - boolean m; - boolean isOr = false; - Pattern reg; - for (Object r : array) { - if (r instanceof String == false) { - throw new UnsupportedDataTypeException(rk + ":" + rv + " 中value只支持 String 或 [String] 类型!"); - } - reg = COMPILE_MAP.get(r); - if (reg == null) { - reg = Pattern.compile((String) r); - } - m = reg.matcher("" + rv).matches(); - if (m) { - if (logic.isNot()) { - throw new IllegalArgumentException(rk + ":value 中value不合法!必须匹配 " + tk + ":" + tv + " !"); - } - if (logic.isOr()) { - isOr = true; - break; - } - } else { - if (logic.isAnd()) { - throw new IllegalArgumentException(rk + ":value 中value不合法!必须匹配 " + tk + ":" + tv + " !"); - } - } - } - - if (isOr == false && logic.isOr()) { - throw new IllegalArgumentException(rk + ":value 中value不合法!必须匹配 " + tk + ":" + tv + " !"); - } - } - else if (tk.endsWith("{}")) { //rv符合tv条件或在tv内 - if (tv instanceof String) {//TODO >= 0, < 10 - sqlVerify("{}", real, tk, tv, creator); - } - else if (tv instanceof JSONArray) { - logic = new Logic(tk.substring(0, tk.length() - 2)); - rk = logic.getKey(); - rv = real.get(rk); - if (rv == null) { - return; - } - - if (((JSONArray) tv).contains(rv) == logic.isNot()) { - throw new IllegalArgumentException(rk + ":value 中value不合法!必须匹配 " + tk + ":" + tv + " !"); - } - } - else { - throw new UnsupportedDataTypeException("服务器Request表verify配置错误!"); - } - } - else if (tk.endsWith("<>")) { //rv包含tv内的值 - logic = new Logic(tk.substring(0, tk.length() - 2)); - rk = logic.getKey(); - rv = real.get(rk); - if (rv == null) { - return; - } - - if (rv instanceof JSONArray == false) { - throw new UnsupportedDataTypeException("服务器Request表verify配置错误!"); - } - - JSONArray array = AbstractSQLConfig.newJSONArray(tv); - - boolean isOr = false; - for (Object o : array) { - if (((JSONArray) rv).contains(o)) { - if (logic.isNot()) { - throw new IllegalArgumentException(rk + ":value 中value不合法!必须匹配 " + tk + ":" + tv + " !"); - } - if (logic.isOr()) { - isOr = true; - break; - } - } else { - if (logic.isAnd()) { - throw new IllegalArgumentException(rk + ":value 中value不合法!必须匹配 " + tk + ":" + tv + " !"); - } - } - } - - if (isOr == false && logic.isOr()) { - throw new IllegalArgumentException(rk + ":value 中value不合法!必须匹配 " + tk + ":" + tv + " !"); - } - } - else { - throw new IllegalArgumentException("服务器Request表verify配置错误!"); - } - } - - /**通过数据库执行SQL语句来验证条件 - * @param funChar - * @param real - * @param tk - * @param tv - * @param creator - * @throws Exception - */ - private static void sqlVerify(@NotNull String funChar, @NotNull JSONObject real, @NotNull String tk, @NotNull Object tv - , @NotNull SQLCreator creator) throws Exception { - //不能用Parser, 0 这种不符合 StringUtil.isName ! - Logic logic = new Logic(tk.substring(0, tk.length() - funChar.length())); - String rk = logic.getKey(); - Object rv = real.get(rk); - if (rv == null) { - return; - } - - if (rv instanceof String && ((String) rv).contains("'")) { // || key.contains("#") || key.contains("--")) { - throw new IllegalArgumentException(rk + ":value 中value不合法!value 中不允许有单引号 ' !"); - } - - SQLConfig config = creator.createSQLConfig().setMethod(RequestMethod.GET).setCount(1).setPage(0); - config.setTest(true); - // config.setTable(Test.class.getSimpleName()); - // config.setColumn(rv + logic.getChar() + funChar) - config.putWhere(rv + logic.getChar() + funChar, tv, false); // 字符串可能 SQL 注入,目前的解决方式是加 TYPE 校验类型或者干脆不用 sqlVerify,而是通过远程函数来校验 - config.setCount(1); - - SQLExecutor executor = creator.createSQLExecutor(); - JSONObject result = null; - try { - result = executor.execute(config, false); - } finally { - executor.close(); - } - if (result != null && JSONResponse.isExist(result.getIntValue(JSONResponse.KEY_CODE)) == false) { - throw new IllegalArgumentException(rk + ":value 中value不合法!必须匹配 '" + tk + "': '" + tv + "' !"); - } + AbstractVerifier.verifyType(tk, tv, rv, isInArray); } @@ -918,31 +199,7 @@ private static void sqlVerify(@NotNull String funChar, @NotNull JSONObject real, * @throws Exception */ public static void verifyExist(String table, String key, Object value, long exceptId, @NotNull SQLCreator creator) throws Exception { - if (key == null || value == null) { - Log.e(TAG, "verifyExist key == null || value == null >> return;"); - return; - } - if (value instanceof JSON) { - throw new UnsupportedDataTypeException(key + ":value 中value的类型不能为JSON!"); - } - - - SQLConfig config = creator.createSQLConfig().setMethod(RequestMethod.HEAD).setCount(1).setPage(0); - config.setTable(table); - config.putWhere(key, value, false); - - SQLExecutor executor = creator.createSQLExecutor(); - try { - JSONObject result = executor.execute(config, false); - if (result == null) { - throw new Exception("服务器内部错误 verifyExist result == null"); - } - if (result.getIntValue(JSONResponse.KEY_COUNT) <= 0) { - throw new ConflictException(key + ": " + value + " 不存在!如果必要请先创建!"); - } - } finally { - executor.close(); - } + AbstractVerifier.verifyExist(table, key, value, exceptId, creator); } /**验证是否重复 @@ -967,6 +224,7 @@ public static void verifyRepeat(String table, String key, Object value, long exc } /**验证是否重复 + * TODO 与 AbstractVerifier.verifyRepeat 代码重复,需要简化 * @param table * @param key * @param value @@ -976,35 +234,7 @@ public static void verifyRepeat(String table, String key, Object value, long exc * @throws Exception */ public static void verifyRepeat(String table, String key, Object value, long exceptId, String idKey, @NotNull SQLCreator creator) throws Exception { - if (key == null || value == null) { - Log.e(TAG, "verifyRepeat key == null || value == null >> return;"); - return; - } - if (value instanceof JSON) { - throw new UnsupportedDataTypeException(key + ":value 中value的类型不能为JSON!"); - } - - String finalIdKey = StringUtil.isEmpty(idKey, false) ? apijson.JSONObject.KEY_ID : idKey; - - SQLConfig config = creator.createSQLConfig().setMethod(RequestMethod.HEAD).setCount(1).setPage(0); - config.setTable(table); - if (exceptId > 0) {//允许修改自己的属性为该属性原来的值 - config.putWhere(finalIdKey + "!", exceptId, false); - } - config.putWhere(key, value, false); - - SQLExecutor executor = creator.createSQLExecutor(); - try { - JSONObject result = executor.execute(config, false); - if (result == null) { - throw new Exception("服务器内部错误 verifyRepeat result == null"); - } - if (result.getIntValue(JSONResponse.KEY_COUNT) > 0) { - throw new ConflictException(key + ": " + value + " 已经存在,不能重复!"); - } - } finally { - executor.close(); - } + AbstractVerifier.verifyRepeat(table, key, value, exceptId, idKey, creator); } diff --git a/APIJSONORM/src/main/java/apijson/orm/Verifier.java b/APIJSONORM/src/main/java/apijson/orm/Verifier.java index f3e7c099d..b91549ec0 100755 --- a/APIJSONORM/src/main/java/apijson/orm/Verifier.java +++ b/APIJSONORM/src/main/java/apijson/orm/Verifier.java @@ -5,22 +5,32 @@ package apijson.orm; +import com.alibaba.fastjson.JSONObject; + import apijson.NotNull; import apijson.RequestMethod; import apijson.RequestRole; -/**权限验证器 +/**校验器(权限、请求参数、返回结果等) * @author Lemon */ public interface Verifier { - /**验证权限是否通过 + /**验证权限是否通过,用 verifyAccess 替代,最早 4.5.0 移除 * @param config * @param visitor * @return * @throws Exception */ + @Deprecated boolean verify(SQLConfig config) throws Exception; + /**验证权限是否通过 + * @param config + * @param visitor + * @return + * @throws Exception + */ + boolean verifyAccess(SQLConfig config) throws Exception; /**允许请求,角色不好判断,让访问者发过来角色名,OWNER,CONTACT,ADMIN等 * @param table @@ -62,15 +72,33 @@ public interface Verifier { */ void verifyRepeat(String table, String key, Object value, long exceptId) throws Exception; + /**验证请求参数的数据和结构 + * @param table + * @param key + * @param value + * @param exceptId 不包含id + * @throws Exception + */ + JSONObject verifyRequest(RequestMethod method, String name, JSONObject target, JSONObject request, + int maxUpdateCount, String globleDatabase, String globleSchema, SQLCreator creator) throws Exception; + + /**验证返回结果的数据和结构 + * @param table + * @param key + * @param value + * @param exceptId 不包含id + * @throws Exception + */ + JSONObject verifyResponse(RequestMethod method, String name, JSONObject target, JSONObject response, + String database, String schema, SQLCreator creator, OnParseCallback callback) throws Exception; + @NotNull Parser createParser(); - @NotNull Visitor getVisitor(); Verifier setVisitor(@NotNull Visitor visitor); - String getVisitorIdKey(SQLConfig config); diff --git a/APIJSONORM/src/main/java/apijson/orm/model/TestRecord.java b/APIJSONORM/src/main/java/apijson/orm/model/TestRecord.java index 0ab28d8f4..b8c519e75 100644 --- a/APIJSONORM/src/main/java/apijson/orm/model/TestRecord.java +++ b/APIJSONORM/src/main/java/apijson/orm/model/TestRecord.java @@ -13,7 +13,7 @@ import apijson.MethodAccess; -/**条件测试 +/**测试结果。5.0.0 之后可能改名为 Test * @author Lemon */ @MethodAccess(GET = { LOGIN, ADMIN }, HEAD = { LOGIN, ADMIN }) From a1a0316cf4584c8019c38f2ae3928db353ecd7df Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Sun, 29 Nov 2020 03:54:28 +0800 Subject: [PATCH 0035/1181] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 38db498b5..fcca8d769 100644 --- a/README.md +++ b/README.md @@ -234,7 +234,7 @@ https://github.com/Tencent/APIJSON/issues?q=is%3Aissue+is%3Aclosed https://github.com/Tencent/APIJSON/blob/master/Roadmap.md 理论上所有支持 SQL 与 JDBC/ODBC 的软件,都可以用本项目对接 CRUD,待测试:
-[DB2](https://www.ibm.com/support/knowledgecenter/SSEPGG_11.1.0/com.ibm.db2.luw.sql.ref.doc/doc/r0059224.html), [ClickHouse](https://clickhouse.tech/docs/zh/sql-reference/syntax/), [OceanBase](https://www.oceanbase.com/docs/oceanbase/V2.2.50/ss-sr-select_daur3l), [Spark](http://spark.apache.org/sql/), [Hive](https://cwiki.apache.org/confluence/display/Hive/LanguageManual+Select)(延伸支持 Hadoop, Spark), [Phoenix](http://phoenix.apache.org/language/index.html#select)(延伸支持 HBase) +[DB2](https://www.ibm.com/support/knowledgecenter/SSEPGG_11.1.0/com.ibm.db2.luw.sql.ref.doc/doc/r0059224.html), [ClickHouse](https://clickhouse.tech/docs/zh/sql-reference/syntax/), [OceanBase](https://www.oceanbase.com/docs/oceanbase/V2.2.50/ss-sr-select_daur3l), [Presto](https://prestodb.io/docs/current/admin/function-namespace-managers.html), [Spark](http://spark.apache.org/sql/), [Hive](https://cwiki.apache.org/confluence/display/Hive/LanguageManual+Select)(延伸支持 Hadoop, Spark), [Phoenix](http://phoenix.apache.org/language/index.html#select)(延伸支持 HBase) ### 我要赞赏 如果你喜欢 APIJSON,感觉 APIJSON 帮助到了你,可以点右上角 ⭐Star 支持一下,谢谢 ^_^
From c9c07e18345886bb7dba98307d570cbaef44b09f Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Sun, 29 Nov 2020 18:18:13 +0800 Subject: [PATCH 0036/1181] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index fcca8d769..1f5e147c2 100644 --- a/README.md +++ b/README.md @@ -182,7 +182,7 @@ https://github.com/Tencent/APIJSON/issues?q=is%3Aissue+is%3Aclosed - + From 7dab334ebbd0c565ecc05f74e5d415c6cd15edde Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Mon, 30 Nov 2020 01:11:30 +0800 Subject: [PATCH 0037/1181] Update README.md --- README.md | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 1f5e147c2..1bcd5f389 100644 --- a/README.md +++ b/README.md @@ -131,10 +131,25 @@ APIJSON是一种专为API而生的 JSON网络传输协议 以及 基于这套协
-### 为什么要用 APIJSON? +### 为什么选择 APIJSON? 前后端 关于接口的 开发、文档、联调 等 10 大痛点解析
https://github.com/Tencent/APIJSON/wiki +* 解决十大痛点 (提振开发效率、杜绝联调扯皮、规避文档缺陷 等) +* 开发提速巨大 (CRUD 零代码热更新自动化,对比 SSM 框架提速 20 倍以上) +* 腾讯官方开源 (使用 GitHub、Gitee、工蜂 等平台的官方账号开源,微信公众号、腾讯云+社区 等官方公告) +* 社区影响力大 (GitHub 9.2K Star 在 350W Java 项目中排名前 200,远超 FLAG, BAT 等国内外绝大部分开源项目) +* 各种荣誉成就 (腾讯开源五个第一、首个 GVP 获奖项目、腾讯后端开源项目 Star 第一 等) +* 多样用户案例 (内部用户包含 腾讯互娱、腾讯云 等,已登记外部用户包含 资本雄厚国企、500 强上市公司、互联网独角兽等) +* 适用场景广泛 (社交聊天、阅读资讯、影音视频、办公学习 等各种 App、网站、公众号、小程序等非金融类项目) +* 周边生态丰富 (Android, iOS, Web 等各种 Demo、继承 JSON 的海量生态、针对性的接口测试和单元测试工具等) +* 文档视频齐全 (项目介绍、快速上手、安装部署 等后端、前端、客户端的 图文解说、视频教程、代码注释 等) +* 功能丰富强大 (增删改查、分页排序、分组聚合、各种JOIN、各种子查询、垮库跨表、性能分析、排列组合 等零代码实现) +* 使用安全简单 (自动增删改查、自动生成文档、自动校验权限、自动管理版本、自动防SQL注入 等) +* 灵活定制业务 (在后端编写 远程函数,可以拿到 session、version、当前 JSON 对象、参数名称 等,然后自定义处理) +* 高质可靠代码 (代码工整规范、商业工具代码扫描报告平均每行代码 bug 率低至 0.15%) +* 多年持续迭代 (自 2016 年开源至今已连续 4 年 2000+ Commits、70+ Releases,不断更新迭代中) + ### 常见问题 #### 1.如何定制业务逻辑? From 5f222c60de552b23e7554d206c0a8b881d0e49c0 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Mon, 30 Nov 2020 01:13:53 +0800 Subject: [PATCH 0038/1181] Update README.md --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 1bcd5f389..bb948d647 100644 --- a/README.md +++ b/README.md @@ -140,13 +140,13 @@ https://github.com/Tencent/APIJSON/wiki * 腾讯官方开源 (使用 GitHub、Gitee、工蜂 等平台的官方账号开源,微信公众号、腾讯云+社区 等官方公告) * 社区影响力大 (GitHub 9.2K Star 在 350W Java 项目中排名前 200,远超 FLAG, BAT 等国内外绝大部分开源项目) * 各种荣誉成就 (腾讯开源五个第一、首个 GVP 获奖项目、腾讯后端开源项目 Star 第一 等) -* 多样用户案例 (内部用户包含 腾讯互娱、腾讯云 等,已登记外部用户包含 资本雄厚国企、500 强上市公司、互联网独角兽等) +* 多样用户案例 (内部用户包含 腾讯互娱、腾讯云 等,已登记外部用户包含 资本雄厚国企、500 强上市公司 等) * 适用场景广泛 (社交聊天、阅读资讯、影音视频、办公学习 等各种 App、网站、公众号、小程序等非金融类项目) * 周边生态丰富 (Android, iOS, Web 等各种 Demo、继承 JSON 的海量生态、针对性的接口测试和单元测试工具等) * 文档视频齐全 (项目介绍、快速上手、安装部署 等后端、前端、客户端的 图文解说、视频教程、代码注释 等) -* 功能丰富强大 (增删改查、分页排序、分组聚合、各种JOIN、各种子查询、垮库跨表、性能分析、排列组合 等零代码实现) +* 功能丰富强大 (增删改查、分页排序、分组聚合、各种JOIN、各种子查询、垮库跨表、性能分析 等零代码实现) * 使用安全简单 (自动增删改查、自动生成文档、自动校验权限、自动管理版本、自动防SQL注入 等) -* 灵活定制业务 (在后端编写 远程函数,可以拿到 session、version、当前 JSON 对象、参数名称 等,然后自定义处理) +* 灵活定制业务 (在后端编写 远程函数,可以拿到 session、version、当前 JSON 对象 等,然后自定义处理) * 高质可靠代码 (代码工整规范、商业工具代码扫描报告平均每行代码 bug 率低至 0.15%) * 多年持续迭代 (自 2016 年开源至今已连续 4 年 2000+ Commits、70+ Releases,不断更新迭代中) From 517f1b2548ab0f2d015e211fea3a93a12a61e29b Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Mon, 30 Nov 2020 01:20:12 +0800 Subject: [PATCH 0039/1181] Update README.md --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index bb948d647..de3ed6132 100644 --- a/README.md +++ b/README.md @@ -135,8 +135,8 @@ APIJSON是一种专为API而生的 JSON网络传输协议 以及 基于这套协 前后端 关于接口的 开发、文档、联调 等 10 大痛点解析
https://github.com/Tencent/APIJSON/wiki -* 解决十大痛点 (提振开发效率、杜绝联调扯皮、规避文档缺陷 等) -* 开发提速巨大 (CRUD 零代码热更新自动化,对比 SSM 框架提速 20 倍以上) +* 解决十大痛点 (APIJSON 提振开发效率、杜绝联调扯皮、规避文档缺陷、节省流量带宽 等) +* 开发提速巨大 (CRUD 零代码热更新自动化,APIJSON + SpringBoot 对比 SSM 框架提速 20 倍以上) * 腾讯官方开源 (使用 GitHub、Gitee、工蜂 等平台的官方账号开源,微信公众号、腾讯云+社区 等官方公告) * 社区影响力大 (GitHub 9.2K Star 在 350W Java 项目中排名前 200,远超 FLAG, BAT 等国内外绝大部分开源项目) * 各种荣誉成就 (腾讯开源五个第一、首个 GVP 获奖项目、腾讯后端开源项目 Star 第一 等) From d56f2d15efdca8f63aefb528404fe81f209d4f9b Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Mon, 30 Nov 2020 01:51:05 +0800 Subject: [PATCH 0040/1181] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index de3ed6132..94fd86870 100644 --- a/README.md +++ b/README.md @@ -136,7 +136,7 @@ APIJSON是一种专为API而生的 JSON网络传输协议 以及 基于这套协 https://github.com/Tencent/APIJSON/wiki * 解决十大痛点 (APIJSON 提振开发效率、杜绝联调扯皮、规避文档缺陷、节省流量带宽 等) -* 开发提速巨大 (CRUD 零代码热更新自动化,APIJSON + SpringBoot 对比 SSM 框架提速 20 倍以上) +* 开发提速巨大 (CRUD 零代码热更新自动化,APIJSONBoot 对比 SSM 框架提速 20 倍以上) * 腾讯官方开源 (使用 GitHub、Gitee、工蜂 等平台的官方账号开源,微信公众号、腾讯云+社区 等官方公告) * 社区影响力大 (GitHub 9.2K Star 在 350W Java 项目中排名前 200,远超 FLAG, BAT 等国内外绝大部分开源项目) * 各种荣誉成就 (腾讯开源五个第一、首个 GVP 获奖项目、腾讯后端开源项目 Star 第一 等) From f905876758a0addedfdfd0a9cfaa598e8c5e8a6f Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Mon, 30 Nov 2020 01:52:24 +0800 Subject: [PATCH 0041/1181] Update README.md --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 94fd86870..bca849ee8 100644 --- a/README.md +++ b/README.md @@ -157,11 +157,11 @@ https://github.com/Tencent/APIJSON/wiki https://github.com/Tencent/APIJSON/issues/101 #### 2.如何控制权限? -在 Access 表配置校验规则,默认不允许访问,需要对 每张表、每种角色、每种操作 做相应的配置,粒度细分到 Row 级
+在 Access 表配置校验规则,默认不允许访问,需要对 每张表、每种角色、每种操作 做相应的配置,粒度细分到行级
https://github.com/Tencent/APIJSON/issues/12 #### 3.如何校验参数? -在 Request 表配置校验规则 structure,提供 NECESSARY、TYPE、VERIFY 等通用方法,可通过 远程函数 来完全自定义
+在 Request 表配置校验规则 structure,提供 MUST、TYPE、VERIFY 等通用方法,可通过 远程函数 来完全自定义
https://github.com/Tencent/APIJSON/wiki#%E5%AE%9E%E7%8E%B0%E5%8E%9F%E7%90%86
From 79c8efd62afaade18f83686cd48981521ba81d8b Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Mon, 30 Nov 2020 01:53:41 +0800 Subject: [PATCH 0042/1181] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index bca849ee8..1314d20a9 100644 --- a/README.md +++ b/README.md @@ -139,7 +139,7 @@ https://github.com/Tencent/APIJSON/wiki * 开发提速巨大 (CRUD 零代码热更新自动化,APIJSONBoot 对比 SSM 框架提速 20 倍以上) * 腾讯官方开源 (使用 GitHub、Gitee、工蜂 等平台的官方账号开源,微信公众号、腾讯云+社区 等官方公告) * 社区影响力大 (GitHub 9.2K Star 在 350W Java 项目中排名前 200,远超 FLAG, BAT 等国内外绝大部分开源项目) -* 各种荣誉成就 (腾讯开源五个第一、首个 GVP 获奖项目、腾讯后端开源项目 Star 第一 等) +* 各项荣誉成就 (腾讯开源五个第一、首个 GVP 获奖项目、腾讯后端开源项目 Star 第一 等) * 多样用户案例 (内部用户包含 腾讯互娱、腾讯云 等,已登记外部用户包含 资本雄厚国企、500 强上市公司 等) * 适用场景广泛 (社交聊天、阅读资讯、影音视频、办公学习 等各种 App、网站、公众号、小程序等非金融类项目) * 周边生态丰富 (Android, iOS, Web 等各种 Demo、继承 JSON 的海量生态、针对性的接口测试和单元测试工具等) From e166b3982f9cd6653b68aae788d50c70c5548399 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Mon, 30 Nov 2020 02:02:36 +0800 Subject: [PATCH 0043/1181] Update README.md --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 1314d20a9..08b6b996c 100644 --- a/README.md +++ b/README.md @@ -144,8 +144,8 @@ https://github.com/Tencent/APIJSON/wiki * 适用场景广泛 (社交聊天、阅读资讯、影音视频、办公学习 等各种 App、网站、公众号、小程序等非金融类项目) * 周边生态丰富 (Android, iOS, Web 等各种 Demo、继承 JSON 的海量生态、针对性的接口测试和单元测试工具等) * 文档视频齐全 (项目介绍、快速上手、安装部署 等后端、前端、客户端的 图文解说、视频教程、代码注释 等) -* 功能丰富强大 (增删改查、分页排序、分组聚合、各种JOIN、各种子查询、垮库跨表、性能分析 等零代码实现) -* 使用安全简单 (自动增删改查、自动生成文档、自动校验权限、自动管理版本、自动防SQL注入 等) +* 功能丰富强大 (增删改查、分页排序、分组聚合、各种 JOIN、各种子查询、垮库跨表、性能分析 等零代码实现) +* 使用安全简单 (自动增删改查、自动生成文档、自动校验权限、自动管理版本、自动防 SQL 注入 等) * 灵活定制业务 (在后端编写 远程函数,可以拿到 session、version、当前 JSON 对象 等,然后自定义处理) * 高质可靠代码 (代码工整规范、商业工具代码扫描报告平均每行代码 bug 率低至 0.15%) * 多年持续迭代 (自 2016 年开源至今已连续 4 年 2000+ Commits、70+ Releases,不断更新迭代中) From 5e8c65f5d51a9480af9de27bc98ac258544b7c6f Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Mon, 30 Nov 2020 02:13:57 +0800 Subject: [PATCH 0044/1181] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 08b6b996c..ce5735269 100644 --- a/README.md +++ b/README.md @@ -136,7 +136,7 @@ APIJSON是一种专为API而生的 JSON网络传输协议 以及 基于这套协 https://github.com/Tencent/APIJSON/wiki * 解决十大痛点 (APIJSON 提振开发效率、杜绝联调扯皮、规避文档缺陷、节省流量带宽 等) -* 开发提速巨大 (CRUD 零代码热更新自动化,APIJSONBoot 对比 SSM 框架提速 20 倍以上) +* 开发提速巨大 (CRUD 零代码热更新自动化,APIJSONBoot 对比 SSM 等提速 20 倍以上) * 腾讯官方开源 (使用 GitHub、Gitee、工蜂 等平台的官方账号开源,微信公众号、腾讯云+社区 等官方公告) * 社区影响力大 (GitHub 9.2K Star 在 350W Java 项目中排名前 200,远超 FLAG, BAT 等国内外绝大部分开源项目) * 各项荣誉成就 (腾讯开源五个第一、首个 GVP 获奖项目、腾讯后端开源项目 Star 第一 等) From fe172f48561490ad8bbe6d5e7e907440fbf66cb8 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Mon, 30 Nov 2020 02:15:02 +0800 Subject: [PATCH 0045/1181] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index ce5735269..d28b959ef 100644 --- a/README.md +++ b/README.md @@ -136,7 +136,7 @@ APIJSON是一种专为API而生的 JSON网络传输协议 以及 基于这套协 https://github.com/Tencent/APIJSON/wiki * 解决十大痛点 (APIJSON 提振开发效率、杜绝联调扯皮、规避文档缺陷、节省流量带宽 等) -* 开发提速巨大 (CRUD 零代码热更新自动化,APIJSONBoot 对比 SSM 等提速 20 倍以上) +* 开发提速巨大 (CRUD 零代码热更新自动化,APIJSONBoot 对比 SSM、SSH 等提速 20 倍以上) * 腾讯官方开源 (使用 GitHub、Gitee、工蜂 等平台的官方账号开源,微信公众号、腾讯云+社区 等官方公告) * 社区影响力大 (GitHub 9.2K Star 在 350W Java 项目中排名前 200,远超 FLAG, BAT 等国内外绝大部分开源项目) * 各项荣誉成就 (腾讯开源五个第一、首个 GVP 获奖项目、腾讯后端开源项目 Star 第一 等) From bce6dff5a400849e159ddac470d933fe6a78570f Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Mon, 30 Nov 2020 11:21:47 +0800 Subject: [PATCH 0046/1181] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index d28b959ef..c0aa013f7 100644 --- a/README.md +++ b/README.md @@ -140,7 +140,7 @@ https://github.com/Tencent/APIJSON/wiki * 腾讯官方开源 (使用 GitHub、Gitee、工蜂 等平台的官方账号开源,微信公众号、腾讯云+社区 等官方公告) * 社区影响力大 (GitHub 9.2K Star 在 350W Java 项目中排名前 200,远超 FLAG, BAT 等国内外绝大部分开源项目) * 各项荣誉成就 (腾讯开源五个第一、首个 GVP 获奖项目、腾讯后端开源项目 Star 第一 等) -* 多样用户案例 (内部用户包含 腾讯互娱、腾讯云 等,已登记外部用户包含 资本雄厚国企、500 强上市公司 等) +* 多样用户案例 (内部用户包含 腾讯互娱、腾讯云与智慧 等,已登记外部用户包含 资本雄厚国企、500 强上市公司 等) * 适用场景广泛 (社交聊天、阅读资讯、影音视频、办公学习 等各种 App、网站、公众号、小程序等非金融类项目) * 周边生态丰富 (Android, iOS, Web 等各种 Demo、继承 JSON 的海量生态、针对性的接口测试和单元测试工具等) * 文档视频齐全 (项目介绍、快速上手、安装部署 等后端、前端、客户端的 图文解说、视频教程、代码注释 等) From bda25785fefcc89ffdea56547f2c5b6abda737e8 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Mon, 30 Nov 2020 11:30:06 +0800 Subject: [PATCH 0047/1181] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index c0aa013f7..1aaff4496 100644 --- a/README.md +++ b/README.md @@ -142,7 +142,7 @@ https://github.com/Tencent/APIJSON/wiki * 各项荣誉成就 (腾讯开源五个第一、首个 GVP 获奖项目、腾讯后端开源项目 Star 第一 等) * 多样用户案例 (内部用户包含 腾讯互娱、腾讯云与智慧 等,已登记外部用户包含 资本雄厚国企、500 强上市公司 等) * 适用场景广泛 (社交聊天、阅读资讯、影音视频、办公学习 等各种 App、网站、公众号、小程序等非金融类项目) -* 周边生态丰富 (Android, iOS, Web 等各种 Demo、继承 JSON 的海量生态、针对性的接口测试和单元测试工具等) +* 周边生态丰富 (Android, iOS, Web 等各种 Demo、继承 JSON 的海量生态、零代码 接口测试 和 单元测试 工具等) * 文档视频齐全 (项目介绍、快速上手、安装部署 等后端、前端、客户端的 图文解说、视频教程、代码注释 等) * 功能丰富强大 (增删改查、分页排序、分组聚合、各种 JOIN、各种子查询、垮库跨表、性能分析 等零代码实现) * 使用安全简单 (自动增删改查、自动生成文档、自动校验权限、自动管理版本、自动防 SQL 注入 等) From c233a303220a5aa2a45d81739117553a1b9ef6f5 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Mon, 30 Nov 2020 11:30:22 +0800 Subject: [PATCH 0048/1181] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 1aaff4496..c433d69f7 100644 --- a/README.md +++ b/README.md @@ -141,7 +141,7 @@ https://github.com/Tencent/APIJSON/wiki * 社区影响力大 (GitHub 9.2K Star 在 350W Java 项目中排名前 200,远超 FLAG, BAT 等国内外绝大部分开源项目) * 各项荣誉成就 (腾讯开源五个第一、首个 GVP 获奖项目、腾讯后端开源项目 Star 第一 等) * 多样用户案例 (内部用户包含 腾讯互娱、腾讯云与智慧 等,已登记外部用户包含 资本雄厚国企、500 强上市公司 等) -* 适用场景广泛 (社交聊天、阅读资讯、影音视频、办公学习 等各种 App、网站、公众号、小程序等非金融类项目) +* 适用场景广泛 (社交聊天、阅读资讯、影音视频、办公学习 等各种 App、网站、公众号、小程序 等非金融类项目) * 周边生态丰富 (Android, iOS, Web 等各种 Demo、继承 JSON 的海量生态、零代码 接口测试 和 单元测试 工具等) * 文档视频齐全 (项目介绍、快速上手、安装部署 等后端、前端、客户端的 图文解说、视频教程、代码注释 等) * 功能丰富强大 (增删改查、分页排序、分组聚合、各种 JOIN、各种子查询、垮库跨表、性能分析 等零代码实现) From ae61f77d07efa681c1256a3d33414770230294cc Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Mon, 30 Nov 2020 11:31:07 +0800 Subject: [PATCH 0049/1181] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index c433d69f7..9361f9856 100644 --- a/README.md +++ b/README.md @@ -140,7 +140,7 @@ https://github.com/Tencent/APIJSON/wiki * 腾讯官方开源 (使用 GitHub、Gitee、工蜂 等平台的官方账号开源,微信公众号、腾讯云+社区 等官方公告) * 社区影响力大 (GitHub 9.2K Star 在 350W Java 项目中排名前 200,远超 FLAG, BAT 等国内外绝大部分开源项目) * 各项荣誉成就 (腾讯开源五个第一、首个 GVP 获奖项目、腾讯后端开源项目 Star 第一 等) -* 多样用户案例 (内部用户包含 腾讯互娱、腾讯云与智慧 等,已登记外部用户包含 资本雄厚国企、500 强上市公司 等) +* 多样用户案例 (内部用户包含 腾讯互娱、腾讯云与智慧,已登记外部用户包含 资本雄厚国企、500 强上市公司 等) * 适用场景广泛 (社交聊天、阅读资讯、影音视频、办公学习 等各种 App、网站、公众号、小程序 等非金融类项目) * 周边生态丰富 (Android, iOS, Web 等各种 Demo、继承 JSON 的海量生态、零代码 接口测试 和 单元测试 工具等) * 文档视频齐全 (项目介绍、快速上手、安装部署 等后端、前端、客户端的 图文解说、视频教程、代码注释 等) From d0364fb420ad43bd9a9eacde1933c034792ab95b Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Mon, 30 Nov 2020 11:38:27 +0800 Subject: [PATCH 0050/1181] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 9361f9856..95a629457 100644 --- a/README.md +++ b/README.md @@ -198,8 +198,8 @@ https://github.com/Tencent/APIJSON/issues?q=is%3Aissue+is%3Aclosed - +
From 8854fbd70faf1fe78e54d559c9711a1bdbb475a0 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Mon, 30 Nov 2020 11:50:08 +0800 Subject: [PATCH 0051/1181] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 95a629457..82e511e51 100644 --- a/README.md +++ b/README.md @@ -147,7 +147,7 @@ https://github.com/Tencent/APIJSON/wiki * 功能丰富强大 (增删改查、分页排序、分组聚合、各种 JOIN、各种子查询、垮库跨表、性能分析 等零代码实现) * 使用安全简单 (自动增删改查、自动生成文档、自动校验权限、自动管理版本、自动防 SQL 注入 等) * 灵活定制业务 (在后端编写 远程函数,可以拿到 session、version、当前 JSON 对象 等,然后自定义处理) -* 高质可靠代码 (代码工整规范、商业工具代码扫描报告平均每行代码 bug 率低至 0.15%) +* 高质可靠代码 (代码工整规范、商业分析软件源伞 Pinpoint 代码扫描报告平均每行代码 bug 率低至 0.15%) * 多年持续迭代 (自 2016 年开源至今已连续 4 年 2000+ Commits、70+ Releases,不断更新迭代中) From a2fe797ecd780dfe94fd19626d11c7b8761b29ef Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Mon, 30 Nov 2020 16:06:00 +0800 Subject: [PATCH 0052/1181] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 82e511e51..8412ee0c2 100644 --- a/README.md +++ b/README.md @@ -140,7 +140,7 @@ https://github.com/Tencent/APIJSON/wiki * 腾讯官方开源 (使用 GitHub、Gitee、工蜂 等平台的官方账号开源,微信公众号、腾讯云+社区 等官方公告) * 社区影响力大 (GitHub 9.2K Star 在 350W Java 项目中排名前 200,远超 FLAG, BAT 等国内外绝大部分开源项目) * 各项荣誉成就 (腾讯开源五个第一、首个 GVP 获奖项目、腾讯后端开源项目 Star 第一 等) -* 多样用户案例 (内部用户包含 腾讯互娱、腾讯云与智慧,已登记外部用户包含 资本雄厚国企、500 强上市公司 等) +* 多样用户案例 (内部用户包含 腾讯互娱、腾讯云与智慧,已登记外部用户包含 千亿资本国企、500 强上市公司 等) * 适用场景广泛 (社交聊天、阅读资讯、影音视频、办公学习 等各种 App、网站、公众号、小程序 等非金融类项目) * 周边生态丰富 (Android, iOS, Web 等各种 Demo、继承 JSON 的海量生态、零代码 接口测试 和 单元测试 工具等) * 文档视频齐全 (项目介绍、快速上手、安装部署 等后端、前端、客户端的 图文解说、视频教程、代码注释 等) From 5d23942b2e19c4e8e62e24e4582dace3befd4cf1 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Mon, 30 Nov 2020 20:24:34 +0800 Subject: [PATCH 0053/1181] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 8412ee0c2..a3ebc4868 100644 --- a/README.md +++ b/README.md @@ -33,7 +33,7 @@ This source code is licensed under the Apache License Version 2.0
English  通用文档 视频教程 - 在线工具 + 在线体验

From 9d4bd7b37f9f06ef90f4cb2823902daffc8345b5 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Wed, 2 Dec 2020 00:27:52 +0800 Subject: [PATCH 0054/1181] =?UTF-8?q?=E8=A7=A3=E5=86=B3=20Windows=20mysql-?= =?UTF-8?q?5.6.26-winx64=20=E7=AD=89=E4=BD=8E=E4=BA=8E=205.7=20=E7=9A=84?= =?UTF-8?q?=20MySQL=20=E5=8F=AF=E8=83=BD=20id{}:=20[0]=20=E7=94=9F?= =?UTF-8?q?=E6=88=90=20id=20IN(0)=20=E8=A7=A6=E5=8F=91=20MySQL=20bug=20?= =?UTF-8?q?=E5=AF=BC=E8=87=B4=E5=BF=BD=E7=95=A5=20IN=20=E6=9D=A1=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/apijson/orm/AbstractVerifier.java | 21 ++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractVerifier.java b/APIJSONORM/src/main/java/apijson/orm/AbstractVerifier.java index ea163a480..4f4c135dd 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractVerifier.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractVerifier.java @@ -56,6 +56,7 @@ import apijson.StringUtil; import apijson.orm.AbstractSQLConfig.IdCallback; import apijson.orm.exception.ConflictException; +import apijson.orm.exception.NotExistException; import apijson.orm.exception.NotLoggedInException; import apijson.orm.model.Access; import apijson.orm.model.Column; @@ -610,7 +611,25 @@ private static void verifyId(@NotNull String method, @NotNull String name, @NotN //new ArrayList(idIn) 不能检查类型,Java泛型擦除问题,居然能把 ["a"] 赋值进去还不报错 for (int i = 0; i < idIn.size(); i++) { Object o = idIn.get(i); - if (o != null && o instanceof Number == false && o instanceof String == false) { + if (o == null) { + throw new IllegalArgumentException(method + "请求," + name + "/" + key + + " 里面的 " + idInKey + ":[] 中所有项都不能为 [ null, <= 0 的数字, 空字符串 \"\" ] 中任何一个 !"); + } + if (o instanceof Number) { + //解决 Windows mysql-5.6.26-winx64 等低于 5.7 的 MySQL 可能 id{}: [0] 生成 id IN(0) 触发 MySQL bug 导致忽略 IN 条件 + //例如 UPDATE `apijson`.`TestRecord` SET `testAccountId` = -1 WHERE ( (`id` IN (0)) AND (`userId`= 82001) ) + if (((Number) o).longValue() <= 0) { + throw new IllegalArgumentException(method + "请求," + name + "/" + key + + " 里面的 " + idInKey + ":[] 中所有项都不能为 [ null, <= 0 的数字, 空字符串 \"\" ] 中任何一个 !"); + } + } + else if (o instanceof String) { + if (StringUtil.isEmpty(o, true)) { + throw new IllegalArgumentException(method + "请求," + name + "/" + key + + " 里面的 " + idInKey + ":[] 中所有项都不能为 [ null, <= 0 的数字, 空字符串 \"\" ] 中任何一个 !"); + } + } + else { throw new IllegalArgumentException(method + "请求," + name + "/" + key + " 里面的 " + idInKey + ":[] 中所有项的类型都只能是 Long 或 String !"); } From 1501954efe6319ca35162cc16e78ad948bb26102 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Wed, 2 Dec 2020 11:52:25 +0800 Subject: [PATCH 0055/1181] Update README.md --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index a3ebc4868..cdad5e63c 100644 --- a/README.md +++ b/README.md @@ -148,6 +148,8 @@ https://github.com/Tencent/APIJSON/wiki * 使用安全简单 (自动增删改查、自动生成文档、自动校验权限、自动管理版本、自动防 SQL 注入 等) * 灵活定制业务 (在后端编写 远程函数,可以拿到 session、version、当前 JSON 对象 等,然后自定义处理) * 高质可靠代码 (代码工整规范、商业分析软件源伞 Pinpoint 代码扫描报告平均每行代码 bug 率低至 0.15%) +* 兼容各种项目 (对各类 Web 框架集成友好且提供 SpringBoot, JFinal 的 Demo,协议不限 HTTP,与其它库无冲突) +* 工程轻量小巧 (仅依赖 fastjson,Java 文件仅 59 个共 13719 行代码,Jar 仅 280KB,例如 APIJSONORM 4.3.1) * 多年持续迭代 (自 2016 年开源至今已连续 4 年 2000+ Commits、70+ Releases,不断更新迭代中) From 56318a3243f16229134d802058772c0164b34001 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Wed, 2 Dec 2020 11:53:48 +0800 Subject: [PATCH 0056/1181] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index cdad5e63c..53ff11ddd 100644 --- a/README.md +++ b/README.md @@ -149,7 +149,7 @@ https://github.com/Tencent/APIJSON/wiki * 灵活定制业务 (在后端编写 远程函数,可以拿到 session、version、当前 JSON 对象 等,然后自定义处理) * 高质可靠代码 (代码工整规范、商业分析软件源伞 Pinpoint 代码扫描报告平均每行代码 bug 率低至 0.15%) * 兼容各种项目 (对各类 Web 框架集成友好且提供 SpringBoot, JFinal 的 Demo,协议不限 HTTP,与其它库无冲突) -* 工程轻量小巧 (仅依赖 fastjson,Java 文件仅 59 个共 13719 行代码,Jar 仅 280KB,例如 APIJSONORM 4.3.1) +* 工程轻量小巧 (仅依赖 fastjson,Jar 仅 280KB,Java 文件仅 59 个共 13719 行代码,例如 APIJSONORM 4.3.1) * 多年持续迭代 (自 2016 年开源至今已连续 4 年 2000+ Commits、70+ Releases,不断更新迭代中) From 19e00de3eb106284225890cd7397fb59fcd75157 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Wed, 2 Dec 2020 11:58:58 +0800 Subject: [PATCH 0057/1181] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 53ff11ddd..68948e2f6 100644 --- a/README.md +++ b/README.md @@ -150,7 +150,7 @@ https://github.com/Tencent/APIJSON/wiki * 高质可靠代码 (代码工整规范、商业分析软件源伞 Pinpoint 代码扫描报告平均每行代码 bug 率低至 0.15%) * 兼容各种项目 (对各类 Web 框架集成友好且提供 SpringBoot, JFinal 的 Demo,协议不限 HTTP,与其它库无冲突) * 工程轻量小巧 (仅依赖 fastjson,Jar 仅 280KB,Java 文件仅 59 个共 13719 行代码,例如 APIJSONORM 4.3.1) -* 多年持续迭代 (自 2016 年开源至今已连续 4 年 2000+ Commits、70+ Releases,不断更新迭代中) +* 多年持续迭代 (自 2016 年开源至今已连续 4 年 2000+ Commits、70+ Releases,不断更新迭代中...) ### 常见问题 From 462dd34bb080f7389a5f1d51c62e4bcaa6b9d158 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Wed, 2 Dec 2020 12:03:11 +0800 Subject: [PATCH 0058/1181] Update README.md --- README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 68948e2f6..4f215d138 100644 --- a/README.md +++ b/README.md @@ -135,17 +135,17 @@ APIJSON是一种专为API而生的 JSON网络传输协议 以及 基于这套协 前后端 关于接口的 开发、文档、联调 等 10 大痛点解析
https://github.com/Tencent/APIJSON/wiki -* 解决十大痛点 (APIJSON 提振开发效率、杜绝联调扯皮、规避文档缺陷、节省流量带宽 等) -* 开发提速巨大 (CRUD 零代码热更新自动化,APIJSONBoot 对比 SSM、SSH 等提速 20 倍以上) +* 解决十大痛点 (APIJSON 大幅提振开发效率、强力杜绝联调扯皮、巧妙规避文档缺陷、非常节省流量带宽 等) +* 开发提速巨大 (CRUD 零代码热更新自动化,APIJSONBoot 对比 SSM、SSH 等保守估计可提速 20 倍以上) * 腾讯官方开源 (使用 GitHub、Gitee、工蜂 等平台的官方账号开源,微信公众号、腾讯云+社区 等官方公告) * 社区影响力大 (GitHub 9.2K Star 在 350W Java 项目中排名前 200,远超 FLAG, BAT 等国内外绝大部分开源项目) -* 各项荣誉成就 (腾讯开源五个第一、首个 GVP 获奖项目、腾讯后端开源项目 Star 第一 等) +* 各项荣誉成就 (腾讯开源五个第一、首个 GVP 获奖项目、腾讯后端开源项目 Star 第一、GitHub Java 周榜第一 等) * 多样用户案例 (内部用户包含 腾讯互娱、腾讯云与智慧,已登记外部用户包含 千亿资本国企、500 强上市公司 等) * 适用场景广泛 (社交聊天、阅读资讯、影音视频、办公学习 等各种 App、网站、公众号、小程序 等非金融类项目) * 周边生态丰富 (Android, iOS, Web 等各种 Demo、继承 JSON 的海量生态、零代码 接口测试 和 单元测试 工具等) * 文档视频齐全 (项目介绍、快速上手、安装部署 等后端、前端、客户端的 图文解说、视频教程、代码注释 等) * 功能丰富强大 (增删改查、分页排序、分组聚合、各种 JOIN、各种子查询、垮库跨表、性能分析 等零代码实现) -* 使用安全简单 (自动增删改查、自动生成文档、自动校验权限、自动管理版本、自动防 SQL 注入 等) +* 使用安全简单 (自动增删改查、自动生成文档、自动管理版本、自动校验权限、自动校验参数、自动防 SQL 注入 等) * 灵活定制业务 (在后端编写 远程函数,可以拿到 session、version、当前 JSON 对象 等,然后自定义处理) * 高质可靠代码 (代码工整规范、商业分析软件源伞 Pinpoint 代码扫描报告平均每行代码 bug 率低至 0.15%) * 兼容各种项目 (对各类 Web 框架集成友好且提供 SpringBoot, JFinal 的 Demo,协议不限 HTTP,与其它库无冲突) From 03f51fce6c532a79da59af9472ea8dbd3e1bdf05 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Wed, 2 Dec 2020 12:07:10 +0800 Subject: [PATCH 0059/1181] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 4f215d138..b1bf6ffd6 100644 --- a/README.md +++ b/README.md @@ -139,7 +139,7 @@ https://github.com/Tencent/APIJSON/wiki * 开发提速巨大 (CRUD 零代码热更新自动化,APIJSONBoot 对比 SSM、SSH 等保守估计可提速 20 倍以上) * 腾讯官方开源 (使用 GitHub、Gitee、工蜂 等平台的官方账号开源,微信公众号、腾讯云+社区 等官方公告) * 社区影响力大 (GitHub 9.2K Star 在 350W Java 项目中排名前 200,远超 FLAG, BAT 等国内外绝大部分开源项目) -* 各项荣誉成就 (腾讯开源五个第一、首个 GVP 获奖项目、腾讯后端开源项目 Star 第一、GitHub Java 周榜第一 等) +* 各项荣誉成就 (腾讯开源五个第一、腾讯首个 GVP 获奖项目、腾讯后端项目 Star 第一、GitHub Java 周榜第一 等) * 多样用户案例 (内部用户包含 腾讯互娱、腾讯云与智慧,已登记外部用户包含 千亿资本国企、500 强上市公司 等) * 适用场景广泛 (社交聊天、阅读资讯、影音视频、办公学习 等各种 App、网站、公众号、小程序 等非金融类项目) * 周边生态丰富 (Android, iOS, Web 等各种 Demo、继承 JSON 的海量生态、零代码 接口测试 和 单元测试 工具等) From 15e9aa94a6ef8e2793590bee1b019142b3ea8fd8 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Wed, 2 Dec 2020 23:35:42 +0800 Subject: [PATCH 0060/1181] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index b1bf6ffd6..c6866df38 100644 --- a/README.md +++ b/README.md @@ -145,7 +145,7 @@ https://github.com/Tencent/APIJSON/wiki * 周边生态丰富 (Android, iOS, Web 等各种 Demo、继承 JSON 的海量生态、零代码 接口测试 和 单元测试 工具等) * 文档视频齐全 (项目介绍、快速上手、安装部署 等后端、前端、客户端的 图文解说、视频教程、代码注释 等) * 功能丰富强大 (增删改查、分页排序、分组聚合、各种 JOIN、各种子查询、垮库跨表、性能分析 等零代码实现) -* 使用安全简单 (自动增删改查、自动生成文档、自动管理版本、自动校验权限、自动校验参数、自动防 SQL 注入 等) +* 使用安全简单 (自动增删改查、自动生成文档、自动管理版本、自动校验权限、自动校验参数、自动防 SQL 注入等) * 灵活定制业务 (在后端编写 远程函数,可以拿到 session、version、当前 JSON 对象 等,然后自定义处理) * 高质可靠代码 (代码工整规范、商业分析软件源伞 Pinpoint 代码扫描报告平均每行代码 bug 率低至 0.15%) * 兼容各种项目 (对各类 Web 框架集成友好且提供 SpringBoot, JFinal 的 Demo,协议不限 HTTP,与其它库无冲突) From 14be79985294243f5d5d0bb3b26f95f793d8bba9 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Wed, 2 Dec 2020 23:49:54 +0800 Subject: [PATCH 0061/1181] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index c6866df38..70b685a53 100644 --- a/README.md +++ b/README.md @@ -145,7 +145,7 @@ https://github.com/Tencent/APIJSON/wiki * 周边生态丰富 (Android, iOS, Web 等各种 Demo、继承 JSON 的海量生态、零代码 接口测试 和 单元测试 工具等) * 文档视频齐全 (项目介绍、快速上手、安装部署 等后端、前端、客户端的 图文解说、视频教程、代码注释 等) * 功能丰富强大 (增删改查、分页排序、分组聚合、各种 JOIN、各种子查询、垮库跨表、性能分析 等零代码实现) -* 使用安全简单 (自动增删改查、自动生成文档、自动管理版本、自动校验权限、自动校验参数、自动防 SQL 注入等) +* 使用安全简单 (自动增删改查、自动生成文档、自动管理版本、自动校验权限、自动校验参数、自动防SQL注入等) * 灵活定制业务 (在后端编写 远程函数,可以拿到 session、version、当前 JSON 对象 等,然后自定义处理) * 高质可靠代码 (代码工整规范、商业分析软件源伞 Pinpoint 代码扫描报告平均每行代码 bug 率低至 0.15%) * 兼容各种项目 (对各类 Web 框架集成友好且提供 SpringBoot, JFinal 的 Demo,协议不限 HTTP,与其它库无冲突) From c7136d0d9937d75910150eed13319b5b207f8011 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Sun, 6 Dec 2020 19:39:40 +0800 Subject: [PATCH 0062/1181] Update README.md --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 70b685a53..b1860cd77 100644 --- a/README.md +++ b/README.md @@ -281,6 +281,8 @@ QQ 技术群: 734652054(新)、607020115(旧) [学习自动化接口APIJSON](https://www.jianshu.com/p/981a2a630c7b) +[APIJSON使用例子总结](https://blog.csdn.net/weixin_41077841/article/details/110518007) + [APIJSON 自动化接口和文档的快速开发神器 (一)](https://blog.csdn.net/qq_41829492/article/details/88670940) [APIJSON在mac电脑环境下配置去连接SQL Server](https://juejin.im/post/5e16d21ef265da3e2e4f4956) From 6b72cbf5fa38895e393bb7f82d4ecb0431c9a2b6 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Tue, 8 Dec 2020 17:07:13 +0800 Subject: [PATCH 0063/1181] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index b1860cd77..d8375254c 100644 --- a/README.md +++ b/README.md @@ -251,7 +251,7 @@ https://github.com/Tencent/APIJSON/issues?q=is%3Aissue+is%3Aclosed https://github.com/Tencent/APIJSON/blob/master/Roadmap.md 理论上所有支持 SQL 与 JDBC/ODBC 的软件,都可以用本项目对接 CRUD,待测试:
-[DB2](https://www.ibm.com/support/knowledgecenter/SSEPGG_11.1.0/com.ibm.db2.luw.sql.ref.doc/doc/r0059224.html), [ClickHouse](https://clickhouse.tech/docs/zh/sql-reference/syntax/), [OceanBase](https://www.oceanbase.com/docs/oceanbase/V2.2.50/ss-sr-select_daur3l), [Presto](https://prestodb.io/docs/current/admin/function-namespace-managers.html), [Spark](http://spark.apache.org/sql/), [Hive](https://cwiki.apache.org/confluence/display/Hive/LanguageManual+Select)(延伸支持 Hadoop, Spark), [Phoenix](http://phoenix.apache.org/language/index.html#select)(延伸支持 HBase) +[DB2](https://www.ibm.com/support/knowledgecenter/SSEPGG_11.1.0/com.ibm.db2.luw.sql.ref.doc/doc/r0059224.html), [Elasticsearch](https://www.elastic.co/cn/what-is/elasticsearch-sql), [ClickHouse](https://clickhouse.tech/docs/zh/sql-reference/syntax/), [OceanBase](https://www.oceanbase.com/docs/oceanbase/V2.2.50/ss-sr-select_daur3l), [Presto](https://prestodb.io/docs/current/admin/function-namespace-managers.html), [Spark](http://spark.apache.org/sql/), [Hive](https://cwiki.apache.org/confluence/display/Hive/LanguageManual+Select)(延伸支持 Hadoop, Spark), [Phoenix](http://phoenix.apache.org/language/index.html#select)(延伸支持 HBase) ### 我要赞赏 如果你喜欢 APIJSON,感觉 APIJSON 帮助到了你,可以点右上角 ⭐Star 支持一下,谢谢 ^_^
From 264781e56b7abce20703fe839a3d1c8e2536586a Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Tue, 8 Dec 2020 20:38:11 +0800 Subject: [PATCH 0064/1181] =?UTF-8?q?=E8=A7=A3=E5=86=B3=20"isPraised-()":?= =?UTF-8?q?=20"isContain(praiseUserIdList,userId)"=20=E8=BF=99=E7=A7=8D?= =?UTF-8?q?=E6=9C=89=E5=89=8D=E7=BD=AE=E6=89=A7=E8=A1=8C=20=E8=BF=9C?= =?UTF-8?q?=E7=A8=8B=E5=87=BD=E6=95=B0=20=E7=9A=84=E8=A1=A8=E5=AF=B9?= =?UTF-8?q?=E8=B1=A1=E5=8F=AF=E8=83=BD=E8=BF=94=E5=9B=9E=E6=B2=A1=E6=9C=89?= =?UTF-8?q?=E8=A1=A8=E6=95=B0=E6=8D=AE=E7=9A=84=E5=AF=B9=E8=B1=A1=EF=BC=8C?= =?UTF-8?q?=E5=8F=AA=E6=9C=89=20=20{=20"isPraised":=20true=20}=20=E8=BF=99?= =?UTF-8?q?=E7=A7=8D=E6=97=A0=E6=84=8F=E4=B9=89=E7=9A=84=E5=AF=B9=E8=B1=A1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- APIJSONORM/src/main/java/apijson/orm/AbstractObjectParser.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractObjectParser.java b/APIJSONORM/src/main/java/apijson/orm/AbstractObjectParser.java index acb7571cb..435941320 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractObjectParser.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractObjectParser.java @@ -702,7 +702,7 @@ public AbstractObjectParser executeSQL() throws Exception { public JSONObject response() throws Exception { if (sqlReponse == null || sqlReponse.isEmpty()) { if (isTable) {//Table自身都获取不到值,则里面的Child都无意义,不需要再解析 - return response; + return null; // response; } } else { response.putAll(sqlReponse); From 9cfa86ef98dae3aaa903202534d8fbef12ed397b Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Thu, 10 Dec 2020 11:26:48 +0800 Subject: [PATCH 0065/1181] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index d8375254c..d15084822 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,7 @@ This source code is licensed under the Apache License Version 2.0
    - +

From bf891b7b1b088d9058e5a246d55d9c115d3c7747 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Thu, 10 Dec 2020 20:52:12 +0800 Subject: [PATCH 0066/1181] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index d15084822..0a74c3a4d 100644 --- a/README.md +++ b/README.md @@ -140,7 +140,7 @@ https://github.com/Tencent/APIJSON/wiki * 腾讯官方开源 (使用 GitHub、Gitee、工蜂 等平台的官方账号开源,微信公众号、腾讯云+社区 等官方公告) * 社区影响力大 (GitHub 9.2K Star 在 350W Java 项目中排名前 200,远超 FLAG, BAT 等国内外绝大部分开源项目) * 各项荣誉成就 (腾讯开源五个第一、腾讯首个 GVP 获奖项目、腾讯后端项目 Star 第一、GitHub Java 周榜第一 等) -* 多样用户案例 (内部用户包含 腾讯互娱、腾讯云与智慧,已登记外部用户包含 千亿资本国企、500 强上市公司 等) +* 多样用户案例 (内部用户包含 腾讯互娱、腾讯云与智慧、腾讯音乐,已登记外部用户包含 500强上市公司、千亿资本国企 等) * 适用场景广泛 (社交聊天、阅读资讯、影音视频、办公学习 等各种 App、网站、公众号、小程序 等非金融类项目) * 周边生态丰富 (Android, iOS, Web 等各种 Demo、继承 JSON 的海量生态、零代码 接口测试 和 单元测试 工具等) * 文档视频齐全 (项目介绍、快速上手、安装部署 等后端、前端、客户端的 图文解说、视频教程、代码注释 等) From 65d00472287a3a9dcefc1ee0d291c2d1f1173448 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Thu, 10 Dec 2020 20:53:13 +0800 Subject: [PATCH 0067/1181] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 0a74c3a4d..040576c8d 100644 --- a/README.md +++ b/README.md @@ -140,7 +140,7 @@ https://github.com/Tencent/APIJSON/wiki * 腾讯官方开源 (使用 GitHub、Gitee、工蜂 等平台的官方账号开源,微信公众号、腾讯云+社区 等官方公告) * 社区影响力大 (GitHub 9.2K Star 在 350W Java 项目中排名前 200,远超 FLAG, BAT 等国内外绝大部分开源项目) * 各项荣誉成就 (腾讯开源五个第一、腾讯首个 GVP 获奖项目、腾讯后端项目 Star 第一、GitHub Java 周榜第一 等) -* 多样用户案例 (内部用户包含 腾讯互娱、腾讯云与智慧、腾讯音乐,已登记外部用户包含 500强上市公司、千亿资本国企 等) +* 多样用户案例 (腾讯内部用户包含 互娱、云与智慧、音乐,已登记外部用户包含 500强上市公司、千亿资本国企 等) * 适用场景广泛 (社交聊天、阅读资讯、影音视频、办公学习 等各种 App、网站、公众号、小程序 等非金融类项目) * 周边生态丰富 (Android, iOS, Web 等各种 Demo、继承 JSON 的海量生态、零代码 接口测试 和 单元测试 工具等) * 文档视频齐全 (项目介绍、快速上手、安装部署 等后端、前端、客户端的 图文解说、视频教程、代码注释 等) From 173413f5fe412d79bd4c114cfd2148414fda0c1b Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Thu, 10 Dec 2020 20:55:32 +0800 Subject: [PATCH 0068/1181] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 040576c8d..436706f50 100644 --- a/README.md +++ b/README.md @@ -140,7 +140,7 @@ https://github.com/Tencent/APIJSON/wiki * 腾讯官方开源 (使用 GitHub、Gitee、工蜂 等平台的官方账号开源,微信公众号、腾讯云+社区 等官方公告) * 社区影响力大 (GitHub 9.2K Star 在 350W Java 项目中排名前 200,远超 FLAG, BAT 等国内外绝大部分开源项目) * 各项荣誉成就 (腾讯开源五个第一、腾讯首个 GVP 获奖项目、腾讯后端项目 Star 第一、GitHub Java 周榜第一 等) -* 多样用户案例 (腾讯内部用户包含 互娱、云与智慧、音乐,已登记外部用户包含 500强上市公司、千亿资本国企 等) +* 多样用户案例 (腾讯内部用户包含 互娱、音乐、云与智慧,外部用户包含 500强上市公司、数千亿资本国企 等) * 适用场景广泛 (社交聊天、阅读资讯、影音视频、办公学习 等各种 App、网站、公众号、小程序 等非金融类项目) * 周边生态丰富 (Android, iOS, Web 等各种 Demo、继承 JSON 的海量生态、零代码 接口测试 和 单元测试 工具等) * 文档视频齐全 (项目介绍、快速上手、安装部署 等后端、前端、客户端的 图文解说、视频教程、代码注释 等) From 48d8358597515f9cedaad4e7027aecbe20860b7a Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Thu, 10 Dec 2020 20:56:24 +0800 Subject: [PATCH 0069/1181] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 436706f50..c9d832af2 100644 --- a/README.md +++ b/README.md @@ -140,7 +140,7 @@ https://github.com/Tencent/APIJSON/wiki * 腾讯官方开源 (使用 GitHub、Gitee、工蜂 等平台的官方账号开源,微信公众号、腾讯云+社区 等官方公告) * 社区影响力大 (GitHub 9.2K Star 在 350W Java 项目中排名前 200,远超 FLAG, BAT 等国内外绝大部分开源项目) * 各项荣誉成就 (腾讯开源五个第一、腾讯首个 GVP 获奖项目、腾讯后端项目 Star 第一、GitHub Java 周榜第一 等) -* 多样用户案例 (腾讯内部用户包含 互娱、音乐、云与智慧,外部用户包含 500强上市公司、数千亿资本国企 等) +* 多样用户案例 (腾讯内部用户包含 互娱、音乐、云与智慧,外部用户包含 500 强上市公司、数千亿资本国企 等) * 适用场景广泛 (社交聊天、阅读资讯、影音视频、办公学习 等各种 App、网站、公众号、小程序 等非金融类项目) * 周边生态丰富 (Android, iOS, Web 等各种 Demo、继承 JSON 的海量生态、零代码 接口测试 和 单元测试 工具等) * 文档视频齐全 (项目介绍、快速上手、安装部署 等后端、前端、客户端的 图文解说、视频教程、代码注释 等) From 0d56303ebf67ad723312e1ebd5ec703d258dd4a5 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Thu, 10 Dec 2020 20:59:30 +0800 Subject: [PATCH 0070/1181] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index c9d832af2..07ab86d12 100644 --- a/README.md +++ b/README.md @@ -93,7 +93,7 @@ APIJSON是一种专为API而生的 JSON网络传输协议 以及 基于这套协

- APIAuto 自动生成前端(客户端)请求代码、后端接口代码、测试用例代码,一键下载 + APIAuto 自动生成前端(客户端)请求代码 和 Python 测试用例代码,一键下载

![](https://oscimg.oschina.net/oscnet/up-637193bbd89b41c3264827786319e842aee.JPEG) From b848b8228f67ea635dee4557cc354c8947d704c8 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Thu, 10 Dec 2020 21:10:56 +0800 Subject: [PATCH 0071/1181] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 07ab86d12..ab659b326 100644 --- a/README.md +++ b/README.md @@ -138,7 +138,7 @@ https://github.com/Tencent/APIJSON/wiki * 解决十大痛点 (APIJSON 大幅提振开发效率、强力杜绝联调扯皮、巧妙规避文档缺陷、非常节省流量带宽 等) * 开发提速巨大 (CRUD 零代码热更新自动化,APIJSONBoot 对比 SSM、SSH 等保守估计可提速 20 倍以上) * 腾讯官方开源 (使用 GitHub、Gitee、工蜂 等平台的官方账号开源,微信公众号、腾讯云+社区 等官方公告) -* 社区影响力大 (GitHub 9.2K Star 在 350W Java 项目中排名前 200,远超 FLAG, BAT 等国内外绝大部分开源项目) +* 社区影响力大 (GitHub 9.3K Star 在 350W Java 项目中排名前 200,远超 FLAG, BAT 等国内外绝大部分开源项目) * 各项荣誉成就 (腾讯开源五个第一、腾讯首个 GVP 获奖项目、腾讯后端项目 Star 第一、GitHub Java 周榜第一 等) * 多样用户案例 (腾讯内部用户包含 互娱、音乐、云与智慧,外部用户包含 500 强上市公司、数千亿资本国企 等) * 适用场景广泛 (社交聊天、阅读资讯、影音视频、办公学习 等各种 App、网站、公众号、小程序 等非金融类项目) From f08b1397904ddf479d7ac39e84b5f91797e4b71f Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Sat, 12 Dec 2020 14:09:19 +0800 Subject: [PATCH 0072/1181] Update README.md --- README.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index ab659b326..7aa2c5884 100644 --- a/README.md +++ b/README.md @@ -43,13 +43,13 @@ This source code is licensed under the Apache License Version 2.0
--- -APIJSON是一种专为API而生的 JSON网络传输协议 以及 基于这套协议实现的ORM库。
+APIJSON 是一种专为 API 而生的 JSON 网络传输协议 以及 基于这套协议实现的 ORM 库。
为 简单的增删改查、复杂的查询、简单的事务操作 提供了完全自动化的API。
能大幅降低开发和沟通成本,简化开发流程,缩短开发周期。
适合中小型前后端分离的项目,尤其是 BaaS、Serverless、互联网创业项目和企业自用项目。
-通过自动化API,前端可以定制任何数据、任何结构!
-大部分HTTP请求后端再也不用写接口了,更不用写文档了!
+通过万能的 API,前端可以定制任何数据、任何结构!
+大部分 HTTP 请求后端再也不用写接口了,更不用写文档了!
前端再也不用和后端沟通接口或文档问题了!再也不会被文档各种错误坑了!
后端再也不用为了兼容旧接口写新版接口和文档了!再也不会被前端随时随地没完没了地烦了! @@ -68,10 +68,10 @@ APIJSON是一种专为API而生的 JSON网络传输协议 以及 基于这套协 * 能去除重复数据,节省流量提高速度 #### 对于后端 -* 提供通用接口,大部分API不用再写 +* 提供通用接口,大部分 API 不用再写 * 自动生成文档,不用再编写和维护 -* 自动校验权限、自动管理版本、自动防SQL注入 -* 开放API无需划分版本,始终保持兼容 +* 自动校验权限、自动管理版本、自动防 SQL 注入 +* 开放 API 无需划分版本,始终保持兼容 * 支持增删改查、模糊搜索、正则匹配、远程函数等
From ece07545c741febf54839d720ee5079d1184939a Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Sat, 12 Dec 2020 14:10:04 +0800 Subject: [PATCH 0073/1181] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 7aa2c5884..06758363e 100644 --- a/README.md +++ b/README.md @@ -44,7 +44,7 @@ This source code is licensed under the Apache License Version 2.0
APIJSON 是一种专为 API 而生的 JSON 网络传输协议 以及 基于这套协议实现的 ORM 库。
-为 简单的增删改查、复杂的查询、简单的事务操作 提供了完全自动化的API。
+为 简单的增删改查、复杂的查询、简单的事务操作 提供了完全自动化的万能 API。
能大幅降低开发和沟通成本,简化开发流程,缩短开发周期。
适合中小型前后端分离的项目,尤其是 BaaS、Serverless、互联网创业项目和企业自用项目。
From 16615a58aa58fd1b72df5a75f7ac5898a8bf7b8f Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Sat, 12 Dec 2020 14:18:24 +0800 Subject: [PATCH 0074/1181] Update README.md --- README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/README.md b/README.md index 06758363e..444d5f1c5 100644 --- a/README.md +++ b/README.md @@ -21,7 +21,6 @@ This source code is licensed under the Apache License Version 2.0
      -

From ad4ff1a1c5d4df4a4305919aef8567c7bff3a701 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Sat, 12 Dec 2020 14:20:14 +0800 Subject: [PATCH 0075/1181] Update README.md --- README.md | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/README.md b/README.md index 444d5f1c5..5506cb7b5 100644 --- a/README.md +++ b/README.md @@ -134,22 +134,22 @@ APIJSON 是一种专为 API 而生的 JSON 网络传输协议 以及 基于这 前后端 关于接口的 开发、文档、联调 等 10 大痛点解析
https://github.com/Tencent/APIJSON/wiki -* 解决十大痛点 (APIJSON 大幅提振开发效率、强力杜绝联调扯皮、巧妙规避文档缺陷、非常节省流量带宽 等) -* 开发提速巨大 (CRUD 零代码热更新自动化,APIJSONBoot 对比 SSM、SSH 等保守估计可提速 20 倍以上) -* 腾讯官方开源 (使用 GitHub、Gitee、工蜂 等平台的官方账号开源,微信公众号、腾讯云+社区 等官方公告) -* 社区影响力大 (GitHub 9.3K Star 在 350W Java 项目中排名前 200,远超 FLAG, BAT 等国内外绝大部分开源项目) -* 各项荣誉成就 (腾讯开源五个第一、腾讯首个 GVP 获奖项目、腾讯后端项目 Star 第一、GitHub Java 周榜第一 等) -* 多样用户案例 (腾讯内部用户包含 互娱、音乐、云与智慧,外部用户包含 500 强上市公司、数千亿资本国企 等) -* 适用场景广泛 (社交聊天、阅读资讯、影音视频、办公学习 等各种 App、网站、公众号、小程序 等非金融类项目) -* 周边生态丰富 (Android, iOS, Web 等各种 Demo、继承 JSON 的海量生态、零代码 接口测试 和 单元测试 工具等) -* 文档视频齐全 (项目介绍、快速上手、安装部署 等后端、前端、客户端的 图文解说、视频教程、代码注释 等) -* 功能丰富强大 (增删改查、分页排序、分组聚合、各种 JOIN、各种子查询、垮库跨表、性能分析 等零代码实现) -* 使用安全简单 (自动增删改查、自动生成文档、自动管理版本、自动校验权限、自动校验参数、自动防SQL注入等) -* 灵活定制业务 (在后端编写 远程函数,可以拿到 session、version、当前 JSON 对象 等,然后自定义处理) -* 高质可靠代码 (代码工整规范、商业分析软件源伞 Pinpoint 代码扫描报告平均每行代码 bug 率低至 0.15%) -* 兼容各种项目 (对各类 Web 框架集成友好且提供 SpringBoot, JFinal 的 Demo,协议不限 HTTP,与其它库无冲突) -* 工程轻量小巧 (仅依赖 fastjson,Jar 仅 280KB,Java 文件仅 59 个共 13719 行代码,例如 APIJSONORM 4.3.1) -* 多年持续迭代 (自 2016 年开源至今已连续 4 年 2000+ Commits、70+ Releases,不断更新迭代中...) +* **解决十大痛点** (APIJSON 大幅提振开发效率、强力杜绝联调扯皮、巧妙规避文档缺陷、非常节省流量带宽 等) +* **开发提速巨大** (CRUD 零代码热更新自动化,APIJSONBoot 对比 SSM、SSH 等保守估计可提速 20 倍以上) +* **腾讯官方开源** (使用 GitHub、Gitee、工蜂 等平台的官方账号开源,微信公众号、腾讯云+社区 等官方公告) +* **社区影响力大** (GitHub 9.3K Star 在 350W Java 项目中排名前 200,远超 FLAG, BAT 等国内外绝大部分开源项目) +* **各项荣誉成就** (腾讯开源五个第一、腾讯首个 GVP 获奖项目、腾讯后端项目 Star 第一、GitHub Java 周榜第一 等) +* **多样用户案例** (腾讯内部用户包含 互娱、音乐、云与智慧,外部用户包含 500 强上市公司、数千亿资本国企 等) +* **适用场景广泛** (社交聊天、阅读资讯、影音视频、办公学习 等各种 App、网站、公众号、小程序 等非金融类项目) +* **周边生态丰富** (Android, iOS, Web 等各种 Demo、继承 JSON 的海量生态、零代码 接口测试 和 单元测试 工具等) +* **文档视频齐全** (项目介绍、快速上手、安装部署 等后端、前端、客户端的 图文解说、视频教程、代码注释 等) +* **功能丰富强大** (增删改查、分页排序、分组聚合、各种 JOIN、各种子查询、垮库跨表、性能分析 等零代码实现) +* **使用安全简单** (自动增删改查、自动生成文档、自动管理版本、自动校验权限、自动校验参数、自动防SQL注入等) +* **灵活定制业务** (在后端编写 远程函数,可以拿到 session、version、当前 JSON 对象 等,然后自定义处理) +* **高质可靠代码** (代码工整规范、商业分析软件源伞 Pinpoint 代码扫描报告平均每行代码 bug 率低至 0.15%) +* **兼容各种项目** (对各类 Web 框架集成友好且提供 SpringBoot, JFinal 的 Demo,协议不限 HTTP,与其它库无冲突) +* **工程轻量小巧** (仅依赖 fastjson,Jar 仅 280KB,Java 文件仅 59 个共 13719 行代码,例如 APIJSONORM 4.3.1) +* **多年持续迭代** (自 2016 年开源至今已连续 4 年 2000+ Commits、70+ Releases,不断更新迭代中...) ### 常见问题 From b9bbee4da34f549fb7801400f917a138d5fe245d Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Sat, 12 Dec 2020 14:29:06 +0800 Subject: [PATCH 0076/1181] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 5506cb7b5..07c890c6c 100644 --- a/README.md +++ b/README.md @@ -144,7 +144,7 @@ https://github.com/Tencent/APIJSON/wiki * **周边生态丰富** (Android, iOS, Web 等各种 Demo、继承 JSON 的海量生态、零代码 接口测试 和 单元测试 工具等) * **文档视频齐全** (项目介绍、快速上手、安装部署 等后端、前端、客户端的 图文解说、视频教程、代码注释 等) * **功能丰富强大** (增删改查、分页排序、分组聚合、各种 JOIN、各种子查询、垮库跨表、性能分析 等零代码实现) -* **使用安全简单** (自动增删改查、自动生成文档、自动管理版本、自动校验权限、自动校验参数、自动防SQL注入等) +* **使用安全简单** (自动增删改查、自动生成文档、自动管理版本、自动校验权限、自动校验参数、自动防 SQL 注入等) * **灵活定制业务** (在后端编写 远程函数,可以拿到 session、version、当前 JSON 对象 等,然后自定义处理) * **高质可靠代码** (代码工整规范、商业分析软件源伞 Pinpoint 代码扫描报告平均每行代码 bug 率低至 0.15%) * **兼容各种项目** (对各类 Web 框架集成友好且提供 SpringBoot, JFinal 的 Demo,协议不限 HTTP,与其它库无冲突) From 6c31a3fface0426aacf2c7e9572d291cfd58dc22 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Sat, 12 Dec 2020 14:30:31 +0800 Subject: [PATCH 0077/1181] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 07c890c6c..5506cb7b5 100644 --- a/README.md +++ b/README.md @@ -144,7 +144,7 @@ https://github.com/Tencent/APIJSON/wiki * **周边生态丰富** (Android, iOS, Web 等各种 Demo、继承 JSON 的海量生态、零代码 接口测试 和 单元测试 工具等) * **文档视频齐全** (项目介绍、快速上手、安装部署 等后端、前端、客户端的 图文解说、视频教程、代码注释 等) * **功能丰富强大** (增删改查、分页排序、分组聚合、各种 JOIN、各种子查询、垮库跨表、性能分析 等零代码实现) -* **使用安全简单** (自动增删改查、自动生成文档、自动管理版本、自动校验权限、自动校验参数、自动防 SQL 注入等) +* **使用安全简单** (自动增删改查、自动生成文档、自动管理版本、自动校验权限、自动校验参数、自动防SQL注入等) * **灵活定制业务** (在后端编写 远程函数,可以拿到 session、version、当前 JSON 对象 等,然后自定义处理) * **高质可靠代码** (代码工整规范、商业分析软件源伞 Pinpoint 代码扫描报告平均每行代码 bug 率低至 0.15%) * **兼容各种项目** (对各类 Web 框架集成友好且提供 SpringBoot, JFinal 的 Demo,协议不限 HTTP,与其它库无冲突) From 65fc1c85a2938fac99a63e49ea147c2c4d12e268 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Sat, 12 Dec 2020 14:44:58 +0800 Subject: [PATCH 0078/1181] Update README.md --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 5506cb7b5..3c9648483 100644 --- a/README.md +++ b/README.md @@ -143,8 +143,8 @@ https://github.com/Tencent/APIJSON/wiki * **适用场景广泛** (社交聊天、阅读资讯、影音视频、办公学习 等各种 App、网站、公众号、小程序 等非金融类项目) * **周边生态丰富** (Android, iOS, Web 等各种 Demo、继承 JSON 的海量生态、零代码 接口测试 和 单元测试 工具等) * **文档视频齐全** (项目介绍、快速上手、安装部署 等后端、前端、客户端的 图文解说、视频教程、代码注释 等) -* **功能丰富强大** (增删改查、分页排序、分组聚合、各种 JOIN、各种子查询、垮库跨表、性能分析 等零代码实现) -* **使用安全简单** (自动增删改查、自动生成文档、自动管理版本、自动校验权限、自动校验参数、自动防SQL注入等) +* **功能丰富强大** (增删改查、分页排序、分组聚合、各种 JOIN、各种子查询、跨库跨表、性能分析 等零代码实现) +* **使用安全简单** (自动增删改查、自动生成文档、自动管理版本、自动校验权限、自动校验参数、自动防 SQL 注入等) * **灵活定制业务** (在后端编写 远程函数,可以拿到 session、version、当前 JSON 对象 等,然后自定义处理) * **高质可靠代码** (代码工整规范、商业分析软件源伞 Pinpoint 代码扫描报告平均每行代码 bug 率低至 0.15%) * **兼容各种项目** (对各类 Web 框架集成友好且提供 SpringBoot, JFinal 的 Demo,协议不限 HTTP,与其它库无冲突) From 55015489e434996a26df42f070f8cd33558e8a29 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Sat, 12 Dec 2020 14:48:00 +0800 Subject: [PATCH 0079/1181] Update README.md --- README.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/README.md b/README.md index 3c9648483..14dc662d1 100644 --- a/README.md +++ b/README.md @@ -134,6 +134,9 @@ APIJSON 是一种专为 API 而生的 JSON 网络传输协议 以及 基于这 前后端 关于接口的 开发、文档、联调 等 10 大痛点解析
https://github.com/Tencent/APIJSON/wiki +



+ + * **解决十大痛点** (APIJSON 大幅提振开发效率、强力杜绝联调扯皮、巧妙规避文档缺陷、非常节省流量带宽 等) * **开发提速巨大** (CRUD 零代码热更新自动化,APIJSONBoot 对比 SSM、SSH 等保守估计可提速 20 倍以上) * **腾讯官方开源** (使用 GitHub、Gitee、工蜂 等平台的官方账号开源,微信公众号、腾讯云+社区 等官方公告) @@ -151,6 +154,8 @@ https://github.com/Tencent/APIJSON/wiki * **工程轻量小巧** (仅依赖 fastjson,Jar 仅 280KB,Java 文件仅 59 个共 13719 行代码,例如 APIJSONORM 4.3.1) * **多年持续迭代** (自 2016 年开源至今已连续 4 年 2000+ Commits、70+ Releases,不断更新迭代中...) +



+ ### 常见问题 #### 1.如何定制业务逻辑? From 06d594845c0108d38aab3abd5ab9d6a7c60b1898 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Sat, 12 Dec 2020 14:56:52 +0800 Subject: [PATCH 0080/1181] Update README.md --- README.md | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/README.md b/README.md index 14dc662d1..8d8b3d38e 100644 --- a/README.md +++ b/README.md @@ -81,7 +81,7 @@ APIJSON 是一种专为 API 而生的 JSON 网络传输协议 以及 基于这
#### APIAuto 展示 APIJSON -搭配 APIAuto-机器学习接口工具 来管理和测试 APIJSON 可大幅提升接口联调效率
+使用 APIAuto-机器学习接口工具 来管理和测试 HTTP API 可大幅提升接口联调效率
(注意网页工具界面是 APIAuto,里面的 URL+JSON 才是 APIJSON 的 HTTP API):

@@ -134,9 +134,6 @@ APIJSON 是一种专为 API 而生的 JSON 网络传输协议 以及 基于这 前后端 关于接口的 开发、文档、联调 等 10 大痛点解析
https://github.com/Tencent/APIJSON/wiki -



- - * **解决十大痛点** (APIJSON 大幅提振开发效率、强力杜绝联调扯皮、巧妙规避文档缺陷、非常节省流量带宽 等) * **开发提速巨大** (CRUD 零代码热更新自动化,APIJSONBoot 对比 SSM、SSH 等保守估计可提速 20 倍以上) * **腾讯官方开源** (使用 GitHub、Gitee、工蜂 等平台的官方账号开源,微信公众号、腾讯云+社区 等官方公告) @@ -154,8 +151,6 @@ https://github.com/Tencent/APIJSON/wiki * **工程轻量小巧** (仅依赖 fastjson,Jar 仅 280KB,Java 文件仅 59 个共 13719 行代码,例如 APIJSONORM 4.3.1) * **多年持续迭代** (自 2016 年开源至今已连续 4 年 2000+ Commits、70+ Releases,不断更新迭代中...) -



- ### 常见问题 #### 1.如何定制业务逻辑? From 43f00265dab0cba95e6b4e980d4dc9f78ba6aa72 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Sat, 12 Dec 2020 14:57:20 +0800 Subject: [PATCH 0081/1181] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 8d8b3d38e..3821729b9 100644 --- a/README.md +++ b/README.md @@ -144,7 +144,7 @@ https://github.com/Tencent/APIJSON/wiki * **周边生态丰富** (Android, iOS, Web 等各种 Demo、继承 JSON 的海量生态、零代码 接口测试 和 单元测试 工具等) * **文档视频齐全** (项目介绍、快速上手、安装部署 等后端、前端、客户端的 图文解说、视频教程、代码注释 等) * **功能丰富强大** (增删改查、分页排序、分组聚合、各种 JOIN、各种子查询、跨库跨表、性能分析 等零代码实现) -* **使用安全简单** (自动增删改查、自动生成文档、自动管理版本、自动校验权限、自动校验参数、自动防 SQL 注入等) +* **使用安全简单** (自动增删改查、自动生成文档、自动管理版本、自动校验权限、自动校验参数、自动防SQL注入等) * **灵活定制业务** (在后端编写 远程函数,可以拿到 session、version、当前 JSON 对象 等,然后自定义处理) * **高质可靠代码** (代码工整规范、商业分析软件源伞 Pinpoint 代码扫描报告平均每行代码 bug 率低至 0.15%) * **兼容各种项目** (对各类 Web 框架集成友好且提供 SpringBoot, JFinal 的 Demo,协议不限 HTTP,与其它库无冲突) From b4667b30f5f704156c60c276601fb09cb876ea3c Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Sat, 12 Dec 2020 15:48:11 +0800 Subject: [PATCH 0082/1181] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 3821729b9..e49e6b6f3 100644 --- a/README.md +++ b/README.md @@ -144,7 +144,7 @@ https://github.com/Tencent/APIJSON/wiki * **周边生态丰富** (Android, iOS, Web 等各种 Demo、继承 JSON 的海量生态、零代码 接口测试 和 单元测试 工具等) * **文档视频齐全** (项目介绍、快速上手、安装部署 等后端、前端、客户端的 图文解说、视频教程、代码注释 等) * **功能丰富强大** (增删改查、分页排序、分组聚合、各种 JOIN、各种子查询、跨库跨表、性能分析 等零代码实现) -* **使用安全简单** (自动增删改查、自动生成文档、自动管理版本、自动校验权限、自动校验参数、自动防SQL注入等) +* **使用安全简单** (自动增删改查、自动生成文档、自动管理版本、自动控制权限、自动校验参数、自动防SQL注入等) * **灵活定制业务** (在后端编写 远程函数,可以拿到 session、version、当前 JSON 对象 等,然后自定义处理) * **高质可靠代码** (代码工整规范、商业分析软件源伞 Pinpoint 代码扫描报告平均每行代码 bug 率低至 0.15%) * **兼容各种项目** (对各类 Web 框架集成友好且提供 SpringBoot, JFinal 的 Demo,协议不限 HTTP,与其它库无冲突) From 278ff6d4bafd5b471122917282fdcc68416ca040 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Sun, 13 Dec 2020 20:22:51 +0800 Subject: [PATCH 0083/1181] =?UTF-8?q?=E5=8E=9F=E5=A7=8B=20SQL=20=E7=89=87?= =?UTF-8?q?=E6=AE=B5=20@raw:"key"=20=E6=96=B0=E5=A2=9E=E6=94=AF=E6=8C=81?= =?UTF-8?q?=E6=9D=A1=E4=BB=B6=E8=8C=83=E5=9B=B4=20key{}:=20"(`Comment`.`us?= =?UTF-8?q?erId`=3D`to`.`userId`)"=E3=80=81=E6=AF=94=E8=BE=83=E8=BF=90?= =?UTF-8?q?=E7=AE=97=20key>:=20"to.momentId"=E3=80=81=E8=BF=87=E6=BB=A4?= =?UTF-8?q?=E5=AD=97=E6=AE=B5=20@column:=20"SUBSTRING=5FINDEX(SUBSTRING=5F?= =?UTF-8?q?INDEX(content,',',1),',',-1)"?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/main/java/apijson/JSONObject.java | 23 +- .../src/main/java/apijson/StringUtil.java | 10 +- .../java/apijson/orm/AbstractSQLConfig.java | 261 +++++++++++++----- .../src/main/java/apijson/orm/SQLConfig.java | 8 +- 4 files changed, 224 insertions(+), 78 deletions(-) diff --git a/APIJSONORM/src/main/java/apijson/JSONObject.java b/APIJSONORM/src/main/java/apijson/JSONObject.java index b058b8b9e..75ca4256d 100755 --- a/APIJSONORM/src/main/java/apijson/JSONObject.java +++ b/APIJSONORM/src/main/java/apijson/JSONObject.java @@ -145,8 +145,8 @@ public JSONObject setUserIdIn(List list) { public static final String KEY_GROUP = "@group"; //分组方式 public static final String KEY_HAVING = "@having"; //聚合函数条件,一般和@group一起用 public static final String KEY_ORDER = "@order"; //排序方式 + public static final String KEY_RAW = "@raw"; // 自定义原始 SQL 片段 public static final String KEY_JSON = "@json"; //SQL Server 把字段转为 JSON 输出 - public static final String KEY_RAW = "@raw"; //自定义where条件拼接 public static final List TABLE_KEY_LIST; static { @@ -162,8 +162,8 @@ public JSONObject setUserIdIn(List list) { TABLE_KEY_LIST.add(KEY_GROUP); TABLE_KEY_LIST.add(KEY_HAVING); TABLE_KEY_LIST.add(KEY_ORDER); - TABLE_KEY_LIST.add(KEY_JSON); TABLE_KEY_LIST.add(KEY_RAW); + TABLE_KEY_LIST.add(KEY_JSON); } //@key关键字都放这个类 >>>>>>>>>>>>>>>>>>>>>> @@ -325,10 +325,29 @@ public JSONObject setOrder(String keys) { return puts(KEY_ORDER, keys); } + /**set keys to raw + * @param keys "key0,key1,key2..." + * @return + */ + public JSONObject setRaw(String keys) { + return puts(KEY_RAW, keys); + } + /**set keys to cast to json * @param keys "key0,key1,key2..." * @return */ + public JSONObject setJson(String keys) { + return puts(KEY_JSON, keys); + } + + /**用 setJson 替代。 + * set keys to cast to json + * @param keys "key0,key1,key2..." + * @return + * @see #{@link #setJson(String)} + */ + @Deprecated public JSONObject setJSON(String keys) { return puts(KEY_JSON, keys); } diff --git a/APIJSONORM/src/main/java/apijson/StringUtil.java b/APIJSONORM/src/main/java/apijson/StringUtil.java index c4f2474a2..7f8fa831c 100755 --- a/APIJSONORM/src/main/java/apijson/StringUtil.java +++ b/APIJSONORM/src/main/java/apijson/StringUtil.java @@ -736,6 +736,14 @@ public static String[] split(String s) { public static String[] split(String s, String split) { return split(s, split, true); } + /**将s用split分割成String[] + * @param s + * @param trim 去掉前后两端的split + * @return + */ + public static String[] split(String s, boolean trim) { + return split(s, null, trim); + } /**将s用split分割成String[] * @param s * @param split @@ -747,7 +755,7 @@ public static String[] split(String s, String split, boolean trim) { if (s.isEmpty()) { return null; } - if (isNotEmpty(split, false) == false) { + if (isEmpty(split, false)) { split = ","; } if (trim) { diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java index 892e87719..613b3b251 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java @@ -87,7 +87,7 @@ public abstract class AbstractSQLConfig implements SQLConfig { public static final Map TABLE_KEY_MAP; public static final List CONFIG_TABLE_LIST; public static final List DATABASE_LIST; - // 自定义where条件拼接 + // 自定义原始 SQL 片段 Map:当 substring 为 null 时忽略;当 substring 为 "" 时整个 value 是 raw SQL;其它情况则只是 substring 这段为 raw SQL public static final Map RAW_MAP; static { TABLE_KEY_MAP = new HashMap(); @@ -98,7 +98,7 @@ public abstract class AbstractSQLConfig implements SQLConfig { TABLE_KEY_MAP.put(SysTable.class.getSimpleName(), SysTable.TABLE_NAME); TABLE_KEY_MAP.put(SysColumn.class.getSimpleName(), SysColumn.TABLE_NAME); TABLE_KEY_MAP.put(ExtendedProperty.class.getSimpleName(), ExtendedProperty.TABLE_NAME); - + CONFIG_TABLE_LIST = new ArrayList<>(); // Table, Column 等是系统表 AbstractVerifier.SYSTEM_ACCESS_MAP.keySet()); CONFIG_TABLE_LIST.add(Function.class.getSimpleName()); CONFIG_TABLE_LIST.add(Request.class.getSimpleName()); @@ -107,7 +107,7 @@ public abstract class AbstractSQLConfig implements SQLConfig { CONFIG_TABLE_LIST.add(Access.class.getSimpleName()); CONFIG_TABLE_LIST.add(Document.class.getSimpleName()); CONFIG_TABLE_LIST.add(TestRecord.class.getSimpleName()); - + DATABASE_LIST = new ArrayList<>(); DATABASE_LIST.add(DATABASE_MYSQL); @@ -116,7 +116,7 @@ public abstract class AbstractSQLConfig implements SQLConfig { DATABASE_LIST.add(DATABASE_ORACLE); DATABASE_LIST.add(DATABASE_DB2); - RAW_MAP = new HashMap<>(); + RAW_MAP = new LinkedHashMap<>(); // 保证顺序,避免配置冲突等意外情况 } @Override @@ -152,6 +152,7 @@ public String getUserIdKey() { private String group; //分组方式的字符串数组,','分隔 private String having; //聚合函数的字符串数组,','分隔 private String order; //排序方式的字符串数组,','分隔 + private List raw; //需要保留原始 SQL 的字段,','分隔 private List json; //需要转为 JSON 的字段,','分隔 private Subquery from; //子查询临时表 private List column; //表内字段名(或函数名,仅查询操作可用)的字符串数组,','分隔 @@ -684,7 +685,7 @@ public String getOrderString(boolean hasPrefix) { else { sort = " ASC "; } - + String origin = index < 0 ? item : item.substring(0, index); if (isPrepared()) { //不能通过 ? 来代替,SELECT 'id','name' 返回的就是 id:"id", name:"name",而不是数据库里的值! @@ -694,13 +695,53 @@ public String getOrderString(boolean hasPrefix) { + "每一项必须是 随机函数 rand() 或 column+ / column- 且其中 column 必须是 1 个单词!并且不要有多余的空格!"); } } - + keys[i] = getKey(origin) + sort; } return (hasPrefix ? " ORDER BY " : "") + StringUtil.concat(StringUtil.getString(keys), joinOrder, ", "); } + @Override + public List getRaw() { + return raw; + } + @Override + public SQLConfig setRaw(List raw) { + this.raw = raw; + return this; + } + + /**获取原始 SQL 片段 + * @param key + * @param value + * @return + * @throws Exception + */ + @Override + public String getRawSQL(String key, Object value) throws Exception { + List rawList = getRaw(); + boolean containRaw = rawList != null && rawList.contains(key); + if (containRaw && value instanceof String == false) { + throw new UnsupportedOperationException("@raw:value 的 value 中 " + key + " 不合法!" + + "对应的 " + key + ":value 中 value 类型只能为 String!"); + } + + String rawSQL = containRaw ? RAW_MAP.get(value) : null; + if (containRaw) { + if (rawSQL == null) { + throw new UnsupportedOperationException("@raw:value 的 value 中 " + key + " 不合法!" + + "对应的 " + key + ":value 中 value 值 " + value + " 未在后端 RAW_MAP 中配置 !"); + } + + if ("".equals(rawSQL)) { + return (String) value; + } + } + + return rawSQL; + } + @Override public List getJson() { @@ -823,6 +864,9 @@ public String getColumnString(boolean inSQLJoin) throws Exception { } + List raw = getRaw(); + boolean containRaw = raw != null && raw.contains(KEY_COLUMN); + String expression; String method = null; @@ -832,13 +876,30 @@ public String getColumnString(boolean inSQLJoin) throws Exception { //fun(arg0,arg1,...) expression = keys[i]; + if (containRaw) { + if (RAW_MAP.containsValue(expression) || "".equals(RAW_MAP.get(expression))) { // newSQLConfig 提前处理好的 + continue; + } + + // 简单点, 后台配置就带上 AS +// int index = expression.lastIndexOf(":"); +// String alias = expression.substring(index+1); +// boolean hasAlias = StringUtil.isName(alias); +// String pre = index > 0 && hasAlias ? expression.substring(0, index) : expression; +// if (RAW_MAP.containsValue(pre) || "".equals(RAW_MAP.get(pre))) { // newSQLConfig 提前处理好的 +// expression = pre + (hasAlias ? " AS " + alias : ""); +// continue; +// } + } + + int start = expression.indexOf("("); int end = 0; if (start >= 0) { end = expression.indexOf(")"); if (start >= end) { throw new IllegalArgumentException("字符 " + expression + " 不合法!" - + "@having:value 中 value 里的 SQL函数必须为 function(arg0,arg1,...) 这种格式!"); + + "@column:value 中 value 里的 SQL函数必须为 function(arg0,arg1,...) 这种格式!"); } method = expression.substring(0, start); @@ -1383,7 +1444,7 @@ else if ("!".equals(ce.getKey())) { //各种 JOIN 没办法统一用 & | !连接,只能按优先级,和 @combine 一样? for (Join j : joinList) { String jt = j.getJoinType(); - + switch (jt) { case "*": // CROSS JOIN case "@": // APP JOIN @@ -1409,7 +1470,7 @@ else if ("!".equals(ce.getKey())) { boolean isAntiJoin = "(".equals(jt); boolean isForeignJoin = ")".equals(jt); boolean isWsEmpty = StringUtil.isEmpty(ws, true); - + if (isWsEmpty) { if (isOuterJoin) { // ! OUTER JOIN: ! (A | B) throw new NotExistException("no result for ! OUTER JOIN( ! (A | B) ) when A or B is empty!"); @@ -1418,7 +1479,7 @@ else if ("!".equals(ce.getKey())) { throw new NotExistException("no result for ) FOREIGN JOIN( B & ! A ) when A is empty!"); } } - + if (StringUtil.isEmpty(js, true)) { if (isOuterJoin) { // ! OUTER JOIN: ! (A | B) throw new NotExistException("no result for ! OUTER JOIN( ! (A | B) ) when A or B is empty!"); @@ -1426,7 +1487,7 @@ else if ("!".equals(ce.getKey())) { if (isAntiJoin) { // ( ANTI JOIN: A & ! B throw new NotExistException("no result for ( ANTI JOIN( A & ! B ) when B is empty!"); } - + if (isWsEmpty) { if (isSideJoin) { throw new NotExistException("no result for ^ SIDE JOIN( ! (A & B) ) when both A and B are empty!"); @@ -1435,13 +1496,13 @@ else if ("!".equals(ce.getKey())) { else { if (isSideJoin || isForeignJoin) { newWs += " ( " + getCondition(true, ws) + " ) "; - + newPvl.addAll(pvl); newPvl.addAll(jc.getPreparedValueList()); changed = true; } } - + continue; } @@ -1484,7 +1545,7 @@ else if (isSideJoin) { // ^ SIDE JOIN: ! (A & B) "join:value 中 value 里的 " + jt + "/" + j.getPath() + "错误!不支持 " + jt + " 等 [ @ APP, < LEFT, > RIGHT, * CROSS" + ", & INNER, | FULL, ! OUTER, ^ SIDE, ( ANTI, ) FOREIGN ] 之外的 JOIN 类型 !" - ); + ); } } @@ -1515,8 +1576,7 @@ private String getWhereItem(String key, Object value , RequestMethod method, boolean verifyName) throws Exception { Log.d(TAG, "getWhereItem key = " + key); //避免筛选到全部 value = key == null ? null : where.get(key); - if (key == null || value == null || key.endsWith("()") - || (key.startsWith("@") && KEY_RAW.equals(key) == false)) { //关键字||方法, +或-直接报错 + if (key == null || value == null || key.endsWith("()") || key.startsWith("@")) { //关键字||方法, +或-直接报错 Log.d(TAG, "getWhereItem key == null || value == null" + " || key.startsWith(@) || key.endsWith(()) >> continue;"); return null; @@ -1526,6 +1586,9 @@ private String getWhereItem(String key, Object value throw new IllegalArgumentException(TAG + ".getWhereItem: 字符 " + key + " 不合法!"); } + // 原始 SQL 片段 + String rawSQL = getRawSQL(key, value); + int keyType; if (key.endsWith("$")) { keyType = 1; @@ -1556,12 +1619,10 @@ else if (key.endsWith(">")) { } else if (key.endsWith("<")) { keyType = 10; - } - else if (key.startsWith("@")) { - keyType = 11; - } else { //else绝对不能省,避免再次踩坑! keyType = 0; 写在for循环外面都没注意! + } else { // else绝对不能省,避免再次踩坑! keyType = 0; 写在for循环外面都没注意! keyType = 0; } + key = getRealKey(method, key, false, true, verifyName, getQuote()); switch (keyType) { @@ -1571,46 +1632,26 @@ else if (key.startsWith("@")) { case 2: return getRegExpString(key, value, keyType < 0); case 3: - return getBetweenString(key, value); + return getBetweenString(key, value, rawSQL); case 4: - return getRangeString(key, value); + return getRangeString(key, value, rawSQL); case 5: return getExistsString(key, value); case 6: - return getContainString(key, value); + return getContainString(key, value, rawSQL); case 7: - return getCompareString(key, value, ">="); + return getCompareString(key, value, ">=", rawSQL); case 8: - return getCompareString(key, value, "<="); + return getCompareString(key, value, "<=", rawSQL); case 9: - return getCompareString(key, value, ">"); + return getCompareString(key, value, ">", rawSQL); case 10: - return getCompareString(key, value, "<"); - case 11: - return getRaw(key,value); - default: //TODO MySQL JSON类型的字段对比 key='[]' 会无结果! key LIKE '[1, 2, 3]' //TODO MySQL , 后面有空格! + return getCompareString(key, value, "<", rawSQL); + default: // TODO MySQL JSON类型的字段对比 key='[]' 会无结果! key LIKE '[1, 2, 3]' //TODO MySQL , 后面有空格! return getEqualString(key, value); } } - @JSONField(serialize = false) - public String getRaw(String key, Object value) throws Exception { - if (JSON.isBooleanOrNumberOrString(value) == false && value instanceof Subquery == false) { - throw new IllegalArgumentException(key + ":value 中value不合法!非PUT请求只支持 [Boolean, Number, String] 内的类型 !"); - } - - String[] rawList = ((String)value).split(","); - String whereItem = ""; - for (int i = 0; i < rawList.length; i++) { - if(rawList.length>1&& i!=0){ - whereItem += " and " + RAW_MAP.get(rawList[i]); - }else{ - whereItem += RAW_MAP.get(rawList[i]); - } - } - - return whereItem; - } @JSONField(serialize = false) public String getEqualString(String key, Object value) throws Exception { @@ -1630,7 +1671,7 @@ public String getEqualString(String key, Object value) throws Exception { } @JSONField(serialize = false) - public String getCompareString(String key, Object value, String type) throws Exception { + public String getCompareString(String key, Object value, String type, String rawSQL) throws Exception { if (JSON.isBooleanOrNumberOrString(value) == false && value instanceof Subquery == false) { throw new IllegalArgumentException(key + type + ":value 中value不合法!比较运算 [>, <, >=, <=] 只支持 [Boolean, Number, String] 内的类型 !"); } @@ -1638,7 +1679,7 @@ public String getCompareString(String key, Object value, String type) throws Exc throw new IllegalArgumentException(key + type + ":value 中key不合法!比较运算 [>, <, >=, <=] 不支持 [&, !, |] 中任何逻辑运算符 !"); } - return getKey(key) + " " + type + " " + (value instanceof Subquery ? getSubqueryString((Subquery) value) : getValue(value)); + return getKey(key) + " " + type + " " + (value instanceof Subquery ? getSubqueryString((Subquery) value) : (rawSQL != null ? rawSQL : getValue(value))); } public String getKey(String key) { @@ -1648,7 +1689,7 @@ public String getKey(String key) { } return getSQLValue(key).toString(); } - + return getSQLKey(key); } public String getSQLKey(String key) { @@ -1905,7 +1946,7 @@ public String getBetweenString(String key, Object start, Object end) throws Ille * @throws Exception */ @JSONField(serialize = false) - public String getRangeString(String key, Object range) throws Exception { + public String getRangeString(String key, Object range, String rawSQL) throws Exception { Log.i(TAG, "getRangeString key = " + key); if (range == null) {//依赖的对象都没有给出有效值,这个存在无意义。如果是客户端传的,那就能在客户端确定了。 throw new NotExistException(TAG + "getRangeString(" + key + ", " + range @@ -1917,6 +1958,11 @@ public String getRangeString(String key, Object range) throws Exception { Log.i(TAG, "getRangeString k = " + k); if (range instanceof List) { + if (rawSQL != null) { + throw new UnsupportedOperationException("@raw:value 的 value 中 " + key + "{} 不合法!" + + "Raw SQL 不支持 key{}:[] 这种键值对!"); + } + if (logic.isOr() || logic.isNot()) { List l = (List) range; if (logic.isNot() && l.isEmpty()) { @@ -1927,13 +1973,43 @@ public String getRangeString(String key, Object range) throws Exception { throw new IllegalArgumentException(key + "{}\":[] 中 {} 前面的逻辑运算符错误!只能用'|','!'中的一种 !"); } else if (range instanceof String) {//非Number类型需要客户端拼接成 < 'value0', >= 'value1'这种 - String[] cs = StringUtil.split((String) range); String condition = ""; + String[] cs = rawSQL != null ? null : StringUtil.split((String) range, false); + + if (rawSQL != null) { + int index = rawSQL == null ? -1 : rawSQL.indexOf("("); + condition = (index >= 0 && index < rawSQL.indexOf(")") ? "" : getKey(k) + " ") + rawSQL; + } + + // 还是只支持整段为 Raw SQL 比较好 + // boolean appendRaw = false; + // if ("".equals(rawSQL)) { + // condition = rawSQL; + // cs = null; + // } + // else { + // if (rawSQL != null) { //先找出所有 rawSQL 的位置,然后去掉,再最后按原位置来拼接 + // String[] rs = StringUtil.split((String) range, rawSQL, false); + // + // if (rs != null && rs.length > 0) { + // String cond = ""; + // for (int i = 0; i < rs.length; i++) { + // cond += rs[i]; + // } + // range = cond; + // appendRaw = true; + // } + // } + // + // cs = StringUtil.split((String) range, false); + // } + if (cs != null) { String c; int index; for (int i = 0; i < cs.length; i++) {//对函数条件length(key)<=5这种不再在开头加key c = cs[i]; + if ("=null".equals(c)) { c = SQL.isNull(); } @@ -1946,9 +2022,9 @@ else if (isPrepared() && PATTERN_RANGE.matcher(c).matches() == false) { } index = c == null ? -1 : c.indexOf("("); - condition += ((i <= 0 ? "" : (logic.isAnd() ? AND : OR))//连接方式 - + (index >= 0 && index < c.indexOf(")") ? "" : getKey(k) + " ")//函数和非函数条件 - + c);//单个条件 + condition += ((i <= 0 ? "" : (logic.isAnd() ? AND : OR)) //连接方式 + + (index >= 0 && index < c.indexOf(")") ? "" : getKey(k) + " ") //函数和非函数条件 + + c); // 还是只支持整段为 Raw SQL 比较好 (appendRaw && index > 0 ? rawSQL : "") + c); //单个条件,如果有 Raw SQL 则按原来位置拼接 } } if (condition.isEmpty()) { @@ -2020,7 +2096,7 @@ public String getExistsString(String key, Object value) throws Exception { * @throws NotExistException */ @JSONField(serialize = false) - public String getContainString(String key, Object value) throws IllegalArgumentException { + public String getContainString(String key, Object value, String rawSQL) throws IllegalArgumentException { if (value == null) { return ""; } @@ -2267,7 +2343,7 @@ public static String getSQL(AbstractSQLConfig config) throws Exception { String q = config.getQuote(); // 生成 SELECT ( (24 >=0 AND 24 <3) ) AS `code` LIMIT 1 OFFSET 0 return explain + "SELECT " + config.getWhereString(false) + " AS " + q + JSONResponse.KEY_CODE + q + config.getLimitString(); } - + config.setPreparedValueList(new ArrayList()); String column = config.getColumnString(); return explain + "SELECT " + (config.getCache() == JSONRequest.CACHE_RAM ? "SQL_NO_CACHE " : "") + column + " FROM " + getConditionString(column, tablePath, config); @@ -2411,7 +2487,7 @@ public String getJoinString() throws Exception { "join:value 中 value 里的 " + jt + "/" + j.getPath() + "错误!不支持 " + jt + " 等 [ @ APP, < LEFT, > RIGHT, * CROSS" + ", & INNER, | FULL, ! OUTER, ^ SIDE, ( ANTI, ) FOREIGN ] 之外的 JOIN 类型 !" - ); + ); } joinOns += " \n " + sql; @@ -2533,6 +2609,7 @@ else if (id instanceof Subquery) {} String group = request.getString(KEY_GROUP); String having = request.getString(KEY_HAVING); String order = request.getString(KEY_ORDER); + String raw = request.getString(KEY_RAW); String json = request.getString(KEY_JSON); //强制作为条件且放在最前面优化性能 @@ -2550,8 +2627,11 @@ else if (id instanceof Subquery) {} request.remove(KEY_GROUP); request.remove(KEY_HAVING); request.remove(KEY_ORDER); + request.remove(KEY_RAW); request.remove(KEY_JSON); + String[] rawArr = StringUtil.split(raw); + config.setRaw(rawArr == null || rawArr.length <= 0 ? null : new ArrayList<>(Arrays.asList(rawArr))); Map tableWhere = new LinkedHashMap();//保证顺序好优化 WHERE id > 1 AND name LIKE... @@ -2706,20 +2786,54 @@ else if (whereList != null && whereList.contains(key)) { config.setContent(tableContent); } - boolean distinct = column == null ? false : column.startsWith(PREFFIX_DISTINCT); List cs = new ArrayList<>(); - String[] fks = StringUtil.split(distinct ? column.substring(PREFFIX_DISTINCT.length()) : column, ";"); // key0,key1;fun0(key0,...);fun1(key0,...);key3;fun2(key0,...) - if (fks != null) { - String[] ks; - for (String fk : fks) { - if (fk.contains("(")) { //fun0(key0,...) - cs.add(fk); + + String rawColumnSQL = null; + List rawList = config.getRaw(); + boolean containRaw = rawList != null && rawList.contains(KEY_COLUMN); + + if (containRaw) { + try { + rawColumnSQL = config.getRawSQL(KEY_COLUMN, column); + if (rawColumnSQL != null) { + cs.add(rawColumnSQL); } - else { //key0,key1... - ks = StringUtil.split(fk); - if (ks != null && ks.length > 0) { - cs.addAll(Arrays.asList(ks)); + } catch (Exception e) { + Log.e(TAG, "newSQLConfig config instanceof AbstractSQLConfig >> try { " + + " rawColumnSQL = ((AbstractSQLConfig) config).getRawSQL(KEY_COLUMN, column); " + + "} catch (Exception e) = " + e.getMessage()); + } + } + + boolean distinct = column == null || rawColumnSQL != null ? false : column.startsWith(PREFFIX_DISTINCT); + if (rawColumnSQL == null) { + String[] fks = StringUtil.split(distinct ? column.substring(PREFFIX_DISTINCT.length()) : column, ";"); // key0,key1;fun0(key0,...);fun1(key0,...);key3;fun2(key0,...) + if (fks != null) { + String[] ks; + for (String fk : fks) { + if (containRaw) { + try { + String rawSQL = config.getRawSQL(KEY_COLUMN, fk); + if (rawSQL != null) { + cs.add(rawSQL); + continue; + } + } catch (Exception e) { + Log.e(TAG, "newSQLConfig rawColumnSQL == null >> try { " + + " String rawSQL = ((AbstractSQLConfig) config).getRawSQL(KEY_COLUMN, fk); ... " + + "} catch (Exception e) = " + e.getMessage()); + } + } + + if (fk.contains("(")) { // fun0(key0,...) + cs.add(fk); + } + else { //key0,key1... + ks = StringUtil.split(fk); + if (ks != null && ks.length > 0) { + cs.addAll(Arrays.asList(ks)); + } } } } @@ -2761,6 +2875,7 @@ else if (whereList != null && whereList.contains(key)) { request.put(KEY_GROUP, group); request.put(KEY_HAVING, having); request.put(KEY_ORDER, order); + request.put(KEY_RAW, raw); request.put(KEY_JSON, json); return config; @@ -2979,7 +3094,7 @@ public static interface IdCallback { */ String getUserIdKey(String database, String schema, String table); } - + public static interface Callback extends IdCallback { /**获取 SQLConfig 的实例 * @param method @@ -2989,7 +3104,7 @@ public static interface Callback extends IdCallback { * @return */ SQLConfig getSQLConfig(RequestMethod method, String database, String schema, String table); - + /**combine 里的 key 在 request 中 value 为 null 或不存在,即 request 中缺少用来作为 combine 条件的 key: value * @param combine * @param key @@ -3015,7 +3130,7 @@ public String getIdKey(String database, String schema, String table) { public String getUserIdKey(String database, String schema, String table) { return KEY_USER_ID; } - + @Override public void onMissingKey4Combine(String name, JSONObject request, String combine, String item, String key) throws Exception { throw new IllegalArgumentException(name + ":{} 里的 @combine:value 中的value里 " + item + " 对应的条件 " + key + ":value 中 value 不能为 null!"); diff --git a/APIJSONORM/src/main/java/apijson/orm/SQLConfig.java b/APIJSONORM/src/main/java/apijson/orm/SQLConfig.java index c41c176df..ed466e2cf 100755 --- a/APIJSONORM/src/main/java/apijson/orm/SQLConfig.java +++ b/APIJSONORM/src/main/java/apijson/orm/SQLConfig.java @@ -138,6 +138,9 @@ public interface SQLConfig { String getTablePath(); + List getRaw(); + SQLConfig setRaw(List raw); + SQLConfig setTable(String table); String getGroup(); @@ -164,8 +167,6 @@ public interface SQLConfig { Map getContent(); SQLConfig setContent(Map content); - - Map getWhere(); SQLConfig setWhere(Map where); @@ -213,6 +214,8 @@ public interface SQLConfig { String getWhereString(boolean hasPrefix) throws Exception; + String getRawSQL(String key, Object value) throws Exception; + boolean isKeyPrefix(); SQLConfig setKeyPrefix(boolean keyPrefix); @@ -229,4 +232,5 @@ public interface SQLConfig { SQLConfig setProcedure(String procedure); + } From a215d49f309e1f3f6e1ee0617af74e9ea97919c2 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Sun, 13 Dec 2020 22:53:55 +0800 Subject: [PATCH 0084/1181] Update Document.md --- Document.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Document.md b/Document.md index 048695ecb..7196faa44 100644 --- a/Document.md +++ b/Document.md @@ -354,7 +354,7 @@ DELETE:
删除数据 | base_url/delete/ | {
   TableName:{< ------------ | ------------ | ------------ 查询数组 | "key[]":{},后面是JSONObject,key可省略。当key和里面的Table名相同时,Table会被提取出来,即 {Table:{Content}} 会被转化为 {Content} | [{"User[]":{"User":{}}}](http://apijson.cn:8080/get/{"User[]":{"count":3,"User":{}}}),查询一个User数组。这里key和Table名都是User,User会被提取出来,即 {"User":{"id", ...}} 会被转化为 {"id", ...} 匹配选项范围 | "key{}":[],后面是JSONArray,作为key可取的值的选项 | ["id{}":[38710,82001,70793]](http://apijson.cn:8080/get/{"User[]":{"count":3,"User":{"id{}":[38710,82001,70793]}}}),对应SQL是`id IN(38710,82001,70793)`,查询id符合38710,82001,70793中任意一个的一个User数组 - 匹配条件范围 | "key{}":"条件0,条件1...",条件为任意SQL比较表达式字符串,非Number类型必须用''包含条件的值,如'a' | ["id{}":"<=80000,\>90000"](http://apijson.cn:8080/get/{"User[]":{"count":3,"User":{"id{}":"<=80000,\>90000"}}}),对应SQL是`id<=80000 OR id>90000`,查询id符合id\<=80000 \| id>90000的一个User数组 + 匹配条件范围 | "key{}":"条件0,条件1...",条件为SQL表达式字符串,可进行简单的数学计算 | ["id{}":"<=80000,\>90000"](http://apijson.cn:8080/get/{"User[]":{"count":3,"User":{"id{}":"<=80000,\>90000"}}}),对应SQL是`id<=80000 OR id>90000`,查询id符合id\<=80000 \| id>90000的一个User数组 包含选项范围 | "key<\>":Object => "key<\>":[Object],key对应值的类型必须为JSONArray,Object类型不能为JSON | ["contactIdList<\>":38710](http://apijson.cn:8080/get/{"User[]":{"count":3,"User":{"contactIdList<\>":38710}}}),对应SQL是`json_contains(contactIdList,38710)`,查询contactIdList包含38710的一个User数组 判断是否存在 | "key}{@":{
   "from":"Table",
   "Table":{ ... }
}
其中:
}{ 表示 EXISTS;
key 用来标识是哪个判断;
@ 后面是 子查询 对象,具体见下方 子查询 的说明。 | ["id}{@":{
   "from":"Comment",
   "Comment":{
      "momentId":15
   }
}](http://apijson.cn:8080/get/{"User":{"id}{@":{"from":"Comment","Comment":{"momentId":15}}}})
WHERE EXISTS(SELECT * FROM Comment WHERE momentId=15) 远程调用函数 | "key()":"函数表达式",函数表达式为 function(key0,key1...),会调用后端对应的函数 function(JSONObject request, String key0, String key1...),实现 参数校验、数值计算、数据同步、消息推送、字段拼接、结构变换 等特定的业务逻辑处理,
可使用 - 和 + 表示优先级,解析 key-() > 解析当前对象 > 解析 key() > 解析子对象 > 解析 key+() | ["isPraised()":"isContain(praiseUserIdList,userId)"](http://apijson.cn:8080/get/{"Moment":{"id":301,"isPraised()":"isContain(praiseUserIdList,userId)"}}),会调用远程函数 boolean isContain(JSONObject request, String array, String value) ,然后变为 "isPraised":true 这种(假设点赞用户id列表包含了userId,即这个User点了赞) From 73b89000776c52e669ad398eeeb8c8affd002bee Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Sun, 13 Dec 2020 22:54:59 +0800 Subject: [PATCH 0085/1181] Update Document.md --- Document.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Document.md b/Document.md index 7196faa44..8f2bde12a 100644 --- a/Document.md +++ b/Document.md @@ -358,7 +358,7 @@ DELETE:
删除数据 | base_url/delete/ | {
   TableName:{< 包含选项范围 | "key<\>":Object => "key<\>":[Object],key对应值的类型必须为JSONArray,Object类型不能为JSON | ["contactIdList<\>":38710](http://apijson.cn:8080/get/{"User[]":{"count":3,"User":{"contactIdList<\>":38710}}}),对应SQL是`json_contains(contactIdList,38710)`,查询contactIdList包含38710的一个User数组 判断是否存在 | "key}{@":{
   "from":"Table",
   "Table":{ ... }
}
其中:
}{ 表示 EXISTS;
key 用来标识是哪个判断;
@ 后面是 子查询 对象,具体见下方 子查询 的说明。 | ["id}{@":{
   "from":"Comment",
   "Comment":{
      "momentId":15
   }
}](http://apijson.cn:8080/get/{"User":{"id}{@":{"from":"Comment","Comment":{"momentId":15}}}})
WHERE EXISTS(SELECT * FROM Comment WHERE momentId=15) 远程调用函数 | "key()":"函数表达式",函数表达式为 function(key0,key1...),会调用后端对应的函数 function(JSONObject request, String key0, String key1...),实现 参数校验、数值计算、数据同步、消息推送、字段拼接、结构变换 等特定的业务逻辑处理,
可使用 - 和 + 表示优先级,解析 key-() > 解析当前对象 > 解析 key() > 解析子对象 > 解析 key+() | ["isPraised()":"isContain(praiseUserIdList,userId)"](http://apijson.cn:8080/get/{"Moment":{"id":301,"isPraised()":"isContain(praiseUserIdList,userId)"}}),会调用远程函数 boolean isContain(JSONObject request, String array, String value) ,然后变为 "isPraised":true 这种(假设点赞用户id列表包含了userId,即这个User点了赞) - 存储过程 | "@key()":"SQL函数表达式",函数表达式为
function(key0,key1...)
会调用后端数据库对应的存储过程 SQL函数
function(String key0, String key1...)
除了参数会提前赋值,其它和 远程函数 一致 | ["@limit":10,
"@offset":0,
"@procedure()":"getCommentByUserId(id,@limit,@offset)"](http://apijson.cn:8080/get/{"User":{"@limit":10,"@offset":0,"@procedure()":"getCommentByUserId(id,@limit,@offset)"}})
会转为
`getCommentByUserId(38710,10,0)`
来调用存储过程 SQL 函数
`getCommentByUserId(IN id bigint, IN limit int, IN offset int)`
然后变为
"@procedure":{
   "count":-1,
   "update":false,
   "list":[]
}
其中 count 是指写操作影响记录行数,-1 表示不是写操作;update 是指是否为写操作(增删改);list 为返回结果集 + 存储过程 | "@key()":"SQL函数表达式",函数表达式为
function(key0,key1...)
会调用后端数据库对应的存储过程 SQL函数
function(String key0, String key1...)
除了参数会提前赋值,其它和 远程函数 一致 | ["@limit":10,
"@offset":0,
"@procedure()":"getCommentByUserId(id,@limit,@offset)"](http://apijson.cn:8080/get/{"User":{"@limit":10,"@offset":0,"@procedure()":"getCommentByUserId(id,@limit,@offset)"}})
会转为
`getCommentByUserId(38710,10,0)`
来调用存储过程 SQL 函数
`getCommentByUserId(IN id bigint, IN limit int, IN offset int)`
然后变为
"procedure":{
   "count":-1,
   "update":false,
   "list":[]
}
其中 count 是指写操作影响记录行数,-1 表示不是写操作;update 是指是否为写操作(增删改);list 为返回结果集 引用赋值 | "key@":"key0/key1/.../refKey",引用路径为用/分隔的字符串。以/开头的是缺省引用路径,从声明key所处容器的父容器路径开始;其它是完整引用路径,从最外层开始。
被引用的refKey必须在声明key的上面。如果对refKey的容器指定了返回字段,则被引用的refKey必须写在@column对应的值内,例如 "@column":"refKey,key1,..." | ["Moment":{
   "userId":38710
},
"User":{
   "id@":"/Moment/userId"
}](http://apijson.cn:8080/get/{"Moment":{"userId":38710},"User":{"id@":"%252FMoment%252FuserId"}})
User内的id引用了与User同级的Moment内的userId,
即User.id = Moment.userId,请求完成后
"id@":"/Moment/userId" 会变成 "id":38710 子查询 | "key@":{
   "range":"ALL",
   "from":"Table",
   "Table":{ ... }
}
其中:
range 可为 ALL,ANY;
from 为目标表 Table 的名称;
@ 后面的对象类似数组对象,可使用 count 和 join 等功能。 | ["id@":{
   "from":"Comment",
   "Comment":{
      "@column":"min(userId)"
   }
}](http://apijson.cn:8080/get/{"User":{"id@":{"from":"Comment","Comment":{"@column":"min(userId)"}}}})
WHERE id=(SELECT min(userId) FROM Comment) 模糊搜索 | "key$":"SQL搜索表达式" => "key$":["SQL搜索表达式"],任意SQL搜索表达式字符串,如 %key%(包含key), key%(以key开始), %k%e%y%(包含字母k,e,y) 等,%表示任意字符 | ["name$":"%m%"](http://apijson.cn:8080/get/{"User[]":{"count":3,"User":{"name$":"%2525m%2525"}}}),对应SQL是`name LIKE '%m%'`,查询name包含"m"的一个User数组 From 7958fd3a8b1e7c1d1c0a2b4caae2e058b13c2529 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Sun, 13 Dec 2020 22:56:26 +0800 Subject: [PATCH 0086/1181] Update Document.md --- Document.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Document.md b/Document.md index 8f2bde12a..0110e5521 100644 --- a/Document.md +++ b/Document.md @@ -354,7 +354,7 @@ DELETE:
删除数据 | base_url/delete/ | {
   TableName:{< ------------ | ------------ | ------------ 查询数组 | "key[]":{},后面是JSONObject,key可省略。当key和里面的Table名相同时,Table会被提取出来,即 {Table:{Content}} 会被转化为 {Content} | [{"User[]":{"User":{}}}](http://apijson.cn:8080/get/{"User[]":{"count":3,"User":{}}}),查询一个User数组。这里key和Table名都是User,User会被提取出来,即 {"User":{"id", ...}} 会被转化为 {"id", ...} 匹配选项范围 | "key{}":[],后面是JSONArray,作为key可取的值的选项 | ["id{}":[38710,82001,70793]](http://apijson.cn:8080/get/{"User[]":{"count":3,"User":{"id{}":[38710,82001,70793]}}}),对应SQL是`id IN(38710,82001,70793)`,查询id符合38710,82001,70793中任意一个的一个User数组 - 匹配条件范围 | "key{}":"条件0,条件1...",条件为SQL表达式字符串,可进行简单的数学计算 | ["id{}":"<=80000,\>90000"](http://apijson.cn:8080/get/{"User[]":{"count":3,"User":{"id{}":"<=80000,\>90000"}}}),对应SQL是`id<=80000 OR id>90000`,查询id符合id\<=80000 \| id>90000的一个User数组 + 匹配条件范围 | "key{}":"条件0,条件1...",条件为SQL表达式字符串,可进行数字比较运算等 | ["id{}":"<=80000,\>90000"](http://apijson.cn:8080/get/{"User[]":{"count":3,"User":{"id{}":"<=80000,\>90000"}}}),对应SQL是`id<=80000 OR id>90000`,查询id符合id\<=80000 \| id>90000的一个User数组 包含选项范围 | "key<\>":Object => "key<\>":[Object],key对应值的类型必须为JSONArray,Object类型不能为JSON | ["contactIdList<\>":38710](http://apijson.cn:8080/get/{"User[]":{"count":3,"User":{"contactIdList<\>":38710}}}),对应SQL是`json_contains(contactIdList,38710)`,查询contactIdList包含38710的一个User数组 判断是否存在 | "key}{@":{
   "from":"Table",
   "Table":{ ... }
}
其中:
}{ 表示 EXISTS;
key 用来标识是哪个判断;
@ 后面是 子查询 对象,具体见下方 子查询 的说明。 | ["id}{@":{
   "from":"Comment",
   "Comment":{
      "momentId":15
   }
}](http://apijson.cn:8080/get/{"User":{"id}{@":{"from":"Comment","Comment":{"momentId":15}}}})
WHERE EXISTS(SELECT * FROM Comment WHERE momentId=15) 远程调用函数 | "key()":"函数表达式",函数表达式为 function(key0,key1...),会调用后端对应的函数 function(JSONObject request, String key0, String key1...),实现 参数校验、数值计算、数据同步、消息推送、字段拼接、结构变换 等特定的业务逻辑处理,
可使用 - 和 + 表示优先级,解析 key-() > 解析当前对象 > 解析 key() > 解析子对象 > 解析 key+() | ["isPraised()":"isContain(praiseUserIdList,userId)"](http://apijson.cn:8080/get/{"Moment":{"id":301,"isPraised()":"isContain(praiseUserIdList,userId)"}}),会调用远程函数 boolean isContain(JSONObject request, String array, String value) ,然后变为 "isPraised":true 这种(假设点赞用户id列表包含了userId,即这个User点了赞) From d959c6c655b78be433d499184fd87b3209321e40 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Sun, 13 Dec 2020 23:11:20 +0800 Subject: [PATCH 0087/1181] =?UTF-8?q?=E5=AD=98=E5=82=A8=E8=BF=87=E7=A8=8B?= =?UTF-8?q?=20@procedure():"fun(key0,key1..)"=20=E8=BF=94=E5=9B=9E?= =?UTF-8?q?=E6=A0=BC=E5=BC=8F=E4=B8=AD=E5=8E=BB=E6=8E=89=20key=20=E7=9A=84?= =?UTF-8?q?=20@=20=E5=89=8D=E7=BC=80=EF=BC=9B=E5=8F=96=E6=B6=88=E6=94=AF?= =?UTF-8?q?=E6=8C=81=20key=3F=20=E8=BF=99=E7=A7=8D=E6=AD=A3=E5=88=99?= =?UTF-8?q?=E5=8C=B9=E9=85=8D=E6=96=B9=E5=BC=8F=EF=BC=8C=E5=85=A8=E9=9D=A2?= =?UTF-8?q?=E7=94=A8=20=20key~=20=E6=9B=BF=E4=BB=A3=EF=BC=9B=E4=BC=98?= =?UTF-8?q?=E5=8C=96=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../apijson/orm/AbstractObjectParser.java | 8 ++-- .../java/apijson/orm/AbstractSQLConfig.java | 40 +++++++++---------- .../java/apijson/orm/AbstractVerifier.java | 5 +-- 3 files changed, 25 insertions(+), 28 deletions(-) diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractObjectParser.java b/APIJSONORM/src/main/java/apijson/orm/AbstractObjectParser.java index 435941320..2a59b731c 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractObjectParser.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractObjectParser.java @@ -513,7 +513,7 @@ public void onPUTArrayParse(@NotNull String key, @NotNull JSONArray array) throw // throw new IllegalAccessException("PUT " + path + ", PUT Array不允许 " + key + // " 这种没有 + 或 - 结尾的key!不允许整个替换掉原来的Array!"); } - String realKey = AbstractSQLConfig.getRealKey(method, key, false, false, "`"); //FIXME PG 是 " + String realKey = AbstractSQLConfig.getRealKey(method, key, false, false); //GET > add all 或 remove all > PUT > remove key @@ -746,19 +746,21 @@ public void onFunctionResponse(String type) throws Exception { public void parseFunction(String key, String value, String parentPath, String currentName, JSONObject currentObject) throws Exception { Object result; - if (key.startsWith("@")) { //TODO 以后这种小众功能从 ORM 移出,作为一个 plugin/APIJSONProcedure + if (key.startsWith("@")) { FunctionBean fb = AbstractFunctionParser.parseFunction(value, currentObject, true); SQLConfig config = newSQLConfig(true); config.setProcedure(fb.toFunctionCallString(true)); result = parseResponse(config, true); + + key = key.substring(1); } else { result = parser.onFunctionParse(key, value, parentPath, currentName, currentObject); } if (result != null) { - String k = AbstractSQLConfig.getRealKey(method, key, false, false, "`"); //FIXME PG 是 " + String k = AbstractSQLConfig.getRealKey(method, key, false, false); response.put(k, result); parser.putQueryResult(AbstractParser.getAbsPath(path, k), result); diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java index 613b3b251..98fc29bb4 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java @@ -882,14 +882,14 @@ public String getColumnString(boolean inSQLJoin) throws Exception { } // 简单点, 后台配置就带上 AS -// int index = expression.lastIndexOf(":"); -// String alias = expression.substring(index+1); -// boolean hasAlias = StringUtil.isName(alias); -// String pre = index > 0 && hasAlias ? expression.substring(0, index) : expression; -// if (RAW_MAP.containsValue(pre) || "".equals(RAW_MAP.get(pre))) { // newSQLConfig 提前处理好的 -// expression = pre + (hasAlias ? " AS " + alias : ""); -// continue; -// } + // int index = expression.lastIndexOf(":"); + // String alias = expression.substring(index+1); + // boolean hasAlias = StringUtil.isName(alias); + // String pre = index > 0 && hasAlias ? expression.substring(0, index) : expression; + // if (RAW_MAP.containsValue(pre) || "".equals(RAW_MAP.get(pre))) { // newSQLConfig 提前处理好的 + // expression = pre + (hasAlias ? " AS " + alias : ""); + // continue; + // } } @@ -1572,8 +1572,7 @@ else if (isSideJoin) { // ^ SIDE JOIN: ! (A & B) * @return * @throws Exception */ - private String getWhereItem(String key, Object value - , RequestMethod method, boolean verifyName) throws Exception { + protected String getWhereItem(String key, Object value, RequestMethod method, boolean verifyName) throws Exception { Log.d(TAG, "getWhereItem key = " + key); //避免筛选到全部 value = key == null ? null : where.get(key); if (key == null || value == null || key.endsWith("()") || key.startsWith("@")) { //关键字||方法, +或-直接报错 @@ -1593,7 +1592,7 @@ private String getWhereItem(String key, Object value if (key.endsWith("$")) { keyType = 1; } - else if (key.endsWith("~") || key.endsWith("?")) { //TODO ?可能以后会被废弃,全用 ~ 和 *~ 替代,更接近 PostgreSQL 语法 + else if (key.endsWith("~")) { keyType = key.charAt(key.length() - 2) == '*' ? -2 : 2; //FIXME StringIndexOutOfBoundsException } else if (key.endsWith("%")) { @@ -1623,7 +1622,7 @@ else if (key.endsWith("<")) { keyType = 0; } - key = getRealKey(method, key, false, true, verifyName, getQuote()); + key = getRealKey(method, key, false, true, verifyName); switch (keyType) { case 1: @@ -2223,8 +2222,6 @@ public String getSetString(RequestMethod method, Map content, bo String setString = ""; if (set != null && set.size() > 0) { - String quote = getQuote(); - boolean isFirst = true; int keyType;// 0 - =; 1 - +, 2 - - Object value; @@ -2244,7 +2241,7 @@ public String getSetString(RequestMethod method, Map content, bo keyType = 0; //注意重置类型,不然不该加减的字段会跟着加减 } value = content.get(key); - key = getRealKey(method, key, false, true, verifyName, quote); + key = getRealKey(method, key, false, true, verifyName); setString += (isFirst ? "" : ", ") + (getKey(key) + " = " + (keyType == 1 ? getAddString(key, value) : (keyType == 2 ? getRemoveString(key, value) : getValue(value)) ) ); @@ -2967,8 +2964,8 @@ LEFT JOIN ( SELECT count(*) AS count FROM sys.Comment ) AS Comment ON Comment.m * @return */ public static String getRealKey(RequestMethod method, String originKey - , boolean isTableKey, boolean saveLogic, String quote) throws Exception { - return getRealKey(method, originKey, isTableKey, saveLogic, true, quote); + , boolean isTableKey, boolean saveLogic) throws Exception { + return getRealKey(method, originKey, isTableKey, saveLogic, true); } /**获取客户端实际需要的key * @param method @@ -2979,11 +2976,10 @@ public static String getRealKey(RequestMethod method, String originKey * @return */ public static String getRealKey(RequestMethod method, String originKey - , boolean isTableKey, boolean saveLogic, boolean verifyName, String quote) throws Exception { + , boolean isTableKey, boolean saveLogic, boolean verifyName) throws Exception { Log.i(TAG, "getRealKey saveLogic = " + saveLogic + "; originKey = " + originKey); - if (originKey == null || originKey.startsWith(quote) || apijson.JSONObject.isArrayKey(originKey)) { - Log.w(TAG, "getRealKey originKey == null || originKey.startsWith(`)" - + " || apijson.JSONObject.isArrayKey(originKey) >> return originKey;"); + if (originKey == null || apijson.JSONObject.isArrayKey(originKey)) { + Log.w(TAG, "getRealKey originKey == null || apijson.JSONObject.isArrayKey(originKey) >> return originKey;"); return originKey; } @@ -2991,7 +2987,7 @@ public static String getRealKey(RequestMethod method, String originKey if (key.endsWith("$")) {//搜索 LIKE,查询时处理 key = key.substring(0, key.length() - 1); } - else if (key.endsWith("~") || key.endsWith("?")) {//匹配正则表达式 REGEXP,查询时处理 TODO ?可能以后会被废弃,全用 ~ 和 *~ 替代,更接近 PostgreSQL 语法 + else if (key.endsWith("~")) {//匹配正则表达式 REGEXP,查询时处理 key = key.substring(0, key.length() - 1); if (key.endsWith("*")) {//忽略大小写 key = key.substring(0, key.length() - 1); diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractVerifier.java b/APIJSONORM/src/main/java/apijson/orm/AbstractVerifier.java index 4f4c135dd..9783918f6 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractVerifier.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractVerifier.java @@ -56,7 +56,6 @@ import apijson.StringUtil; import apijson.orm.AbstractSQLConfig.IdCallback; import apijson.orm.exception.ConflictException; -import apijson.orm.exception.NotExistException; import apijson.orm.exception.NotLoggedInException; import apijson.orm.model.Access; import apijson.orm.model.Column; @@ -1182,10 +1181,10 @@ private static void verifyValue(@NotNull String tk, @NotNull Object tv, @NotNull String rk; Object rv; Logic logic; - if (tk.endsWith("$")) { //搜索 + if (tk.endsWith("$")) { // 模糊搜索 verifyCondition("$", real, tk, tv, creator); } - else if (tk.endsWith("~") || tk.endsWith("?")) { //TODO 正则表达式, 以后可能取消支持 ? 作为 正则匹配 的功能符 + else if (tk.endsWith("~")) { // 正则匹配 logic = new Logic(tk.substring(0, tk.length() - 1)); rk = logic.getKey(); rv = real.get(rk); From 3dc9dd1ce54e8572a96a49d428a1aa89df4194cc Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Mon, 14 Dec 2020 01:37:31 +0800 Subject: [PATCH 0088/1181] =?UTF-8?q?@raw=20=E6=94=AF=E6=8C=81=20key:value?= =?UTF-8?q?=20=E5=92=8C=20@having=EF=BC=9B@having=20=E5=92=8C=20@column=20?= =?UTF-8?q?=E4=B8=80=E6=A0=B7=E6=94=AF=E6=8C=81=20function(arg,&char,!)=20?= =?UTF-8?q?=E4=B8=AD=E5=8C=85=E5=90=AB=E4=B8=8D=E7=AC=A6=E5=90=88=20?= =?UTF-8?q?=E5=AD=97=E6=AE=B5=E5=91=BD=E5=90=8D=20=E7=9A=84=E5=AD=97?= =?UTF-8?q?=E7=AC=A6=EF=BC=9B=E4=BC=98=E5=8C=96=E4=BB=A3=E7=A0=81=E5=92=8C?= =?UTF-8?q?=E6=8A=A5=E9=94=99=E6=8F=90=E7=A4=BA=EF=BC=9B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../main/java/apijson/orm/AbstractParser.java | 4 + .../java/apijson/orm/AbstractSQLConfig.java | 150 +++++++++++++----- 2 files changed, 112 insertions(+), 42 deletions(-) diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java b/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java index 002fd9609..d8230e850 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java @@ -385,6 +385,10 @@ public JSONObject parseResponse(JSONObject request) { requestObject.put("sql:generate|cache|execute|maxExecute", getSQLExecutor().getGeneratedSQLCount() + "|" + getSQLExecutor().getCachedSQLCount() + "|" + getSQLExecutor().getExecutedSQLCount() + "|" + getMaxSQLCount()); requestObject.put("depth:count|max", queryDepth + "|" + getMaxQueryDepth()); requestObject.put("time:start|duration|end", startTime + "|" + duration + "|" + endTime); + if (error != null) { + requestObject.put("throw", error.getClass().getName()); + requestObject.put("trace", error.getStackTrace()); + } } onClose(); diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java index 98fc29bb4..aa4639eb5 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java @@ -81,6 +81,10 @@ public abstract class AbstractSQLConfig implements SQLConfig { public static String DEFAULT_SCHEMA = "sys"; public static String PREFFIX_DISTINCT = "DISTINCT "; + // * 和 / 不能同时出现,防止 /* */ 段注释! # 和 -- 不能出现,防止行注释! ; 不能出现,防止隔断SQL语句!空格不能出现,防止 CRUD,DROP,SHOW TABLES等语句! + private static final Pattern PATTERN_RANGE; + private static final Pattern PATTERN_FUNCTION; + /** * 表名映射,隐藏真实表名,对安全要求很高的表可以这么做 */ @@ -89,7 +93,11 @@ public abstract class AbstractSQLConfig implements SQLConfig { public static final List DATABASE_LIST; // 自定义原始 SQL 片段 Map:当 substring 为 null 时忽略;当 substring 为 "" 时整个 value 是 raw SQL;其它情况则只是 substring 这段为 raw SQL public static final Map RAW_MAP; - static { + static { // 凡是 SQL 边界符、分隔符、注释符 都不允许,例如 ' " ` ( ) ; # -- ,以免拼接 SQL 时被注入意外可执行指令 + PATTERN_RANGE = Pattern.compile("^[0-9%,!=\\<\\>/\\.\\+\\-\\*\\^]+$"); // ^[a-zA-Z0-9_*%!=<>(),"]+$ 导致 exists(select*from(Comment)) 通过! + PATTERN_FUNCTION = Pattern.compile("^[A-Za-z0-9%,:_@&~!=\\<\\>\\|\\[\\]\\{\\} /\\.\\+\\-\\*\\^\\?\\$]+$"); //TODO 改成更好的正则,校验前面为单词,中间为操作符,后面为值 + + TABLE_KEY_MAP = new HashMap(); TABLE_KEY_MAP.put(Table.class.getSimpleName(), Table.TABLE_NAME); TABLE_KEY_MAP.put(Column.class.getSimpleName(), Column.TABLE_NAME); @@ -119,6 +127,7 @@ public abstract class AbstractSQLConfig implements SQLConfig { RAW_MAP = new LinkedHashMap<>(); // 保证顺序,避免配置冲突等意外情况 } + @Override public boolean limitSQLCount() { return Log.DEBUG == false || AbstractVerifier.SYSTEM_ACCESS_MAP.containsKey(getTable()) == false; @@ -487,7 +496,7 @@ public AbstractSQLConfig setHaving(String having) { this.having = having; return this; } - /** + /**TODO @having 改为默认 | 或连接,且支持 @having: { "key1>": 1, "key{}": "length(key2)>0", "@combine": "key1,key2" } * @return HAVING conditoin0 AND condition1 OR condition2 ... */ @JSONField(serialize = false) @@ -517,12 +526,17 @@ public String getHavingString(boolean hasPrefix) { } } - having = StringUtil.getTrimedString(having); - String[] keys = StringUtil.split(having, ";"); + String[] keys = StringUtil.split(getHaving(), ";"); if (keys == null || keys.length <= 0) { return StringUtil.isEmpty(joinHaving, true) ? "" : (hasPrefix ? " HAVING " : "") + joinHaving; } + + String quote = getQuote(); + String tableAlias = getAliasWithQuote(); + List raw = getRaw(); + boolean containRaw = raw != null && raw.contains(KEY_HAVING); + String expression; String method; //暂时不允许 String prefix; @@ -533,13 +547,31 @@ public String getHavingString(boolean hasPrefix) { //fun(arg0,arg1,...) expression = keys[i]; + if (containRaw) { + try { + String rawSQL = getRawSQL(KEY_HAVING, expression); + if (rawSQL != null) { + keys[i] = rawSQL; + continue; + } + } catch (Exception e) { + Log.e(TAG, "newSQLConfig rawColumnSQL == null >> try { " + + " String rawSQL = ((AbstractSQLConfig) config).getRawSQL(KEY_COLUMN, fk); ... " + + "} catch (Exception e) = " + e.getMessage()); + } + } + + if (expression.length() > 50) { + throw new UnsupportedOperationException("@having:value 的 value 中字符串 " + expression + " 不合法!" + + "不允许传超过 50 个字符的函数或表达式!请用 @raw 简化传参!"); + } int start = expression.indexOf("("); if (start < 0) { - if (isPrepared() && PATTERN_HAVING.matcher(expression).matches() == false) { + if (isPrepared() && PATTERN_FUNCTION.matcher(expression).matches() == false) { throw new UnsupportedOperationException("字符串 " + expression + " 不合法!" + "预编译模式下 @having:\"column?value;function(arg0,arg1,...)?value...\"" - + " 中 column?value 必须符合正则表达式 ^[A-Za-z0-9%!=<>]+$ !不允许空格!"); + + " 中 column?value 必须符合正则表达式 " + PATTERN_FUNCTION + " 且不包含连续减号 -- !不允许空格!"); } continue; } @@ -560,24 +592,40 @@ public String getHavingString(boolean hasPrefix) { suffix = expression.substring(end + 1, expression.length()); - if (isPrepared() && PATTERN_HAVING_SUFFIX.matcher((String) suffix).matches() == false) { + if (isPrepared() && (((String) suffix).contains("--") || PATTERN_RANGE.matcher((String) suffix).matches() == false)) { throw new UnsupportedOperationException("字符串 " + suffix + " 不合法!" + "预编译模式下 @having:\"column?value;function(arg0,arg1,...)?value...\"" - + " 中 ?value 必须符合正则表达式 ^[0-9%!=<>]+$ !不允许空格!"); + + " 中 ?value 必须符合正则表达式 " + PATTERN_RANGE + " 且不包含连续减号 -- !不允许多余的空格!"); } String[] ckeys = StringUtil.split(expression.substring(start + 1, end)); if (ckeys != null) { for (int j = 0; j < ckeys.length; j++) { + String origin = ckeys[j]; + + if (isPrepared()) { + if (origin.startsWith("_") || origin.contains("--") || PATTERN_FUNCTION.matcher(origin).matches() == false) { + throw new IllegalArgumentException("字符 " + ckeys[j] + " 不合法!" + + "预编译模式下 @having:\"column?value;function(arg0,arg1,...)?value...\"" + + " 中所有 column, arg 都必须是1个不以 _ 开头的单词 或者 符合正则表达式 " + PATTERN_FUNCTION + " 且不包含连续减号 -- !不允许多余的空格!"); + } + } - if (isPrepared() && (StringUtil.isName(ckeys[j]) == false || ckeys[j].startsWith("_"))) { - throw new IllegalArgumentException("字符 " + ckeys[j] + " 不合法!" - + "预编译模式下 @having:\"column?value;function(arg0,arg1,...)?value...\"" - + " 中所有 arg 都必须是1个不以 _ 开头的单词!并且不要有空格!"); + //JOIN 副表不再在外层加副表名前缀 userId AS `Commet.userId`, 而是直接 userId AS `userId` + boolean isName = false; + if (StringUtil.isNumer(origin)) { + //do nothing + } + else if (StringUtil.isName(origin)) { + origin = quote + origin + quote; + isName = true; + } + else { + origin = getValue(origin).toString(); } - ckeys[j] = getKey(ckeys[j]); + ckeys[j] = (isName && isKeyPrefix() ? tableAlias + "." : "") + origin; } } @@ -876,8 +924,8 @@ public String getColumnString(boolean inSQLJoin) throws Exception { //fun(arg0,arg1,...) expression = keys[i]; - if (containRaw) { - if (RAW_MAP.containsValue(expression) || "".equals(RAW_MAP.get(expression))) { // newSQLConfig 提前处理好的 + if (containRaw) { // 由于 HashMap 对 key 做了 hash 处理,所以 get 比 containsValue 更快 + if ("".equals(RAW_MAP.get(expression)) || RAW_MAP.containsValue(expression)) { // newSQLConfig 提前处理好的 continue; } @@ -892,6 +940,11 @@ public String getColumnString(boolean inSQLJoin) throws Exception { // } } + if (expression.length() > 50) { + throw new UnsupportedOperationException("@column:value 的 value 中字符串 " + expression + " 不合法!" + + "不允许传超过 50 个字符的函数或表达式!请用 @raw 简化传参!"); + } + int start = expression.indexOf("("); int end = 0; @@ -944,10 +997,10 @@ public String getColumnString(boolean inSQLJoin) throws Exception { } else { // if ((StringUtil.isName(origin) == false || origin.startsWith("_"))) { - if (origin.startsWith("_") || PATTERN_FUNCTION.matcher(origin).matches() == false) { + if (origin.startsWith("_") || origin.contains("--") || PATTERN_FUNCTION.matcher(origin).matches() == false) { throw new IllegalArgumentException("字符 " + ckeys[j] + " 不合法!" + "预编译模式下 @column:\"column0,column1:alias;function0(arg0,arg1,...);function1(...):alias...\"" - + " 中所有 arg 都必须是1个不以 _ 开头的单词 或者符合正则表达式 " + PATTERN_FUNCTION + " !DISTINCT 必须全大写,且后面必须有且只有 1 个空格!其它情况不允许空格!"); + + " 中所有 arg 都必须是1个不以 _ 开头的单词 或者符合正则表达式 " + PATTERN_FUNCTION + " 且不包含连续减号 -- !DISTINCT 必须全大写,且后面必须有且只有 1 个空格!其它情况不允许空格!"); } } } @@ -1626,16 +1679,16 @@ else if (key.endsWith("<")) { switch (keyType) { case 1: - return getSearchString(key, value); + return getSearchString(key, value, rawSQL); case -2: case 2: - return getRegExpString(key, value, keyType < 0); + return getRegExpString(key, value, keyType < 0, rawSQL); case 3: return getBetweenString(key, value, rawSQL); case 4: return getRangeString(key, value, rawSQL); case 5: - return getExistsString(key, value); + return getExistsString(key, value, rawSQL); case 6: return getContainString(key, value, rawSQL); case 7: @@ -1647,13 +1700,13 @@ else if (key.endsWith("<")) { case 10: return getCompareString(key, value, "<", rawSQL); default: // TODO MySQL JSON类型的字段对比 key='[]' 会无结果! key LIKE '[1, 2, 3]' //TODO MySQL , 后面有空格! - return getEqualString(key, value); + return getEqualString(key, value, rawSQL); } } @JSONField(serialize = false) - public String getEqualString(String key, Object value) throws Exception { + public String getEqualString(String key, Object value, String rawSQL) throws Exception { if (JSON.isBooleanOrNumberOrString(value) == false && value instanceof Subquery == false) { throw new IllegalArgumentException(key + ":value 中value不合法!非PUT请求只支持 [Boolean, Number, String] 内的类型 !"); } @@ -1666,7 +1719,7 @@ public String getEqualString(String key, Object value) throws Exception { throw new IllegalArgumentException(key + ":value 中key不合法!不支持 ! 以外的逻辑符 !"); } - return getKey(key) + (not ? " != " : " = ") + (value instanceof Subquery ? getSubqueryString((Subquery) value) : getValue(value)); + return getKey(key) + (not ? " != " : " = ") + (value instanceof Subquery ? getSubqueryString((Subquery) value) : (rawSQL != null ? rawSQL : getValue(value))); } @JSONField(serialize = false) @@ -1729,7 +1782,10 @@ public AbstractSQLConfig setPreparedValueList(List preparedValueList) { * @throws IllegalArgumentException */ @JSONField(serialize = false) - public String getSearchString(String key, Object value) throws IllegalArgumentException { + public String getSearchString(String key, Object value, String rawSQL) throws IllegalArgumentException { + if (rawSQL != null) { + throw new UnsupportedOperationException("@raw:value 中 " + key + " 不合法!@raw 不支持 key$ 这种功能符 !只支持 key, key!, key<, key{} 等比较运算 和 @column, @having !"); + } if (value == null) { return ""; } @@ -1789,7 +1845,10 @@ public String getLikeString(String key, Object value) { * @throws IllegalArgumentException */ @JSONField(serialize = false) - public String getRegExpString(String key, Object value, boolean ignoreCase) throws IllegalArgumentException { + public String getRegExpString(String key, Object value, boolean ignoreCase, String rawSQL) throws IllegalArgumentException { + if (rawSQL != null) { + throw new UnsupportedOperationException("@raw:value 中 " + key + " 不合法!@raw 不支持 key~ 这种功能符 !只支持 key, key!, key<, key{} 等比较运算 和 @column, @having !"); + } if (value == null) { return ""; } @@ -1925,18 +1984,6 @@ public String getBetweenString(String key, Object start, Object end) throws Ille //{} range <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< - // * 和 / 不能同时出现,防止 /* */ 段注释! # 和 -- 不能出现,防止行注释! ; 不能出现,防止隔断SQL语句!空格不能出现,防止 CRUD,DROP,SHOW TABLES等语句! - private static final Pattern PATTERN_RANGE; - private static final Pattern PATTERN_FUNCTION; - private static final Pattern PATTERN_HAVING; - private static final Pattern PATTERN_HAVING_SUFFIX; - static { - PATTERN_RANGE = Pattern.compile("^[0-9%!=<>,]+$"); // ^[a-zA-Z0-9_*%!=<>(),"]+$ 导致 exists(select*from(Comment)) 通过! - PATTERN_FUNCTION = Pattern.compile("^[A-Za-z0-9%-_:!=<> ]+$"); //TODO 改成更好的正则,校验前面为单词,中间为操作符,后面为值 - PATTERN_HAVING = Pattern.compile("^[A-Za-z0-9%!=<>]+$"); //TODO 改成更好的正则,校验前面为单词,中间为操作符,后面为值 - PATTERN_HAVING_SUFFIX = Pattern.compile("^[0-9%!=<>]+$"); // ^[a-zA-Z0-9_*%!=<>(),"]+$ 导致 exists(select*from(Comment)) 通过! - } - /**WHERE key > 'key0' AND key <= 'key1' AND ... * @param key @@ -2070,7 +2117,10 @@ public String getInString(String key, Object[] in, boolean not) throws NotExistE * @throws NotExistException */ @JSONField(serialize = false) - public String getExistsString(String key, Object value) throws Exception { + public String getExistsString(String key, Object value, String rawSQL) throws Exception { + if (rawSQL != null) { + throw new UnsupportedOperationException("@raw:value 中 " + key + " 不合法!@raw 不支持 key}{ 这种功能符 !只支持 key, key!, key<, key{} 等比较运算 和 @column, @having !"); + } if (value == null) { return ""; } @@ -2562,6 +2612,22 @@ public static SQLConfig newSQLConfig(RequestMethod method, String table, String } } + if (idIn instanceof List) { // 排除掉 0, 负数, 空字符串 等无效 id 值 + List ids = ((List) idIn); + List newIdIn = new ArrayList<>(); + Object d; + for (int i = 0; i < ids.size(); i++) { //不用 idIn.contains(id) 因为 idIn 里存到很可能是 Integer,id 又是 Long! + d = ids.get(i); + if ((d instanceof Number && ((Number) d).longValue() > 0) || (d instanceof String && StringUtil.isNotEmpty(d, true))) { + newIdIn.add(d); + } + } + if (newIdIn.isEmpty()) { + throw new NotExistException(TAG + ": newSQLConfig idIn instanceof List >> 去掉无效 id 后 newIdIn.isEmpty()"); + } + idIn = newIdIn; + } + //对id和id{}处理,这两个一定会作为条件 Object id = request.get(idKey); if (id != null) { //null无效 @@ -2786,11 +2852,11 @@ else if (whereList != null && whereList.contains(key)) { List cs = new ArrayList<>(); - String rawColumnSQL = null; List rawList = config.getRaw(); - boolean containRaw = rawList != null && rawList.contains(KEY_COLUMN); + boolean containColumnRaw = rawList != null && rawList.contains(KEY_COLUMN); - if (containRaw) { + String rawColumnSQL = null; + if (containColumnRaw) { try { rawColumnSQL = config.getRawSQL(KEY_COLUMN, column); if (rawColumnSQL != null) { @@ -2809,7 +2875,7 @@ else if (whereList != null && whereList.contains(key)) { if (fks != null) { String[] ks; for (String fk : fks) { - if (containRaw) { + if (containColumnRaw) { try { String rawSQL = config.getRawSQL(KEY_COLUMN, fk); if (rawSQL != null) { From b47140b477b2afa2874378ac25f5f62f7b959348 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Mon, 14 Dec 2020 02:01:49 +0800 Subject: [PATCH 0089/1181] =?UTF-8?q?=E5=8C=B9=E9=85=8D=E6=9D=A1=E4=BB=B6?= =?UTF-8?q?=E8=8C=83=E5=9B=B4=20key{}:"=E8=A1=A8=E8=BE=BE=E5=BC=8F"=20?= =?UTF-8?q?=E4=B8=8D=E5=85=81=E8=AE=B8=E8=BF=9E=E7=BB=AD=E5=87=8F=E5=8F=B7?= =?UTF-8?q?=20--=EF=BC=9B=E5=8D=87=E7=BA=A7=E9=A1=B9=E7=9B=AE=E7=89=88?= =?UTF-8?q?=E6=9C=AC=E5=8F=B7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- APIJSONORM/pom.xml | 2 +- APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/APIJSONORM/pom.xml b/APIJSONORM/pom.xml index af8e3b3a3..3756b076f 100755 --- a/APIJSONORM/pom.xml +++ b/APIJSONORM/pom.xml @@ -5,7 +5,7 @@ apijson.orm apijson-orm - 4.3.0 + 4.4.0 jar APIJSONORM diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java index aa4639eb5..9d306d2b9 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java @@ -2062,9 +2062,9 @@ else if (range instanceof String) {//非Number类型需要客户端拼接成 < ' else if ("!=null".equals(c)) { c = SQL.isNull(false); } - else if (isPrepared() && PATTERN_RANGE.matcher(c).matches() == false) { + else if (isPrepared() && (c.contains("--") || PATTERN_RANGE.matcher(c).matches() == false)) { throw new UnsupportedOperationException(key + "{}:value 的 value 中 " + c + " 不合法!" - + "预编译模式下 key{}:\"condition\" 中 condition 必须 为 =null 或 !=null 或 符合正则表达式 ^[0-9%!=<>,]+$ !不允许空格!"); + + "预编译模式下 key{}:\"condition\" 中 condition 必须 为 =null 或 !=null 或 符合正则表达式 ^[0-9%!=<>,]+$ !不允许连续减号 -- !不允许空格!"); } index = c == null ? -1 : c.indexOf("("); From 7fa6ac181e4871e54f9215d2c2ec9726693bd219 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Mon, 14 Dec 2020 02:05:31 +0800 Subject: [PATCH 0090/1181] =?UTF-8?q?=E4=BC=98=E5=8C=96=E5=8C=B9=E9=85=8D?= =?UTF-8?q?=E6=9D=A1=E4=BB=B6=E8=8C=83=E5=9B=B4=E7=9A=84=E6=8A=A5=E9=94=99?= =?UTF-8?q?=E6=8F=90=E7=A4=BA=EF=BC=9B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java index 9d306d2b9..d621f17e8 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java @@ -2064,7 +2064,7 @@ else if ("!=null".equals(c)) { } else if (isPrepared() && (c.contains("--") || PATTERN_RANGE.matcher(c).matches() == false)) { throw new UnsupportedOperationException(key + "{}:value 的 value 中 " + c + " 不合法!" - + "预编译模式下 key{}:\"condition\" 中 condition 必须 为 =null 或 !=null 或 符合正则表达式 ^[0-9%!=<>,]+$ !不允许连续减号 -- !不允许空格!"); + + "预编译模式下 key{}:\"condition\" 中 condition 必须 为 =null 或 !=null 或 符合正则表达式 " + PATTERN_RANGE + " !不允许连续减号 -- !不允许空格!"); } index = c == null ? -1 : c.indexOf("("); From 81656d852d99a9e3cec26f6261796b86bffc09cb Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Mon, 14 Dec 2020 15:43:28 +0800 Subject: [PATCH 0091/1181] Update Document.md --- Document.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Document.md b/Document.md index 0110e5521..df4a6bcba 100644 --- a/Document.md +++ b/Document.md @@ -1,5 +1,5 @@ # APIJSON通用文档 -后端开发者可以先看这个详细的 [图文入门教程](https://vincentcheng.github.io/apijson-doc/zh) +后端开发者可以先看这个详细的 [图文入门教程](https://vincentcheng.github.io/apijson-doc/zh)(如果和本文档有出入,以本文档为准) * ### [1.示例](#1) * ### [2.对比传统方式](#2) From 12489963e19ea7f9024c42c74ed6648ea5162634 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Mon, 14 Dec 2020 15:45:20 +0800 Subject: [PATCH 0092/1181] Update Document.md --- Document.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Document.md b/Document.md index df4a6bcba..a5718483a 100644 --- a/Document.md +++ b/Document.md @@ -1,5 +1,5 @@ # APIJSON通用文档 -后端开发者可以先看这个详细的 [图文入门教程](https://vincentcheng.github.io/apijson-doc/zh)(如果和本文档有出入,以本文档为准) +后端开发者可以先看这个详细的 [图文入门教程](https://vincentcheng.github.io/apijson-doc/zh)(如果和本文档有出入,以本文档为准,例如 key? 以废弃,全面用 key~ 替代) * ### [1.示例](#1) * ### [2.对比传统方式](#2) From 0ae912e8a563d36a1ba4aa159fa892bce76a542a Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Mon, 14 Dec 2020 15:45:34 +0800 Subject: [PATCH 0093/1181] Update Document.md --- Document.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Document.md b/Document.md index a5718483a..a239f30ef 100644 --- a/Document.md +++ b/Document.md @@ -1,5 +1,5 @@ # APIJSON通用文档 -后端开发者可以先看这个详细的 [图文入门教程](https://vincentcheng.github.io/apijson-doc/zh)(如果和本文档有出入,以本文档为准,例如 key? 以废弃,全面用 key~ 替代) +后端开发者可以先看这个详细的 [图文入门教程](https://vincentcheng.github.io/apijson-doc/zh)(如果和本文档有出入,以本文档为准,例如 key? 已废弃,全面用 key~ 替代) * ### [1.示例](#1) * ### [2.对比传统方式](#2) From cb72eda29cf1cfb6566c2513af9ad75e9fd4ebaf Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Mon, 14 Dec 2020 15:46:15 +0800 Subject: [PATCH 0094/1181] Update Document.md --- Document.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Document.md b/Document.md index a239f30ef..061e98e42 100644 --- a/Document.md +++ b/Document.md @@ -1,5 +1,5 @@ # APIJSON通用文档 -后端开发者可以先看这个详细的 [图文入门教程](https://vincentcheng.github.io/apijson-doc/zh)(如果和本文档有出入,以本文档为准,例如 key? 已废弃,全面用 key~ 替代) +后端开发者可以先看这个详细的 [图文入门教程](https://vincentcheng.github.io/apijson-doc/zh)(如果和本文档有出入,以本文档为准,例如正则匹配 key? 已废弃,全面用 key~ 替代) * ### [1.示例](#1) * ### [2.对比传统方式](#2) From 183f05a06a7b49b286c354db2c51c63196933488 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Mon, 14 Dec 2020 16:11:11 +0800 Subject: [PATCH 0095/1181] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index e49e6b6f3..d951bcc8a 100644 --- a/README.md +++ b/README.md @@ -32,7 +32,7 @@ This source code is licensed under the Apache License Version 2.0
English  通用文档 视频教程 - 在线体验 + 在线体验

From 2179b56dc5c0e7f68aaee1ad06e6c51fc5c5ef29 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Mon, 14 Dec 2020 17:56:59 +0800 Subject: [PATCH 0096/1181] Update README-English.md --- README-English.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README-English.md b/README-English.md index aee69a6c0..67223f81a 100644 --- a/README-English.md +++ b/README-English.md @@ -33,7 +33,7 @@ This source code is licensed under the Apache License Version 2.0
 中文版   Document   Video  -  Test  +  Test 

From 9bb8e5b4211e3913d13a468d4f59e7204f6881f4 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Fri, 18 Dec 2020 10:38:14 +0800 Subject: [PATCH 0097/1181] Update README.md --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index d951bcc8a..363bcda73 100644 --- a/README.md +++ b/README.md @@ -296,9 +296,9 @@ QQ 技术群: 734652054(新)、607020115(旧) [apijson-framework](https://github.com/APIJSON/apijson-framework) APIJSON 服务端框架,可通过 Maven, Gradle 等远程依赖 -[APIAuto](https://github.com/TommyLemon/APIAuto) 机器学习测试、自动生成代码、自动静态检查、自动生成文档与注释等,做最先进的接口管理工具 +[APIAuto](https://github.com/TommyLemon/APIAuto) 敏捷开发最强大易用的 HTTP 接口工具,机器学习零代码测试、生成代码与静态检查、生成文档与光标悬浮注释 -[UnitAuto](https://github.com/TommyLemon/UnitAuto) 机器学习自动化单元测试平台,零代码、全方位、自动化 测试 方法/函数 的正确性和可用性 +[UnitAuto](https://github.com/TommyLemon/UnitAuto) 机器学习单元测试平台,零代码、全方位、自动化 测试 方法/函数 的正确性和可用性 [apijson-doc](https://github.com/vincentCheng/apijson-doc) APIJSON 官方文档,提供排版清晰、搜索方便的文档内容展示,包括设计规范、图文教程等 From 8766cf8db8099148b158b0f9e430e2738cbabe43 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Fri, 18 Dec 2020 10:54:04 +0800 Subject: [PATCH 0098/1181] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 363bcda73..e2488c867 100644 --- a/README.md +++ b/README.md @@ -290,7 +290,7 @@ QQ 技术群: 734652054(新)、607020115(旧) ### 生态项目 -[APIJSON-Demo](https://github.com/APIJSON/APIJSON-Demo) APIJSON 各种语言、各种框架的使用示例项目 +[APIJSON-Demo](https://github.com/APIJSON/APIJSON-Demo) APIJSON 各种语言、各种框架的使用示例项目及上手文档 [apijson-orm](https://github.com/APIJSON/apijson-orm) APIJSON ORM 库,可通过 Maven, Gradle 等远程依赖 From b2500657ff2f095af5a5adb3546ccf8353978c6d Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Fri, 18 Dec 2020 10:54:57 +0800 Subject: [PATCH 0099/1181] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index e2488c867..c9e0704bc 100644 --- a/README.md +++ b/README.md @@ -290,7 +290,7 @@ QQ 技术群: 734652054(新)、607020115(旧) ### 生态项目 -[APIJSON-Demo](https://github.com/APIJSON/APIJSON-Demo) APIJSON 各种语言、各种框架的使用示例项目及上手文档 +[APIJSON-Demo](https://github.com/APIJSON/APIJSON-Demo) APIJSON 各种语言、各种框架的 使用示例项目、SQL 测试数据、上手文档 等 [apijson-orm](https://github.com/APIJSON/apijson-orm) APIJSON ORM 库,可通过 Maven, Gradle 等远程依赖 From a2f2c07a285995be3af59e47fdcba63b30998066 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Fri, 18 Dec 2020 10:55:24 +0800 Subject: [PATCH 0100/1181] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index c9e0704bc..347b3108c 100644 --- a/README.md +++ b/README.md @@ -290,7 +290,7 @@ QQ 技术群: 734652054(新)、607020115(旧) ### 生态项目 -[APIJSON-Demo](https://github.com/APIJSON/APIJSON-Demo) APIJSON 各种语言、各种框架的 使用示例项目、SQL 测试数据、上手文档 等 +[APIJSON-Demo](https://github.com/APIJSON/APIJSON-Demo) APIJSON 各种语言、各种框架 的 使用示例项目、上手文档、测试数据 SQL 文件 等 [apijson-orm](https://github.com/APIJSON/apijson-orm) APIJSON ORM 库,可通过 Maven, Gradle 等远程依赖 From 2ae7013904eeea416508e5b0ddce348aa1358d82 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Mon, 21 Dec 2020 23:48:12 +0800 Subject: [PATCH 0101/1181] Update Document.md --- Document.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Document.md b/Document.md index 061e98e42..82cbca4dc 100644 --- a/Document.md +++ b/Document.md @@ -370,6 +370,6 @@ DELETE:
删除数据 | base_url/delete/ | {
   TableName:{< 比较运算 | >, <, >=, <= 比较运算符,用于
① 提供 "id{}":"<=90000" 这种条件范围的简化写法

② 实现子查询相关比较运算

不支持 "key=":Object 和 "key!=":Object 这两种写法,直接用更简单的 "key":Object 和 "key!":Object 替代。 | ① ["id<=":90000](http://apijson.cn:8080/get/{"[]":{"User":{"id<=":90000}}}),对应SQL是`id<=90000`,查询符合id<=90000的一个User数组

② ["id>@":{
   "from":"Comment",
   "Comment":{
      "@column":"min(userId)"
   }
}](http://apijson.cn:8080/get/{"User":{"id>@":{"from":"Comment","Comment":{"@column":"min(userId)"}}}})
WHERE id>(SELECT min(userId) FROM Comment) 逻辑运算 | &, \|, ! 逻辑运算符,对应数据库 SQL 中的 AND, OR, NOT。
横或纵与:同一键值对的值内条件默认 \| 或连接,不同键值对的条件默认 & 与连接。

① & 可用于"key&{}":"条件"等

② \| 可用于"key\|{}":"条件", "key\|{}":[]等,一般可省略

③ ! 可单独使用,如"key!":Object,也可像&,\|一样配合其他功能符使用
"key!":null 无效,null 值会导致整个键值对被忽略解析,可以用 "key{}":"!=null" 替代,
"key":null 同理,用 "key{}":"=null" 替代。 | ① ["id&{}":">80000,<=90000"](http://apijson.cn:8080/head/{"User":{"id&{}":">80000,<=90000"}}),对应SQL是`id>80000 AND id<=90000`,即id满足id>80000 & id<=90000

② ["id\|{}":">90000,<=80000"](http://apijson.cn:8080/head/{"User":{"id\|{}":">90000,<=80000"}}),同"id{}":">90000,<=80000",对应SQL是`id>80000 OR id<=90000`,即id满足id>90000 \| id<=80000

③ ["id!{}":[82001,38710]](http://apijson.cn:8080/head/{"User":{"id!{}":[82001,38710]}}),对应SQL是`id NOT IN(82001,38710)`,即id满足 ! (id=82001 \| id=38710),可过滤黑名单的消息 数组关键词,可自定义 | "key":Object,key为 "[]":{} 中{}内的关键词,Object的类型由key指定

① "count":Integer,查询数量,0 表示最大值,默认最大值为100

② "page":Integer,查询页码,从0开始,默认最大值为100,一般和count一起用

③ "query":Integer,查询内容
0-对象,1-总数和分页详情,2-以上全部
总数关键词为 total,分页详情关键词为 info,
它们都和 query 同级,通过引用赋值得到,例如
"total@":"/[]/total", "info@":"/[]/info"
这里query及total仅为GET类型的请求提供方便,
一般可直接用HEAD类型的请求获取总数

④ "join":"&/Table0/key0@,\多表连接方式:
"\<" - LEFT JOIN
">" - RIGHT JOIN
"&" - INNER JOIN
"\|" - FULL JOIN
"!" - OUTTER JOIN
"@" - APP JOIN
其中 @ APP JOIN 为应用层连表,会从已查出的主表里取得所有副表 key@ 关联的主表内的 refKey 作为一个数组 refKeys: [value0, value1...],然后把原来副表 count 次查询 key=$refKey 的 SQL 用 key IN($refKeys) 的方式合并为一条 SQL 来优化性能;
其它 JOIN 都是 SQL JOIN,具体功能和 MySQL,PostgreSQL 等数据库的 JOIN 一一对应
`"join":"`"MainTable":{},`
`"ViceTable":{"key@":"/MainTable/refKey"}`
会对应生成
`MainTable LEFT JOIN ViceTable`
`ON ViceTable.key=MainTable.refKey`

⑤ "otherKey":Object,自定义关键词,名称和以上系统关键词不一样,且原样返回上传的值 | ① 查询User数组,最多5个:
["count":5](http://apijson.cn:8080/get/{"[]":{"count":5,"User":{}}})

② 查询第3页的User数组,每页5个:
["count":5,
"page":3](http://apijson.cn:8080/get/{"[]":{"count":5,"page":3,"User":{}}})

③ 查询User数组和对应的User总数:
["[]":{
   "query":2,
   "User":{}
},
"total@":"/[]/total",
"info@":"/[]/info"](http://apijson.cn:8080/get/{"[]":{"query":2,"count":5,"User":{}},"total@":"%252F[]%252Ftotal","info@":"%252F[]%252Finfo"})
返回的数据中,总数及分页详情结构为:
"total":139, //总数
"info":{ //分页详情
   "total":139, //总数
   "count":5, //每页数量
   "page":0, //当前页码
   "max":27, //最大页码
   "more":true, //是否还有更多
   "first":true, //是否为首页
   "last":false //是否为尾页
}

④ Moment INNER JOIN User LEFT JOIN Comment:
["[]":{
   "join":"&/User/id@,\    "Moment":{},
   "User":{
     "name~":"t",
     "id@":"/Moment/userId"
   },
   "Comment":{
     "momentId@":"/Moment/id"
   }
}](http://apijson.cn:8080/get/{"[]":{"count":5,"join":"&%252FUser%252Fid@,\<%252FComment%252FmomentId@","Moment":{"@column":"id,userId,content"},"User":{"name~":"t","id@":"%252FMoment%252FuserId","@column":"id,name,head"},"Comment":{"momentId@":"%252FMoment%252Fid","@column":"id,momentId,content"}}})

⑤ 每一层都加当前用户名:
["User":{},
"[]":{
   "name@":"User/name", //自定义关键词
   "Moment":{}
}](http://apijson.cn:8080/get/{"User":{},"[]":{"name@":"User%252Fname","Moment":{}}}) - 对象关键词,可自定义 | "@key":Object,@key为 Table:{} 中{}内的关键词,Object的类型由@key指定

① "@combine":"&key0,&key1,\|key2,key3,
!key4,!key5,&key6,key7...",条件组合方式,\| 可省略。会自动把同类的合并,外层按照 & \| ! 顺序,内层的按传参顺序组合成
(key0 & key1 & key6 & 其它key) & (key2 \| key3 \| key7) & !(key4 \| key5)
这种连接方式,其中 "其它key" 是指与 @combine 在同一对象,且未被它声明的条件 key,默认都是 & 连接

② "@column":"column;function(arg)...",返回字段

③ "@order":"column0+,column1-...",排序方式

④ "@group":"column0,column1...",分组方式。如果@column里声明了Table的id,则id也必须在@group中声明;其它情况下必须满足至少一个条件:
1.分组的key在@column里声明
2.Table主键在@group中声明

⑤ "@having":"function0(...)?value0;function1(...)?value1;function2(...)?value2...",SQL函数条件,一般和@group一起用,函数一般在@column里声明

⑥ "@schema":"sys",集合空间(模式),非默认的值可通过它来指定,可以在最外层作为全局默认配置

⑦ "@database":"POSTGRESQL",跨数据库,非默认的值可通过它来指定,可以在最外层作为全局默认配置

⑧ "@json":"key0,key1...",转为 JSON 格式返回,符合 JSONObject 则转为 {...},符合 JSONArray 则转为 \[...]

⑨ "@role":"OWNER",来访角色,包括
UNKNOWN,LOGIN,CONTACT,CIRCLE,OWNER,ADMIN,
可以在最外层作为全局默认配置,
可自定义其它角色并重写 Verifier.verify 等相关方法来自定义校验

⑩ "@explain":true,性能分析,可以在最外层作为全局默认配置

⑪ "@otherKey":Object,自定义关键词,名称和以上系统关键词不一样,且原样返回上传的值 | ① 搜索name或tag任何一个字段包含字符a的User列表:
["name~":"a",
"tag~":"a",
"@combine":"name~,tag~"](http://apijson.cn:8080/get/{"User[]":{"count":10,"User":{"@column":"id,name,tag","name~":"a","tag~":"a","@combine":"name~,tag~"}}})

② 只查询id,sex,name这几列并且请求结果也按照这个顺序:
["@column":"id,sex,name"](http://apijson.cn:8080/get/{"User":{"@column":"id,sex,name","id":38710}})

③ 查询按 name降序、id默认顺序 排序的User数组:
["@order":"name-,id"](http://apijson.cn:8080/get/{"[]":{"count":10,"User":{"@column":"name,id","@order":"name-,id"}}})

④ 查询按userId分组的Moment数组:
["@group":"userId,id"](http://apijson.cn:8080/get/{"[]":{"count":10,"Moment":%7B"@column":"userId,id","@group":"userId,id"}}})

⑤ 查询 按userId分组、id最大值>=100 的Moment数组:
["@column":"userId;max(id)",
"@group":"userId",
"@having":"max(id)>=100"](http://apijson.cn:8080/get/{"[]":{"count":10,"Moment":{"@column":"userId%253Bmax(id)","@group":"userId","@having":"max(id)>=100"}}})
还可以指定函数返回名:
["@column":"userId;max(id):maxId",
"@group":"userId",
"@having":"maxId>=100"](http://apijson.cn:8080/get/{"[]":{"count":10,"Moment":{"@column":"userId%253Bmax(id):maxId","@group":"userId","@having":"maxId>=100"}}})

⑥ 查询 sys 内的 User 表:
["@schema":"sys"](http://apijson.cn:8080/get/{"User":{"@schema":"sys"}})

⑦ 查询 PostgreSQL 数据库的 User 表:
["@database":"POSTGRESQL"](http://apijson.cn:8080/get/{"User":{"@database":"POSTGRESQL","@explain":true}})

⑧ 将 VARCHAR 字符串字段 get 转为 JSONArray 返回:
["@json":"get"](http://apijson.cn:8080/get/{"Access":{"@json":"get"}})

⑨ 查询当前用户的动态:
["@role":"OWNER"](http://apijson.cn:8080/get/{"[]":{"Moment":{"@role":"OWNER"}}})

⑩ 开启性能分析:
["@explain":true](http://apijson.cn:8080/get/{"[]":{"Moment":{"@explain":true}}})

⑪ 从pictureList获取第0张图片:
["@position":0, //自定义关键词
"firstPicture()":"getFromArray(pictureList,@position)"](http://apijson.cn:8080/get/{"User":{"id":38710,"@position":0,"firstPicture()":"getFromArray(pictureList,@position)"}}) + 对象关键词,可自定义 | "@key":Object,@key为 Table:{} 中{}内的关键词,Object的类型由@key指定

① "@combine":"&key0,&key1,\|key2,key3,
!key4,!key5,&key6,key7...",条件组合方式,\| 可省略。会自动把同类的合并,外层按照 & \| ! 顺序,内层的按传参顺序组合成
(key0 & key1 & key6 & 其它key) & (key2 \| key3 \| key7) & !(key4 \| key5)
这种连接方式,其中 "其它key" 是指与 @combine 在同一对象,且未被它声明的条件 key,默认都是 & 连接

② "@column":"column;function(arg)...",返回字段

③ "@order":"column0+,column1-...",排序方式

④ "@group":"column0,column1...",分组方式。如果@column里声明了Table的id,则id也必须在@group中声明;其它情况下必须满足至少一个条件:
1.分组的key在@column里声明
2.Table主键在@group中声明

⑤ "@having":"function0(...)?value0;function1(...)?value1;function2(...)?value2...",SQL函数条件,一般和@group一起用,函数一般在@column里声明

⑥ "@schema":"sys",集合空间(模式),非默认的值可通过它来指定,可以在最外层作为全局默认配置

⑦ "@database":"POSTGRESQL",跨数据库,非默认的值可通过它来指定,可以在最外层作为全局默认配置

⑧ "@json":"key0,key1...",转为 JSON 格式返回,符合 JSONObject 则转为 {...},符合 JSONArray 则转为 \[...]

⑨ "@role":"OWNER",来访角色,包括
UNKNOWN,LOGIN,CONTACT,CIRCLE,OWNER,ADMIN,
可以在最外层作为全局默认配置,
可自定义其它角色并重写 Verifier.verify 等相关方法来自定义校验

⑩ "@explain":true,性能分析,可以在最外层作为全局默认配置

⑪ "@otherKey":Object,自定义关键词,名称和以上系统关键词不一样,且原样返回上传的值 | ① 搜索name或tag任何一个字段包含字符a的User列表:
["name~":"a",
"tag~":"a",
"@combine":"name~,tag~"](http://apijson.cn:8080/get/{"User[]":{"count":10,"User":{"@column":"id,name,tag","name~":"a","tag~":"a","@combine":"name~,tag~"}}})
对应SQL是`name REGEXP 'a' OR tag REGEXP 'a'`

② 只查询id,sex,name这几列并且请求结果也按照这个顺序:
["@column":"id,sex,name"](http://apijson.cn:8080/get/{"User":{"@column":"id,sex,name","id":38710}})
对应SQL是`SELECT id,sex,name`

③ 查询按 name降序、id默认顺序 排序的User数组:
["@order":"name-,id"](http://apijson.cn:8080/get/{"[]":{"count":10,"User":{"@column":"name,id","@order":"name-,id"}}})
对应SQL是`ORDER BY name DESC,id`

④ 查询按userId分组的Moment数组:
["@group":"userId,id"](http://apijson.cn:8080/get/{"[]":{"count":10,"Moment":%7B"@column":"userId,id","@group":"userId,id"}}})
对应SQL是`GROUP BY userId,id`

⑤ 查询 按userId分组、id最大值>=100 的Moment数组:
["@column":"userId;max(id)",
"@group":"userId",
"@having":"max(id)>=100"](http://apijson.cn:8080/get/{"[]":{"count":10,"Moment":{"@column":"userId%253Bmax(id)","@group":"userId","@having":"max(id)>=100"}}})
对应SQL是`SELECT userId,max(id) GROUP BY userId HAVING max(id)>=100`
还可以指定函数返回名:
["@column":"userId;max(id):maxId",
"@group":"userId",
"@having":"maxId>=100"](http://apijson.cn:8080/get/{"[]":{"count":10,"Moment":{"@column":"userId%253Bmax(id):maxId","@group":"userId","@having":"maxId>=100"}}})
对应SQL是`SELECT userId,max(id) AS maxId GROUP BY userId HAVING maxId>=100`

⑥ 查询 sys 内的 User 表:
["@schema":"sys"](http://apijson.cn:8080/get/{"User":{"@schema":"sys"}})
对应SQL是`FROM sys.User`

⑦ 查询 PostgreSQL 数据库的 User 表:
["@database":"POSTGRESQL"](http://apijson.cn:8080/get/{"User":{"@database":"POSTGRESQL","@explain":true}})

⑧ 将 VARCHAR 字符串字段 get 转为 JSONArray 返回:
["@json":"get"](http://apijson.cn:8080/get/{"Access":{"@json":"get"}})

⑨ 查询当前用户的动态:
["@role":"OWNER"](http://apijson.cn:8080/get/{"[]":{"Moment":{"@role":"OWNER"}}})

⑩ 开启性能分析:
["@explain":true](http://apijson.cn:8080/get/{"[]":{"Moment":{"@explain":true}}})

⑪ 从pictureList获取第0张图片:
["@position":0, //自定义关键词
"firstPicture()":"getFromArray(pictureList,@position)"](http://apijson.cn:8080/get/{"User":{"id":38710,"@position":0,"firstPicture()":"getFromArray(pictureList,@position)"}})
From 4bc35509e72ab2804d3581073d0802e255cec0fa Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Mon, 21 Dec 2020 23:54:37 +0800 Subject: [PATCH 0102/1181] Update Document.md --- Document.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Document.md b/Document.md index 82cbca4dc..1d8b6835e 100644 --- a/Document.md +++ b/Document.md @@ -352,7 +352,7 @@ DELETE:
删除数据 | base_url/delete/ | {
   TableName:{< 功能 | 键值对格式 | 使用示例 ------------ | ------------ | ------------ - 查询数组 | "key[]":{},后面是JSONObject,key可省略。当key和里面的Table名相同时,Table会被提取出来,即 {Table:{Content}} 会被转化为 {Content} | [{"User[]":{"User":{}}}](http://apijson.cn:8080/get/{"User[]":{"count":3,"User":{}}}),查询一个User数组。这里key和Table名都是User,User会被提取出来,即 {"User":{"id", ...}} 会被转化为 {"id", ...} + 查询数组 | "key[]":{},后面是JSONObject,key可省略。当key和里面的Table名相同时,Table会被提取出来,即 {Table:{Content}} 会被转化为 {Content} | [{"User[]":{"User":{}}}](http://apijson.cn:8080/get/{"User[]":{"count":3,"User":{}}}),查询一个User数组。这里key和Table名都是User,User会被提取出来,即 {"User":{"id", ...}} 会被转化为 {"id", ...},如果要进一步提取User中的id,可以把User[]改为User-id[] 匹配选项范围 | "key{}":[],后面是JSONArray,作为key可取的值的选项 | ["id{}":[38710,82001,70793]](http://apijson.cn:8080/get/{"User[]":{"count":3,"User":{"id{}":[38710,82001,70793]}}}),对应SQL是`id IN(38710,82001,70793)`,查询id符合38710,82001,70793中任意一个的一个User数组 匹配条件范围 | "key{}":"条件0,条件1...",条件为SQL表达式字符串,可进行数字比较运算等 | ["id{}":"<=80000,\>90000"](http://apijson.cn:8080/get/{"User[]":{"count":3,"User":{"id{}":"<=80000,\>90000"}}}),对应SQL是`id<=80000 OR id>90000`,查询id符合id\<=80000 \| id>90000的一个User数组 包含选项范围 | "key<\>":Object => "key<\>":[Object],key对应值的类型必须为JSONArray,Object类型不能为JSON | ["contactIdList<\>":38710](http://apijson.cn:8080/get/{"User[]":{"count":3,"User":{"contactIdList<\>":38710}}}),对应SQL是`json_contains(contactIdList,38710)`,查询contactIdList包含38710的一个User数组 @@ -369,7 +369,7 @@ DELETE:
删除数据 | base_url/delete/ | {
   TableName:{< 减少 或 去除 | "key-":Object,与"key+"相反 | "balance-":100.00,对应SQL是`balance = balance - 100.00`,余额减少100.00,即花费了100元 比较运算 | >, <, >=, <= 比较运算符,用于
① 提供 "id{}":"<=90000" 这种条件范围的简化写法

② 实现子查询相关比较运算

不支持 "key=":Object 和 "key!=":Object 这两种写法,直接用更简单的 "key":Object 和 "key!":Object 替代。 | ① ["id<=":90000](http://apijson.cn:8080/get/{"[]":{"User":{"id<=":90000}}}),对应SQL是`id<=90000`,查询符合id<=90000的一个User数组

② ["id>@":{
   "from":"Comment",
   "Comment":{
      "@column":"min(userId)"
   }
}](http://apijson.cn:8080/get/{"User":{"id>@":{"from":"Comment","Comment":{"@column":"min(userId)"}}}})
WHERE id>(SELECT min(userId) FROM Comment) 逻辑运算 | &, \|, ! 逻辑运算符,对应数据库 SQL 中的 AND, OR, NOT。
横或纵与:同一键值对的值内条件默认 \| 或连接,不同键值对的条件默认 & 与连接。

① & 可用于"key&{}":"条件"等

② \| 可用于"key\|{}":"条件", "key\|{}":[]等,一般可省略

③ ! 可单独使用,如"key!":Object,也可像&,\|一样配合其他功能符使用
"key!":null 无效,null 值会导致整个键值对被忽略解析,可以用 "key{}":"!=null" 替代,
"key":null 同理,用 "key{}":"=null" 替代。 | ① ["id&{}":">80000,<=90000"](http://apijson.cn:8080/head/{"User":{"id&{}":">80000,<=90000"}}),对应SQL是`id>80000 AND id<=90000`,即id满足id>80000 & id<=90000

② ["id\|{}":">90000,<=80000"](http://apijson.cn:8080/head/{"User":{"id\|{}":">90000,<=80000"}}),同"id{}":">90000,<=80000",对应SQL是`id>80000 OR id<=90000`,即id满足id>90000 \| id<=80000

③ ["id!{}":[82001,38710]](http://apijson.cn:8080/head/{"User":{"id!{}":[82001,38710]}}),对应SQL是`id NOT IN(82001,38710)`,即id满足 ! (id=82001 \| id=38710),可过滤黑名单的消息 - 数组关键词,可自定义 | "key":Object,key为 "[]":{} 中{}内的关键词,Object的类型由key指定

① "count":Integer,查询数量,0 表示最大值,默认最大值为100

② "page":Integer,查询页码,从0开始,默认最大值为100,一般和count一起用

③ "query":Integer,查询内容
0-对象,1-总数和分页详情,2-以上全部
总数关键词为 total,分页详情关键词为 info,
它们都和 query 同级,通过引用赋值得到,例如
"total@":"/[]/total", "info@":"/[]/info"
这里query及total仅为GET类型的请求提供方便,
一般可直接用HEAD类型的请求获取总数

④ "join":"&/Table0/key0@,\多表连接方式:
"\<" - LEFT JOIN
">" - RIGHT JOIN
"&" - INNER JOIN
"\|" - FULL JOIN
"!" - OUTTER JOIN
"@" - APP JOIN
其中 @ APP JOIN 为应用层连表,会从已查出的主表里取得所有副表 key@ 关联的主表内的 refKey 作为一个数组 refKeys: [value0, value1...],然后把原来副表 count 次查询 key=$refKey 的 SQL 用 key IN($refKeys) 的方式合并为一条 SQL 来优化性能;
其它 JOIN 都是 SQL JOIN,具体功能和 MySQL,PostgreSQL 等数据库的 JOIN 一一对应
`"join":"`"MainTable":{},`
`"ViceTable":{"key@":"/MainTable/refKey"}`
会对应生成
`MainTable LEFT JOIN ViceTable`
`ON ViceTable.key=MainTable.refKey`

⑤ "otherKey":Object,自定义关键词,名称和以上系统关键词不一样,且原样返回上传的值 | ① 查询User数组,最多5个:
["count":5](http://apijson.cn:8080/get/{"[]":{"count":5,"User":{}}})

② 查询第3页的User数组,每页5个:
["count":5,
"page":3](http://apijson.cn:8080/get/{"[]":{"count":5,"page":3,"User":{}}})

③ 查询User数组和对应的User总数:
["[]":{
   "query":2,
   "User":{}
},
"total@":"/[]/total",
"info@":"/[]/info"](http://apijson.cn:8080/get/{"[]":{"query":2,"count":5,"User":{}},"total@":"%252F[]%252Ftotal","info@":"%252F[]%252Finfo"})
返回的数据中,总数及分页详情结构为:
"total":139, //总数
"info":{ //分页详情
   "total":139, //总数
   "count":5, //每页数量
   "page":0, //当前页码
   "max":27, //最大页码
   "more":true, //是否还有更多
   "first":true, //是否为首页
   "last":false //是否为尾页
}

④ Moment INNER JOIN User LEFT JOIN Comment:
["[]":{
   "join":"&/User/id@,\    "Moment":{},
   "User":{
     "name~":"t",
     "id@":"/Moment/userId"
   },
   "Comment":{
     "momentId@":"/Moment/id"
   }
}](http://apijson.cn:8080/get/{"[]":{"count":5,"join":"&%252FUser%252Fid@,\<%252FComment%252FmomentId@","Moment":{"@column":"id,userId,content"},"User":{"name~":"t","id@":"%252FMoment%252FuserId","@column":"id,name,head"},"Comment":{"momentId@":"%252FMoment%252Fid","@column":"id,momentId,content"}}})

⑤ 每一层都加当前用户名:
["User":{},
"[]":{
   "name@":"User/name", //自定义关键词
   "Moment":{}
}](http://apijson.cn:8080/get/{"User":{},"[]":{"name@":"User%252Fname","Moment":{}}}) + 数组关键词,可自定义 | "key":Object,key为 "[]":{} 中{}内的关键词,Object的类型由key指定

① "count":Integer,查询数量,0 表示最大值,默认最大值为100

② "page":Integer,查询页码,从0开始,默认最大值为100,一般和count一起用

③ "query":Integer,查询内容
0-对象,1-总数和分页详情,2-以上全部
总数关键词为 total,分页详情关键词为 info,
它们都和 query 同级,通过引用赋值得到,例如
"total@":"/[]/total", "info@":"/[]/info"
这里query及total仅为GET类型的请求提供方便,
一般可直接用HEAD类型的请求获取总数

④ "join":"&/Table0/key0@,\多表连接方式:
"\<" - LEFT JOIN
">" - RIGHT JOIN
"&" - INNER JOIN
"\|" - FULL JOIN
"!" - OUTTER JOIN
"@" - APP JOIN
其中 @ APP JOIN 为应用层连表,会从已查出的主表里取得所有副表 key@ 关联的主表内的 refKey 作为一个数组 refKeys: [value0, value1...],然后把原来副表 count 次查询 key=$refKey 的 SQL 用 key IN($refKeys) 的方式合并为一条 SQL 来优化性能;
其它 JOIN 都是 SQL JOIN,具体功能和 MySQL,PostgreSQL 等数据库的 JOIN 一一对应
`"join":"`"MainTable":{},`
`"ViceTable":{"key@":"/MainTable/refKey"}`
会对应生成
`MainTable LEFT JOIN ViceTable`
`ON ViceTable.key=MainTable.refKey`

⑤ "otherKey":Object,自定义关键词,名称和以上系统关键词不一样,且原样返回上传的值 | ① 查询User数组,最多5个:
["count":5](http://apijson.cn:8080/get/{"[]":{"count":5,"User":{}}})
对应SQL是`LIMIT 5`

② 查询第3页的User数组,每页5个:
["count":5,
"page":3](http://apijson.cn:8080/get/{"[]":{"count":5,"page":3,"User":{}}})
对应SQL是`LIMIT 5 OFFSET 15`

③ 查询User数组和对应的User总数:
["[]":{
   "query":2,
   "User":{}
},
"total@":"/[]/total",
"info@":"/[]/info"](http://apijson.cn:8080/get/{"[]":{"query":2,"count":5,"User":{}},"total@":"%252F[]%252Ftotal","info@":"%252F[]%252Finfo"})
返回的数据中,总数及分页详情结构为:
"total":139, //总数
"info":{ //分页详情
   "total":139, //总数
   "count":5, //每页数量
   "page":0, //当前页码
   "max":27, //最大页码
   "more":true, //是否还有更多
   "first":true, //是否为首页
   "last":false //是否为尾页
}

④ Moment INNER JOIN User LEFT JOIN Comment:
["[]":{
   "join":"&/User/id@,\    "Moment":{},
   "User":{
     "name~":"t",
     "id@":"/Moment/userId"
   },
   "Comment":{
     "momentId@":"/Moment/id"
   }
}](http://apijson.cn:8080/get/{"[]":{"count":5,"join":"&%252FUser%252Fid@,\<%252FComment%252FmomentId@","Moment":{"@column":"id,userId,content"},"User":{"name~":"t","id@":"%252FMoment%252FuserId","@column":"id,name,head"},"Comment":{"momentId@":"%252FMoment%252Fid","@column":"id,momentId,content"}}})

⑤ 每一层都加当前用户名:
["User":{},
"[]":{
   "name@":"User/name", //自定义关键词
   "Moment":{}
}](http://apijson.cn:8080/get/{"User":{},"[]":{"name@":"User%252Fname","Moment":{}}}) 对象关键词,可自定义 | "@key":Object,@key为 Table:{} 中{}内的关键词,Object的类型由@key指定

① "@combine":"&key0,&key1,\|key2,key3,
!key4,!key5,&key6,key7...",条件组合方式,\| 可省略。会自动把同类的合并,外层按照 & \| ! 顺序,内层的按传参顺序组合成
(key0 & key1 & key6 & 其它key) & (key2 \| key3 \| key7) & !(key4 \| key5)
这种连接方式,其中 "其它key" 是指与 @combine 在同一对象,且未被它声明的条件 key,默认都是 & 连接

② "@column":"column;function(arg)...",返回字段

③ "@order":"column0+,column1-...",排序方式

④ "@group":"column0,column1...",分组方式。如果@column里声明了Table的id,则id也必须在@group中声明;其它情况下必须满足至少一个条件:
1.分组的key在@column里声明
2.Table主键在@group中声明

⑤ "@having":"function0(...)?value0;function1(...)?value1;function2(...)?value2...",SQL函数条件,一般和@group一起用,函数一般在@column里声明

⑥ "@schema":"sys",集合空间(模式),非默认的值可通过它来指定,可以在最外层作为全局默认配置

⑦ "@database":"POSTGRESQL",跨数据库,非默认的值可通过它来指定,可以在最外层作为全局默认配置

⑧ "@json":"key0,key1...",转为 JSON 格式返回,符合 JSONObject 则转为 {...},符合 JSONArray 则转为 \[...]

⑨ "@role":"OWNER",来访角色,包括
UNKNOWN,LOGIN,CONTACT,CIRCLE,OWNER,ADMIN,
可以在最外层作为全局默认配置,
可自定义其它角色并重写 Verifier.verify 等相关方法来自定义校验

⑩ "@explain":true,性能分析,可以在最外层作为全局默认配置

⑪ "@otherKey":Object,自定义关键词,名称和以上系统关键词不一样,且原样返回上传的值 | ① 搜索name或tag任何一个字段包含字符a的User列表:
["name~":"a",
"tag~":"a",
"@combine":"name~,tag~"](http://apijson.cn:8080/get/{"User[]":{"count":10,"User":{"@column":"id,name,tag","name~":"a","tag~":"a","@combine":"name~,tag~"}}})
对应SQL是`name REGEXP 'a' OR tag REGEXP 'a'`

② 只查询id,sex,name这几列并且请求结果也按照这个顺序:
["@column":"id,sex,name"](http://apijson.cn:8080/get/{"User":{"@column":"id,sex,name","id":38710}})
对应SQL是`SELECT id,sex,name`

③ 查询按 name降序、id默认顺序 排序的User数组:
["@order":"name-,id"](http://apijson.cn:8080/get/{"[]":{"count":10,"User":{"@column":"name,id","@order":"name-,id"}}})
对应SQL是`ORDER BY name DESC,id`

④ 查询按userId分组的Moment数组:
["@group":"userId,id"](http://apijson.cn:8080/get/{"[]":{"count":10,"Moment":%7B"@column":"userId,id","@group":"userId,id"}}})
对应SQL是`GROUP BY userId,id`

⑤ 查询 按userId分组、id最大值>=100 的Moment数组:
["@column":"userId;max(id)",
"@group":"userId",
"@having":"max(id)>=100"](http://apijson.cn:8080/get/{"[]":{"count":10,"Moment":{"@column":"userId%253Bmax(id)","@group":"userId","@having":"max(id)>=100"}}})
对应SQL是`SELECT userId,max(id) GROUP BY userId HAVING max(id)>=100`
还可以指定函数返回名:
["@column":"userId;max(id):maxId",
"@group":"userId",
"@having":"maxId>=100"](http://apijson.cn:8080/get/{"[]":{"count":10,"Moment":{"@column":"userId%253Bmax(id):maxId","@group":"userId","@having":"maxId>=100"}}})
对应SQL是`SELECT userId,max(id) AS maxId GROUP BY userId HAVING maxId>=100`

⑥ 查询 sys 内的 User 表:
["@schema":"sys"](http://apijson.cn:8080/get/{"User":{"@schema":"sys"}})
对应SQL是`FROM sys.User`

⑦ 查询 PostgreSQL 数据库的 User 表:
["@database":"POSTGRESQL"](http://apijson.cn:8080/get/{"User":{"@database":"POSTGRESQL","@explain":true}})

⑧ 将 VARCHAR 字符串字段 get 转为 JSONArray 返回:
["@json":"get"](http://apijson.cn:8080/get/{"Access":{"@json":"get"}})

⑨ 查询当前用户的动态:
["@role":"OWNER"](http://apijson.cn:8080/get/{"[]":{"Moment":{"@role":"OWNER"}}})

⑩ 开启性能分析:
["@explain":true](http://apijson.cn:8080/get/{"[]":{"Moment":{"@explain":true}}})

⑪ 从pictureList获取第0张图片:
["@position":0, //自定义关键词
"firstPicture()":"getFromArray(pictureList,@position)"](http://apijson.cn:8080/get/{"User":{"id":38710,"@position":0,"firstPicture()":"getFromArray(pictureList,@position)"}})
From 495733f58f5e55236a5c63007775c81b2c0f3f15 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Tue, 22 Dec 2020 00:15:05 +0800 Subject: [PATCH 0103/1181] Update Document.md --- Document.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Document.md b/Document.md index 1d8b6835e..ddf42266f 100644 --- a/Document.md +++ b/Document.md @@ -370,6 +370,6 @@ DELETE:
删除数据 | base_url/delete/ | {
   TableName:{< 比较运算 | >, <, >=, <= 比较运算符,用于
① 提供 "id{}":"<=90000" 这种条件范围的简化写法

② 实现子查询相关比较运算

不支持 "key=":Object 和 "key!=":Object 这两种写法,直接用更简单的 "key":Object 和 "key!":Object 替代。 | ① ["id<=":90000](http://apijson.cn:8080/get/{"[]":{"User":{"id<=":90000}}}),对应SQL是`id<=90000`,查询符合id<=90000的一个User数组

② ["id>@":{
   "from":"Comment",
   "Comment":{
      "@column":"min(userId)"
   }
}](http://apijson.cn:8080/get/{"User":{"id>@":{"from":"Comment","Comment":{"@column":"min(userId)"}}}})
WHERE id>(SELECT min(userId) FROM Comment) 逻辑运算 | &, \|, ! 逻辑运算符,对应数据库 SQL 中的 AND, OR, NOT。
横或纵与:同一键值对的值内条件默认 \| 或连接,不同键值对的条件默认 & 与连接。

① & 可用于"key&{}":"条件"等

② \| 可用于"key\|{}":"条件", "key\|{}":[]等,一般可省略

③ ! 可单独使用,如"key!":Object,也可像&,\|一样配合其他功能符使用
"key!":null 无效,null 值会导致整个键值对被忽略解析,可以用 "key{}":"!=null" 替代,
"key":null 同理,用 "key{}":"=null" 替代。 | ① ["id&{}":">80000,<=90000"](http://apijson.cn:8080/head/{"User":{"id&{}":">80000,<=90000"}}),对应SQL是`id>80000 AND id<=90000`,即id满足id>80000 & id<=90000

② ["id\|{}":">90000,<=80000"](http://apijson.cn:8080/head/{"User":{"id\|{}":">90000,<=80000"}}),同"id{}":">90000,<=80000",对应SQL是`id>80000 OR id<=90000`,即id满足id>90000 \| id<=80000

③ ["id!{}":[82001,38710]](http://apijson.cn:8080/head/{"User":{"id!{}":[82001,38710]}}),对应SQL是`id NOT IN(82001,38710)`,即id满足 ! (id=82001 \| id=38710),可过滤黑名单的消息 数组关键词,可自定义 | "key":Object,key为 "[]":{} 中{}内的关键词,Object的类型由key指定

① "count":Integer,查询数量,0 表示最大值,默认最大值为100

② "page":Integer,查询页码,从0开始,默认最大值为100,一般和count一起用

③ "query":Integer,查询内容
0-对象,1-总数和分页详情,2-以上全部
总数关键词为 total,分页详情关键词为 info,
它们都和 query 同级,通过引用赋值得到,例如
"total@":"/[]/total", "info@":"/[]/info"
这里query及total仅为GET类型的请求提供方便,
一般可直接用HEAD类型的请求获取总数

④ "join":"&/Table0/key0@,\多表连接方式:
"\<" - LEFT JOIN
">" - RIGHT JOIN
"&" - INNER JOIN
"\|" - FULL JOIN
"!" - OUTTER JOIN
"@" - APP JOIN
其中 @ APP JOIN 为应用层连表,会从已查出的主表里取得所有副表 key@ 关联的主表内的 refKey 作为一个数组 refKeys: [value0, value1...],然后把原来副表 count 次查询 key=$refKey 的 SQL 用 key IN($refKeys) 的方式合并为一条 SQL 来优化性能;
其它 JOIN 都是 SQL JOIN,具体功能和 MySQL,PostgreSQL 等数据库的 JOIN 一一对应
`"join":"`"MainTable":{},`
`"ViceTable":{"key@":"/MainTable/refKey"}`
会对应生成
`MainTable LEFT JOIN ViceTable`
`ON ViceTable.key=MainTable.refKey`

⑤ "otherKey":Object,自定义关键词,名称和以上系统关键词不一样,且原样返回上传的值 | ① 查询User数组,最多5个:
["count":5](http://apijson.cn:8080/get/{"[]":{"count":5,"User":{}}})
对应SQL是`LIMIT 5`

② 查询第3页的User数组,每页5个:
["count":5,
"page":3](http://apijson.cn:8080/get/{"[]":{"count":5,"page":3,"User":{}}})
对应SQL是`LIMIT 5 OFFSET 15`

③ 查询User数组和对应的User总数:
["[]":{
   "query":2,
   "User":{}
},
"total@":"/[]/total",
"info@":"/[]/info"](http://apijson.cn:8080/get/{"[]":{"query":2,"count":5,"User":{}},"total@":"%252F[]%252Ftotal","info@":"%252F[]%252Finfo"})
返回的数据中,总数及分页详情结构为:
"total":139, //总数
"info":{ //分页详情
   "total":139, //总数
   "count":5, //每页数量
   "page":0, //当前页码
   "max":27, //最大页码
   "more":true, //是否还有更多
   "first":true, //是否为首页
   "last":false //是否为尾页
}

④ Moment INNER JOIN User LEFT JOIN Comment:
["[]":{
   "join":"&/User/id@,\    "Moment":{},
   "User":{
     "name~":"t",
     "id@":"/Moment/userId"
   },
   "Comment":{
     "momentId@":"/Moment/id"
   }
}](http://apijson.cn:8080/get/{"[]":{"count":5,"join":"&%252FUser%252Fid@,\<%252FComment%252FmomentId@","Moment":{"@column":"id,userId,content"},"User":{"name~":"t","id@":"%252FMoment%252FuserId","@column":"id,name,head"},"Comment":{"momentId@":"%252FMoment%252Fid","@column":"id,momentId,content"}}})

⑤ 每一层都加当前用户名:
["User":{},
"[]":{
   "name@":"User/name", //自定义关键词
   "Moment":{}
}](http://apijson.cn:8080/get/{"User":{},"[]":{"name@":"User%252Fname","Moment":{}}}) - 对象关键词,可自定义 | "@key":Object,@key为 Table:{} 中{}内的关键词,Object的类型由@key指定

① "@combine":"&key0,&key1,\|key2,key3,
!key4,!key5,&key6,key7...",条件组合方式,\| 可省略。会自动把同类的合并,外层按照 & \| ! 顺序,内层的按传参顺序组合成
(key0 & key1 & key6 & 其它key) & (key2 \| key3 \| key7) & !(key4 \| key5)
这种连接方式,其中 "其它key" 是指与 @combine 在同一对象,且未被它声明的条件 key,默认都是 & 连接

② "@column":"column;function(arg)...",返回字段

③ "@order":"column0+,column1-...",排序方式

④ "@group":"column0,column1...",分组方式。如果@column里声明了Table的id,则id也必须在@group中声明;其它情况下必须满足至少一个条件:
1.分组的key在@column里声明
2.Table主键在@group中声明

⑤ "@having":"function0(...)?value0;function1(...)?value1;function2(...)?value2...",SQL函数条件,一般和@group一起用,函数一般在@column里声明

⑥ "@schema":"sys",集合空间(模式),非默认的值可通过它来指定,可以在最外层作为全局默认配置

⑦ "@database":"POSTGRESQL",跨数据库,非默认的值可通过它来指定,可以在最外层作为全局默认配置

⑧ "@json":"key0,key1...",转为 JSON 格式返回,符合 JSONObject 则转为 {...},符合 JSONArray 则转为 \[...]

⑨ "@role":"OWNER",来访角色,包括
UNKNOWN,LOGIN,CONTACT,CIRCLE,OWNER,ADMIN,
可以在最外层作为全局默认配置,
可自定义其它角色并重写 Verifier.verify 等相关方法来自定义校验

⑩ "@explain":true,性能分析,可以在最外层作为全局默认配置

⑪ "@otherKey":Object,自定义关键词,名称和以上系统关键词不一样,且原样返回上传的值 | ① 搜索name或tag任何一个字段包含字符a的User列表:
["name~":"a",
"tag~":"a",
"@combine":"name~,tag~"](http://apijson.cn:8080/get/{"User[]":{"count":10,"User":{"@column":"id,name,tag","name~":"a","tag~":"a","@combine":"name~,tag~"}}})
对应SQL是`name REGEXP 'a' OR tag REGEXP 'a'`

② 只查询id,sex,name这几列并且请求结果也按照这个顺序:
["@column":"id,sex,name"](http://apijson.cn:8080/get/{"User":{"@column":"id,sex,name","id":38710}})
对应SQL是`SELECT id,sex,name`

③ 查询按 name降序、id默认顺序 排序的User数组:
["@order":"name-,id"](http://apijson.cn:8080/get/{"[]":{"count":10,"User":{"@column":"name,id","@order":"name-,id"}}})
对应SQL是`ORDER BY name DESC,id`

④ 查询按userId分组的Moment数组:
["@group":"userId,id"](http://apijson.cn:8080/get/{"[]":{"count":10,"Moment":%7B"@column":"userId,id","@group":"userId,id"}}})
对应SQL是`GROUP BY userId,id`

⑤ 查询 按userId分组、id最大值>=100 的Moment数组:
["@column":"userId;max(id)",
"@group":"userId",
"@having":"max(id)>=100"](http://apijson.cn:8080/get/{"[]":{"count":10,"Moment":{"@column":"userId%253Bmax(id)","@group":"userId","@having":"max(id)>=100"}}})
对应SQL是`SELECT userId,max(id) GROUP BY userId HAVING max(id)>=100`
还可以指定函数返回名:
["@column":"userId;max(id):maxId",
"@group":"userId",
"@having":"maxId>=100"](http://apijson.cn:8080/get/{"[]":{"count":10,"Moment":{"@column":"userId%253Bmax(id):maxId","@group":"userId","@having":"maxId>=100"}}})
对应SQL是`SELECT userId,max(id) AS maxId GROUP BY userId HAVING maxId>=100`

⑥ 查询 sys 内的 User 表:
["@schema":"sys"](http://apijson.cn:8080/get/{"User":{"@schema":"sys"}})
对应SQL是`FROM sys.User`

⑦ 查询 PostgreSQL 数据库的 User 表:
["@database":"POSTGRESQL"](http://apijson.cn:8080/get/{"User":{"@database":"POSTGRESQL","@explain":true}})

⑧ 将 VARCHAR 字符串字段 get 转为 JSONArray 返回:
["@json":"get"](http://apijson.cn:8080/get/{"Access":{"@json":"get"}})

⑨ 查询当前用户的动态:
["@role":"OWNER"](http://apijson.cn:8080/get/{"[]":{"Moment":{"@role":"OWNER"}}})

⑩ 开启性能分析:
["@explain":true](http://apijson.cn:8080/get/{"[]":{"Moment":{"@explain":true}}})

⑪ 从pictureList获取第0张图片:
["@position":0, //自定义关键词
"firstPicture()":"getFromArray(pictureList,@position)"](http://apijson.cn:8080/get/{"User":{"id":38710,"@position":0,"firstPicture()":"getFromArray(pictureList,@position)"}}) + 对象关键词,可自定义 | "@key":Object,@key为 Table:{} 中{}内的关键词,Object的类型由@key指定

① "@combine":"&key0,&key1,\|key2,key3,
!key4,!key5,&key6,key7...",条件组合方式,\| 可省略。会自动把同类的合并,外层按照 & \| ! 顺序,内层的按传参顺序组合成
(key0 & key1 & key6 & 其它key) & (key2 \| key3 \| key7) & !(key4 \| key5)
这种连接方式,其中 "其它key" 是指与 @combine 在同一对象,且未被它声明的条件 key,默认都是 & 连接

② "@column":"column;function(arg)...",返回字段

③ "@order":"column0+,column1-...",排序方式

④ "@group":"column0,column1...",分组方式。如果@column里声明了Table的id,则id也必须在@group中声明;其它情况下必须满足至少一个条件:
1.分组的key在@column里声明
2.Table主键在@group中声明

⑤ "@having":"function0(...)?value0;function1(...)?value1;function2(...)?value2...",SQL函数条件,一般和@group一起用,函数一般在@column里声明

⑥ "@schema":"sys",集合空间(模式),非默认的值可通过它来指定,可以在最外层作为全局默认配置

⑦ "@database":"POSTGRESQL",跨数据库,非默认的值可通过它来指定,可以在最外层作为全局默认配置

⑧ "@json":"key0,key1...",转为 JSON 格式返回,符合 JSONObject 则转为 {...},符合 JSONArray 则转为 \[...]

⑨ "@role":"OWNER",来访角色,包括
UNKNOWN,LOGIN,CONTACT,CIRCLE,OWNER,ADMIN,
可以在最外层作为全局默认配置,
可自定义其它角色并重写 Verifier.verify 等相关方法来自定义校验

⑩ "@explain":true,性能分析,可以在最外层作为全局默认配置

⑪ "@raw":"SQL片段或SQL片段的别名",自定义原始SQL片段,可扩展嵌套SQL函数等复杂语句,必须是后端已配置的,谨慎使用,其它功能符做不到才考虑

⑫ "@otherKey":Object,自定义关键词,名称和以上系统关键词不一样,且原样返回上传的值 | ① 搜索name或tag任何一个字段包含字符a的User列表:
["name~":"a",
"tag~":"a",
"@combine":"name~,tag~"](http://apijson.cn:8080/get/{"User[]":{"count":10,"User":{"@column":"id,name,tag","name~":"a","tag~":"a","@combine":"name~,tag~"}}})
对应SQL是`name REGEXP 'a' OR tag REGEXP 'a'`

② 只查询id,sex,name这几列并且请求结果也按照这个顺序:
["@column":"id,sex,name"](http://apijson.cn:8080/get/{"User":{"@column":"id,sex,name","id":38710}})
对应SQL是`SELECT id,sex,name`

③ 查询按 name降序、id默认顺序 排序的User数组:
["@order":"name-,id"](http://apijson.cn:8080/get/{"[]":{"count":10,"User":{"@column":"name,id","@order":"name-,id"}}})
对应SQL是`ORDER BY name DESC,id`

④ 查询按userId分组的Moment数组:
["@group":"userId,id"](http://apijson.cn:8080/get/{"[]":{"count":10,"Moment":%7B"@column":"userId,id","@group":"userId,id"}}})
对应SQL是`GROUP BY userId,id`

⑤ 查询 按userId分组、id最大值>=100 的Moment数组:
["@column":"userId;max(id)",
"@group":"userId",
"@having":"max(id)>=100"](http://apijson.cn:8080/get/{"[]":{"count":10,"Moment":{"@column":"userId%253Bmax(id)","@group":"userId","@having":"max(id)>=100"}}})
对应SQL是`SELECT userId,max(id) GROUP BY userId HAVING max(id)>=100`
还可以指定函数返回名:
["@column":"userId;max(id):maxId",
"@group":"userId",
"@having":"maxId>=100"](http://apijson.cn:8080/get/{"[]":{"count":10,"Moment":{"@column":"userId%253Bmax(id):maxId","@group":"userId","@having":"maxId>=100"}}})
对应SQL是`SELECT userId,max(id) AS maxId GROUP BY userId HAVING maxId>=100`

⑥ 查询 sys 内的 User 表:
["@schema":"sys"](http://apijson.cn:8080/get/{"User":{"@schema":"sys"}})
对应SQL是`FROM sys.User`

⑦ 查询 PostgreSQL 数据库的 User 表:
["@database":"POSTGRESQL"](http://apijson.cn:8080/get/{"User":{"@database":"POSTGRESQL","@explain":true}})

⑧ 将 VARCHAR 字符串字段 get 转为 JSONArray 返回:
["@json":"get"](http://apijson.cn:8080/get/{"Access":{"@json":"get"}})

⑨ 查询当前用户的动态:
["@role":"OWNER"](http://apijson.cn:8080/get/{"[]":{"Moment":{"@role":"OWNER"}}})

⑩ 开启性能分析:
["@explain":true](http://apijson.cn:8080/get/{"[]":{"Moment":{"@explain":true}}})

⑪ 统计最近7天偶数数量
["@column":"userId;sum(if(userId%2=0,1,0))",
"@having":"to_days(now())-to_days(\`date\`)<=7"](http://apijson.cn:8080/get/{"[]":{"Comment":{@column":"userId;sum(if(userId%2=0,1,0))","@having":"to_days(now())-to_days(\`date\`)<=7"}}})

⑫ 从pictureList获取第0张图片:
["@position":0, //自定义关键词
"firstPicture()":"getFromArray(pictureList,@position)"](http://apijson.cn:8080/get/{"User":{"id":38710,"@position":0,"firstPicture()":"getFromArray(pictureList,@position)"}})
From 9c67823eefa8fb55f65fee7cfe59593fdb9e874e Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Tue, 22 Dec 2020 00:21:44 +0800 Subject: [PATCH 0104/1181] Update Document.md --- Document.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Document.md b/Document.md index ddf42266f..2872fa9e4 100644 --- a/Document.md +++ b/Document.md @@ -370,6 +370,6 @@ DELETE:
删除数据 | base_url/delete/ | {
   TableName:{< 比较运算 | >, <, >=, <= 比较运算符,用于
① 提供 "id{}":"<=90000" 这种条件范围的简化写法

② 实现子查询相关比较运算

不支持 "key=":Object 和 "key!=":Object 这两种写法,直接用更简单的 "key":Object 和 "key!":Object 替代。 | ① ["id<=":90000](http://apijson.cn:8080/get/{"[]":{"User":{"id<=":90000}}}),对应SQL是`id<=90000`,查询符合id<=90000的一个User数组

② ["id>@":{
   "from":"Comment",
   "Comment":{
      "@column":"min(userId)"
   }
}](http://apijson.cn:8080/get/{"User":{"id>@":{"from":"Comment","Comment":{"@column":"min(userId)"}}}})
WHERE id>(SELECT min(userId) FROM Comment) 逻辑运算 | &, \|, ! 逻辑运算符,对应数据库 SQL 中的 AND, OR, NOT。
横或纵与:同一键值对的值内条件默认 \| 或连接,不同键值对的条件默认 & 与连接。

① & 可用于"key&{}":"条件"等

② \| 可用于"key\|{}":"条件", "key\|{}":[]等,一般可省略

③ ! 可单独使用,如"key!":Object,也可像&,\|一样配合其他功能符使用
"key!":null 无效,null 值会导致整个键值对被忽略解析,可以用 "key{}":"!=null" 替代,
"key":null 同理,用 "key{}":"=null" 替代。 | ① ["id&{}":">80000,<=90000"](http://apijson.cn:8080/head/{"User":{"id&{}":">80000,<=90000"}}),对应SQL是`id>80000 AND id<=90000`,即id满足id>80000 & id<=90000

② ["id\|{}":">90000,<=80000"](http://apijson.cn:8080/head/{"User":{"id\|{}":">90000,<=80000"}}),同"id{}":">90000,<=80000",对应SQL是`id>80000 OR id<=90000`,即id满足id>90000 \| id<=80000

③ ["id!{}":[82001,38710]](http://apijson.cn:8080/head/{"User":{"id!{}":[82001,38710]}}),对应SQL是`id NOT IN(82001,38710)`,即id满足 ! (id=82001 \| id=38710),可过滤黑名单的消息 数组关键词,可自定义 | "key":Object,key为 "[]":{} 中{}内的关键词,Object的类型由key指定

① "count":Integer,查询数量,0 表示最大值,默认最大值为100

② "page":Integer,查询页码,从0开始,默认最大值为100,一般和count一起用

③ "query":Integer,查询内容
0-对象,1-总数和分页详情,2-以上全部
总数关键词为 total,分页详情关键词为 info,
它们都和 query 同级,通过引用赋值得到,例如
"total@":"/[]/total", "info@":"/[]/info"
这里query及total仅为GET类型的请求提供方便,
一般可直接用HEAD类型的请求获取总数

④ "join":"&/Table0/key0@,\多表连接方式:
"\<" - LEFT JOIN
">" - RIGHT JOIN
"&" - INNER JOIN
"\|" - FULL JOIN
"!" - OUTTER JOIN
"@" - APP JOIN
其中 @ APP JOIN 为应用层连表,会从已查出的主表里取得所有副表 key@ 关联的主表内的 refKey 作为一个数组 refKeys: [value0, value1...],然后把原来副表 count 次查询 key=$refKey 的 SQL 用 key IN($refKeys) 的方式合并为一条 SQL 来优化性能;
其它 JOIN 都是 SQL JOIN,具体功能和 MySQL,PostgreSQL 等数据库的 JOIN 一一对应
`"join":"`"MainTable":{},`
`"ViceTable":{"key@":"/MainTable/refKey"}`
会对应生成
`MainTable LEFT JOIN ViceTable`
`ON ViceTable.key=MainTable.refKey`

⑤ "otherKey":Object,自定义关键词,名称和以上系统关键词不一样,且原样返回上传的值 | ① 查询User数组,最多5个:
["count":5](http://apijson.cn:8080/get/{"[]":{"count":5,"User":{}}})
对应SQL是`LIMIT 5`

② 查询第3页的User数组,每页5个:
["count":5,
"page":3](http://apijson.cn:8080/get/{"[]":{"count":5,"page":3,"User":{}}})
对应SQL是`LIMIT 5 OFFSET 15`

③ 查询User数组和对应的User总数:
["[]":{
   "query":2,
   "User":{}
},
"total@":"/[]/total",
"info@":"/[]/info"](http://apijson.cn:8080/get/{"[]":{"query":2,"count":5,"User":{}},"total@":"%252F[]%252Ftotal","info@":"%252F[]%252Finfo"})
返回的数据中,总数及分页详情结构为:
"total":139, //总数
"info":{ //分页详情
   "total":139, //总数
   "count":5, //每页数量
   "page":0, //当前页码
   "max":27, //最大页码
   "more":true, //是否还有更多
   "first":true, //是否为首页
   "last":false //是否为尾页
}

④ Moment INNER JOIN User LEFT JOIN Comment:
["[]":{
   "join":"&/User/id@,\    "Moment":{},
   "User":{
     "name~":"t",
     "id@":"/Moment/userId"
   },
   "Comment":{
     "momentId@":"/Moment/id"
   }
}](http://apijson.cn:8080/get/{"[]":{"count":5,"join":"&%252FUser%252Fid@,\<%252FComment%252FmomentId@","Moment":{"@column":"id,userId,content"},"User":{"name~":"t","id@":"%252FMoment%252FuserId","@column":"id,name,head"},"Comment":{"momentId@":"%252FMoment%252Fid","@column":"id,momentId,content"}}})

⑤ 每一层都加当前用户名:
["User":{},
"[]":{
   "name@":"User/name", //自定义关键词
   "Moment":{}
}](http://apijson.cn:8080/get/{"User":{},"[]":{"name@":"User%252Fname","Moment":{}}}) - 对象关键词,可自定义 | "@key":Object,@key为 Table:{} 中{}内的关键词,Object的类型由@key指定

① "@combine":"&key0,&key1,\|key2,key3,
!key4,!key5,&key6,key7...",条件组合方式,\| 可省略。会自动把同类的合并,外层按照 & \| ! 顺序,内层的按传参顺序组合成
(key0 & key1 & key6 & 其它key) & (key2 \| key3 \| key7) & !(key4 \| key5)
这种连接方式,其中 "其它key" 是指与 @combine 在同一对象,且未被它声明的条件 key,默认都是 & 连接

② "@column":"column;function(arg)...",返回字段

③ "@order":"column0+,column1-...",排序方式

④ "@group":"column0,column1...",分组方式。如果@column里声明了Table的id,则id也必须在@group中声明;其它情况下必须满足至少一个条件:
1.分组的key在@column里声明
2.Table主键在@group中声明

⑤ "@having":"function0(...)?value0;function1(...)?value1;function2(...)?value2...",SQL函数条件,一般和@group一起用,函数一般在@column里声明

⑥ "@schema":"sys",集合空间(模式),非默认的值可通过它来指定,可以在最外层作为全局默认配置

⑦ "@database":"POSTGRESQL",跨数据库,非默认的值可通过它来指定,可以在最外层作为全局默认配置

⑧ "@json":"key0,key1...",转为 JSON 格式返回,符合 JSONObject 则转为 {...},符合 JSONArray 则转为 \[...]

⑨ "@role":"OWNER",来访角色,包括
UNKNOWN,LOGIN,CONTACT,CIRCLE,OWNER,ADMIN,
可以在最外层作为全局默认配置,
可自定义其它角色并重写 Verifier.verify 等相关方法来自定义校验

⑩ "@explain":true,性能分析,可以在最外层作为全局默认配置

⑪ "@raw":"SQL片段或SQL片段的别名",自定义原始SQL片段,可扩展嵌套SQL函数等复杂语句,必须是后端已配置的,谨慎使用,其它功能符做不到才考虑

⑫ "@otherKey":Object,自定义关键词,名称和以上系统关键词不一样,且原样返回上传的值 | ① 搜索name或tag任何一个字段包含字符a的User列表:
["name~":"a",
"tag~":"a",
"@combine":"name~,tag~"](http://apijson.cn:8080/get/{"User[]":{"count":10,"User":{"@column":"id,name,tag","name~":"a","tag~":"a","@combine":"name~,tag~"}}})
对应SQL是`name REGEXP 'a' OR tag REGEXP 'a'`

② 只查询id,sex,name这几列并且请求结果也按照这个顺序:
["@column":"id,sex,name"](http://apijson.cn:8080/get/{"User":{"@column":"id,sex,name","id":38710}})
对应SQL是`SELECT id,sex,name`

③ 查询按 name降序、id默认顺序 排序的User数组:
["@order":"name-,id"](http://apijson.cn:8080/get/{"[]":{"count":10,"User":{"@column":"name,id","@order":"name-,id"}}})
对应SQL是`ORDER BY name DESC,id`

④ 查询按userId分组的Moment数组:
["@group":"userId,id"](http://apijson.cn:8080/get/{"[]":{"count":10,"Moment":%7B"@column":"userId,id","@group":"userId,id"}}})
对应SQL是`GROUP BY userId,id`

⑤ 查询 按userId分组、id最大值>=100 的Moment数组:
["@column":"userId;max(id)",
"@group":"userId",
"@having":"max(id)>=100"](http://apijson.cn:8080/get/{"[]":{"count":10,"Moment":{"@column":"userId%253Bmax(id)","@group":"userId","@having":"max(id)>=100"}}})
对应SQL是`SELECT userId,max(id) GROUP BY userId HAVING max(id)>=100`
还可以指定函数返回名:
["@column":"userId;max(id):maxId",
"@group":"userId",
"@having":"maxId>=100"](http://apijson.cn:8080/get/{"[]":{"count":10,"Moment":{"@column":"userId%253Bmax(id):maxId","@group":"userId","@having":"maxId>=100"}}})
对应SQL是`SELECT userId,max(id) AS maxId GROUP BY userId HAVING maxId>=100`

⑥ 查询 sys 内的 User 表:
["@schema":"sys"](http://apijson.cn:8080/get/{"User":{"@schema":"sys"}})
对应SQL是`FROM sys.User`

⑦ 查询 PostgreSQL 数据库的 User 表:
["@database":"POSTGRESQL"](http://apijson.cn:8080/get/{"User":{"@database":"POSTGRESQL","@explain":true}})

⑧ 将 VARCHAR 字符串字段 get 转为 JSONArray 返回:
["@json":"get"](http://apijson.cn:8080/get/{"Access":{"@json":"get"}})

⑨ 查询当前用户的动态:
["@role":"OWNER"](http://apijson.cn:8080/get/{"[]":{"Moment":{"@role":"OWNER"}}})

⑩ 开启性能分析:
["@explain":true](http://apijson.cn:8080/get/{"[]":{"Moment":{"@explain":true}}})

⑪ 统计最近7天偶数数量
["@column":"userId;sum(if(userId%2=0,1,0))",
"@having":"to_days(now())-to_days(\`date\`)<=7"](http://apijson.cn:8080/get/{"[]":{"Comment":{@column":"userId;sum(if(userId%2=0,1,0))","@having":"to_days(now())-to_days(\`date\`)<=7"}}})

⑫ 从pictureList获取第0张图片:
["@position":0, //自定义关键词
"firstPicture()":"getFromArray(pictureList,@position)"](http://apijson.cn:8080/get/{"User":{"id":38710,"@position":0,"firstPicture()":"getFromArray(pictureList,@position)"}}) + 对象关键词,可自定义 | "@key":Object,@key为 Table:{} 中{}内的关键词,Object的类型由@key指定

① "@combine":"&key0,&key1,\|key2,key3,
!key4,!key5,&key6,key7...",条件组合方式,\| 可省略。会自动把同类的合并,外层按照 & \| ! 顺序,内层的按传参顺序组合成
(key0 & key1 & key6 & 其它key) & (key2 \| key3 \| key7) & !(key4 \| key5)
这种连接方式,其中 "其它key" 是指与 @combine 在同一对象,且未被它声明的条件 key,默认都是 & 连接

② "@column":"column;function(arg)...",返回字段

③ "@order":"column0+,column1-...",排序方式

④ "@group":"column0,column1...",分组方式。如果@column里声明了Table的id,则id也必须在@group中声明;其它情况下必须满足至少一个条件:
1.分组的key在@column里声明
2.Table主键在@group中声明

⑤ "@having":"function0(...)?value0;function1(...)?value1;function2(...)?value2...",SQL函数条件,一般和@group一起用,函数一般在@column里声明

⑥ "@schema":"sys",集合空间(模式),非默认的值可通过它来指定,可以在最外层作为全局默认配置

⑦ "@database":"POSTGRESQL",跨数据库,非默认的值可通过它来指定,可以在最外层作为全局默认配置

⑧ "@json":"key0,key1...",转为 JSON 格式返回,符合 JSONObject 则转为 {...},符合 JSONArray 则转为 \[...]

⑨ "@role":"OWNER",来访角色,包括
UNKNOWN,LOGIN,CONTACT,CIRCLE,OWNER,ADMIN,
可以在最外层作为全局默认配置,
可自定义其它角色并重写 Verifier.verify 等相关方法来自定义校验

⑩ "@explain":true,性能分析,可以在最外层作为全局默认配置

⑪ "@raw":"SQL片段或SQL片段的别名",自定义原始SQL片段,可扩展嵌套SQL函数等复杂语句,必须是后端已配置的,谨慎使用,其它功能符做不到才考虑

⑫ "@otherKey":Object,自定义关键词,名称和以上系统关键词不一样,且原样返回上传的值 | ① 搜索name或tag任何一个字段包含字符a的User列表:
["name~":"a",
"tag~":"a",
"@combine":"name~,tag~"](http://apijson.cn:8080/get/{"User[]":{"count":10,"User":{"@column":"id,name,tag","name~":"a","tag~":"a","@combine":"name~,tag~"}}})
对应SQL是`name REGEXP 'a' OR tag REGEXP 'a'`

② 只查询id,sex,name这几列并且请求结果也按照这个顺序:
["@column":"id,sex,name"](http://apijson.cn:8080/get/{"User":{"@column":"id,sex,name","id":38710}})
对应SQL是`SELECT id,sex,name`

③ 查询按 name降序、id默认顺序 排序的User数组:
["@order":"name-,id"](http://apijson.cn:8080/get/{"[]":{"count":10,"User":{"@column":"name,id","@order":"name-,id"}}})
对应SQL是`ORDER BY name DESC,id`

④ 查询按userId分组的Moment数组:
["@group":"userId,id"](http://apijson.cn:8080/get/{"[]":{"count":10,"Moment":%7B"@column":"userId,id","@group":"userId,id"}}})
对应SQL是`GROUP BY userId,id`

⑤ 查询 按userId分组、id最大值>=100 的Moment数组:
["@column":"userId;max(id)",
"@group":"userId",
"@having":"max(id)>=100"](http://apijson.cn:8080/get/{"[]":{"count":10,"Moment":{"@column":"userId%253Bmax(id)","@group":"userId","@having":"max(id)>=100"}}})
对应SQL是`SELECT userId,max(id) GROUP BY userId HAVING max(id)>=100`
还可以指定函数返回名:
["@column":"userId;max(id):maxId",
"@group":"userId",
"@having":"maxId>=100"](http://apijson.cn:8080/get/{"[]":{"count":10,"Moment":{"@column":"userId%253Bmax(id):maxId","@group":"userId","@having":"maxId>=100"}}})
对应SQL是`SELECT userId,max(id) AS maxId GROUP BY userId HAVING maxId>=100`

⑥ 查询 sys 内的 User 表:
["@schema":"sys"](http://apijson.cn:8080/get/{"User":{"@schema":"sys"}})
对应SQL是`FROM sys.User`

⑦ 查询 PostgreSQL 数据库的 User 表:
["@database":"POSTGRESQL"](http://apijson.cn:8080/get/{"User":{"@database":"POSTGRESQL","@explain":true}})

⑧ 将 VARCHAR 字符串字段 get 转为 JSONArray 返回:
["@json":"get"](http://apijson.cn:8080/get/{"Access":{"@json":"get"}})

⑨ 查询当前用户的动态:
["@role":"OWNER"](http://apijson.cn:8080/get/{"[]":{"Moment":{"@role":"OWNER"}}})

⑩ 开启性能分析:
["@explain":true](http://apijson.cn:8080/get/{"[]":{"Moment":{"@explain":true}}})

⑪ 统计最近7天偶数userId数量
["@column":"userId;sum(if(userId%2=0,1,0))",
"@having":"to_days(now())-to_days(\`date\`)<=7"](http://apijson.cn:8080/get/{"[]":{"Comment":{@column":"sum(if(userId%252=0,1,0))","@having":"to_days(now())-to_days(\`date\`)<=7"}}})

⑫ 从pictureList获取第0张图片:
["@position":0, //自定义关键词
"firstPicture()":"getFromArray(pictureList,@position)"](http://apijson.cn:8080/get/{"User":{"id":38710,"@position":0,"firstPicture()":"getFromArray(pictureList,@position)"}})
From 26cc21958647bbcd1efd3206c3d70926b6bb42c9 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Tue, 22 Dec 2020 00:22:11 +0800 Subject: [PATCH 0105/1181] Update Document.md --- Document.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Document.md b/Document.md index 2872fa9e4..067f83fc9 100644 --- a/Document.md +++ b/Document.md @@ -370,6 +370,6 @@ DELETE:
删除数据 | base_url/delete/ | {
   TableName:{< 比较运算 | >, <, >=, <= 比较运算符,用于
① 提供 "id{}":"<=90000" 这种条件范围的简化写法

② 实现子查询相关比较运算

不支持 "key=":Object 和 "key!=":Object 这两种写法,直接用更简单的 "key":Object 和 "key!":Object 替代。 | ① ["id<=":90000](http://apijson.cn:8080/get/{"[]":{"User":{"id<=":90000}}}),对应SQL是`id<=90000`,查询符合id<=90000的一个User数组

② ["id>@":{
   "from":"Comment",
   "Comment":{
      "@column":"min(userId)"
   }
}](http://apijson.cn:8080/get/{"User":{"id>@":{"from":"Comment","Comment":{"@column":"min(userId)"}}}})
WHERE id>(SELECT min(userId) FROM Comment) 逻辑运算 | &, \|, ! 逻辑运算符,对应数据库 SQL 中的 AND, OR, NOT。
横或纵与:同一键值对的值内条件默认 \| 或连接,不同键值对的条件默认 & 与连接。

① & 可用于"key&{}":"条件"等

② \| 可用于"key\|{}":"条件", "key\|{}":[]等,一般可省略

③ ! 可单独使用,如"key!":Object,也可像&,\|一样配合其他功能符使用
"key!":null 无效,null 值会导致整个键值对被忽略解析,可以用 "key{}":"!=null" 替代,
"key":null 同理,用 "key{}":"=null" 替代。 | ① ["id&{}":">80000,<=90000"](http://apijson.cn:8080/head/{"User":{"id&{}":">80000,<=90000"}}),对应SQL是`id>80000 AND id<=90000`,即id满足id>80000 & id<=90000

② ["id\|{}":">90000,<=80000"](http://apijson.cn:8080/head/{"User":{"id\|{}":">90000,<=80000"}}),同"id{}":">90000,<=80000",对应SQL是`id>80000 OR id<=90000`,即id满足id>90000 \| id<=80000

③ ["id!{}":[82001,38710]](http://apijson.cn:8080/head/{"User":{"id!{}":[82001,38710]}}),对应SQL是`id NOT IN(82001,38710)`,即id满足 ! (id=82001 \| id=38710),可过滤黑名单的消息 数组关键词,可自定义 | "key":Object,key为 "[]":{} 中{}内的关键词,Object的类型由key指定

① "count":Integer,查询数量,0 表示最大值,默认最大值为100

② "page":Integer,查询页码,从0开始,默认最大值为100,一般和count一起用

③ "query":Integer,查询内容
0-对象,1-总数和分页详情,2-以上全部
总数关键词为 total,分页详情关键词为 info,
它们都和 query 同级,通过引用赋值得到,例如
"total@":"/[]/total", "info@":"/[]/info"
这里query及total仅为GET类型的请求提供方便,
一般可直接用HEAD类型的请求获取总数

④ "join":"&/Table0/key0@,\多表连接方式:
"\<" - LEFT JOIN
">" - RIGHT JOIN
"&" - INNER JOIN
"\|" - FULL JOIN
"!" - OUTTER JOIN
"@" - APP JOIN
其中 @ APP JOIN 为应用层连表,会从已查出的主表里取得所有副表 key@ 关联的主表内的 refKey 作为一个数组 refKeys: [value0, value1...],然后把原来副表 count 次查询 key=$refKey 的 SQL 用 key IN($refKeys) 的方式合并为一条 SQL 来优化性能;
其它 JOIN 都是 SQL JOIN,具体功能和 MySQL,PostgreSQL 等数据库的 JOIN 一一对应
`"join":"`"MainTable":{},`
`"ViceTable":{"key@":"/MainTable/refKey"}`
会对应生成
`MainTable LEFT JOIN ViceTable`
`ON ViceTable.key=MainTable.refKey`

⑤ "otherKey":Object,自定义关键词,名称和以上系统关键词不一样,且原样返回上传的值 | ① 查询User数组,最多5个:
["count":5](http://apijson.cn:8080/get/{"[]":{"count":5,"User":{}}})
对应SQL是`LIMIT 5`

② 查询第3页的User数组,每页5个:
["count":5,
"page":3](http://apijson.cn:8080/get/{"[]":{"count":5,"page":3,"User":{}}})
对应SQL是`LIMIT 5 OFFSET 15`

③ 查询User数组和对应的User总数:
["[]":{
   "query":2,
   "User":{}
},
"total@":"/[]/total",
"info@":"/[]/info"](http://apijson.cn:8080/get/{"[]":{"query":2,"count":5,"User":{}},"total@":"%252F[]%252Ftotal","info@":"%252F[]%252Finfo"})
返回的数据中,总数及分页详情结构为:
"total":139, //总数
"info":{ //分页详情
   "total":139, //总数
   "count":5, //每页数量
   "page":0, //当前页码
   "max":27, //最大页码
   "more":true, //是否还有更多
   "first":true, //是否为首页
   "last":false //是否为尾页
}

④ Moment INNER JOIN User LEFT JOIN Comment:
["[]":{
   "join":"&/User/id@,\    "Moment":{},
   "User":{
     "name~":"t",
     "id@":"/Moment/userId"
   },
   "Comment":{
     "momentId@":"/Moment/id"
   }
}](http://apijson.cn:8080/get/{"[]":{"count":5,"join":"&%252FUser%252Fid@,\<%252FComment%252FmomentId@","Moment":{"@column":"id,userId,content"},"User":{"name~":"t","id@":"%252FMoment%252FuserId","@column":"id,name,head"},"Comment":{"momentId@":"%252FMoment%252Fid","@column":"id,momentId,content"}}})

⑤ 每一层都加当前用户名:
["User":{},
"[]":{
   "name@":"User/name", //自定义关键词
   "Moment":{}
}](http://apijson.cn:8080/get/{"User":{},"[]":{"name@":"User%252Fname","Moment":{}}}) - 对象关键词,可自定义 | "@key":Object,@key为 Table:{} 中{}内的关键词,Object的类型由@key指定

① "@combine":"&key0,&key1,\|key2,key3,
!key4,!key5,&key6,key7...",条件组合方式,\| 可省略。会自动把同类的合并,外层按照 & \| ! 顺序,内层的按传参顺序组合成
(key0 & key1 & key6 & 其它key) & (key2 \| key3 \| key7) & !(key4 \| key5)
这种连接方式,其中 "其它key" 是指与 @combine 在同一对象,且未被它声明的条件 key,默认都是 & 连接

② "@column":"column;function(arg)...",返回字段

③ "@order":"column0+,column1-...",排序方式

④ "@group":"column0,column1...",分组方式。如果@column里声明了Table的id,则id也必须在@group中声明;其它情况下必须满足至少一个条件:
1.分组的key在@column里声明
2.Table主键在@group中声明

⑤ "@having":"function0(...)?value0;function1(...)?value1;function2(...)?value2...",SQL函数条件,一般和@group一起用,函数一般在@column里声明

⑥ "@schema":"sys",集合空间(模式),非默认的值可通过它来指定,可以在最外层作为全局默认配置

⑦ "@database":"POSTGRESQL",跨数据库,非默认的值可通过它来指定,可以在最外层作为全局默认配置

⑧ "@json":"key0,key1...",转为 JSON 格式返回,符合 JSONObject 则转为 {...},符合 JSONArray 则转为 \[...]

⑨ "@role":"OWNER",来访角色,包括
UNKNOWN,LOGIN,CONTACT,CIRCLE,OWNER,ADMIN,
可以在最外层作为全局默认配置,
可自定义其它角色并重写 Verifier.verify 等相关方法来自定义校验

⑩ "@explain":true,性能分析,可以在最外层作为全局默认配置

⑪ "@raw":"SQL片段或SQL片段的别名",自定义原始SQL片段,可扩展嵌套SQL函数等复杂语句,必须是后端已配置的,谨慎使用,其它功能符做不到才考虑

⑫ "@otherKey":Object,自定义关键词,名称和以上系统关键词不一样,且原样返回上传的值 | ① 搜索name或tag任何一个字段包含字符a的User列表:
["name~":"a",
"tag~":"a",
"@combine":"name~,tag~"](http://apijson.cn:8080/get/{"User[]":{"count":10,"User":{"@column":"id,name,tag","name~":"a","tag~":"a","@combine":"name~,tag~"}}})
对应SQL是`name REGEXP 'a' OR tag REGEXP 'a'`

② 只查询id,sex,name这几列并且请求结果也按照这个顺序:
["@column":"id,sex,name"](http://apijson.cn:8080/get/{"User":{"@column":"id,sex,name","id":38710}})
对应SQL是`SELECT id,sex,name`

③ 查询按 name降序、id默认顺序 排序的User数组:
["@order":"name-,id"](http://apijson.cn:8080/get/{"[]":{"count":10,"User":{"@column":"name,id","@order":"name-,id"}}})
对应SQL是`ORDER BY name DESC,id`

④ 查询按userId分组的Moment数组:
["@group":"userId,id"](http://apijson.cn:8080/get/{"[]":{"count":10,"Moment":%7B"@column":"userId,id","@group":"userId,id"}}})
对应SQL是`GROUP BY userId,id`

⑤ 查询 按userId分组、id最大值>=100 的Moment数组:
["@column":"userId;max(id)",
"@group":"userId",
"@having":"max(id)>=100"](http://apijson.cn:8080/get/{"[]":{"count":10,"Moment":{"@column":"userId%253Bmax(id)","@group":"userId","@having":"max(id)>=100"}}})
对应SQL是`SELECT userId,max(id) GROUP BY userId HAVING max(id)>=100`
还可以指定函数返回名:
["@column":"userId;max(id):maxId",
"@group":"userId",
"@having":"maxId>=100"](http://apijson.cn:8080/get/{"[]":{"count":10,"Moment":{"@column":"userId%253Bmax(id):maxId","@group":"userId","@having":"maxId>=100"}}})
对应SQL是`SELECT userId,max(id) AS maxId GROUP BY userId HAVING maxId>=100`

⑥ 查询 sys 内的 User 表:
["@schema":"sys"](http://apijson.cn:8080/get/{"User":{"@schema":"sys"}})
对应SQL是`FROM sys.User`

⑦ 查询 PostgreSQL 数据库的 User 表:
["@database":"POSTGRESQL"](http://apijson.cn:8080/get/{"User":{"@database":"POSTGRESQL","@explain":true}})

⑧ 将 VARCHAR 字符串字段 get 转为 JSONArray 返回:
["@json":"get"](http://apijson.cn:8080/get/{"Access":{"@json":"get"}})

⑨ 查询当前用户的动态:
["@role":"OWNER"](http://apijson.cn:8080/get/{"[]":{"Moment":{"@role":"OWNER"}}})

⑩ 开启性能分析:
["@explain":true](http://apijson.cn:8080/get/{"[]":{"Moment":{"@explain":true}}})

⑪ 统计最近7天偶数userId数量
["@column":"userId;sum(if(userId%2=0,1,0))",
"@having":"to_days(now())-to_days(\`date\`)<=7"](http://apijson.cn:8080/get/{"[]":{"Comment":{@column":"sum(if(userId%252=0,1,0))","@having":"to_days(now())-to_days(\`date\`)<=7"}}})

⑫ 从pictureList获取第0张图片:
["@position":0, //自定义关键词
"firstPicture()":"getFromArray(pictureList,@position)"](http://apijson.cn:8080/get/{"User":{"id":38710,"@position":0,"firstPicture()":"getFromArray(pictureList,@position)"}}) + 对象关键词,可自定义 | "@key":Object,@key为 Table:{} 中{}内的关键词,Object的类型由@key指定

① "@combine":"&key0,&key1,\|key2,key3,
!key4,!key5,&key6,key7...",条件组合方式,\| 可省略。会自动把同类的合并,外层按照 & \| ! 顺序,内层的按传参顺序组合成
(key0 & key1 & key6 & 其它key) & (key2 \| key3 \| key7) & !(key4 \| key5)
这种连接方式,其中 "其它key" 是指与 @combine 在同一对象,且未被它声明的条件 key,默认都是 & 连接

② "@column":"column;function(arg)...",返回字段

③ "@order":"column0+,column1-...",排序方式

④ "@group":"column0,column1...",分组方式。如果@column里声明了Table的id,则id也必须在@group中声明;其它情况下必须满足至少一个条件:
1.分组的key在@column里声明
2.Table主键在@group中声明

⑤ "@having":"function0(...)?value0;function1(...)?value1;function2(...)?value2...",SQL函数条件,一般和@group一起用,函数一般在@column里声明

⑥ "@schema":"sys",集合空间(模式),非默认的值可通过它来指定,可以在最外层作为全局默认配置

⑦ "@database":"POSTGRESQL",跨数据库,非默认的值可通过它来指定,可以在最外层作为全局默认配置

⑧ "@json":"key0,key1...",转为 JSON 格式返回,符合 JSONObject 则转为 {...},符合 JSONArray 则转为 \[...]

⑨ "@role":"OWNER",来访角色,包括
UNKNOWN,LOGIN,CONTACT,CIRCLE,OWNER,ADMIN,
可以在最外层作为全局默认配置,
可自定义其它角色并重写 Verifier.verify 等相关方法来自定义校验

⑩ "@explain":true,性能分析,可以在最外层作为全局默认配置

⑪ "@raw":"SQL片段或SQL片段的别名",自定义原始SQL片段,可扩展嵌套SQL函数等复杂语句,必须是后端已配置的,谨慎使用,其它功能符做不到才考虑

⑫ "@otherKey":Object,自定义关键词,名称和以上系统关键词不一样,且原样返回上传的值 | ① 搜索name或tag任何一个字段包含字符a的User列表:
["name~":"a",
"tag~":"a",
"@combine":"name~,tag~"](http://apijson.cn:8080/get/{"User[]":{"count":10,"User":{"@column":"id,name,tag","name~":"a","tag~":"a","@combine":"name~,tag~"}}})
对应SQL是`name REGEXP 'a' OR tag REGEXP 'a'`

② 只查询id,sex,name这几列并且请求结果也按照这个顺序:
["@column":"id,sex,name"](http://apijson.cn:8080/get/{"User":{"@column":"id,sex,name","id":38710}})
对应SQL是`SELECT id,sex,name`

③ 查询按 name降序、id默认顺序 排序的User数组:
["@order":"name-,id"](http://apijson.cn:8080/get/{"[]":{"count":10,"User":{"@column":"name,id","@order":"name-,id"}}})
对应SQL是`ORDER BY name DESC,id`

④ 查询按userId分组的Moment数组:
["@group":"userId,id"](http://apijson.cn:8080/get/{"[]":{"count":10,"Moment":%7B"@column":"userId,id","@group":"userId,id"}}})
对应SQL是`GROUP BY userId,id`

⑤ 查询 按userId分组、id最大值>=100 的Moment数组:
["@column":"userId;max(id)",
"@group":"userId",
"@having":"max(id)>=100"](http://apijson.cn:8080/get/{"[]":{"count":10,"Moment":{"@column":"userId%253Bmax(id)","@group":"userId","@having":"max(id)>=100"}}})
对应SQL是`SELECT userId,max(id) GROUP BY userId HAVING max(id)>=100`
还可以指定函数返回名:
["@column":"userId;max(id):maxId",
"@group":"userId",
"@having":"maxId>=100"](http://apijson.cn:8080/get/{"[]":{"count":10,"Moment":{"@column":"userId%253Bmax(id):maxId","@group":"userId","@having":"maxId>=100"}}})
对应SQL是`SELECT userId,max(id) AS maxId GROUP BY userId HAVING maxId>=100`

⑥ 查询 sys 内的 User 表:
["@schema":"sys"](http://apijson.cn:8080/get/{"User":{"@schema":"sys"}})
对应SQL是`FROM sys.User`

⑦ 查询 PostgreSQL 数据库的 User 表:
["@database":"POSTGRESQL"](http://apijson.cn:8080/get/{"User":{"@database":"POSTGRESQL","@explain":true}})

⑧ 将 VARCHAR 字符串字段 get 转为 JSONArray 返回:
["@json":"get"](http://apijson.cn:8080/get/{"Access":{"@json":"get"}})

⑨ 查询当前用户的动态:
["@role":"OWNER"](http://apijson.cn:8080/get/{"[]":{"Moment":{"@role":"OWNER"}}})

⑩ 开启性能分析:
["@explain":true](http://apijson.cn:8080/get/{"[]":{"Moment":{"@explain":true}}})

⑪ 统计最近7天偶数userId数量
["@column":"sum(if(userId%2=0,1,0))",
"@having":"to_days(now())-to_days(\`date\`)<=7"](http://apijson.cn:8080/get/{"[]":{"Comment":{@column":"sum(if(userId%252=0,1,0))","@having":"to_days(now())-to_days(\`date\`)<=7"}}})

⑫ 从pictureList获取第0张图片:
["@position":0, //自定义关键词
"firstPicture()":"getFromArray(pictureList,@position)"](http://apijson.cn:8080/get/{"User":{"id":38710,"@position":0,"firstPicture()":"getFromArray(pictureList,@position)"}})
From 2a1d06c6b871c2f3ea079f1f00bd32f58d8219eb Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Tue, 22 Dec 2020 00:23:15 +0800 Subject: [PATCH 0106/1181] Update Document.md --- Document.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Document.md b/Document.md index 067f83fc9..923404a05 100644 --- a/Document.md +++ b/Document.md @@ -370,6 +370,6 @@ DELETE:
删除数据 | base_url/delete/ | {
   TableName:{< 比较运算 | >, <, >=, <= 比较运算符,用于
① 提供 "id{}":"<=90000" 这种条件范围的简化写法

② 实现子查询相关比较运算

不支持 "key=":Object 和 "key!=":Object 这两种写法,直接用更简单的 "key":Object 和 "key!":Object 替代。 | ① ["id<=":90000](http://apijson.cn:8080/get/{"[]":{"User":{"id<=":90000}}}),对应SQL是`id<=90000`,查询符合id<=90000的一个User数组

② ["id>@":{
   "from":"Comment",
   "Comment":{
      "@column":"min(userId)"
   }
}](http://apijson.cn:8080/get/{"User":{"id>@":{"from":"Comment","Comment":{"@column":"min(userId)"}}}})
WHERE id>(SELECT min(userId) FROM Comment) 逻辑运算 | &, \|, ! 逻辑运算符,对应数据库 SQL 中的 AND, OR, NOT。
横或纵与:同一键值对的值内条件默认 \| 或连接,不同键值对的条件默认 & 与连接。

① & 可用于"key&{}":"条件"等

② \| 可用于"key\|{}":"条件", "key\|{}":[]等,一般可省略

③ ! 可单独使用,如"key!":Object,也可像&,\|一样配合其他功能符使用
"key!":null 无效,null 值会导致整个键值对被忽略解析,可以用 "key{}":"!=null" 替代,
"key":null 同理,用 "key{}":"=null" 替代。 | ① ["id&{}":">80000,<=90000"](http://apijson.cn:8080/head/{"User":{"id&{}":">80000,<=90000"}}),对应SQL是`id>80000 AND id<=90000`,即id满足id>80000 & id<=90000

② ["id\|{}":">90000,<=80000"](http://apijson.cn:8080/head/{"User":{"id\|{}":">90000,<=80000"}}),同"id{}":">90000,<=80000",对应SQL是`id>80000 OR id<=90000`,即id满足id>90000 \| id<=80000

③ ["id!{}":[82001,38710]](http://apijson.cn:8080/head/{"User":{"id!{}":[82001,38710]}}),对应SQL是`id NOT IN(82001,38710)`,即id满足 ! (id=82001 \| id=38710),可过滤黑名单的消息 数组关键词,可自定义 | "key":Object,key为 "[]":{} 中{}内的关键词,Object的类型由key指定

① "count":Integer,查询数量,0 表示最大值,默认最大值为100

② "page":Integer,查询页码,从0开始,默认最大值为100,一般和count一起用

③ "query":Integer,查询内容
0-对象,1-总数和分页详情,2-以上全部
总数关键词为 total,分页详情关键词为 info,
它们都和 query 同级,通过引用赋值得到,例如
"total@":"/[]/total", "info@":"/[]/info"
这里query及total仅为GET类型的请求提供方便,
一般可直接用HEAD类型的请求获取总数

④ "join":"&/Table0/key0@,\多表连接方式:
"\<" - LEFT JOIN
">" - RIGHT JOIN
"&" - INNER JOIN
"\|" - FULL JOIN
"!" - OUTTER JOIN
"@" - APP JOIN
其中 @ APP JOIN 为应用层连表,会从已查出的主表里取得所有副表 key@ 关联的主表内的 refKey 作为一个数组 refKeys: [value0, value1...],然后把原来副表 count 次查询 key=$refKey 的 SQL 用 key IN($refKeys) 的方式合并为一条 SQL 来优化性能;
其它 JOIN 都是 SQL JOIN,具体功能和 MySQL,PostgreSQL 等数据库的 JOIN 一一对应
`"join":"`"MainTable":{},`
`"ViceTable":{"key@":"/MainTable/refKey"}`
会对应生成
`MainTable LEFT JOIN ViceTable`
`ON ViceTable.key=MainTable.refKey`

⑤ "otherKey":Object,自定义关键词,名称和以上系统关键词不一样,且原样返回上传的值 | ① 查询User数组,最多5个:
["count":5](http://apijson.cn:8080/get/{"[]":{"count":5,"User":{}}})
对应SQL是`LIMIT 5`

② 查询第3页的User数组,每页5个:
["count":5,
"page":3](http://apijson.cn:8080/get/{"[]":{"count":5,"page":3,"User":{}}})
对应SQL是`LIMIT 5 OFFSET 15`

③ 查询User数组和对应的User总数:
["[]":{
   "query":2,
   "User":{}
},
"total@":"/[]/total",
"info@":"/[]/info"](http://apijson.cn:8080/get/{"[]":{"query":2,"count":5,"User":{}},"total@":"%252F[]%252Ftotal","info@":"%252F[]%252Finfo"})
返回的数据中,总数及分页详情结构为:
"total":139, //总数
"info":{ //分页详情
   "total":139, //总数
   "count":5, //每页数量
   "page":0, //当前页码
   "max":27, //最大页码
   "more":true, //是否还有更多
   "first":true, //是否为首页
   "last":false //是否为尾页
}

④ Moment INNER JOIN User LEFT JOIN Comment:
["[]":{
   "join":"&/User/id@,\    "Moment":{},
   "User":{
     "name~":"t",
     "id@":"/Moment/userId"
   },
   "Comment":{
     "momentId@":"/Moment/id"
   }
}](http://apijson.cn:8080/get/{"[]":{"count":5,"join":"&%252FUser%252Fid@,\<%252FComment%252FmomentId@","Moment":{"@column":"id,userId,content"},"User":{"name~":"t","id@":"%252FMoment%252FuserId","@column":"id,name,head"},"Comment":{"momentId@":"%252FMoment%252Fid","@column":"id,momentId,content"}}})

⑤ 每一层都加当前用户名:
["User":{},
"[]":{
   "name@":"User/name", //自定义关键词
   "Moment":{}
}](http://apijson.cn:8080/get/{"User":{},"[]":{"name@":"User%252Fname","Moment":{}}}) - 对象关键词,可自定义 | "@key":Object,@key为 Table:{} 中{}内的关键词,Object的类型由@key指定

① "@combine":"&key0,&key1,\|key2,key3,
!key4,!key5,&key6,key7...",条件组合方式,\| 可省略。会自动把同类的合并,外层按照 & \| ! 顺序,内层的按传参顺序组合成
(key0 & key1 & key6 & 其它key) & (key2 \| key3 \| key7) & !(key4 \| key5)
这种连接方式,其中 "其它key" 是指与 @combine 在同一对象,且未被它声明的条件 key,默认都是 & 连接

② "@column":"column;function(arg)...",返回字段

③ "@order":"column0+,column1-...",排序方式

④ "@group":"column0,column1...",分组方式。如果@column里声明了Table的id,则id也必须在@group中声明;其它情况下必须满足至少一个条件:
1.分组的key在@column里声明
2.Table主键在@group中声明

⑤ "@having":"function0(...)?value0;function1(...)?value1;function2(...)?value2...",SQL函数条件,一般和@group一起用,函数一般在@column里声明

⑥ "@schema":"sys",集合空间(模式),非默认的值可通过它来指定,可以在最外层作为全局默认配置

⑦ "@database":"POSTGRESQL",跨数据库,非默认的值可通过它来指定,可以在最外层作为全局默认配置

⑧ "@json":"key0,key1...",转为 JSON 格式返回,符合 JSONObject 则转为 {...},符合 JSONArray 则转为 \[...]

⑨ "@role":"OWNER",来访角色,包括
UNKNOWN,LOGIN,CONTACT,CIRCLE,OWNER,ADMIN,
可以在最外层作为全局默认配置,
可自定义其它角色并重写 Verifier.verify 等相关方法来自定义校验

⑩ "@explain":true,性能分析,可以在最外层作为全局默认配置

⑪ "@raw":"SQL片段或SQL片段的别名",自定义原始SQL片段,可扩展嵌套SQL函数等复杂语句,必须是后端已配置的,谨慎使用,其它功能符做不到才考虑

⑫ "@otherKey":Object,自定义关键词,名称和以上系统关键词不一样,且原样返回上传的值 | ① 搜索name或tag任何一个字段包含字符a的User列表:
["name~":"a",
"tag~":"a",
"@combine":"name~,tag~"](http://apijson.cn:8080/get/{"User[]":{"count":10,"User":{"@column":"id,name,tag","name~":"a","tag~":"a","@combine":"name~,tag~"}}})
对应SQL是`name REGEXP 'a' OR tag REGEXP 'a'`

② 只查询id,sex,name这几列并且请求结果也按照这个顺序:
["@column":"id,sex,name"](http://apijson.cn:8080/get/{"User":{"@column":"id,sex,name","id":38710}})
对应SQL是`SELECT id,sex,name`

③ 查询按 name降序、id默认顺序 排序的User数组:
["@order":"name-,id"](http://apijson.cn:8080/get/{"[]":{"count":10,"User":{"@column":"name,id","@order":"name-,id"}}})
对应SQL是`ORDER BY name DESC,id`

④ 查询按userId分组的Moment数组:
["@group":"userId,id"](http://apijson.cn:8080/get/{"[]":{"count":10,"Moment":%7B"@column":"userId,id","@group":"userId,id"}}})
对应SQL是`GROUP BY userId,id`

⑤ 查询 按userId分组、id最大值>=100 的Moment数组:
["@column":"userId;max(id)",
"@group":"userId",
"@having":"max(id)>=100"](http://apijson.cn:8080/get/{"[]":{"count":10,"Moment":{"@column":"userId%253Bmax(id)","@group":"userId","@having":"max(id)>=100"}}})
对应SQL是`SELECT userId,max(id) GROUP BY userId HAVING max(id)>=100`
还可以指定函数返回名:
["@column":"userId;max(id):maxId",
"@group":"userId",
"@having":"maxId>=100"](http://apijson.cn:8080/get/{"[]":{"count":10,"Moment":{"@column":"userId%253Bmax(id):maxId","@group":"userId","@having":"maxId>=100"}}})
对应SQL是`SELECT userId,max(id) AS maxId GROUP BY userId HAVING maxId>=100`

⑥ 查询 sys 内的 User 表:
["@schema":"sys"](http://apijson.cn:8080/get/{"User":{"@schema":"sys"}})
对应SQL是`FROM sys.User`

⑦ 查询 PostgreSQL 数据库的 User 表:
["@database":"POSTGRESQL"](http://apijson.cn:8080/get/{"User":{"@database":"POSTGRESQL","@explain":true}})

⑧ 将 VARCHAR 字符串字段 get 转为 JSONArray 返回:
["@json":"get"](http://apijson.cn:8080/get/{"Access":{"@json":"get"}})

⑨ 查询当前用户的动态:
["@role":"OWNER"](http://apijson.cn:8080/get/{"[]":{"Moment":{"@role":"OWNER"}}})

⑩ 开启性能分析:
["@explain":true](http://apijson.cn:8080/get/{"[]":{"Moment":{"@explain":true}}})

⑪ 统计最近7天偶数userId数量
["@column":"sum(if(userId%2=0,1,0))",
"@having":"to_days(now())-to_days(\`date\`)<=7"](http://apijson.cn:8080/get/{"[]":{"Comment":{@column":"sum(if(userId%252=0,1,0))","@having":"to_days(now())-to_days(\`date\`)<=7"}}})

⑫ 从pictureList获取第0张图片:
["@position":0, //自定义关键词
"firstPicture()":"getFromArray(pictureList,@position)"](http://apijson.cn:8080/get/{"User":{"id":38710,"@position":0,"firstPicture()":"getFromArray(pictureList,@position)"}}) + 对象关键词,可自定义 | "@key":Object,@key为 Table:{} 中{}内的关键词,Object的类型由@key指定

① "@combine":"&key0,&key1,\|key2,key3,
!key4,!key5,&key6,key7...",条件组合方式,\| 可省略。会自动把同类的合并,外层按照 & \| ! 顺序,内层的按传参顺序组合成
(key0 & key1 & key6 & 其它key) & (key2 \| key3 \| key7) & !(key4 \| key5)
这种连接方式,其中 "其它key" 是指与 @combine 在同一对象,且未被它声明的条件 key,默认都是 & 连接

② "@column":"column;function(arg)...",返回字段

③ "@order":"column0+,column1-...",排序方式

④ "@group":"column0,column1...",分组方式。如果@column里声明了Table的id,则id也必须在@group中声明;其它情况下必须满足至少一个条件:
1.分组的key在@column里声明
2.Table主键在@group中声明

⑤ "@having":"function0(...)?value0;function1(...)?value1;function2(...)?value2...",SQL函数条件,一般和@group一起用,函数一般在@column里声明

⑥ "@schema":"sys",集合空间(模式),非默认的值可通过它来指定,可以在最外层作为全局默认配置

⑦ "@database":"POSTGRESQL",跨数据库,非默认的值可通过它来指定,可以在最外层作为全局默认配置

⑧ "@json":"key0,key1...",转为 JSON 格式返回,符合 JSONObject 则转为 {...},符合 JSONArray 则转为 \[...]

⑨ "@role":"OWNER",来访角色,包括
UNKNOWN,LOGIN,CONTACT,CIRCLE,OWNER,ADMIN,
可以在最外层作为全局默认配置,
可自定义其它角色并重写 Verifier.verify 等相关方法来自定义校验

⑩ "@explain":true,性能分析,可以在最外层作为全局默认配置

⑪ "@raw":"SQL片段或SQL片段的别名",自定义原始SQL片段,可扩展嵌套SQL函数等复杂语句,必须是后端已配置的,谨慎使用,其它功能符做不到才考虑

⑫ "@otherKey":Object,自定义关键词,名称和以上系统关键词不一样,且原样返回上传的值 | ① 搜索name或tag任何一个字段包含字符a的User列表:
["name~":"a",
"tag~":"a",
"@combine":"name~,tag~"](http://apijson.cn:8080/get/{"User[]":{"count":10,"User":{"@column":"id,name,tag","name~":"a","tag~":"a","@combine":"name~,tag~"}}})
对应SQL是`name REGEXP 'a' OR tag REGEXP 'a'`

② 只查询id,sex,name这几列并且请求结果也按照这个顺序:
["@column":"id,sex,name"](http://apijson.cn:8080/get/{"User":{"@column":"id,sex,name","id":38710}})
对应SQL是`SELECT id,sex,name`

③ 查询按 name降序、id默认顺序 排序的User数组:
["@order":"name-,id"](http://apijson.cn:8080/get/{"[]":{"count":10,"User":{"@column":"name,id","@order":"name-,id"}}})
对应SQL是`ORDER BY name DESC,id`

④ 查询按userId分组的Moment数组:
["@group":"userId,id"](http://apijson.cn:8080/get/{"[]":{"count":10,"Moment":%7B"@column":"userId,id","@group":"userId,id"}}})
对应SQL是`GROUP BY userId,id`

⑤ 查询 按userId分组、id最大值>=100 的Moment数组:
["@column":"userId;max(id)",
"@group":"userId",
"@having":"max(id)>=100"](http://apijson.cn:8080/get/{"[]":{"count":10,"Moment":{"@column":"userId%253Bmax(id)","@group":"userId","@having":"max(id)>=100"}}})
对应SQL是`SELECT userId,max(id) GROUP BY userId HAVING max(id)>=100`
还可以指定函数返回名:
["@column":"userId;max(id):maxId",
"@group":"userId",
"@having":"maxId>=100"](http://apijson.cn:8080/get/{"[]":{"count":10,"Moment":{"@column":"userId%253Bmax(id):maxId","@group":"userId","@having":"maxId>=100"}}})
对应SQL是`SELECT userId,max(id) AS maxId GROUP BY userId HAVING maxId>=100`

⑥ 查询 sys 内的 User 表:
["@schema":"sys"](http://apijson.cn:8080/get/{"User":{"@schema":"sys"}})
对应SQL是`FROM sys.User`

⑦ 查询 PostgreSQL 数据库的 User 表:
["@database":"POSTGRESQL"](http://apijson.cn:8080/get/{"User":{"@database":"POSTGRESQL","@explain":true}})

⑧ 将 VARCHAR 字符串字段 get 转为 JSONArray 返回:
["@json":"get"](http://apijson.cn:8080/get/{"Access":{"@json":"get"}})

⑨ 查询当前用户的动态:
["@role":"OWNER"](http://apijson.cn:8080/get/{"[]":{"Moment":{"@role":"OWNER"}}})

⑩ 开启性能分析:
["@explain":true](http://apijson.cn:8080/get/{"[]":{"Moment":{"@explain":true}}})

⑪ 统计最近7天偶数userId数量
["@column":"sum(if(userId%2=0,1,0))",
"@having":"to_days(now())-to_days(\`date\`)<=7"](http://apijson.cn:8080/get/{"[]":{"Comment":{"@column":"sum(if(userId%252=0,1,0))","@having":"to_days(now())-to_days(\`date\`)<=7"}}})

⑫ 从pictureList获取第0张图片:
["@position":0, //自定义关键词
"firstPicture()":"getFromArray(pictureList,@position)"](http://apijson.cn:8080/get/{"User":{"id":38710,"@position":0,"firstPicture()":"getFromArray(pictureList,@position)"}})
From b3894239f72a26ec8f2901012c7f6c85ae064e15 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Tue, 22 Dec 2020 00:24:44 +0800 Subject: [PATCH 0107/1181] Update Document.md --- Document.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Document.md b/Document.md index 923404a05..79d78c465 100644 --- a/Document.md +++ b/Document.md @@ -370,6 +370,6 @@ DELETE:
删除数据 | base_url/delete/ | {
   TableName:{< 比较运算 | >, <, >=, <= 比较运算符,用于
① 提供 "id{}":"<=90000" 这种条件范围的简化写法

② 实现子查询相关比较运算

不支持 "key=":Object 和 "key!=":Object 这两种写法,直接用更简单的 "key":Object 和 "key!":Object 替代。 | ① ["id<=":90000](http://apijson.cn:8080/get/{"[]":{"User":{"id<=":90000}}}),对应SQL是`id<=90000`,查询符合id<=90000的一个User数组

② ["id>@":{
   "from":"Comment",
   "Comment":{
      "@column":"min(userId)"
   }
}](http://apijson.cn:8080/get/{"User":{"id>@":{"from":"Comment","Comment":{"@column":"min(userId)"}}}})
WHERE id>(SELECT min(userId) FROM Comment) 逻辑运算 | &, \|, ! 逻辑运算符,对应数据库 SQL 中的 AND, OR, NOT。
横或纵与:同一键值对的值内条件默认 \| 或连接,不同键值对的条件默认 & 与连接。

① & 可用于"key&{}":"条件"等

② \| 可用于"key\|{}":"条件", "key\|{}":[]等,一般可省略

③ ! 可单独使用,如"key!":Object,也可像&,\|一样配合其他功能符使用
"key!":null 无效,null 值会导致整个键值对被忽略解析,可以用 "key{}":"!=null" 替代,
"key":null 同理,用 "key{}":"=null" 替代。 | ① ["id&{}":">80000,<=90000"](http://apijson.cn:8080/head/{"User":{"id&{}":">80000,<=90000"}}),对应SQL是`id>80000 AND id<=90000`,即id满足id>80000 & id<=90000

② ["id\|{}":">90000,<=80000"](http://apijson.cn:8080/head/{"User":{"id\|{}":">90000,<=80000"}}),同"id{}":">90000,<=80000",对应SQL是`id>80000 OR id<=90000`,即id满足id>90000 \| id<=80000

③ ["id!{}":[82001,38710]](http://apijson.cn:8080/head/{"User":{"id!{}":[82001,38710]}}),对应SQL是`id NOT IN(82001,38710)`,即id满足 ! (id=82001 \| id=38710),可过滤黑名单的消息 数组关键词,可自定义 | "key":Object,key为 "[]":{} 中{}内的关键词,Object的类型由key指定

① "count":Integer,查询数量,0 表示最大值,默认最大值为100

② "page":Integer,查询页码,从0开始,默认最大值为100,一般和count一起用

③ "query":Integer,查询内容
0-对象,1-总数和分页详情,2-以上全部
总数关键词为 total,分页详情关键词为 info,
它们都和 query 同级,通过引用赋值得到,例如
"total@":"/[]/total", "info@":"/[]/info"
这里query及total仅为GET类型的请求提供方便,
一般可直接用HEAD类型的请求获取总数

④ "join":"&/Table0/key0@,\多表连接方式:
"\<" - LEFT JOIN
">" - RIGHT JOIN
"&" - INNER JOIN
"\|" - FULL JOIN
"!" - OUTTER JOIN
"@" - APP JOIN
其中 @ APP JOIN 为应用层连表,会从已查出的主表里取得所有副表 key@ 关联的主表内的 refKey 作为一个数组 refKeys: [value0, value1...],然后把原来副表 count 次查询 key=$refKey 的 SQL 用 key IN($refKeys) 的方式合并为一条 SQL 来优化性能;
其它 JOIN 都是 SQL JOIN,具体功能和 MySQL,PostgreSQL 等数据库的 JOIN 一一对应
`"join":"`"MainTable":{},`
`"ViceTable":{"key@":"/MainTable/refKey"}`
会对应生成
`MainTable LEFT JOIN ViceTable`
`ON ViceTable.key=MainTable.refKey`

⑤ "otherKey":Object,自定义关键词,名称和以上系统关键词不一样,且原样返回上传的值 | ① 查询User数组,最多5个:
["count":5](http://apijson.cn:8080/get/{"[]":{"count":5,"User":{}}})
对应SQL是`LIMIT 5`

② 查询第3页的User数组,每页5个:
["count":5,
"page":3](http://apijson.cn:8080/get/{"[]":{"count":5,"page":3,"User":{}}})
对应SQL是`LIMIT 5 OFFSET 15`

③ 查询User数组和对应的User总数:
["[]":{
   "query":2,
   "User":{}
},
"total@":"/[]/total",
"info@":"/[]/info"](http://apijson.cn:8080/get/{"[]":{"query":2,"count":5,"User":{}},"total@":"%252F[]%252Ftotal","info@":"%252F[]%252Finfo"})
返回的数据中,总数及分页详情结构为:
"total":139, //总数
"info":{ //分页详情
   "total":139, //总数
   "count":5, //每页数量
   "page":0, //当前页码
   "max":27, //最大页码
   "more":true, //是否还有更多
   "first":true, //是否为首页
   "last":false //是否为尾页
}

④ Moment INNER JOIN User LEFT JOIN Comment:
["[]":{
   "join":"&/User/id@,\    "Moment":{},
   "User":{
     "name~":"t",
     "id@":"/Moment/userId"
   },
   "Comment":{
     "momentId@":"/Moment/id"
   }
}](http://apijson.cn:8080/get/{"[]":{"count":5,"join":"&%252FUser%252Fid@,\<%252FComment%252FmomentId@","Moment":{"@column":"id,userId,content"},"User":{"name~":"t","id@":"%252FMoment%252FuserId","@column":"id,name,head"},"Comment":{"momentId@":"%252FMoment%252Fid","@column":"id,momentId,content"}}})

⑤ 每一层都加当前用户名:
["User":{},
"[]":{
   "name@":"User/name", //自定义关键词
   "Moment":{}
}](http://apijson.cn:8080/get/{"User":{},"[]":{"name@":"User%252Fname","Moment":{}}}) - 对象关键词,可自定义 | "@key":Object,@key为 Table:{} 中{}内的关键词,Object的类型由@key指定

① "@combine":"&key0,&key1,\|key2,key3,
!key4,!key5,&key6,key7...",条件组合方式,\| 可省略。会自动把同类的合并,外层按照 & \| ! 顺序,内层的按传参顺序组合成
(key0 & key1 & key6 & 其它key) & (key2 \| key3 \| key7) & !(key4 \| key5)
这种连接方式,其中 "其它key" 是指与 @combine 在同一对象,且未被它声明的条件 key,默认都是 & 连接

② "@column":"column;function(arg)...",返回字段

③ "@order":"column0+,column1-...",排序方式

④ "@group":"column0,column1...",分组方式。如果@column里声明了Table的id,则id也必须在@group中声明;其它情况下必须满足至少一个条件:
1.分组的key在@column里声明
2.Table主键在@group中声明

⑤ "@having":"function0(...)?value0;function1(...)?value1;function2(...)?value2...",SQL函数条件,一般和@group一起用,函数一般在@column里声明

⑥ "@schema":"sys",集合空间(模式),非默认的值可通过它来指定,可以在最外层作为全局默认配置

⑦ "@database":"POSTGRESQL",跨数据库,非默认的值可通过它来指定,可以在最外层作为全局默认配置

⑧ "@json":"key0,key1...",转为 JSON 格式返回,符合 JSONObject 则转为 {...},符合 JSONArray 则转为 \[...]

⑨ "@role":"OWNER",来访角色,包括
UNKNOWN,LOGIN,CONTACT,CIRCLE,OWNER,ADMIN,
可以在最外层作为全局默认配置,
可自定义其它角色并重写 Verifier.verify 等相关方法来自定义校验

⑩ "@explain":true,性能分析,可以在最外层作为全局默认配置

⑪ "@raw":"SQL片段或SQL片段的别名",自定义原始SQL片段,可扩展嵌套SQL函数等复杂语句,必须是后端已配置的,谨慎使用,其它功能符做不到才考虑

⑫ "@otherKey":Object,自定义关键词,名称和以上系统关键词不一样,且原样返回上传的值 | ① 搜索name或tag任何一个字段包含字符a的User列表:
["name~":"a",
"tag~":"a",
"@combine":"name~,tag~"](http://apijson.cn:8080/get/{"User[]":{"count":10,"User":{"@column":"id,name,tag","name~":"a","tag~":"a","@combine":"name~,tag~"}}})
对应SQL是`name REGEXP 'a' OR tag REGEXP 'a'`

② 只查询id,sex,name这几列并且请求结果也按照这个顺序:
["@column":"id,sex,name"](http://apijson.cn:8080/get/{"User":{"@column":"id,sex,name","id":38710}})
对应SQL是`SELECT id,sex,name`

③ 查询按 name降序、id默认顺序 排序的User数组:
["@order":"name-,id"](http://apijson.cn:8080/get/{"[]":{"count":10,"User":{"@column":"name,id","@order":"name-,id"}}})
对应SQL是`ORDER BY name DESC,id`

④ 查询按userId分组的Moment数组:
["@group":"userId,id"](http://apijson.cn:8080/get/{"[]":{"count":10,"Moment":%7B"@column":"userId,id","@group":"userId,id"}}})
对应SQL是`GROUP BY userId,id`

⑤ 查询 按userId分组、id最大值>=100 的Moment数组:
["@column":"userId;max(id)",
"@group":"userId",
"@having":"max(id)>=100"](http://apijson.cn:8080/get/{"[]":{"count":10,"Moment":{"@column":"userId%253Bmax(id)","@group":"userId","@having":"max(id)>=100"}}})
对应SQL是`SELECT userId,max(id) GROUP BY userId HAVING max(id)>=100`
还可以指定函数返回名:
["@column":"userId;max(id):maxId",
"@group":"userId",
"@having":"maxId>=100"](http://apijson.cn:8080/get/{"[]":{"count":10,"Moment":{"@column":"userId%253Bmax(id):maxId","@group":"userId","@having":"maxId>=100"}}})
对应SQL是`SELECT userId,max(id) AS maxId GROUP BY userId HAVING maxId>=100`

⑥ 查询 sys 内的 User 表:
["@schema":"sys"](http://apijson.cn:8080/get/{"User":{"@schema":"sys"}})
对应SQL是`FROM sys.User`

⑦ 查询 PostgreSQL 数据库的 User 表:
["@database":"POSTGRESQL"](http://apijson.cn:8080/get/{"User":{"@database":"POSTGRESQL","@explain":true}})

⑧ 将 VARCHAR 字符串字段 get 转为 JSONArray 返回:
["@json":"get"](http://apijson.cn:8080/get/{"Access":{"@json":"get"}})

⑨ 查询当前用户的动态:
["@role":"OWNER"](http://apijson.cn:8080/get/{"[]":{"Moment":{"@role":"OWNER"}}})

⑩ 开启性能分析:
["@explain":true](http://apijson.cn:8080/get/{"[]":{"Moment":{"@explain":true}}})

⑪ 统计最近7天偶数userId数量
["@column":"sum(if(userId%2=0,1,0))",
"@having":"to_days(now())-to_days(\`date\`)<=7"](http://apijson.cn:8080/get/{"[]":{"Comment":{"@column":"sum(if(userId%252=0,1,0))","@having":"to_days(now())-to_days(\`date\`)<=7"}}})

⑫ 从pictureList获取第0张图片:
["@position":0, //自定义关键词
"firstPicture()":"getFromArray(pictureList,@position)"](http://apijson.cn:8080/get/{"User":{"id":38710,"@position":0,"firstPicture()":"getFromArray(pictureList,@position)"}}) + 对象关键词,可自定义 | "@key":Object,@key为 Table:{} 中{}内的关键词,Object的类型由@key指定

① "@combine":"&key0,&key1,\|key2,key3,
!key4,!key5,&key6,key7...",条件组合方式,\| 可省略。会自动把同类的合并,外层按照 & \| ! 顺序,内层的按传参顺序组合成
(key0 & key1 & key6 & 其它key) & (key2 \| key3 \| key7) & !(key4 \| key5)
这种连接方式,其中 "其它key" 是指与 @combine 在同一对象,且未被它声明的条件 key,默认都是 & 连接

② "@column":"column;function(arg)...",返回字段

③ "@order":"column0+,column1-...",排序方式

④ "@group":"column0,column1...",分组方式。如果@column里声明了Table的id,则id也必须在@group中声明;其它情况下必须满足至少一个条件:
1.分组的key在@column里声明
2.Table主键在@group中声明

⑤ "@having":"function0(...)?value0;function1(...)?value1;function2(...)?value2...",SQL函数条件,一般和@group一起用,函数一般在@column里声明

⑥ "@schema":"sys",集合空间(模式),非默认的值可通过它来指定,可以在最外层作为全局默认配置

⑦ "@database":"POSTGRESQL",跨数据库,非默认的值可通过它来指定,可以在最外层作为全局默认配置

⑧ "@json":"key0,key1...",转为 JSON 格式返回,符合 JSONObject 则转为 {...},符合 JSONArray 则转为 \[...]

⑨ "@role":"OWNER",来访角色,包括
UNKNOWN,LOGIN,CONTACT,CIRCLE,OWNER,ADMIN,
可以在最外层作为全局默认配置,
可自定义其它角色并重写 Verifier.verify 等相关方法来自定义校验

⑩ "@explain":true,性能分析,可以在最外层作为全局默认配置

⑪ "@raw":"SQL片段或SQL片段的别名",自定义原始SQL片段,可扩展嵌套SQL函数等复杂语句,必须是后端已配置的,谨慎使用,其它功能符做不到才考虑

⑫ "@otherKey":Object,自定义关键词,名称和以上系统关键词不一样,且原样返回上传的值 | ① 搜索name或tag任何一个字段包含字符a的User列表:
["name~":"a",
"tag~":"a",
"@combine":"name~,tag~"](http://apijson.cn:8080/get/{"User[]":{"count":10,"User":{"@column":"id,name,tag","name~":"a","tag~":"a","@combine":"name~,tag~"}}})
对应SQL是`name REGEXP 'a' OR tag REGEXP 'a'`

② 只查询id,sex,name这几列并且请求结果也按照这个顺序:
["@column":"id,sex,name"](http://apijson.cn:8080/get/{"User":{"@column":"id,sex,name","id":38710}})
对应SQL是`SELECT id,sex,name`

③ 查询按 name降序、id默认顺序 排序的User数组:
["@order":"name-,id"](http://apijson.cn:8080/get/{"[]":{"count":10,"User":{"@column":"name,id","@order":"name-,id"}}})
对应SQL是`ORDER BY name DESC,id`

④ 查询按userId分组的Moment数组:
["@group":"userId,id"](http://apijson.cn:8080/get/{"[]":{"count":10,"Moment":%7B"@column":"userId,id","@group":"userId,id"}}})
对应SQL是`GROUP BY userId,id`

⑤ 查询 按userId分组、id最大值>=100 的Moment数组:
["@column":"userId;max(id)",
"@group":"userId",
"@having":"max(id)>=100"](http://apijson.cn:8080/get/{"[]":{"count":10,"Moment":{"@column":"userId%253Bmax(id)","@group":"userId","@having":"max(id)>=100"}}})
对应SQL是`SELECT userId,max(id) GROUP BY userId HAVING max(id)>=100`
还可以指定函数返回名:
["@column":"userId;max(id):maxId",
"@group":"userId",
"@having":"maxId>=100"](http://apijson.cn:8080/get/{"[]":{"count":10,"Moment":{"@column":"userId%253Bmax(id):maxId","@group":"userId","@having":"maxId>=100"}}})
对应SQL是`SELECT userId,max(id) AS maxId GROUP BY userId HAVING maxId>=100`

⑥ 查询 sys 内的 User 表:
["@schema":"sys"](http://apijson.cn:8080/get/{"User":{"@schema":"sys"}})
对应SQL是`FROM sys.User`

⑦ 查询 PostgreSQL 数据库的 User 表:
["@database":"POSTGRESQL"](http://apijson.cn:8080/get/{"User":{"@database":"POSTGRESQL","@explain":true}})

⑧ 将 VARCHAR 字符串字段 get 转为 JSONArray 返回:
["@json":"get"](http://apijson.cn:8080/get/{"Access":{"@json":"get"}})

⑨ 查询当前用户的动态:
["@role":"OWNER"](http://apijson.cn:8080/get/{"[]":{"Moment":{"@role":"OWNER"}}})

⑩ 开启性能分析:
["@explain":true](http://apijson.cn:8080/get/{"[]":{"Moment":{"@explain":true}}})

⑪ 统计最近7天偶数userId数量
["@column":"sum(if(userId%2=0,1,0))",
"@having":"to_days(now())-to_days(\`date\`)<=7"](http://apijson.cn:8080/get/{"Comment":{"@column":"sum(if(userId%252=0,1,0))","@having":"to_days(now())-to_days(\`date\`)<=7"}})

⑫ 从pictureList获取第0张图片:
["@position":0, //自定义关键词
"firstPicture()":"getFromArray(pictureList,@position)"](http://apijson.cn:8080/get/{"User":{"id":38710,"@position":0,"firstPicture()":"getFromArray(pictureList,@position)"}})
From 8a69a7d2f329b5015a70f8cc54ed5493e10cd462 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Tue, 22 Dec 2020 00:26:06 +0800 Subject: [PATCH 0108/1181] Update Document.md --- Document.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Document.md b/Document.md index 79d78c465..0da6ee8e9 100644 --- a/Document.md +++ b/Document.md @@ -370,6 +370,6 @@ DELETE:
删除数据 | base_url/delete/ | {
   TableName:{< 比较运算 | >, <, >=, <= 比较运算符,用于
① 提供 "id{}":"<=90000" 这种条件范围的简化写法

② 实现子查询相关比较运算

不支持 "key=":Object 和 "key!=":Object 这两种写法,直接用更简单的 "key":Object 和 "key!":Object 替代。 | ① ["id<=":90000](http://apijson.cn:8080/get/{"[]":{"User":{"id<=":90000}}}),对应SQL是`id<=90000`,查询符合id<=90000的一个User数组

② ["id>@":{
   "from":"Comment",
   "Comment":{
      "@column":"min(userId)"
   }
}](http://apijson.cn:8080/get/{"User":{"id>@":{"from":"Comment","Comment":{"@column":"min(userId)"}}}})
WHERE id>(SELECT min(userId) FROM Comment) 逻辑运算 | &, \|, ! 逻辑运算符,对应数据库 SQL 中的 AND, OR, NOT。
横或纵与:同一键值对的值内条件默认 \| 或连接,不同键值对的条件默认 & 与连接。

① & 可用于"key&{}":"条件"等

② \| 可用于"key\|{}":"条件", "key\|{}":[]等,一般可省略

③ ! 可单独使用,如"key!":Object,也可像&,\|一样配合其他功能符使用
"key!":null 无效,null 值会导致整个键值对被忽略解析,可以用 "key{}":"!=null" 替代,
"key":null 同理,用 "key{}":"=null" 替代。 | ① ["id&{}":">80000,<=90000"](http://apijson.cn:8080/head/{"User":{"id&{}":">80000,<=90000"}}),对应SQL是`id>80000 AND id<=90000`,即id满足id>80000 & id<=90000

② ["id\|{}":">90000,<=80000"](http://apijson.cn:8080/head/{"User":{"id\|{}":">90000,<=80000"}}),同"id{}":">90000,<=80000",对应SQL是`id>80000 OR id<=90000`,即id满足id>90000 \| id<=80000

③ ["id!{}":[82001,38710]](http://apijson.cn:8080/head/{"User":{"id!{}":[82001,38710]}}),对应SQL是`id NOT IN(82001,38710)`,即id满足 ! (id=82001 \| id=38710),可过滤黑名单的消息 数组关键词,可自定义 | "key":Object,key为 "[]":{} 中{}内的关键词,Object的类型由key指定

① "count":Integer,查询数量,0 表示最大值,默认最大值为100

② "page":Integer,查询页码,从0开始,默认最大值为100,一般和count一起用

③ "query":Integer,查询内容
0-对象,1-总数和分页详情,2-以上全部
总数关键词为 total,分页详情关键词为 info,
它们都和 query 同级,通过引用赋值得到,例如
"total@":"/[]/total", "info@":"/[]/info"
这里query及total仅为GET类型的请求提供方便,
一般可直接用HEAD类型的请求获取总数

④ "join":"&/Table0/key0@,\多表连接方式:
"\<" - LEFT JOIN
">" - RIGHT JOIN
"&" - INNER JOIN
"\|" - FULL JOIN
"!" - OUTTER JOIN
"@" - APP JOIN
其中 @ APP JOIN 为应用层连表,会从已查出的主表里取得所有副表 key@ 关联的主表内的 refKey 作为一个数组 refKeys: [value0, value1...],然后把原来副表 count 次查询 key=$refKey 的 SQL 用 key IN($refKeys) 的方式合并为一条 SQL 来优化性能;
其它 JOIN 都是 SQL JOIN,具体功能和 MySQL,PostgreSQL 等数据库的 JOIN 一一对应
`"join":"`"MainTable":{},`
`"ViceTable":{"key@":"/MainTable/refKey"}`
会对应生成
`MainTable LEFT JOIN ViceTable`
`ON ViceTable.key=MainTable.refKey`

⑤ "otherKey":Object,自定义关键词,名称和以上系统关键词不一样,且原样返回上传的值 | ① 查询User数组,最多5个:
["count":5](http://apijson.cn:8080/get/{"[]":{"count":5,"User":{}}})
对应SQL是`LIMIT 5`

② 查询第3页的User数组,每页5个:
["count":5,
"page":3](http://apijson.cn:8080/get/{"[]":{"count":5,"page":3,"User":{}}})
对应SQL是`LIMIT 5 OFFSET 15`

③ 查询User数组和对应的User总数:
["[]":{
   "query":2,
   "User":{}
},
"total@":"/[]/total",
"info@":"/[]/info"](http://apijson.cn:8080/get/{"[]":{"query":2,"count":5,"User":{}},"total@":"%252F[]%252Ftotal","info@":"%252F[]%252Finfo"})
返回的数据中,总数及分页详情结构为:
"total":139, //总数
"info":{ //分页详情
   "total":139, //总数
   "count":5, //每页数量
   "page":0, //当前页码
   "max":27, //最大页码
   "more":true, //是否还有更多
   "first":true, //是否为首页
   "last":false //是否为尾页
}

④ Moment INNER JOIN User LEFT JOIN Comment:
["[]":{
   "join":"&/User/id@,\    "Moment":{},
   "User":{
     "name~":"t",
     "id@":"/Moment/userId"
   },
   "Comment":{
     "momentId@":"/Moment/id"
   }
}](http://apijson.cn:8080/get/{"[]":{"count":5,"join":"&%252FUser%252Fid@,\<%252FComment%252FmomentId@","Moment":{"@column":"id,userId,content"},"User":{"name~":"t","id@":"%252FMoment%252FuserId","@column":"id,name,head"},"Comment":{"momentId@":"%252FMoment%252Fid","@column":"id,momentId,content"}}})

⑤ 每一层都加当前用户名:
["User":{},
"[]":{
   "name@":"User/name", //自定义关键词
   "Moment":{}
}](http://apijson.cn:8080/get/{"User":{},"[]":{"name@":"User%252Fname","Moment":{}}}) - 对象关键词,可自定义 | "@key":Object,@key为 Table:{} 中{}内的关键词,Object的类型由@key指定

① "@combine":"&key0,&key1,\|key2,key3,
!key4,!key5,&key6,key7...",条件组合方式,\| 可省略。会自动把同类的合并,外层按照 & \| ! 顺序,内层的按传参顺序组合成
(key0 & key1 & key6 & 其它key) & (key2 \| key3 \| key7) & !(key4 \| key5)
这种连接方式,其中 "其它key" 是指与 @combine 在同一对象,且未被它声明的条件 key,默认都是 & 连接

② "@column":"column;function(arg)...",返回字段

③ "@order":"column0+,column1-...",排序方式

④ "@group":"column0,column1...",分组方式。如果@column里声明了Table的id,则id也必须在@group中声明;其它情况下必须满足至少一个条件:
1.分组的key在@column里声明
2.Table主键在@group中声明

⑤ "@having":"function0(...)?value0;function1(...)?value1;function2(...)?value2...",SQL函数条件,一般和@group一起用,函数一般在@column里声明

⑥ "@schema":"sys",集合空间(模式),非默认的值可通过它来指定,可以在最外层作为全局默认配置

⑦ "@database":"POSTGRESQL",跨数据库,非默认的值可通过它来指定,可以在最外层作为全局默认配置

⑧ "@json":"key0,key1...",转为 JSON 格式返回,符合 JSONObject 则转为 {...},符合 JSONArray 则转为 \[...]

⑨ "@role":"OWNER",来访角色,包括
UNKNOWN,LOGIN,CONTACT,CIRCLE,OWNER,ADMIN,
可以在最外层作为全局默认配置,
可自定义其它角色并重写 Verifier.verify 等相关方法来自定义校验

⑩ "@explain":true,性能分析,可以在最外层作为全局默认配置

⑪ "@raw":"SQL片段或SQL片段的别名",自定义原始SQL片段,可扩展嵌套SQL函数等复杂语句,必须是后端已配置的,谨慎使用,其它功能符做不到才考虑

⑫ "@otherKey":Object,自定义关键词,名称和以上系统关键词不一样,且原样返回上传的值 | ① 搜索name或tag任何一个字段包含字符a的User列表:
["name~":"a",
"tag~":"a",
"@combine":"name~,tag~"](http://apijson.cn:8080/get/{"User[]":{"count":10,"User":{"@column":"id,name,tag","name~":"a","tag~":"a","@combine":"name~,tag~"}}})
对应SQL是`name REGEXP 'a' OR tag REGEXP 'a'`

② 只查询id,sex,name这几列并且请求结果也按照这个顺序:
["@column":"id,sex,name"](http://apijson.cn:8080/get/{"User":{"@column":"id,sex,name","id":38710}})
对应SQL是`SELECT id,sex,name`

③ 查询按 name降序、id默认顺序 排序的User数组:
["@order":"name-,id"](http://apijson.cn:8080/get/{"[]":{"count":10,"User":{"@column":"name,id","@order":"name-,id"}}})
对应SQL是`ORDER BY name DESC,id`

④ 查询按userId分组的Moment数组:
["@group":"userId,id"](http://apijson.cn:8080/get/{"[]":{"count":10,"Moment":%7B"@column":"userId,id","@group":"userId,id"}}})
对应SQL是`GROUP BY userId,id`

⑤ 查询 按userId分组、id最大值>=100 的Moment数组:
["@column":"userId;max(id)",
"@group":"userId",
"@having":"max(id)>=100"](http://apijson.cn:8080/get/{"[]":{"count":10,"Moment":{"@column":"userId%253Bmax(id)","@group":"userId","@having":"max(id)>=100"}}})
对应SQL是`SELECT userId,max(id) GROUP BY userId HAVING max(id)>=100`
还可以指定函数返回名:
["@column":"userId;max(id):maxId",
"@group":"userId",
"@having":"maxId>=100"](http://apijson.cn:8080/get/{"[]":{"count":10,"Moment":{"@column":"userId%253Bmax(id):maxId","@group":"userId","@having":"maxId>=100"}}})
对应SQL是`SELECT userId,max(id) AS maxId GROUP BY userId HAVING maxId>=100`

⑥ 查询 sys 内的 User 表:
["@schema":"sys"](http://apijson.cn:8080/get/{"User":{"@schema":"sys"}})
对应SQL是`FROM sys.User`

⑦ 查询 PostgreSQL 数据库的 User 表:
["@database":"POSTGRESQL"](http://apijson.cn:8080/get/{"User":{"@database":"POSTGRESQL","@explain":true}})

⑧ 将 VARCHAR 字符串字段 get 转为 JSONArray 返回:
["@json":"get"](http://apijson.cn:8080/get/{"Access":{"@json":"get"}})

⑨ 查询当前用户的动态:
["@role":"OWNER"](http://apijson.cn:8080/get/{"[]":{"Moment":{"@role":"OWNER"}}})

⑩ 开启性能分析:
["@explain":true](http://apijson.cn:8080/get/{"[]":{"Moment":{"@explain":true}}})

⑪ 统计最近7天偶数userId数量
["@column":"sum(if(userId%2=0,1,0))",
"@having":"to_days(now())-to_days(\`date\`)<=7"](http://apijson.cn:8080/get/{"Comment":{"@column":"sum(if(userId%252=0,1,0))","@having":"to_days(now())-to_days(\`date\`)<=7"}})

⑫ 从pictureList获取第0张图片:
["@position":0, //自定义关键词
"firstPicture()":"getFromArray(pictureList,@position)"](http://apijson.cn:8080/get/{"User":{"id":38710,"@position":0,"firstPicture()":"getFromArray(pictureList,@position)"}}) + 对象关键词,可自定义 | "@key":Object,@key为 Table:{} 中{}内的关键词,Object的类型由@key指定

① "@combine":"&key0,&key1,\|key2,key3,
!key4,!key5,&key6,key7...",条件组合方式,\| 可省略。会自动把同类的合并,外层按照 & \| ! 顺序,内层的按传参顺序组合成
(key0 & key1 & key6 & 其它key) & (key2 \| key3 \| key7) & !(key4 \| key5)
这种连接方式,其中 "其它key" 是指与 @combine 在同一对象,且未被它声明的条件 key,默认都是 & 连接

② "@column":"column;function(arg)...",返回字段

③ "@order":"column0+,column1-...",排序方式

④ "@group":"column0,column1...",分组方式。如果@column里声明了Table的id,则id也必须在@group中声明;其它情况下必须满足至少一个条件:
1.分组的key在@column里声明
2.Table主键在@group中声明

⑤ "@having":"function0(...)?value0;function1(...)?value1;function2(...)?value2...",SQL函数条件,一般和@group一起用,函数一般在@column里声明

⑥ "@schema":"sys",集合空间(模式),非默认的值可通过它来指定,可以在最外层作为全局默认配置

⑦ "@database":"POSTGRESQL",跨数据库,非默认的值可通过它来指定,可以在最外层作为全局默认配置

⑧ "@json":"key0,key1...",转为 JSON 格式返回,符合 JSONObject 则转为 {...},符合 JSONArray 则转为 \[...]

⑨ "@role":"OWNER",来访角色,包括
UNKNOWN,LOGIN,CONTACT,CIRCLE,OWNER,ADMIN,
可以在最外层作为全局默认配置,
可自定义其它角色并重写 Verifier.verify 等相关方法来自定义校验

⑩ "@explain":true,性能分析,可以在最外层作为全局默认配置

⑪ "@raw":"SQL片段或SQL片段的别名",自定义原始SQL片段,可扩展嵌套SQL函数等复杂语句,必须是后端已配置的,谨慎使用,其它功能符做不到才考虑

⑫ "@otherKey":Object,自定义关键词,名称和以上系统关键词不一样,且原样返回上传的值 | ① 搜索name或tag任何一个字段包含字符a的User列表:
["name~":"a",
"tag~":"a",
"@combine":"name~,tag~"](http://apijson.cn:8080/get/{"User[]":{"count":10,"User":{"@column":"id,name,tag","name~":"a","tag~":"a","@combine":"name~,tag~"}}})
对应SQL是`name REGEXP 'a' OR tag REGEXP 'a'`

② 只查询id,sex,name这几列并且请求结果也按照这个顺序:
["@column":"id,sex,name"](http://apijson.cn:8080/get/{"User":{"@column":"id,sex,name","id":38710}})
对应SQL是`SELECT id,sex,name`

③ 查询按 name降序、id默认顺序 排序的User数组:
["@order":"name-,id"](http://apijson.cn:8080/get/{"[]":{"count":10,"User":{"@column":"name,id","@order":"name-,id"}}})
对应SQL是`ORDER BY name DESC,id`

④ 查询按userId分组的Moment数组:
["@group":"userId,id"](http://apijson.cn:8080/get/{"[]":{"count":10,"Moment":%7B"@column":"userId,id","@group":"userId,id"}}})
对应SQL是`GROUP BY userId,id`

⑤ 查询 按userId分组、id最大值>=100 的Moment数组:
["@column":"userId;max(id)",
"@group":"userId",
"@having":"max(id)>=100"](http://apijson.cn:8080/get/{"[]":{"count":10,"Moment":{"@column":"userId%253Bmax(id)","@group":"userId","@having":"max(id)>=100"}}})
对应SQL是`SELECT userId,max(id) GROUP BY userId HAVING max(id)>=100`
还可以指定函数返回名:
["@column":"userId;max(id):maxId",
"@group":"userId",
"@having":"maxId>=100"](http://apijson.cn:8080/get/{"[]":{"count":10,"Moment":{"@column":"userId%253Bmax(id):maxId","@group":"userId","@having":"maxId>=100"}}})
对应SQL是`SELECT userId,max(id) AS maxId GROUP BY userId HAVING maxId>=100`

⑥ 查询 sys 内的 User 表:
["@schema":"sys"](http://apijson.cn:8080/get/{"User":{"@schema":"sys"}})
对应SQL是`FROM sys.User`

⑦ 查询 PostgreSQL 数据库的 User 表:
["@database":"POSTGRESQL"](http://apijson.cn:8080/get/{"User":{"@database":"POSTGRESQL","@explain":true}})

⑧ 将 VARCHAR 字符串字段 get 转为 JSONArray 返回:
["@json":"get"](http://apijson.cn:8080/get/{"Access":{"@json":"get"}})

⑨ 查询当前用户的动态:
["@role":"OWNER"](http://apijson.cn:8080/get/{"[]":{"Moment":{"@role":"OWNER"}}})

⑩ 开启性能分析:
["@explain":true](http://apijson.cn:8080/get/{"[]":{"Moment":{"@explain":true}}})

⑪ 统计最近一周偶数userId数量
["@column":"sum(if(userId%2=0,1,0))",
"@having":"to_days(now())-to_days(\`date\`)<=7"](http://apijson.cn:8080/get/{"Comment":{"@column":"sum(if(userId%252=0,1,0))","@having":"to_days(now())-to_days(\`date\`)<=7"}})

⑫ 从pictureList获取第0张图片:
["@position":0, //自定义关键词
"firstPicture()":"getFromArray(pictureList,@position)"](http://apijson.cn:8080/get/{"User":{"id":38710,"@position":0,"firstPicture()":"getFromArray(pictureList,@position)"}})
From 4a76a2801ad14b5bbb0671f580114ac18914e649 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Tue, 22 Dec 2020 00:28:17 +0800 Subject: [PATCH 0109/1181] Update Document.md --- Document.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Document.md b/Document.md index 0da6ee8e9..c01eb2f12 100644 --- a/Document.md +++ b/Document.md @@ -370,6 +370,6 @@ DELETE:
删除数据 | base_url/delete/ | {
   TableName:{< 比较运算 | >, <, >=, <= 比较运算符,用于
① 提供 "id{}":"<=90000" 这种条件范围的简化写法

② 实现子查询相关比较运算

不支持 "key=":Object 和 "key!=":Object 这两种写法,直接用更简单的 "key":Object 和 "key!":Object 替代。 | ① ["id<=":90000](http://apijson.cn:8080/get/{"[]":{"User":{"id<=":90000}}}),对应SQL是`id<=90000`,查询符合id<=90000的一个User数组

② ["id>@":{
   "from":"Comment",
   "Comment":{
      "@column":"min(userId)"
   }
}](http://apijson.cn:8080/get/{"User":{"id>@":{"from":"Comment","Comment":{"@column":"min(userId)"}}}})
WHERE id>(SELECT min(userId) FROM Comment) 逻辑运算 | &, \|, ! 逻辑运算符,对应数据库 SQL 中的 AND, OR, NOT。
横或纵与:同一键值对的值内条件默认 \| 或连接,不同键值对的条件默认 & 与连接。

① & 可用于"key&{}":"条件"等

② \| 可用于"key\|{}":"条件", "key\|{}":[]等,一般可省略

③ ! 可单独使用,如"key!":Object,也可像&,\|一样配合其他功能符使用
"key!":null 无效,null 值会导致整个键值对被忽略解析,可以用 "key{}":"!=null" 替代,
"key":null 同理,用 "key{}":"=null" 替代。 | ① ["id&{}":">80000,<=90000"](http://apijson.cn:8080/head/{"User":{"id&{}":">80000,<=90000"}}),对应SQL是`id>80000 AND id<=90000`,即id满足id>80000 & id<=90000

② ["id\|{}":">90000,<=80000"](http://apijson.cn:8080/head/{"User":{"id\|{}":">90000,<=80000"}}),同"id{}":">90000,<=80000",对应SQL是`id>80000 OR id<=90000`,即id满足id>90000 \| id<=80000

③ ["id!{}":[82001,38710]](http://apijson.cn:8080/head/{"User":{"id!{}":[82001,38710]}}),对应SQL是`id NOT IN(82001,38710)`,即id满足 ! (id=82001 \| id=38710),可过滤黑名单的消息 数组关键词,可自定义 | "key":Object,key为 "[]":{} 中{}内的关键词,Object的类型由key指定

① "count":Integer,查询数量,0 表示最大值,默认最大值为100

② "page":Integer,查询页码,从0开始,默认最大值为100,一般和count一起用

③ "query":Integer,查询内容
0-对象,1-总数和分页详情,2-以上全部
总数关键词为 total,分页详情关键词为 info,
它们都和 query 同级,通过引用赋值得到,例如
"total@":"/[]/total", "info@":"/[]/info"
这里query及total仅为GET类型的请求提供方便,
一般可直接用HEAD类型的请求获取总数

④ "join":"&/Table0/key0@,\多表连接方式:
"\<" - LEFT JOIN
">" - RIGHT JOIN
"&" - INNER JOIN
"\|" - FULL JOIN
"!" - OUTTER JOIN
"@" - APP JOIN
其中 @ APP JOIN 为应用层连表,会从已查出的主表里取得所有副表 key@ 关联的主表内的 refKey 作为一个数组 refKeys: [value0, value1...],然后把原来副表 count 次查询 key=$refKey 的 SQL 用 key IN($refKeys) 的方式合并为一条 SQL 来优化性能;
其它 JOIN 都是 SQL JOIN,具体功能和 MySQL,PostgreSQL 等数据库的 JOIN 一一对应
`"join":"`"MainTable":{},`
`"ViceTable":{"key@":"/MainTable/refKey"}`
会对应生成
`MainTable LEFT JOIN ViceTable`
`ON ViceTable.key=MainTable.refKey`

⑤ "otherKey":Object,自定义关键词,名称和以上系统关键词不一样,且原样返回上传的值 | ① 查询User数组,最多5个:
["count":5](http://apijson.cn:8080/get/{"[]":{"count":5,"User":{}}})
对应SQL是`LIMIT 5`

② 查询第3页的User数组,每页5个:
["count":5,
"page":3](http://apijson.cn:8080/get/{"[]":{"count":5,"page":3,"User":{}}})
对应SQL是`LIMIT 5 OFFSET 15`

③ 查询User数组和对应的User总数:
["[]":{
   "query":2,
   "User":{}
},
"total@":"/[]/total",
"info@":"/[]/info"](http://apijson.cn:8080/get/{"[]":{"query":2,"count":5,"User":{}},"total@":"%252F[]%252Ftotal","info@":"%252F[]%252Finfo"})
返回的数据中,总数及分页详情结构为:
"total":139, //总数
"info":{ //分页详情
   "total":139, //总数
   "count":5, //每页数量
   "page":0, //当前页码
   "max":27, //最大页码
   "more":true, //是否还有更多
   "first":true, //是否为首页
   "last":false //是否为尾页
}

④ Moment INNER JOIN User LEFT JOIN Comment:
["[]":{
   "join":"&/User/id@,\    "Moment":{},
   "User":{
     "name~":"t",
     "id@":"/Moment/userId"
   },
   "Comment":{
     "momentId@":"/Moment/id"
   }
}](http://apijson.cn:8080/get/{"[]":{"count":5,"join":"&%252FUser%252Fid@,\<%252FComment%252FmomentId@","Moment":{"@column":"id,userId,content"},"User":{"name~":"t","id@":"%252FMoment%252FuserId","@column":"id,name,head"},"Comment":{"momentId@":"%252FMoment%252Fid","@column":"id,momentId,content"}}})

⑤ 每一层都加当前用户名:
["User":{},
"[]":{
   "name@":"User/name", //自定义关键词
   "Moment":{}
}](http://apijson.cn:8080/get/{"User":{},"[]":{"name@":"User%252Fname","Moment":{}}}) - 对象关键词,可自定义 | "@key":Object,@key为 Table:{} 中{}内的关键词,Object的类型由@key指定

① "@combine":"&key0,&key1,\|key2,key3,
!key4,!key5,&key6,key7...",条件组合方式,\| 可省略。会自动把同类的合并,外层按照 & \| ! 顺序,内层的按传参顺序组合成
(key0 & key1 & key6 & 其它key) & (key2 \| key3 \| key7) & !(key4 \| key5)
这种连接方式,其中 "其它key" 是指与 @combine 在同一对象,且未被它声明的条件 key,默认都是 & 连接

② "@column":"column;function(arg)...",返回字段

③ "@order":"column0+,column1-...",排序方式

④ "@group":"column0,column1...",分组方式。如果@column里声明了Table的id,则id也必须在@group中声明;其它情况下必须满足至少一个条件:
1.分组的key在@column里声明
2.Table主键在@group中声明

⑤ "@having":"function0(...)?value0;function1(...)?value1;function2(...)?value2...",SQL函数条件,一般和@group一起用,函数一般在@column里声明

⑥ "@schema":"sys",集合空间(模式),非默认的值可通过它来指定,可以在最外层作为全局默认配置

⑦ "@database":"POSTGRESQL",跨数据库,非默认的值可通过它来指定,可以在最外层作为全局默认配置

⑧ "@json":"key0,key1...",转为 JSON 格式返回,符合 JSONObject 则转为 {...},符合 JSONArray 则转为 \[...]

⑨ "@role":"OWNER",来访角色,包括
UNKNOWN,LOGIN,CONTACT,CIRCLE,OWNER,ADMIN,
可以在最外层作为全局默认配置,
可自定义其它角色并重写 Verifier.verify 等相关方法来自定义校验

⑩ "@explain":true,性能分析,可以在最外层作为全局默认配置

⑪ "@raw":"SQL片段或SQL片段的别名",自定义原始SQL片段,可扩展嵌套SQL函数等复杂语句,必须是后端已配置的,谨慎使用,其它功能符做不到才考虑

⑫ "@otherKey":Object,自定义关键词,名称和以上系统关键词不一样,且原样返回上传的值 | ① 搜索name或tag任何一个字段包含字符a的User列表:
["name~":"a",
"tag~":"a",
"@combine":"name~,tag~"](http://apijson.cn:8080/get/{"User[]":{"count":10,"User":{"@column":"id,name,tag","name~":"a","tag~":"a","@combine":"name~,tag~"}}})
对应SQL是`name REGEXP 'a' OR tag REGEXP 'a'`

② 只查询id,sex,name这几列并且请求结果也按照这个顺序:
["@column":"id,sex,name"](http://apijson.cn:8080/get/{"User":{"@column":"id,sex,name","id":38710}})
对应SQL是`SELECT id,sex,name`

③ 查询按 name降序、id默认顺序 排序的User数组:
["@order":"name-,id"](http://apijson.cn:8080/get/{"[]":{"count":10,"User":{"@column":"name,id","@order":"name-,id"}}})
对应SQL是`ORDER BY name DESC,id`

④ 查询按userId分组的Moment数组:
["@group":"userId,id"](http://apijson.cn:8080/get/{"[]":{"count":10,"Moment":%7B"@column":"userId,id","@group":"userId,id"}}})
对应SQL是`GROUP BY userId,id`

⑤ 查询 按userId分组、id最大值>=100 的Moment数组:
["@column":"userId;max(id)",
"@group":"userId",
"@having":"max(id)>=100"](http://apijson.cn:8080/get/{"[]":{"count":10,"Moment":{"@column":"userId%253Bmax(id)","@group":"userId","@having":"max(id)>=100"}}})
对应SQL是`SELECT userId,max(id) GROUP BY userId HAVING max(id)>=100`
还可以指定函数返回名:
["@column":"userId;max(id):maxId",
"@group":"userId",
"@having":"maxId>=100"](http://apijson.cn:8080/get/{"[]":{"count":10,"Moment":{"@column":"userId%253Bmax(id):maxId","@group":"userId","@having":"maxId>=100"}}})
对应SQL是`SELECT userId,max(id) AS maxId GROUP BY userId HAVING maxId>=100`

⑥ 查询 sys 内的 User 表:
["@schema":"sys"](http://apijson.cn:8080/get/{"User":{"@schema":"sys"}})
对应SQL是`FROM sys.User`

⑦ 查询 PostgreSQL 数据库的 User 表:
["@database":"POSTGRESQL"](http://apijson.cn:8080/get/{"User":{"@database":"POSTGRESQL","@explain":true}})

⑧ 将 VARCHAR 字符串字段 get 转为 JSONArray 返回:
["@json":"get"](http://apijson.cn:8080/get/{"Access":{"@json":"get"}})

⑨ 查询当前用户的动态:
["@role":"OWNER"](http://apijson.cn:8080/get/{"[]":{"Moment":{"@role":"OWNER"}}})

⑩ 开启性能分析:
["@explain":true](http://apijson.cn:8080/get/{"[]":{"Moment":{"@explain":true}}})

⑪ 统计最近一周偶数userId数量
["@column":"sum(if(userId%2=0,1,0))",
"@having":"to_days(now())-to_days(\`date\`)<=7"](http://apijson.cn:8080/get/{"Comment":{"@column":"sum(if(userId%252=0,1,0))","@having":"to_days(now())-to_days(\`date\`)<=7"}})

⑫ 从pictureList获取第0张图片:
["@position":0, //自定义关键词
"firstPicture()":"getFromArray(pictureList,@position)"](http://apijson.cn:8080/get/{"User":{"id":38710,"@position":0,"firstPicture()":"getFromArray(pictureList,@position)"}}) + 对象关键词,可自定义 | "@key":Object,@key为 Table:{} 中{}内的关键词,Object的类型由@key指定

① "@combine":"&key0,&key1,\|key2,key3,
!key4,!key5,&key6,key7...",条件组合方式,\| 可省略。会自动把同类的合并,外层按照 & \| ! 顺序,内层的按传参顺序组合成
(key0 & key1 & key6 & 其它key) & (key2 \| key3 \| key7) & !(key4 \| key5)
这种连接方式,其中 "其它key" 是指与 @combine 在同一对象,且未被它声明的条件 key,默认都是 & 连接

② "@column":"column;function(arg)...",返回字段

③ "@order":"column0+,column1-...",排序方式

④ "@group":"column0,column1...",分组方式。如果@column里声明了Table的id,则id也必须在@group中声明;其它情况下必须满足至少一个条件:
1.分组的key在@column里声明
2.Table主键在@group中声明

⑤ "@having":"function0(...)?value0;function1(...)?value1;function2(...)?value2...",SQL函数条件,一般和@group一起用,函数一般在@column里声明

⑥ "@schema":"sys",集合空间(模式),非默认的值可通过它来指定,可以在最外层作为全局默认配置

⑦ "@database":"POSTGRESQL",跨数据库,非默认的值可通过它来指定,可以在最外层作为全局默认配置

⑧ "@json":"key0,key1...",转为 JSON 格式返回,符合 JSONObject 则转为 {...},符合 JSONArray 则转为 \[...]

⑨ "@role":"OWNER",来访角色,包括
UNKNOWN,LOGIN,CONTACT,CIRCLE,OWNER,ADMIN,
可以在最外层作为全局默认配置,
可自定义其它角色并重写 Verifier.verify 等相关方法来自定义校验

⑩ "@explain":true,性能分析,可以在最外层作为全局默认配置

⑪ "@raw":"SQL片段或SQL片段的别名",自定义原始SQL片段,可扩展嵌套SQL函数等复杂语句,必须是后端已配置的,谨慎使用,其它功能符做不到才考虑

⑫ "@otherKey":Object,自定义关键词,名称和以上系统关键词不一样,且原样返回上传的值 | ① 搜索name或tag任何一个字段包含字符a的User列表:
["name~":"a",
"tag~":"a",
"@combine":"name~,tag~"](http://apijson.cn:8080/get/{"User[]":{"count":10,"User":{"@column":"id,name,tag","name~":"a","tag~":"a","@combine":"name~,tag~"}}})
对应SQL是`name REGEXP 'a' OR tag REGEXP 'a'`

② 只查询id,sex,name这几列并且请求结果也按照这个顺序:
["@column":"id,sex,name"](http://apijson.cn:8080/get/{"User":{"@column":"id,sex,name","id":38710}})
对应SQL是`SELECT id,sex,name`

③ 查询按 name降序、id默认顺序 排序的User数组:
["@order":"name-,id"](http://apijson.cn:8080/get/{"[]":{"count":10,"User":{"@column":"name,id","@order":"name-,id"}}})
对应SQL是`ORDER BY name DESC,id`

④ 查询按userId分组的Moment数组:
["@group":"userId,id"](http://apijson.cn:8080/get/{"[]":{"count":10,"Moment":%7B"@column":"userId,id","@group":"userId,id"}}})
对应SQL是`GROUP BY userId,id`

⑤ 查询 按userId分组、id最大值>=100 的Moment数组:
["@column":"userId;max(id)",
"@group":"userId",
"@having":"max(id)>=100"](http://apijson.cn:8080/get/{"[]":{"count":10,"Moment":{"@column":"userId%253Bmax(id)","@group":"userId","@having":"max(id)>=100"}}})
对应SQL是`SELECT userId,max(id) GROUP BY userId HAVING max(id)>=100`
还可以指定函数返回名:
["@column":"userId;max(id):maxId",
"@group":"userId",
"@having":"maxId>=100"](http://apijson.cn:8080/get/{"[]":{"count":10,"Moment":{"@column":"userId%253Bmax(id):maxId","@group":"userId","@having":"maxId>=100"}}})
对应SQL是`SELECT userId,max(id) AS maxId GROUP BY userId HAVING maxId>=100`

⑥ 查询 sys 内的 User 表:
["@schema":"sys"](http://apijson.cn:8080/get/{"User":{"@schema":"sys"}})
对应SQL是`FROM sys.User`

⑦ 查询 PostgreSQL 数据库的 User 表:
["@database":"POSTGRESQL"](http://apijson.cn:8080/get/{"User":{"@database":"POSTGRESQL","@explain":true}})

⑧ 将 VARCHAR 字符串字段 get 转为 JSONArray 返回:
["@json":"get"](http://apijson.cn:8080/get/{"Access":{"@json":"get"}})

⑨ 查询当前用户的动态:
["@role":"OWNER"](http://apijson.cn:8080/get/{"[]":{"Moment":{"@role":"OWNER"}}})

⑩ 开启性能分析:
["@explain":true](http://apijson.cn:8080/get/{"[]":{"Moment":{"@explain":true}}})
对应SQL是`EXPLAIN`

⑪ 统计最近一周偶数userId数量
["@column":"sum(if(userId%2=0,1,0))",
"@having":"to_days(now())-to_days(\`date\`)<=7"](http://apijson.cn:8080/get/{"Comment":{"@column":"sum(if(userId%252=0,1,0))","@having":"to_days(now())-to_days(\`date\`)<=7"}})
对应SQL是`SELECT sum(if(userId%2=0,1,0)) ... HAVING to_days(now())-to_days(\`date\`)<=7`

⑫ 从pictureList获取第0张图片:
["@position":0, //自定义关键词
"firstPicture()":"getFromArray(pictureList,@position)"](http://apijson.cn:8080/get/{"User":{"id":38710,"@position":0,"firstPicture()":"getFromArray(pictureList,@position)"}})
From af611f59322b447a6e3ee4ddcfc94a52ed6e78a9 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Tue, 22 Dec 2020 00:30:55 +0800 Subject: [PATCH 0110/1181] Update Document.md --- Document.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Document.md b/Document.md index c01eb2f12..48974e709 100644 --- a/Document.md +++ b/Document.md @@ -370,6 +370,6 @@ DELETE:
删除数据 | base_url/delete/ | {
   TableName:{< 比较运算 | >, <, >=, <= 比较运算符,用于
① 提供 "id{}":"<=90000" 这种条件范围的简化写法

② 实现子查询相关比较运算

不支持 "key=":Object 和 "key!=":Object 这两种写法,直接用更简单的 "key":Object 和 "key!":Object 替代。 | ① ["id<=":90000](http://apijson.cn:8080/get/{"[]":{"User":{"id<=":90000}}}),对应SQL是`id<=90000`,查询符合id<=90000的一个User数组

② ["id>@":{
   "from":"Comment",
   "Comment":{
      "@column":"min(userId)"
   }
}](http://apijson.cn:8080/get/{"User":{"id>@":{"from":"Comment","Comment":{"@column":"min(userId)"}}}})
WHERE id>(SELECT min(userId) FROM Comment) 逻辑运算 | &, \|, ! 逻辑运算符,对应数据库 SQL 中的 AND, OR, NOT。
横或纵与:同一键值对的值内条件默认 \| 或连接,不同键值对的条件默认 & 与连接。

① & 可用于"key&{}":"条件"等

② \| 可用于"key\|{}":"条件", "key\|{}":[]等,一般可省略

③ ! 可单独使用,如"key!":Object,也可像&,\|一样配合其他功能符使用
"key!":null 无效,null 值会导致整个键值对被忽略解析,可以用 "key{}":"!=null" 替代,
"key":null 同理,用 "key{}":"=null" 替代。 | ① ["id&{}":">80000,<=90000"](http://apijson.cn:8080/head/{"User":{"id&{}":">80000,<=90000"}}),对应SQL是`id>80000 AND id<=90000`,即id满足id>80000 & id<=90000

② ["id\|{}":">90000,<=80000"](http://apijson.cn:8080/head/{"User":{"id\|{}":">90000,<=80000"}}),同"id{}":">90000,<=80000",对应SQL是`id>80000 OR id<=90000`,即id满足id>90000 \| id<=80000

③ ["id!{}":[82001,38710]](http://apijson.cn:8080/head/{"User":{"id!{}":[82001,38710]}}),对应SQL是`id NOT IN(82001,38710)`,即id满足 ! (id=82001 \| id=38710),可过滤黑名单的消息 数组关键词,可自定义 | "key":Object,key为 "[]":{} 中{}内的关键词,Object的类型由key指定

① "count":Integer,查询数量,0 表示最大值,默认最大值为100

② "page":Integer,查询页码,从0开始,默认最大值为100,一般和count一起用

③ "query":Integer,查询内容
0-对象,1-总数和分页详情,2-以上全部
总数关键词为 total,分页详情关键词为 info,
它们都和 query 同级,通过引用赋值得到,例如
"total@":"/[]/total", "info@":"/[]/info"
这里query及total仅为GET类型的请求提供方便,
一般可直接用HEAD类型的请求获取总数

④ "join":"&/Table0/key0@,\多表连接方式:
"\<" - LEFT JOIN
">" - RIGHT JOIN
"&" - INNER JOIN
"\|" - FULL JOIN
"!" - OUTTER JOIN
"@" - APP JOIN
其中 @ APP JOIN 为应用层连表,会从已查出的主表里取得所有副表 key@ 关联的主表内的 refKey 作为一个数组 refKeys: [value0, value1...],然后把原来副表 count 次查询 key=$refKey 的 SQL 用 key IN($refKeys) 的方式合并为一条 SQL 来优化性能;
其它 JOIN 都是 SQL JOIN,具体功能和 MySQL,PostgreSQL 等数据库的 JOIN 一一对应
`"join":"`"MainTable":{},`
`"ViceTable":{"key@":"/MainTable/refKey"}`
会对应生成
`MainTable LEFT JOIN ViceTable`
`ON ViceTable.key=MainTable.refKey`

⑤ "otherKey":Object,自定义关键词,名称和以上系统关键词不一样,且原样返回上传的值 | ① 查询User数组,最多5个:
["count":5](http://apijson.cn:8080/get/{"[]":{"count":5,"User":{}}})
对应SQL是`LIMIT 5`

② 查询第3页的User数组,每页5个:
["count":5,
"page":3](http://apijson.cn:8080/get/{"[]":{"count":5,"page":3,"User":{}}})
对应SQL是`LIMIT 5 OFFSET 15`

③ 查询User数组和对应的User总数:
["[]":{
   "query":2,
   "User":{}
},
"total@":"/[]/total",
"info@":"/[]/info"](http://apijson.cn:8080/get/{"[]":{"query":2,"count":5,"User":{}},"total@":"%252F[]%252Ftotal","info@":"%252F[]%252Finfo"})
返回的数据中,总数及分页详情结构为:
"total":139, //总数
"info":{ //分页详情
   "total":139, //总数
   "count":5, //每页数量
   "page":0, //当前页码
   "max":27, //最大页码
   "more":true, //是否还有更多
   "first":true, //是否为首页
   "last":false //是否为尾页
}

④ Moment INNER JOIN User LEFT JOIN Comment:
["[]":{
   "join":"&/User/id@,\    "Moment":{},
   "User":{
     "name~":"t",
     "id@":"/Moment/userId"
   },
   "Comment":{
     "momentId@":"/Moment/id"
   }
}](http://apijson.cn:8080/get/{"[]":{"count":5,"join":"&%252FUser%252Fid@,\<%252FComment%252FmomentId@","Moment":{"@column":"id,userId,content"},"User":{"name~":"t","id@":"%252FMoment%252FuserId","@column":"id,name,head"},"Comment":{"momentId@":"%252FMoment%252Fid","@column":"id,momentId,content"}}})

⑤ 每一层都加当前用户名:
["User":{},
"[]":{
   "name@":"User/name", //自定义关键词
   "Moment":{}
}](http://apijson.cn:8080/get/{"User":{},"[]":{"name@":"User%252Fname","Moment":{}}}) - 对象关键词,可自定义 | "@key":Object,@key为 Table:{} 中{}内的关键词,Object的类型由@key指定

① "@combine":"&key0,&key1,\|key2,key3,
!key4,!key5,&key6,key7...",条件组合方式,\| 可省略。会自动把同类的合并,外层按照 & \| ! 顺序,内层的按传参顺序组合成
(key0 & key1 & key6 & 其它key) & (key2 \| key3 \| key7) & !(key4 \| key5)
这种连接方式,其中 "其它key" 是指与 @combine 在同一对象,且未被它声明的条件 key,默认都是 & 连接

② "@column":"column;function(arg)...",返回字段

③ "@order":"column0+,column1-...",排序方式

④ "@group":"column0,column1...",分组方式。如果@column里声明了Table的id,则id也必须在@group中声明;其它情况下必须满足至少一个条件:
1.分组的key在@column里声明
2.Table主键在@group中声明

⑤ "@having":"function0(...)?value0;function1(...)?value1;function2(...)?value2...",SQL函数条件,一般和@group一起用,函数一般在@column里声明

⑥ "@schema":"sys",集合空间(模式),非默认的值可通过它来指定,可以在最外层作为全局默认配置

⑦ "@database":"POSTGRESQL",跨数据库,非默认的值可通过它来指定,可以在最外层作为全局默认配置

⑧ "@json":"key0,key1...",转为 JSON 格式返回,符合 JSONObject 则转为 {...},符合 JSONArray 则转为 \[...]

⑨ "@role":"OWNER",来访角色,包括
UNKNOWN,LOGIN,CONTACT,CIRCLE,OWNER,ADMIN,
可以在最外层作为全局默认配置,
可自定义其它角色并重写 Verifier.verify 等相关方法来自定义校验

⑩ "@explain":true,性能分析,可以在最外层作为全局默认配置

⑪ "@raw":"SQL片段或SQL片段的别名",自定义原始SQL片段,可扩展嵌套SQL函数等复杂语句,必须是后端已配置的,谨慎使用,其它功能符做不到才考虑

⑫ "@otherKey":Object,自定义关键词,名称和以上系统关键词不一样,且原样返回上传的值 | ① 搜索name或tag任何一个字段包含字符a的User列表:
["name~":"a",
"tag~":"a",
"@combine":"name~,tag~"](http://apijson.cn:8080/get/{"User[]":{"count":10,"User":{"@column":"id,name,tag","name~":"a","tag~":"a","@combine":"name~,tag~"}}})
对应SQL是`name REGEXP 'a' OR tag REGEXP 'a'`

② 只查询id,sex,name这几列并且请求结果也按照这个顺序:
["@column":"id,sex,name"](http://apijson.cn:8080/get/{"User":{"@column":"id,sex,name","id":38710}})
对应SQL是`SELECT id,sex,name`

③ 查询按 name降序、id默认顺序 排序的User数组:
["@order":"name-,id"](http://apijson.cn:8080/get/{"[]":{"count":10,"User":{"@column":"name,id","@order":"name-,id"}}})
对应SQL是`ORDER BY name DESC,id`

④ 查询按userId分组的Moment数组:
["@group":"userId,id"](http://apijson.cn:8080/get/{"[]":{"count":10,"Moment":%7B"@column":"userId,id","@group":"userId,id"}}})
对应SQL是`GROUP BY userId,id`

⑤ 查询 按userId分组、id最大值>=100 的Moment数组:
["@column":"userId;max(id)",
"@group":"userId",
"@having":"max(id)>=100"](http://apijson.cn:8080/get/{"[]":{"count":10,"Moment":{"@column":"userId%253Bmax(id)","@group":"userId","@having":"max(id)>=100"}}})
对应SQL是`SELECT userId,max(id) GROUP BY userId HAVING max(id)>=100`
还可以指定函数返回名:
["@column":"userId;max(id):maxId",
"@group":"userId",
"@having":"maxId>=100"](http://apijson.cn:8080/get/{"[]":{"count":10,"Moment":{"@column":"userId%253Bmax(id):maxId","@group":"userId","@having":"maxId>=100"}}})
对应SQL是`SELECT userId,max(id) AS maxId GROUP BY userId HAVING maxId>=100`

⑥ 查询 sys 内的 User 表:
["@schema":"sys"](http://apijson.cn:8080/get/{"User":{"@schema":"sys"}})
对应SQL是`FROM sys.User`

⑦ 查询 PostgreSQL 数据库的 User 表:
["@database":"POSTGRESQL"](http://apijson.cn:8080/get/{"User":{"@database":"POSTGRESQL","@explain":true}})

⑧ 将 VARCHAR 字符串字段 get 转为 JSONArray 返回:
["@json":"get"](http://apijson.cn:8080/get/{"Access":{"@json":"get"}})

⑨ 查询当前用户的动态:
["@role":"OWNER"](http://apijson.cn:8080/get/{"[]":{"Moment":{"@role":"OWNER"}}})

⑩ 开启性能分析:
["@explain":true](http://apijson.cn:8080/get/{"[]":{"Moment":{"@explain":true}}})
对应SQL是`EXPLAIN`

⑪ 统计最近一周偶数userId数量
["@column":"sum(if(userId%2=0,1,0))",
"@having":"to_days(now())-to_days(\`date\`)<=7"](http://apijson.cn:8080/get/{"Comment":{"@column":"sum(if(userId%252=0,1,0))","@having":"to_days(now())-to_days(\`date\`)<=7"}})
对应SQL是`SELECT sum(if(userId%2=0,1,0)) ... HAVING to_days(now())-to_days(\`date\`)<=7`

⑫ 从pictureList获取第0张图片:
["@position":0, //自定义关键词
"firstPicture()":"getFromArray(pictureList,@position)"](http://apijson.cn:8080/get/{"User":{"id":38710,"@position":0,"firstPicture()":"getFromArray(pictureList,@position)"}}) + 对象关键词,可自定义 | "@key":Object,@key为 Table:{} 中{}内的关键词,Object的类型由@key指定

① "@combine":"&key0,&key1,\|key2,key3,
!key4,!key5,&key6,key7...",条件组合方式,\| 可省略。会自动把同类的合并,外层按照 & \| ! 顺序,内层的按传参顺序组合成
(key0 & key1 & key6 & 其它key) & (key2 \| key3 \| key7) & !(key4 \| key5)
这种连接方式,其中 "其它key" 是指与 @combine 在同一对象,且未被它声明的条件 key,默认都是 & 连接

② "@column":"column;function(arg)...",返回字段

③ "@order":"column0+,column1-...",排序方式

④ "@group":"column0,column1...",分组方式。如果@column里声明了Table的id,则id也必须在@group中声明;其它情况下必须满足至少一个条件:
1.分组的key在@column里声明
2.Table主键在@group中声明

⑤ "@having":"function0(...)?value0;function1(...)?value1;function2(...)?value2...",SQL函数条件,一般和@group一起用,函数一般在@column里声明

⑥ "@schema":"sys",集合空间(模式),非默认的值可通过它来指定,可以在最外层作为全局默认配置

⑦ "@database":"POSTGRESQL",跨数据库,非默认的值可通过它来指定,可以在最外层作为全局默认配置

⑧ "@json":"key0,key1...",转为 JSON 格式返回,符合 JSONObject 则转为 {...},符合 JSONArray 则转为 \[...]

⑨ "@role":"OWNER",来访角色,包括
UNKNOWN,LOGIN,CONTACT,CIRCLE,OWNER,ADMIN,
可以在最外层作为全局默认配置,
可自定义其它角色并重写 Verifier.verify 等相关方法来自定义校验

⑩ "@explain":true,性能分析,可以在最外层作为全局默认配置

⑪ "@raw":"SQL片段或SQL片段的别名",自定义原始SQL片段,可扩展嵌套SQL函数等复杂语句,必须是后端已配置的,谨慎使用,其它功能符做不到才考虑

⑫ "@otherKey":Object,自定义关键词,名称和以上系统关键词不一样,且原样返回上传的值 | ① 搜索name或tag任何一个字段包含字符a的User列表:
["name~":"a",
"tag~":"a",
"@combine":"name~,tag~"](http://apijson.cn:8080/get/{"User[]":{"count":10,"User":{"@column":"id,name,tag","name~":"a","tag~":"a","@combine":"name~,tag~"}}})
对应SQL是`name REGEXP 'a' OR tag REGEXP 'a'`

② 只查询id,sex,name这几列并且请求结果也按照这个顺序:
["@column":"id,sex,name"](http://apijson.cn:8080/get/{"User":{"@column":"id,sex,name","id":38710}})
对应SQL是`SELECT id,sex,name`

③ 查询按 name降序、id默认顺序 排序的User数组:
["@order":"name-,id"](http://apijson.cn:8080/get/{"[]":{"count":10,"User":{"@column":"name,id","@order":"name-,id"}}})
对应SQL是`ORDER BY name DESC,id`

④ 查询按userId分组的Moment数组:
["@group":"userId,id"](http://apijson.cn:8080/get/{"[]":{"count":10,"Moment":%7B"@column":"userId,id","@group":"userId,id"}}})
对应SQL是`GROUP BY userId,id`

⑤ 查询 按userId分组、id最大值>=100 的Moment数组:
["@column":"userId;max(id)",
"@group":"userId",
"@having":"max(id)>=100"](http://apijson.cn:8080/get/{"[]":{"count":10,"Moment":{"@column":"userId%253Bmax(id)","@group":"userId","@having":"max(id)>=100"}}})
对应SQL是`SELECT userId,max(id) GROUP BY userId HAVING max(id)>=100`
还可以指定函数返回名:
["@column":"userId;max(id):maxId",
"@group":"userId",
"@having":"maxId>=100"](http://apijson.cn:8080/get/{"[]":{"count":10,"Moment":{"@column":"userId%253Bmax(id):maxId","@group":"userId","@having":"maxId>=100"}}})
对应SQL是`SELECT userId,max(id) AS maxId GROUP BY userId HAVING maxId>=100`

⑥ 查询 sys 内的 User 表:
["@schema":"sys"](http://apijson.cn:8080/get/{"User":{"@schema":"sys"}})
对应SQL是`FROM sys.User`

⑦ 查询 PostgreSQL 数据库的 User 表:
["@database":"POSTGRESQL"](http://apijson.cn:8080/get/{"User":{"@database":"POSTGRESQL","@explain":true}})

⑧ 将 VARCHAR 字符串字段 get 转为 JSONArray 返回:
["@json":"get"](http://apijson.cn:8080/get/{"Access":{"@json":"get"}})

⑨ 查询当前用户的动态:
["@role":"OWNER"](http://apijson.cn:8080/get/{"[]":{"Moment":{"@role":"OWNER"}}})

⑩ 开启性能分析:
["@explain":true](http://apijson.cn:8080/get/{"[]":{"Moment":{"@explain":true}}})
对应SQL是`EXPLAIN`

⑪ 统计最近一周偶数userId数量
["@column":"sum(if(userId%2=0,1,0))",
"@having":"to_days(now())-to_days(\`date\`)<=7"](http://apijson.cn:8080/get/{"Comment":{"@column":"sum(if(userId%252=0,1,0))","@having":"to_days(now())-to_days(\`date\`)<=7"}})
对应SQL是`SELECT sum(if(userId%2=0,1,0)) ... HAVING to_days(now())-to_days(``date``)<=7`

⑫ 从pictureList获取第0张图片:
["@position":0, //自定义关键词
"firstPicture()":"getFromArray(pictureList,@position)"](http://apijson.cn:8080/get/{"User":{"id":38710,"@position":0,"firstPicture()":"getFromArray(pictureList,@position)"}})
From b345338247c9e36d105ef7cca7bd0106b4a980dc Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Tue, 22 Dec 2020 00:31:21 +0800 Subject: [PATCH 0111/1181] Update Document.md --- Document.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Document.md b/Document.md index 48974e709..0f4426b5d 100644 --- a/Document.md +++ b/Document.md @@ -370,6 +370,6 @@ DELETE:
删除数据 | base_url/delete/ | {
   TableName:{< 比较运算 | >, <, >=, <= 比较运算符,用于
① 提供 "id{}":"<=90000" 这种条件范围的简化写法

② 实现子查询相关比较运算

不支持 "key=":Object 和 "key!=":Object 这两种写法,直接用更简单的 "key":Object 和 "key!":Object 替代。 | ① ["id<=":90000](http://apijson.cn:8080/get/{"[]":{"User":{"id<=":90000}}}),对应SQL是`id<=90000`,查询符合id<=90000的一个User数组

② ["id>@":{
   "from":"Comment",
   "Comment":{
      "@column":"min(userId)"
   }
}](http://apijson.cn:8080/get/{"User":{"id>@":{"from":"Comment","Comment":{"@column":"min(userId)"}}}})
WHERE id>(SELECT min(userId) FROM Comment) 逻辑运算 | &, \|, ! 逻辑运算符,对应数据库 SQL 中的 AND, OR, NOT。
横或纵与:同一键值对的值内条件默认 \| 或连接,不同键值对的条件默认 & 与连接。

① & 可用于"key&{}":"条件"等

② \| 可用于"key\|{}":"条件", "key\|{}":[]等,一般可省略

③ ! 可单独使用,如"key!":Object,也可像&,\|一样配合其他功能符使用
"key!":null 无效,null 值会导致整个键值对被忽略解析,可以用 "key{}":"!=null" 替代,
"key":null 同理,用 "key{}":"=null" 替代。 | ① ["id&{}":">80000,<=90000"](http://apijson.cn:8080/head/{"User":{"id&{}":">80000,<=90000"}}),对应SQL是`id>80000 AND id<=90000`,即id满足id>80000 & id<=90000

② ["id\|{}":">90000,<=80000"](http://apijson.cn:8080/head/{"User":{"id\|{}":">90000,<=80000"}}),同"id{}":">90000,<=80000",对应SQL是`id>80000 OR id<=90000`,即id满足id>90000 \| id<=80000

③ ["id!{}":[82001,38710]](http://apijson.cn:8080/head/{"User":{"id!{}":[82001,38710]}}),对应SQL是`id NOT IN(82001,38710)`,即id满足 ! (id=82001 \| id=38710),可过滤黑名单的消息 数组关键词,可自定义 | "key":Object,key为 "[]":{} 中{}内的关键词,Object的类型由key指定

① "count":Integer,查询数量,0 表示最大值,默认最大值为100

② "page":Integer,查询页码,从0开始,默认最大值为100,一般和count一起用

③ "query":Integer,查询内容
0-对象,1-总数和分页详情,2-以上全部
总数关键词为 total,分页详情关键词为 info,
它们都和 query 同级,通过引用赋值得到,例如
"total@":"/[]/total", "info@":"/[]/info"
这里query及total仅为GET类型的请求提供方便,
一般可直接用HEAD类型的请求获取总数

④ "join":"&/Table0/key0@,\多表连接方式:
"\<" - LEFT JOIN
">" - RIGHT JOIN
"&" - INNER JOIN
"\|" - FULL JOIN
"!" - OUTTER JOIN
"@" - APP JOIN
其中 @ APP JOIN 为应用层连表,会从已查出的主表里取得所有副表 key@ 关联的主表内的 refKey 作为一个数组 refKeys: [value0, value1...],然后把原来副表 count 次查询 key=$refKey 的 SQL 用 key IN($refKeys) 的方式合并为一条 SQL 来优化性能;
其它 JOIN 都是 SQL JOIN,具体功能和 MySQL,PostgreSQL 等数据库的 JOIN 一一对应
`"join":"`"MainTable":{},`
`"ViceTable":{"key@":"/MainTable/refKey"}`
会对应生成
`MainTable LEFT JOIN ViceTable`
`ON ViceTable.key=MainTable.refKey`

⑤ "otherKey":Object,自定义关键词,名称和以上系统关键词不一样,且原样返回上传的值 | ① 查询User数组,最多5个:
["count":5](http://apijson.cn:8080/get/{"[]":{"count":5,"User":{}}})
对应SQL是`LIMIT 5`

② 查询第3页的User数组,每页5个:
["count":5,
"page":3](http://apijson.cn:8080/get/{"[]":{"count":5,"page":3,"User":{}}})
对应SQL是`LIMIT 5 OFFSET 15`

③ 查询User数组和对应的User总数:
["[]":{
   "query":2,
   "User":{}
},
"total@":"/[]/total",
"info@":"/[]/info"](http://apijson.cn:8080/get/{"[]":{"query":2,"count":5,"User":{}},"total@":"%252F[]%252Ftotal","info@":"%252F[]%252Finfo"})
返回的数据中,总数及分页详情结构为:
"total":139, //总数
"info":{ //分页详情
   "total":139, //总数
   "count":5, //每页数量
   "page":0, //当前页码
   "max":27, //最大页码
   "more":true, //是否还有更多
   "first":true, //是否为首页
   "last":false //是否为尾页
}

④ Moment INNER JOIN User LEFT JOIN Comment:
["[]":{
   "join":"&/User/id@,\    "Moment":{},
   "User":{
     "name~":"t",
     "id@":"/Moment/userId"
   },
   "Comment":{
     "momentId@":"/Moment/id"
   }
}](http://apijson.cn:8080/get/{"[]":{"count":5,"join":"&%252FUser%252Fid@,\<%252FComment%252FmomentId@","Moment":{"@column":"id,userId,content"},"User":{"name~":"t","id@":"%252FMoment%252FuserId","@column":"id,name,head"},"Comment":{"momentId@":"%252FMoment%252Fid","@column":"id,momentId,content"}}})

⑤ 每一层都加当前用户名:
["User":{},
"[]":{
   "name@":"User/name", //自定义关键词
   "Moment":{}
}](http://apijson.cn:8080/get/{"User":{},"[]":{"name@":"User%252Fname","Moment":{}}}) - 对象关键词,可自定义 | "@key":Object,@key为 Table:{} 中{}内的关键词,Object的类型由@key指定

① "@combine":"&key0,&key1,\|key2,key3,
!key4,!key5,&key6,key7...",条件组合方式,\| 可省略。会自动把同类的合并,外层按照 & \| ! 顺序,内层的按传参顺序组合成
(key0 & key1 & key6 & 其它key) & (key2 \| key3 \| key7) & !(key4 \| key5)
这种连接方式,其中 "其它key" 是指与 @combine 在同一对象,且未被它声明的条件 key,默认都是 & 连接

② "@column":"column;function(arg)...",返回字段

③ "@order":"column0+,column1-...",排序方式

④ "@group":"column0,column1...",分组方式。如果@column里声明了Table的id,则id也必须在@group中声明;其它情况下必须满足至少一个条件:
1.分组的key在@column里声明
2.Table主键在@group中声明

⑤ "@having":"function0(...)?value0;function1(...)?value1;function2(...)?value2...",SQL函数条件,一般和@group一起用,函数一般在@column里声明

⑥ "@schema":"sys",集合空间(模式),非默认的值可通过它来指定,可以在最外层作为全局默认配置

⑦ "@database":"POSTGRESQL",跨数据库,非默认的值可通过它来指定,可以在最外层作为全局默认配置

⑧ "@json":"key0,key1...",转为 JSON 格式返回,符合 JSONObject 则转为 {...},符合 JSONArray 则转为 \[...]

⑨ "@role":"OWNER",来访角色,包括
UNKNOWN,LOGIN,CONTACT,CIRCLE,OWNER,ADMIN,
可以在最外层作为全局默认配置,
可自定义其它角色并重写 Verifier.verify 等相关方法来自定义校验

⑩ "@explain":true,性能分析,可以在最外层作为全局默认配置

⑪ "@raw":"SQL片段或SQL片段的别名",自定义原始SQL片段,可扩展嵌套SQL函数等复杂语句,必须是后端已配置的,谨慎使用,其它功能符做不到才考虑

⑫ "@otherKey":Object,自定义关键词,名称和以上系统关键词不一样,且原样返回上传的值 | ① 搜索name或tag任何一个字段包含字符a的User列表:
["name~":"a",
"tag~":"a",
"@combine":"name~,tag~"](http://apijson.cn:8080/get/{"User[]":{"count":10,"User":{"@column":"id,name,tag","name~":"a","tag~":"a","@combine":"name~,tag~"}}})
对应SQL是`name REGEXP 'a' OR tag REGEXP 'a'`

② 只查询id,sex,name这几列并且请求结果也按照这个顺序:
["@column":"id,sex,name"](http://apijson.cn:8080/get/{"User":{"@column":"id,sex,name","id":38710}})
对应SQL是`SELECT id,sex,name`

③ 查询按 name降序、id默认顺序 排序的User数组:
["@order":"name-,id"](http://apijson.cn:8080/get/{"[]":{"count":10,"User":{"@column":"name,id","@order":"name-,id"}}})
对应SQL是`ORDER BY name DESC,id`

④ 查询按userId分组的Moment数组:
["@group":"userId,id"](http://apijson.cn:8080/get/{"[]":{"count":10,"Moment":%7B"@column":"userId,id","@group":"userId,id"}}})
对应SQL是`GROUP BY userId,id`

⑤ 查询 按userId分组、id最大值>=100 的Moment数组:
["@column":"userId;max(id)",
"@group":"userId",
"@having":"max(id)>=100"](http://apijson.cn:8080/get/{"[]":{"count":10,"Moment":{"@column":"userId%253Bmax(id)","@group":"userId","@having":"max(id)>=100"}}})
对应SQL是`SELECT userId,max(id) GROUP BY userId HAVING max(id)>=100`
还可以指定函数返回名:
["@column":"userId;max(id):maxId",
"@group":"userId",
"@having":"maxId>=100"](http://apijson.cn:8080/get/{"[]":{"count":10,"Moment":{"@column":"userId%253Bmax(id):maxId","@group":"userId","@having":"maxId>=100"}}})
对应SQL是`SELECT userId,max(id) AS maxId GROUP BY userId HAVING maxId>=100`

⑥ 查询 sys 内的 User 表:
["@schema":"sys"](http://apijson.cn:8080/get/{"User":{"@schema":"sys"}})
对应SQL是`FROM sys.User`

⑦ 查询 PostgreSQL 数据库的 User 表:
["@database":"POSTGRESQL"](http://apijson.cn:8080/get/{"User":{"@database":"POSTGRESQL","@explain":true}})

⑧ 将 VARCHAR 字符串字段 get 转为 JSONArray 返回:
["@json":"get"](http://apijson.cn:8080/get/{"Access":{"@json":"get"}})

⑨ 查询当前用户的动态:
["@role":"OWNER"](http://apijson.cn:8080/get/{"[]":{"Moment":{"@role":"OWNER"}}})

⑩ 开启性能分析:
["@explain":true](http://apijson.cn:8080/get/{"[]":{"Moment":{"@explain":true}}})
对应SQL是`EXPLAIN`

⑪ 统计最近一周偶数userId数量
["@column":"sum(if(userId%2=0,1,0))",
"@having":"to_days(now())-to_days(\`date\`)<=7"](http://apijson.cn:8080/get/{"Comment":{"@column":"sum(if(userId%252=0,1,0))","@having":"to_days(now())-to_days(\`date\`)<=7"}})
对应SQL是`SELECT sum(if(userId%2=0,1,0)) ... HAVING to_days(now())-to_days(``date``)<=7`

⑫ 从pictureList获取第0张图片:
["@position":0, //自定义关键词
"firstPicture()":"getFromArray(pictureList,@position)"](http://apijson.cn:8080/get/{"User":{"id":38710,"@position":0,"firstPicture()":"getFromArray(pictureList,@position)"}}) + 对象关键词,可自定义 | "@key":Object,@key为 Table:{} 中{}内的关键词,Object的类型由@key指定

① "@combine":"&key0,&key1,\|key2,key3,
!key4,!key5,&key6,key7...",条件组合方式,\| 可省略。会自动把同类的合并,外层按照 & \| ! 顺序,内层的按传参顺序组合成
(key0 & key1 & key6 & 其它key) & (key2 \| key3 \| key7) & !(key4 \| key5)
这种连接方式,其中 "其它key" 是指与 @combine 在同一对象,且未被它声明的条件 key,默认都是 & 连接

② "@column":"column;function(arg)...",返回字段

③ "@order":"column0+,column1-...",排序方式

④ "@group":"column0,column1...",分组方式。如果@column里声明了Table的id,则id也必须在@group中声明;其它情况下必须满足至少一个条件:
1.分组的key在@column里声明
2.Table主键在@group中声明

⑤ "@having":"function0(...)?value0;function1(...)?value1;function2(...)?value2...",SQL函数条件,一般和@group一起用,函数一般在@column里声明

⑥ "@schema":"sys",集合空间(模式),非默认的值可通过它来指定,可以在最外层作为全局默认配置

⑦ "@database":"POSTGRESQL",跨数据库,非默认的值可通过它来指定,可以在最外层作为全局默认配置

⑧ "@json":"key0,key1...",转为 JSON 格式返回,符合 JSONObject 则转为 {...},符合 JSONArray 则转为 \[...]

⑨ "@role":"OWNER",来访角色,包括
UNKNOWN,LOGIN,CONTACT,CIRCLE,OWNER,ADMIN,
可以在最外层作为全局默认配置,
可自定义其它角色并重写 Verifier.verify 等相关方法来自定义校验

⑩ "@explain":true,性能分析,可以在最外层作为全局默认配置

⑪ "@raw":"SQL片段或SQL片段的别名",自定义原始SQL片段,可扩展嵌套SQL函数等复杂语句,必须是后端已配置的,谨慎使用,其它功能符做不到才考虑

⑫ "@otherKey":Object,自定义关键词,名称和以上系统关键词不一样,且原样返回上传的值 | ① 搜索name或tag任何一个字段包含字符a的User列表:
["name~":"a",
"tag~":"a",
"@combine":"name~,tag~"](http://apijson.cn:8080/get/{"User[]":{"count":10,"User":{"@column":"id,name,tag","name~":"a","tag~":"a","@combine":"name~,tag~"}}})
对应SQL是`name REGEXP 'a' OR tag REGEXP 'a'`

② 只查询id,sex,name这几列并且请求结果也按照这个顺序:
["@column":"id,sex,name"](http://apijson.cn:8080/get/{"User":{"@column":"id,sex,name","id":38710}})
对应SQL是`SELECT id,sex,name`

③ 查询按 name降序、id默认顺序 排序的User数组:
["@order":"name-,id"](http://apijson.cn:8080/get/{"[]":{"count":10,"User":{"@column":"name,id","@order":"name-,id"}}})
对应SQL是`ORDER BY name DESC,id`

④ 查询按userId分组的Moment数组:
["@group":"userId,id"](http://apijson.cn:8080/get/{"[]":{"count":10,"Moment":%7B"@column":"userId,id","@group":"userId,id"}}})
对应SQL是`GROUP BY userId,id`

⑤ 查询 按userId分组、id最大值>=100 的Moment数组:
["@column":"userId;max(id)",
"@group":"userId",
"@having":"max(id)>=100"](http://apijson.cn:8080/get/{"[]":{"count":10,"Moment":{"@column":"userId%253Bmax(id)","@group":"userId","@having":"max(id)>=100"}}})
对应SQL是`SELECT userId,max(id) GROUP BY userId HAVING max(id)>=100`
还可以指定函数返回名:
["@column":"userId;max(id):maxId",
"@group":"userId",
"@having":"maxId>=100"](http://apijson.cn:8080/get/{"[]":{"count":10,"Moment":{"@column":"userId%253Bmax(id):maxId","@group":"userId","@having":"maxId>=100"}}})
对应SQL是`SELECT userId,max(id) AS maxId GROUP BY userId HAVING maxId>=100`

⑥ 查询 sys 内的 User 表:
["@schema":"sys"](http://apijson.cn:8080/get/{"User":{"@schema":"sys"}})
对应SQL是`FROM sys.User`

⑦ 查询 PostgreSQL 数据库的 User 表:
["@database":"POSTGRESQL"](http://apijson.cn:8080/get/{"User":{"@database":"POSTGRESQL","@explain":true}})

⑧ 将 VARCHAR 字符串字段 get 转为 JSONArray 返回:
["@json":"get"](http://apijson.cn:8080/get/{"Access":{"@json":"get"}})

⑨ 查询当前用户的动态:
["@role":"OWNER"](http://apijson.cn:8080/get/{"[]":{"Moment":{"@role":"OWNER"}}})

⑩ 开启性能分析:
["@explain":true](http://apijson.cn:8080/get/{"[]":{"Moment":{"@explain":true}}})
对应SQL是`EXPLAIN`

⑪ 统计最近一周偶数userId数量
["@column":"sum(if(userId%2=0,1,0))",
"@having":"to_days(now())-to_days(\`date\`)<=7"](http://apijson.cn:8080/get/{"Comment":{"@column":"sum(if(userId%252=0,1,0))","@having":"to_days(now())-to_days(\`date\`)<=7"}})
对应SQL是`SELECT sum(if(userId%2=0,1,0)) ... HAVING to_days(now())-to_days(`date`)<=7`

⑫ 从pictureList获取第0张图片:
["@position":0, //自定义关键词
"firstPicture()":"getFromArray(pictureList,@position)"](http://apijson.cn:8080/get/{"User":{"id":38710,"@position":0,"firstPicture()":"getFromArray(pictureList,@position)"}})
From 65568cd46972d39b5014d696bb50f0a48fa45206 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Tue, 22 Dec 2020 00:32:02 +0800 Subject: [PATCH 0112/1181] Update Document.md --- Document.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Document.md b/Document.md index 0f4426b5d..a4bd58e2e 100644 --- a/Document.md +++ b/Document.md @@ -370,6 +370,6 @@ DELETE:
删除数据 | base_url/delete/ | {
   TableName:{< 比较运算 | >, <, >=, <= 比较运算符,用于
① 提供 "id{}":"<=90000" 这种条件范围的简化写法

② 实现子查询相关比较运算

不支持 "key=":Object 和 "key!=":Object 这两种写法,直接用更简单的 "key":Object 和 "key!":Object 替代。 | ① ["id<=":90000](http://apijson.cn:8080/get/{"[]":{"User":{"id<=":90000}}}),对应SQL是`id<=90000`,查询符合id<=90000的一个User数组

② ["id>@":{
   "from":"Comment",
   "Comment":{
      "@column":"min(userId)"
   }
}](http://apijson.cn:8080/get/{"User":{"id>@":{"from":"Comment","Comment":{"@column":"min(userId)"}}}})
WHERE id>(SELECT min(userId) FROM Comment) 逻辑运算 | &, \|, ! 逻辑运算符,对应数据库 SQL 中的 AND, OR, NOT。
横或纵与:同一键值对的值内条件默认 \| 或连接,不同键值对的条件默认 & 与连接。

① & 可用于"key&{}":"条件"等

② \| 可用于"key\|{}":"条件", "key\|{}":[]等,一般可省略

③ ! 可单独使用,如"key!":Object,也可像&,\|一样配合其他功能符使用
"key!":null 无效,null 值会导致整个键值对被忽略解析,可以用 "key{}":"!=null" 替代,
"key":null 同理,用 "key{}":"=null" 替代。 | ① ["id&{}":">80000,<=90000"](http://apijson.cn:8080/head/{"User":{"id&{}":">80000,<=90000"}}),对应SQL是`id>80000 AND id<=90000`,即id满足id>80000 & id<=90000

② ["id\|{}":">90000,<=80000"](http://apijson.cn:8080/head/{"User":{"id\|{}":">90000,<=80000"}}),同"id{}":">90000,<=80000",对应SQL是`id>80000 OR id<=90000`,即id满足id>90000 \| id<=80000

③ ["id!{}":[82001,38710]](http://apijson.cn:8080/head/{"User":{"id!{}":[82001,38710]}}),对应SQL是`id NOT IN(82001,38710)`,即id满足 ! (id=82001 \| id=38710),可过滤黑名单的消息 数组关键词,可自定义 | "key":Object,key为 "[]":{} 中{}内的关键词,Object的类型由key指定

① "count":Integer,查询数量,0 表示最大值,默认最大值为100

② "page":Integer,查询页码,从0开始,默认最大值为100,一般和count一起用

③ "query":Integer,查询内容
0-对象,1-总数和分页详情,2-以上全部
总数关键词为 total,分页详情关键词为 info,
它们都和 query 同级,通过引用赋值得到,例如
"total@":"/[]/total", "info@":"/[]/info"
这里query及total仅为GET类型的请求提供方便,
一般可直接用HEAD类型的请求获取总数

④ "join":"&/Table0/key0@,\多表连接方式:
"\<" - LEFT JOIN
">" - RIGHT JOIN
"&" - INNER JOIN
"\|" - FULL JOIN
"!" - OUTTER JOIN
"@" - APP JOIN
其中 @ APP JOIN 为应用层连表,会从已查出的主表里取得所有副表 key@ 关联的主表内的 refKey 作为一个数组 refKeys: [value0, value1...],然后把原来副表 count 次查询 key=$refKey 的 SQL 用 key IN($refKeys) 的方式合并为一条 SQL 来优化性能;
其它 JOIN 都是 SQL JOIN,具体功能和 MySQL,PostgreSQL 等数据库的 JOIN 一一对应
`"join":"`"MainTable":{},`
`"ViceTable":{"key@":"/MainTable/refKey"}`
会对应生成
`MainTable LEFT JOIN ViceTable`
`ON ViceTable.key=MainTable.refKey`

⑤ "otherKey":Object,自定义关键词,名称和以上系统关键词不一样,且原样返回上传的值 | ① 查询User数组,最多5个:
["count":5](http://apijson.cn:8080/get/{"[]":{"count":5,"User":{}}})
对应SQL是`LIMIT 5`

② 查询第3页的User数组,每页5个:
["count":5,
"page":3](http://apijson.cn:8080/get/{"[]":{"count":5,"page":3,"User":{}}})
对应SQL是`LIMIT 5 OFFSET 15`

③ 查询User数组和对应的User总数:
["[]":{
   "query":2,
   "User":{}
},
"total@":"/[]/total",
"info@":"/[]/info"](http://apijson.cn:8080/get/{"[]":{"query":2,"count":5,"User":{}},"total@":"%252F[]%252Ftotal","info@":"%252F[]%252Finfo"})
返回的数据中,总数及分页详情结构为:
"total":139, //总数
"info":{ //分页详情
   "total":139, //总数
   "count":5, //每页数量
   "page":0, //当前页码
   "max":27, //最大页码
   "more":true, //是否还有更多
   "first":true, //是否为首页
   "last":false //是否为尾页
}

④ Moment INNER JOIN User LEFT JOIN Comment:
["[]":{
   "join":"&/User/id@,\    "Moment":{},
   "User":{
     "name~":"t",
     "id@":"/Moment/userId"
   },
   "Comment":{
     "momentId@":"/Moment/id"
   }
}](http://apijson.cn:8080/get/{"[]":{"count":5,"join":"&%252FUser%252Fid@,\<%252FComment%252FmomentId@","Moment":{"@column":"id,userId,content"},"User":{"name~":"t","id@":"%252FMoment%252FuserId","@column":"id,name,head"},"Comment":{"momentId@":"%252FMoment%252Fid","@column":"id,momentId,content"}}})

⑤ 每一层都加当前用户名:
["User":{},
"[]":{
   "name@":"User/name", //自定义关键词
   "Moment":{}
}](http://apijson.cn:8080/get/{"User":{},"[]":{"name@":"User%252Fname","Moment":{}}}) - 对象关键词,可自定义 | "@key":Object,@key为 Table:{} 中{}内的关键词,Object的类型由@key指定

① "@combine":"&key0,&key1,\|key2,key3,
!key4,!key5,&key6,key7...",条件组合方式,\| 可省略。会自动把同类的合并,外层按照 & \| ! 顺序,内层的按传参顺序组合成
(key0 & key1 & key6 & 其它key) & (key2 \| key3 \| key7) & !(key4 \| key5)
这种连接方式,其中 "其它key" 是指与 @combine 在同一对象,且未被它声明的条件 key,默认都是 & 连接

② "@column":"column;function(arg)...",返回字段

③ "@order":"column0+,column1-...",排序方式

④ "@group":"column0,column1...",分组方式。如果@column里声明了Table的id,则id也必须在@group中声明;其它情况下必须满足至少一个条件:
1.分组的key在@column里声明
2.Table主键在@group中声明

⑤ "@having":"function0(...)?value0;function1(...)?value1;function2(...)?value2...",SQL函数条件,一般和@group一起用,函数一般在@column里声明

⑥ "@schema":"sys",集合空间(模式),非默认的值可通过它来指定,可以在最外层作为全局默认配置

⑦ "@database":"POSTGRESQL",跨数据库,非默认的值可通过它来指定,可以在最外层作为全局默认配置

⑧ "@json":"key0,key1...",转为 JSON 格式返回,符合 JSONObject 则转为 {...},符合 JSONArray 则转为 \[...]

⑨ "@role":"OWNER",来访角色,包括
UNKNOWN,LOGIN,CONTACT,CIRCLE,OWNER,ADMIN,
可以在最外层作为全局默认配置,
可自定义其它角色并重写 Verifier.verify 等相关方法来自定义校验

⑩ "@explain":true,性能分析,可以在最外层作为全局默认配置

⑪ "@raw":"SQL片段或SQL片段的别名",自定义原始SQL片段,可扩展嵌套SQL函数等复杂语句,必须是后端已配置的,谨慎使用,其它功能符做不到才考虑

⑫ "@otherKey":Object,自定义关键词,名称和以上系统关键词不一样,且原样返回上传的值 | ① 搜索name或tag任何一个字段包含字符a的User列表:
["name~":"a",
"tag~":"a",
"@combine":"name~,tag~"](http://apijson.cn:8080/get/{"User[]":{"count":10,"User":{"@column":"id,name,tag","name~":"a","tag~":"a","@combine":"name~,tag~"}}})
对应SQL是`name REGEXP 'a' OR tag REGEXP 'a'`

② 只查询id,sex,name这几列并且请求结果也按照这个顺序:
["@column":"id,sex,name"](http://apijson.cn:8080/get/{"User":{"@column":"id,sex,name","id":38710}})
对应SQL是`SELECT id,sex,name`

③ 查询按 name降序、id默认顺序 排序的User数组:
["@order":"name-,id"](http://apijson.cn:8080/get/{"[]":{"count":10,"User":{"@column":"name,id","@order":"name-,id"}}})
对应SQL是`ORDER BY name DESC,id`

④ 查询按userId分组的Moment数组:
["@group":"userId,id"](http://apijson.cn:8080/get/{"[]":{"count":10,"Moment":%7B"@column":"userId,id","@group":"userId,id"}}})
对应SQL是`GROUP BY userId,id`

⑤ 查询 按userId分组、id最大值>=100 的Moment数组:
["@column":"userId;max(id)",
"@group":"userId",
"@having":"max(id)>=100"](http://apijson.cn:8080/get/{"[]":{"count":10,"Moment":{"@column":"userId%253Bmax(id)","@group":"userId","@having":"max(id)>=100"}}})
对应SQL是`SELECT userId,max(id) GROUP BY userId HAVING max(id)>=100`
还可以指定函数返回名:
["@column":"userId;max(id):maxId",
"@group":"userId",
"@having":"maxId>=100"](http://apijson.cn:8080/get/{"[]":{"count":10,"Moment":{"@column":"userId%253Bmax(id):maxId","@group":"userId","@having":"maxId>=100"}}})
对应SQL是`SELECT userId,max(id) AS maxId GROUP BY userId HAVING maxId>=100`

⑥ 查询 sys 内的 User 表:
["@schema":"sys"](http://apijson.cn:8080/get/{"User":{"@schema":"sys"}})
对应SQL是`FROM sys.User`

⑦ 查询 PostgreSQL 数据库的 User 表:
["@database":"POSTGRESQL"](http://apijson.cn:8080/get/{"User":{"@database":"POSTGRESQL","@explain":true}})

⑧ 将 VARCHAR 字符串字段 get 转为 JSONArray 返回:
["@json":"get"](http://apijson.cn:8080/get/{"Access":{"@json":"get"}})

⑨ 查询当前用户的动态:
["@role":"OWNER"](http://apijson.cn:8080/get/{"[]":{"Moment":{"@role":"OWNER"}}})

⑩ 开启性能分析:
["@explain":true](http://apijson.cn:8080/get/{"[]":{"Moment":{"@explain":true}}})
对应SQL是`EXPLAIN`

⑪ 统计最近一周偶数userId数量
["@column":"sum(if(userId%2=0,1,0))",
"@having":"to_days(now())-to_days(\`date\`)<=7"](http://apijson.cn:8080/get/{"Comment":{"@column":"sum(if(userId%252=0,1,0))","@having":"to_days(now())-to_days(\`date\`)<=7"}})
对应SQL是`SELECT sum(if(userId%2=0,1,0)) ... HAVING to_days(now())-to_days(`date`)<=7`

⑫ 从pictureList获取第0张图片:
["@position":0, //自定义关键词
"firstPicture()":"getFromArray(pictureList,@position)"](http://apijson.cn:8080/get/{"User":{"id":38710,"@position":0,"firstPicture()":"getFromArray(pictureList,@position)"}}) + 对象关键词,可自定义 | "@key":Object,@key为 Table:{} 中{}内的关键词,Object的类型由@key指定

① "@combine":"&key0,&key1,\|key2,key3,
!key4,!key5,&key6,key7...",条件组合方式,\| 可省略。会自动把同类的合并,外层按照 & \| ! 顺序,内层的按传参顺序组合成
(key0 & key1 & key6 & 其它key) & (key2 \| key3 \| key7) & !(key4 \| key5)
这种连接方式,其中 "其它key" 是指与 @combine 在同一对象,且未被它声明的条件 key,默认都是 & 连接

② "@column":"column;function(arg)...",返回字段

③ "@order":"column0+,column1-...",排序方式

④ "@group":"column0,column1...",分组方式。如果@column里声明了Table的id,则id也必须在@group中声明;其它情况下必须满足至少一个条件:
1.分组的key在@column里声明
2.Table主键在@group中声明

⑤ "@having":"function0(...)?value0;function1(...)?value1;function2(...)?value2...",SQL函数条件,一般和@group一起用,函数一般在@column里声明

⑥ "@schema":"sys",集合空间(模式),非默认的值可通过它来指定,可以在最外层作为全局默认配置

⑦ "@database":"POSTGRESQL",跨数据库,非默认的值可通过它来指定,可以在最外层作为全局默认配置

⑧ "@json":"key0,key1...",转为 JSON 格式返回,符合 JSONObject 则转为 {...},符合 JSONArray 则转为 \[...]

⑨ "@role":"OWNER",来访角色,包括
UNKNOWN,LOGIN,CONTACT,CIRCLE,OWNER,ADMIN,
可以在最外层作为全局默认配置,
可自定义其它角色并重写 Verifier.verify 等相关方法来自定义校验

⑩ "@explain":true,性能分析,可以在最外层作为全局默认配置

⑪ "@raw":"SQL片段或SQL片段的别名",自定义原始SQL片段,可扩展嵌套SQL函数等复杂语句,必须是后端已配置的,谨慎使用,其它功能符做不到才考虑

⑫ "@otherKey":Object,自定义关键词,名称和以上系统关键词不一样,且原样返回上传的值 | ① 搜索name或tag任何一个字段包含字符a的User列表:
["name~":"a",
"tag~":"a",
"@combine":"name~,tag~"](http://apijson.cn:8080/get/{"User[]":{"count":10,"User":{"@column":"id,name,tag","name~":"a","tag~":"a","@combine":"name~,tag~"}}})
对应SQL是`name REGEXP 'a' OR tag REGEXP 'a'`

② 只查询id,sex,name这几列并且请求结果也按照这个顺序:
["@column":"id,sex,name"](http://apijson.cn:8080/get/{"User":{"@column":"id,sex,name","id":38710}})
对应SQL是`SELECT id,sex,name`

③ 查询按 name降序、id默认顺序 排序的User数组:
["@order":"name-,id"](http://apijson.cn:8080/get/{"[]":{"count":10,"User":{"@column":"name,id","@order":"name-,id"}}})
对应SQL是`ORDER BY name DESC,id`

④ 查询按userId分组的Moment数组:
["@group":"userId,id"](http://apijson.cn:8080/get/{"[]":{"count":10,"Moment":%7B"@column":"userId,id","@group":"userId,id"}}})
对应SQL是`GROUP BY userId,id`

⑤ 查询 按userId分组、id最大值>=100 的Moment数组:
["@column":"userId;max(id)",
"@group":"userId",
"@having":"max(id)>=100"](http://apijson.cn:8080/get/{"[]":{"count":10,"Moment":{"@column":"userId%253Bmax(id)","@group":"userId","@having":"max(id)>=100"}}})
对应SQL是`SELECT userId,max(id) GROUP BY userId HAVING max(id)>=100`
还可以指定函数返回名:
["@column":"userId;max(id):maxId",
"@group":"userId",
"@having":"maxId>=100"](http://apijson.cn:8080/get/{"[]":{"count":10,"Moment":{"@column":"userId%253Bmax(id):maxId","@group":"userId","@having":"maxId>=100"}}})
对应SQL是`SELECT userId,max(id) AS maxId GROUP BY userId HAVING maxId>=100`

⑥ 查询 sys 内的 User 表:
["@schema":"sys"](http://apijson.cn:8080/get/{"User":{"@schema":"sys"}})
对应SQL是`FROM sys.User`

⑦ 查询 PostgreSQL 数据库的 User 表:
["@database":"POSTGRESQL"](http://apijson.cn:8080/get/{"User":{"@database":"POSTGRESQL","@explain":true}})

⑧ 将 VARCHAR 字符串字段 get 转为 JSONArray 返回:
["@json":"get"](http://apijson.cn:8080/get/{"Access":{"@json":"get"}})

⑨ 查询当前用户的动态:
["@role":"OWNER"](http://apijson.cn:8080/get/{"[]":{"Moment":{"@role":"OWNER"}}})

⑩ 开启性能分析:
["@explain":true](http://apijson.cn:8080/get/{"[]":{"Moment":{"@explain":true}}})
对应SQL是`EXPLAIN`

⑪ 统计最近一周偶数userId数量
["@column":"sum(if(userId%2=0,1,0))",
"@having":"to_days(now())-to_days(\`date\`)<=7"](http://apijson.cn:8080/get/{"Comment":{"@column":"sum(if(userId%252=0,1,0))","@having":"to_days(now())-to_days(\`date\`)<=7"}})
对应SQL是`SELECT sum(if(userId%2=0,1,0)) ... HAVING to_days(now())-to_days(`\``date`\``)<=7`

⑫ 从pictureList获取第0张图片:
["@position":0, //自定义关键词
"firstPicture()":"getFromArray(pictureList,@position)"](http://apijson.cn:8080/get/{"User":{"id":38710,"@position":0,"firstPicture()":"getFromArray(pictureList,@position)"}})
From dd5a0f372963ddcd8e1096801b7e4204ed68ba54 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Tue, 22 Dec 2020 00:32:59 +0800 Subject: [PATCH 0113/1181] Update Document.md --- Document.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Document.md b/Document.md index a4bd58e2e..8ab2f9ce0 100644 --- a/Document.md +++ b/Document.md @@ -370,6 +370,6 @@ DELETE:
删除数据 | base_url/delete/ | {
   TableName:{< 比较运算 | >, <, >=, <= 比较运算符,用于
① 提供 "id{}":"<=90000" 这种条件范围的简化写法

② 实现子查询相关比较运算

不支持 "key=":Object 和 "key!=":Object 这两种写法,直接用更简单的 "key":Object 和 "key!":Object 替代。 | ① ["id<=":90000](http://apijson.cn:8080/get/{"[]":{"User":{"id<=":90000}}}),对应SQL是`id<=90000`,查询符合id<=90000的一个User数组

② ["id>@":{
   "from":"Comment",
   "Comment":{
      "@column":"min(userId)"
   }
}](http://apijson.cn:8080/get/{"User":{"id>@":{"from":"Comment","Comment":{"@column":"min(userId)"}}}})
WHERE id>(SELECT min(userId) FROM Comment) 逻辑运算 | &, \|, ! 逻辑运算符,对应数据库 SQL 中的 AND, OR, NOT。
横或纵与:同一键值对的值内条件默认 \| 或连接,不同键值对的条件默认 & 与连接。

① & 可用于"key&{}":"条件"等

② \| 可用于"key\|{}":"条件", "key\|{}":[]等,一般可省略

③ ! 可单独使用,如"key!":Object,也可像&,\|一样配合其他功能符使用
"key!":null 无效,null 值会导致整个键值对被忽略解析,可以用 "key{}":"!=null" 替代,
"key":null 同理,用 "key{}":"=null" 替代。 | ① ["id&{}":">80000,<=90000"](http://apijson.cn:8080/head/{"User":{"id&{}":">80000,<=90000"}}),对应SQL是`id>80000 AND id<=90000`,即id满足id>80000 & id<=90000

② ["id\|{}":">90000,<=80000"](http://apijson.cn:8080/head/{"User":{"id\|{}":">90000,<=80000"}}),同"id{}":">90000,<=80000",对应SQL是`id>80000 OR id<=90000`,即id满足id>90000 \| id<=80000

③ ["id!{}":[82001,38710]](http://apijson.cn:8080/head/{"User":{"id!{}":[82001,38710]}}),对应SQL是`id NOT IN(82001,38710)`,即id满足 ! (id=82001 \| id=38710),可过滤黑名单的消息 数组关键词,可自定义 | "key":Object,key为 "[]":{} 中{}内的关键词,Object的类型由key指定

① "count":Integer,查询数量,0 表示最大值,默认最大值为100

② "page":Integer,查询页码,从0开始,默认最大值为100,一般和count一起用

③ "query":Integer,查询内容
0-对象,1-总数和分页详情,2-以上全部
总数关键词为 total,分页详情关键词为 info,
它们都和 query 同级,通过引用赋值得到,例如
"total@":"/[]/total", "info@":"/[]/info"
这里query及total仅为GET类型的请求提供方便,
一般可直接用HEAD类型的请求获取总数

④ "join":"&/Table0/key0@,\多表连接方式:
"\<" - LEFT JOIN
">" - RIGHT JOIN
"&" - INNER JOIN
"\|" - FULL JOIN
"!" - OUTTER JOIN
"@" - APP JOIN
其中 @ APP JOIN 为应用层连表,会从已查出的主表里取得所有副表 key@ 关联的主表内的 refKey 作为一个数组 refKeys: [value0, value1...],然后把原来副表 count 次查询 key=$refKey 的 SQL 用 key IN($refKeys) 的方式合并为一条 SQL 来优化性能;
其它 JOIN 都是 SQL JOIN,具体功能和 MySQL,PostgreSQL 等数据库的 JOIN 一一对应
`"join":"`"MainTable":{},`
`"ViceTable":{"key@":"/MainTable/refKey"}`
会对应生成
`MainTable LEFT JOIN ViceTable`
`ON ViceTable.key=MainTable.refKey`

⑤ "otherKey":Object,自定义关键词,名称和以上系统关键词不一样,且原样返回上传的值 | ① 查询User数组,最多5个:
["count":5](http://apijson.cn:8080/get/{"[]":{"count":5,"User":{}}})
对应SQL是`LIMIT 5`

② 查询第3页的User数组,每页5个:
["count":5,
"page":3](http://apijson.cn:8080/get/{"[]":{"count":5,"page":3,"User":{}}})
对应SQL是`LIMIT 5 OFFSET 15`

③ 查询User数组和对应的User总数:
["[]":{
   "query":2,
   "User":{}
},
"total@":"/[]/total",
"info@":"/[]/info"](http://apijson.cn:8080/get/{"[]":{"query":2,"count":5,"User":{}},"total@":"%252F[]%252Ftotal","info@":"%252F[]%252Finfo"})
返回的数据中,总数及分页详情结构为:
"total":139, //总数
"info":{ //分页详情
   "total":139, //总数
   "count":5, //每页数量
   "page":0, //当前页码
   "max":27, //最大页码
   "more":true, //是否还有更多
   "first":true, //是否为首页
   "last":false //是否为尾页
}

④ Moment INNER JOIN User LEFT JOIN Comment:
["[]":{
   "join":"&/User/id@,\    "Moment":{},
   "User":{
     "name~":"t",
     "id@":"/Moment/userId"
   },
   "Comment":{
     "momentId@":"/Moment/id"
   }
}](http://apijson.cn:8080/get/{"[]":{"count":5,"join":"&%252FUser%252Fid@,\<%252FComment%252FmomentId@","Moment":{"@column":"id,userId,content"},"User":{"name~":"t","id@":"%252FMoment%252FuserId","@column":"id,name,head"},"Comment":{"momentId@":"%252FMoment%252Fid","@column":"id,momentId,content"}}})

⑤ 每一层都加当前用户名:
["User":{},
"[]":{
   "name@":"User/name", //自定义关键词
   "Moment":{}
}](http://apijson.cn:8080/get/{"User":{},"[]":{"name@":"User%252Fname","Moment":{}}}) - 对象关键词,可自定义 | "@key":Object,@key为 Table:{} 中{}内的关键词,Object的类型由@key指定

① "@combine":"&key0,&key1,\|key2,key3,
!key4,!key5,&key6,key7...",条件组合方式,\| 可省略。会自动把同类的合并,外层按照 & \| ! 顺序,内层的按传参顺序组合成
(key0 & key1 & key6 & 其它key) & (key2 \| key3 \| key7) & !(key4 \| key5)
这种连接方式,其中 "其它key" 是指与 @combine 在同一对象,且未被它声明的条件 key,默认都是 & 连接

② "@column":"column;function(arg)...",返回字段

③ "@order":"column0+,column1-...",排序方式

④ "@group":"column0,column1...",分组方式。如果@column里声明了Table的id,则id也必须在@group中声明;其它情况下必须满足至少一个条件:
1.分组的key在@column里声明
2.Table主键在@group中声明

⑤ "@having":"function0(...)?value0;function1(...)?value1;function2(...)?value2...",SQL函数条件,一般和@group一起用,函数一般在@column里声明

⑥ "@schema":"sys",集合空间(模式),非默认的值可通过它来指定,可以在最外层作为全局默认配置

⑦ "@database":"POSTGRESQL",跨数据库,非默认的值可通过它来指定,可以在最外层作为全局默认配置

⑧ "@json":"key0,key1...",转为 JSON 格式返回,符合 JSONObject 则转为 {...},符合 JSONArray 则转为 \[...]

⑨ "@role":"OWNER",来访角色,包括
UNKNOWN,LOGIN,CONTACT,CIRCLE,OWNER,ADMIN,
可以在最外层作为全局默认配置,
可自定义其它角色并重写 Verifier.verify 等相关方法来自定义校验

⑩ "@explain":true,性能分析,可以在最外层作为全局默认配置

⑪ "@raw":"SQL片段或SQL片段的别名",自定义原始SQL片段,可扩展嵌套SQL函数等复杂语句,必须是后端已配置的,谨慎使用,其它功能符做不到才考虑

⑫ "@otherKey":Object,自定义关键词,名称和以上系统关键词不一样,且原样返回上传的值 | ① 搜索name或tag任何一个字段包含字符a的User列表:
["name~":"a",
"tag~":"a",
"@combine":"name~,tag~"](http://apijson.cn:8080/get/{"User[]":{"count":10,"User":{"@column":"id,name,tag","name~":"a","tag~":"a","@combine":"name~,tag~"}}})
对应SQL是`name REGEXP 'a' OR tag REGEXP 'a'`

② 只查询id,sex,name这几列并且请求结果也按照这个顺序:
["@column":"id,sex,name"](http://apijson.cn:8080/get/{"User":{"@column":"id,sex,name","id":38710}})
对应SQL是`SELECT id,sex,name`

③ 查询按 name降序、id默认顺序 排序的User数组:
["@order":"name-,id"](http://apijson.cn:8080/get/{"[]":{"count":10,"User":{"@column":"name,id","@order":"name-,id"}}})
对应SQL是`ORDER BY name DESC,id`

④ 查询按userId分组的Moment数组:
["@group":"userId,id"](http://apijson.cn:8080/get/{"[]":{"count":10,"Moment":%7B"@column":"userId,id","@group":"userId,id"}}})
对应SQL是`GROUP BY userId,id`

⑤ 查询 按userId分组、id最大值>=100 的Moment数组:
["@column":"userId;max(id)",
"@group":"userId",
"@having":"max(id)>=100"](http://apijson.cn:8080/get/{"[]":{"count":10,"Moment":{"@column":"userId%253Bmax(id)","@group":"userId","@having":"max(id)>=100"}}})
对应SQL是`SELECT userId,max(id) GROUP BY userId HAVING max(id)>=100`
还可以指定函数返回名:
["@column":"userId;max(id):maxId",
"@group":"userId",
"@having":"maxId>=100"](http://apijson.cn:8080/get/{"[]":{"count":10,"Moment":{"@column":"userId%253Bmax(id):maxId","@group":"userId","@having":"maxId>=100"}}})
对应SQL是`SELECT userId,max(id) AS maxId GROUP BY userId HAVING maxId>=100`

⑥ 查询 sys 内的 User 表:
["@schema":"sys"](http://apijson.cn:8080/get/{"User":{"@schema":"sys"}})
对应SQL是`FROM sys.User`

⑦ 查询 PostgreSQL 数据库的 User 表:
["@database":"POSTGRESQL"](http://apijson.cn:8080/get/{"User":{"@database":"POSTGRESQL","@explain":true}})

⑧ 将 VARCHAR 字符串字段 get 转为 JSONArray 返回:
["@json":"get"](http://apijson.cn:8080/get/{"Access":{"@json":"get"}})

⑨ 查询当前用户的动态:
["@role":"OWNER"](http://apijson.cn:8080/get/{"[]":{"Moment":{"@role":"OWNER"}}})

⑩ 开启性能分析:
["@explain":true](http://apijson.cn:8080/get/{"[]":{"Moment":{"@explain":true}}})
对应SQL是`EXPLAIN`

⑪ 统计最近一周偶数userId数量
["@column":"sum(if(userId%2=0,1,0))",
"@having":"to_days(now())-to_days(\`date\`)<=7"](http://apijson.cn:8080/get/{"Comment":{"@column":"sum(if(userId%252=0,1,0))","@having":"to_days(now())-to_days(\`date\`)<=7"}})
对应SQL是`SELECT sum(if(userId%2=0,1,0)) ... HAVING to_days(now())-to_days(`\``date`\``)<=7`

⑫ 从pictureList获取第0张图片:
["@position":0, //自定义关键词
"firstPicture()":"getFromArray(pictureList,@position)"](http://apijson.cn:8080/get/{"User":{"id":38710,"@position":0,"firstPicture()":"getFromArray(pictureList,@position)"}}) + 对象关键词,可自定义 | "@key":Object,@key为 Table:{} 中{}内的关键词,Object的类型由@key指定

① "@combine":"&key0,&key1,\|key2,key3,
!key4,!key5,&key6,key7...",条件组合方式,\| 可省略。会自动把同类的合并,外层按照 & \| ! 顺序,内层的按传参顺序组合成
(key0 & key1 & key6 & 其它key) & (key2 \| key3 \| key7) & !(key4 \| key5)
这种连接方式,其中 "其它key" 是指与 @combine 在同一对象,且未被它声明的条件 key,默认都是 & 连接

② "@column":"column;function(arg)...",返回字段

③ "@order":"column0+,column1-...",排序方式

④ "@group":"column0,column1...",分组方式。如果@column里声明了Table的id,则id也必须在@group中声明;其它情况下必须满足至少一个条件:
1.分组的key在@column里声明
2.Table主键在@group中声明

⑤ "@having":"function0(...)?value0;function1(...)?value1;function2(...)?value2...",SQL函数条件,一般和@group一起用,函数一般在@column里声明

⑥ "@schema":"sys",集合空间(模式),非默认的值可通过它来指定,可以在最外层作为全局默认配置

⑦ "@database":"POSTGRESQL",跨数据库,非默认的值可通过它来指定,可以在最外层作为全局默认配置

⑧ "@json":"key0,key1...",转为 JSON 格式返回,符合 JSONObject 则转为 {...},符合 JSONArray 则转为 \[...]

⑨ "@role":"OWNER",来访角色,包括
UNKNOWN,LOGIN,CONTACT,CIRCLE,OWNER,ADMIN,
可以在最外层作为全局默认配置,
可自定义其它角色并重写 Verifier.verify 等相关方法来自定义校验

⑩ "@explain":true,性能分析,可以在最外层作为全局默认配置

⑪ "@raw":"SQL片段或SQL片段的别名",自定义原始SQL片段,可扩展嵌套SQL函数等复杂语句,必须是后端已配置的,谨慎使用,其它功能符做不到才考虑

⑫ "@otherKey":Object,自定义关键词,名称和以上系统关键词不一样,且原样返回上传的值 | ① 搜索name或tag任何一个字段包含字符a的User列表:
["name~":"a",
"tag~":"a",
"@combine":"name~,tag~"](http://apijson.cn:8080/get/{"User[]":{"count":10,"User":{"@column":"id,name,tag","name~":"a","tag~":"a","@combine":"name~,tag~"}}})
对应SQL是`name REGEXP 'a' OR tag REGEXP 'a'`

② 只查询id,sex,name这几列并且请求结果也按照这个顺序:
["@column":"id,sex,name"](http://apijson.cn:8080/get/{"User":{"@column":"id,sex,name","id":38710}})
对应SQL是`SELECT id,sex,name`

③ 查询按 name降序、id默认顺序 排序的User数组:
["@order":"name-,id"](http://apijson.cn:8080/get/{"[]":{"count":10,"User":{"@column":"name,id","@order":"name-,id"}}})
对应SQL是`ORDER BY name DESC,id`

④ 查询按userId分组的Moment数组:
["@group":"userId,id"](http://apijson.cn:8080/get/{"[]":{"count":10,"Moment":%7B"@column":"userId,id","@group":"userId,id"}}})
对应SQL是`GROUP BY userId,id`

⑤ 查询 按userId分组、id最大值>=100 的Moment数组:
["@column":"userId;max(id)",
"@group":"userId",
"@having":"max(id)>=100"](http://apijson.cn:8080/get/{"[]":{"count":10,"Moment":{"@column":"userId%253Bmax(id)","@group":"userId","@having":"max(id)>=100"}}})
对应SQL是`SELECT userId,max(id) GROUP BY userId HAVING max(id)>=100`
还可以指定函数返回名:
["@column":"userId;max(id):maxId",
"@group":"userId",
"@having":"maxId>=100"](http://apijson.cn:8080/get/{"[]":{"count":10,"Moment":{"@column":"userId%253Bmax(id):maxId","@group":"userId","@having":"maxId>=100"}}})
对应SQL是`SELECT userId,max(id) AS maxId GROUP BY userId HAVING maxId>=100`

⑥ 查询 sys 内的 User 表:
["@schema":"sys"](http://apijson.cn:8080/get/{"User":{"@schema":"sys"}})
对应SQL是`FROM sys.User`

⑦ 查询 PostgreSQL 数据库的 User 表:
["@database":"POSTGRESQL"](http://apijson.cn:8080/get/{"User":{"@database":"POSTGRESQL","@explain":true}})

⑧ 将 VARCHAR 字符串字段 get 转为 JSONArray 返回:
["@json":"get"](http://apijson.cn:8080/get/{"Access":{"@json":"get"}})

⑨ 查询当前用户的动态:
["@role":"OWNER"](http://apijson.cn:8080/get/{"[]":{"Moment":{"@role":"OWNER"}}})

⑩ 开启性能分析:
["@explain":true](http://apijson.cn:8080/get/{"[]":{"Moment":{"@explain":true}}})
对应SQL是`EXPLAIN`

⑪ 统计最近一周偶数userId数量
["@column":"sum(if(userId%2=0,1,0))",
"@having":"to_days(now())-to_days(\`date\`)<=7"](http://apijson.cn:8080/get/{"Comment":{"@column":"sum(if(userId%252=0,1,0))","@having":"to_days(now())-to_days(\`date\`)<=7"}})
对应SQL是`SELECT sum(if(userId%2=0,1,0)) ... HAVING to_days(now())-to_days(\``date``\`)<=7`

⑫ 从pictureList获取第0张图片:
["@position":0, //自定义关键词
"firstPicture()":"getFromArray(pictureList,@position)"](http://apijson.cn:8080/get/{"User":{"id":38710,"@position":0,"firstPicture()":"getFromArray(pictureList,@position)"}})
From a964cf698e28db3af1597500519d5ff1d453d581 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Tue, 22 Dec 2020 00:33:21 +0800 Subject: [PATCH 0114/1181] Update Document.md --- Document.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Document.md b/Document.md index 8ab2f9ce0..dfa29e57a 100644 --- a/Document.md +++ b/Document.md @@ -370,6 +370,6 @@ DELETE:
删除数据 | base_url/delete/ | {
   TableName:{< 比较运算 | >, <, >=, <= 比较运算符,用于
① 提供 "id{}":"<=90000" 这种条件范围的简化写法

② 实现子查询相关比较运算

不支持 "key=":Object 和 "key!=":Object 这两种写法,直接用更简单的 "key":Object 和 "key!":Object 替代。 | ① ["id<=":90000](http://apijson.cn:8080/get/{"[]":{"User":{"id<=":90000}}}),对应SQL是`id<=90000`,查询符合id<=90000的一个User数组

② ["id>@":{
   "from":"Comment",
   "Comment":{
      "@column":"min(userId)"
   }
}](http://apijson.cn:8080/get/{"User":{"id>@":{"from":"Comment","Comment":{"@column":"min(userId)"}}}})
WHERE id>(SELECT min(userId) FROM Comment) 逻辑运算 | &, \|, ! 逻辑运算符,对应数据库 SQL 中的 AND, OR, NOT。
横或纵与:同一键值对的值内条件默认 \| 或连接,不同键值对的条件默认 & 与连接。

① & 可用于"key&{}":"条件"等

② \| 可用于"key\|{}":"条件", "key\|{}":[]等,一般可省略

③ ! 可单独使用,如"key!":Object,也可像&,\|一样配合其他功能符使用
"key!":null 无效,null 值会导致整个键值对被忽略解析,可以用 "key{}":"!=null" 替代,
"key":null 同理,用 "key{}":"=null" 替代。 | ① ["id&{}":">80000,<=90000"](http://apijson.cn:8080/head/{"User":{"id&{}":">80000,<=90000"}}),对应SQL是`id>80000 AND id<=90000`,即id满足id>80000 & id<=90000

② ["id\|{}":">90000,<=80000"](http://apijson.cn:8080/head/{"User":{"id\|{}":">90000,<=80000"}}),同"id{}":">90000,<=80000",对应SQL是`id>80000 OR id<=90000`,即id满足id>90000 \| id<=80000

③ ["id!{}":[82001,38710]](http://apijson.cn:8080/head/{"User":{"id!{}":[82001,38710]}}),对应SQL是`id NOT IN(82001,38710)`,即id满足 ! (id=82001 \| id=38710),可过滤黑名单的消息 数组关键词,可自定义 | "key":Object,key为 "[]":{} 中{}内的关键词,Object的类型由key指定

① "count":Integer,查询数量,0 表示最大值,默认最大值为100

② "page":Integer,查询页码,从0开始,默认最大值为100,一般和count一起用

③ "query":Integer,查询内容
0-对象,1-总数和分页详情,2-以上全部
总数关键词为 total,分页详情关键词为 info,
它们都和 query 同级,通过引用赋值得到,例如
"total@":"/[]/total", "info@":"/[]/info"
这里query及total仅为GET类型的请求提供方便,
一般可直接用HEAD类型的请求获取总数

④ "join":"&/Table0/key0@,\多表连接方式:
"\<" - LEFT JOIN
">" - RIGHT JOIN
"&" - INNER JOIN
"\|" - FULL JOIN
"!" - OUTTER JOIN
"@" - APP JOIN
其中 @ APP JOIN 为应用层连表,会从已查出的主表里取得所有副表 key@ 关联的主表内的 refKey 作为一个数组 refKeys: [value0, value1...],然后把原来副表 count 次查询 key=$refKey 的 SQL 用 key IN($refKeys) 的方式合并为一条 SQL 来优化性能;
其它 JOIN 都是 SQL JOIN,具体功能和 MySQL,PostgreSQL 等数据库的 JOIN 一一对应
`"join":"`"MainTable":{},`
`"ViceTable":{"key@":"/MainTable/refKey"}`
会对应生成
`MainTable LEFT JOIN ViceTable`
`ON ViceTable.key=MainTable.refKey`

⑤ "otherKey":Object,自定义关键词,名称和以上系统关键词不一样,且原样返回上传的值 | ① 查询User数组,最多5个:
["count":5](http://apijson.cn:8080/get/{"[]":{"count":5,"User":{}}})
对应SQL是`LIMIT 5`

② 查询第3页的User数组,每页5个:
["count":5,
"page":3](http://apijson.cn:8080/get/{"[]":{"count":5,"page":3,"User":{}}})
对应SQL是`LIMIT 5 OFFSET 15`

③ 查询User数组和对应的User总数:
["[]":{
   "query":2,
   "User":{}
},
"total@":"/[]/total",
"info@":"/[]/info"](http://apijson.cn:8080/get/{"[]":{"query":2,"count":5,"User":{}},"total@":"%252F[]%252Ftotal","info@":"%252F[]%252Finfo"})
返回的数据中,总数及分页详情结构为:
"total":139, //总数
"info":{ //分页详情
   "total":139, //总数
   "count":5, //每页数量
   "page":0, //当前页码
   "max":27, //最大页码
   "more":true, //是否还有更多
   "first":true, //是否为首页
   "last":false //是否为尾页
}

④ Moment INNER JOIN User LEFT JOIN Comment:
["[]":{
   "join":"&/User/id@,\    "Moment":{},
   "User":{
     "name~":"t",
     "id@":"/Moment/userId"
   },
   "Comment":{
     "momentId@":"/Moment/id"
   }
}](http://apijson.cn:8080/get/{"[]":{"count":5,"join":"&%252FUser%252Fid@,\<%252FComment%252FmomentId@","Moment":{"@column":"id,userId,content"},"User":{"name~":"t","id@":"%252FMoment%252FuserId","@column":"id,name,head"},"Comment":{"momentId@":"%252FMoment%252Fid","@column":"id,momentId,content"}}})

⑤ 每一层都加当前用户名:
["User":{},
"[]":{
   "name@":"User/name", //自定义关键词
   "Moment":{}
}](http://apijson.cn:8080/get/{"User":{},"[]":{"name@":"User%252Fname","Moment":{}}}) - 对象关键词,可自定义 | "@key":Object,@key为 Table:{} 中{}内的关键词,Object的类型由@key指定

① "@combine":"&key0,&key1,\|key2,key3,
!key4,!key5,&key6,key7...",条件组合方式,\| 可省略。会自动把同类的合并,外层按照 & \| ! 顺序,内层的按传参顺序组合成
(key0 & key1 & key6 & 其它key) & (key2 \| key3 \| key7) & !(key4 \| key5)
这种连接方式,其中 "其它key" 是指与 @combine 在同一对象,且未被它声明的条件 key,默认都是 & 连接

② "@column":"column;function(arg)...",返回字段

③ "@order":"column0+,column1-...",排序方式

④ "@group":"column0,column1...",分组方式。如果@column里声明了Table的id,则id也必须在@group中声明;其它情况下必须满足至少一个条件:
1.分组的key在@column里声明
2.Table主键在@group中声明

⑤ "@having":"function0(...)?value0;function1(...)?value1;function2(...)?value2...",SQL函数条件,一般和@group一起用,函数一般在@column里声明

⑥ "@schema":"sys",集合空间(模式),非默认的值可通过它来指定,可以在最外层作为全局默认配置

⑦ "@database":"POSTGRESQL",跨数据库,非默认的值可通过它来指定,可以在最外层作为全局默认配置

⑧ "@json":"key0,key1...",转为 JSON 格式返回,符合 JSONObject 则转为 {...},符合 JSONArray 则转为 \[...]

⑨ "@role":"OWNER",来访角色,包括
UNKNOWN,LOGIN,CONTACT,CIRCLE,OWNER,ADMIN,
可以在最外层作为全局默认配置,
可自定义其它角色并重写 Verifier.verify 等相关方法来自定义校验

⑩ "@explain":true,性能分析,可以在最外层作为全局默认配置

⑪ "@raw":"SQL片段或SQL片段的别名",自定义原始SQL片段,可扩展嵌套SQL函数等复杂语句,必须是后端已配置的,谨慎使用,其它功能符做不到才考虑

⑫ "@otherKey":Object,自定义关键词,名称和以上系统关键词不一样,且原样返回上传的值 | ① 搜索name或tag任何一个字段包含字符a的User列表:
["name~":"a",
"tag~":"a",
"@combine":"name~,tag~"](http://apijson.cn:8080/get/{"User[]":{"count":10,"User":{"@column":"id,name,tag","name~":"a","tag~":"a","@combine":"name~,tag~"}}})
对应SQL是`name REGEXP 'a' OR tag REGEXP 'a'`

② 只查询id,sex,name这几列并且请求结果也按照这个顺序:
["@column":"id,sex,name"](http://apijson.cn:8080/get/{"User":{"@column":"id,sex,name","id":38710}})
对应SQL是`SELECT id,sex,name`

③ 查询按 name降序、id默认顺序 排序的User数组:
["@order":"name-,id"](http://apijson.cn:8080/get/{"[]":{"count":10,"User":{"@column":"name,id","@order":"name-,id"}}})
对应SQL是`ORDER BY name DESC,id`

④ 查询按userId分组的Moment数组:
["@group":"userId,id"](http://apijson.cn:8080/get/{"[]":{"count":10,"Moment":%7B"@column":"userId,id","@group":"userId,id"}}})
对应SQL是`GROUP BY userId,id`

⑤ 查询 按userId分组、id最大值>=100 的Moment数组:
["@column":"userId;max(id)",
"@group":"userId",
"@having":"max(id)>=100"](http://apijson.cn:8080/get/{"[]":{"count":10,"Moment":{"@column":"userId%253Bmax(id)","@group":"userId","@having":"max(id)>=100"}}})
对应SQL是`SELECT userId,max(id) GROUP BY userId HAVING max(id)>=100`
还可以指定函数返回名:
["@column":"userId;max(id):maxId",
"@group":"userId",
"@having":"maxId>=100"](http://apijson.cn:8080/get/{"[]":{"count":10,"Moment":{"@column":"userId%253Bmax(id):maxId","@group":"userId","@having":"maxId>=100"}}})
对应SQL是`SELECT userId,max(id) AS maxId GROUP BY userId HAVING maxId>=100`

⑥ 查询 sys 内的 User 表:
["@schema":"sys"](http://apijson.cn:8080/get/{"User":{"@schema":"sys"}})
对应SQL是`FROM sys.User`

⑦ 查询 PostgreSQL 数据库的 User 表:
["@database":"POSTGRESQL"](http://apijson.cn:8080/get/{"User":{"@database":"POSTGRESQL","@explain":true}})

⑧ 将 VARCHAR 字符串字段 get 转为 JSONArray 返回:
["@json":"get"](http://apijson.cn:8080/get/{"Access":{"@json":"get"}})

⑨ 查询当前用户的动态:
["@role":"OWNER"](http://apijson.cn:8080/get/{"[]":{"Moment":{"@role":"OWNER"}}})

⑩ 开启性能分析:
["@explain":true](http://apijson.cn:8080/get/{"[]":{"Moment":{"@explain":true}}})
对应SQL是`EXPLAIN`

⑪ 统计最近一周偶数userId数量
["@column":"sum(if(userId%2=0,1,0))",
"@having":"to_days(now())-to_days(\`date\`)<=7"](http://apijson.cn:8080/get/{"Comment":{"@column":"sum(if(userId%252=0,1,0))","@having":"to_days(now())-to_days(\`date\`)<=7"}})
对应SQL是`SELECT sum(if(userId%2=0,1,0)) ... HAVING to_days(now())-to_days(\``date``\`)<=7`

⑫ 从pictureList获取第0张图片:
["@position":0, //自定义关键词
"firstPicture()":"getFromArray(pictureList,@position)"](http://apijson.cn:8080/get/{"User":{"id":38710,"@position":0,"firstPicture()":"getFromArray(pictureList,@position)"}}) + 对象关键词,可自定义 | "@key":Object,@key为 Table:{} 中{}内的关键词,Object的类型由@key指定

① "@combine":"&key0,&key1,\|key2,key3,
!key4,!key5,&key6,key7...",条件组合方式,\| 可省略。会自动把同类的合并,外层按照 & \| ! 顺序,内层的按传参顺序组合成
(key0 & key1 & key6 & 其它key) & (key2 \| key3 \| key7) & !(key4 \| key5)
这种连接方式,其中 "其它key" 是指与 @combine 在同一对象,且未被它声明的条件 key,默认都是 & 连接

② "@column":"column;function(arg)...",返回字段

③ "@order":"column0+,column1-...",排序方式

④ "@group":"column0,column1...",分组方式。如果@column里声明了Table的id,则id也必须在@group中声明;其它情况下必须满足至少一个条件:
1.分组的key在@column里声明
2.Table主键在@group中声明

⑤ "@having":"function0(...)?value0;function1(...)?value1;function2(...)?value2...",SQL函数条件,一般和@group一起用,函数一般在@column里声明

⑥ "@schema":"sys",集合空间(模式),非默认的值可通过它来指定,可以在最外层作为全局默认配置

⑦ "@database":"POSTGRESQL",跨数据库,非默认的值可通过它来指定,可以在最外层作为全局默认配置

⑧ "@json":"key0,key1...",转为 JSON 格式返回,符合 JSONObject 则转为 {...},符合 JSONArray 则转为 \[...]

⑨ "@role":"OWNER",来访角色,包括
UNKNOWN,LOGIN,CONTACT,CIRCLE,OWNER,ADMIN,
可以在最外层作为全局默认配置,
可自定义其它角色并重写 Verifier.verify 等相关方法来自定义校验

⑩ "@explain":true,性能分析,可以在最外层作为全局默认配置

⑪ "@raw":"SQL片段或SQL片段的别名",自定义原始SQL片段,可扩展嵌套SQL函数等复杂语句,必须是后端已配置的,谨慎使用,其它功能符做不到才考虑

⑫ "@otherKey":Object,自定义关键词,名称和以上系统关键词不一样,且原样返回上传的值 | ① 搜索name或tag任何一个字段包含字符a的User列表:
["name~":"a",
"tag~":"a",
"@combine":"name~,tag~"](http://apijson.cn:8080/get/{"User[]":{"count":10,"User":{"@column":"id,name,tag","name~":"a","tag~":"a","@combine":"name~,tag~"}}})
对应SQL是`name REGEXP 'a' OR tag REGEXP 'a'`

② 只查询id,sex,name这几列并且请求结果也按照这个顺序:
["@column":"id,sex,name"](http://apijson.cn:8080/get/{"User":{"@column":"id,sex,name","id":38710}})
对应SQL是`SELECT id,sex,name`

③ 查询按 name降序、id默认顺序 排序的User数组:
["@order":"name-,id"](http://apijson.cn:8080/get/{"[]":{"count":10,"User":{"@column":"name,id","@order":"name-,id"}}})
对应SQL是`ORDER BY name DESC,id`

④ 查询按userId分组的Moment数组:
["@group":"userId,id"](http://apijson.cn:8080/get/{"[]":{"count":10,"Moment":%7B"@column":"userId,id","@group":"userId,id"}}})
对应SQL是`GROUP BY userId,id`

⑤ 查询 按userId分组、id最大值>=100 的Moment数组:
["@column":"userId;max(id)",
"@group":"userId",
"@having":"max(id)>=100"](http://apijson.cn:8080/get/{"[]":{"count":10,"Moment":{"@column":"userId%253Bmax(id)","@group":"userId","@having":"max(id)>=100"}}})
对应SQL是`SELECT userId,max(id) GROUP BY userId HAVING max(id)>=100`
还可以指定函数返回名:
["@column":"userId;max(id):maxId",
"@group":"userId",
"@having":"maxId>=100"](http://apijson.cn:8080/get/{"[]":{"count":10,"Moment":{"@column":"userId%253Bmax(id):maxId","@group":"userId","@having":"maxId>=100"}}})
对应SQL是`SELECT userId,max(id) AS maxId GROUP BY userId HAVING maxId>=100`

⑥ 查询 sys 内的 User 表:
["@schema":"sys"](http://apijson.cn:8080/get/{"User":{"@schema":"sys"}})
对应SQL是`FROM sys.User`

⑦ 查询 PostgreSQL 数据库的 User 表:
["@database":"POSTGRESQL"](http://apijson.cn:8080/get/{"User":{"@database":"POSTGRESQL","@explain":true}})

⑧ 将 VARCHAR 字符串字段 get 转为 JSONArray 返回:
["@json":"get"](http://apijson.cn:8080/get/{"Access":{"@json":"get"}})

⑨ 查询当前用户的动态:
["@role":"OWNER"](http://apijson.cn:8080/get/{"[]":{"Moment":{"@role":"OWNER"}}})

⑩ 开启性能分析:
["@explain":true](http://apijson.cn:8080/get/{"[]":{"Moment":{"@explain":true}}})
对应SQL是`EXPLAIN`

⑪ 统计最近一周偶数userId数量
["@column":"sum(if(userId%2=0,1,0))",
"@having":"to_days(now())-to_days(\`date\`)<=7"](http://apijson.cn:8080/get/{"Comment":{"@column":"sum(if(userId%252=0,1,0))","@having":"to_days(now())-to_days(\`date\`)<=7"}})
对应SQL是`SELECT sum(if(userId%2=0,1,0)) ... HAVING to_days(now())-to_days(\` `date` `\`)<=7`

⑫ 从pictureList获取第0张图片:
["@position":0, //自定义关键词
"firstPicture()":"getFromArray(pictureList,@position)"](http://apijson.cn:8080/get/{"User":{"id":38710,"@position":0,"firstPicture()":"getFromArray(pictureList,@position)"}})
From e5b26d98991d3a1a3b8ade017501c5b0cc6e7ddb Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Tue, 22 Dec 2020 00:34:04 +0800 Subject: [PATCH 0115/1181] Update Document.md --- Document.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Document.md b/Document.md index dfa29e57a..17d6e24c4 100644 --- a/Document.md +++ b/Document.md @@ -370,6 +370,6 @@ DELETE:
删除数据 | base_url/delete/ | {
   TableName:{< 比较运算 | >, <, >=, <= 比较运算符,用于
① 提供 "id{}":"<=90000" 这种条件范围的简化写法

② 实现子查询相关比较运算

不支持 "key=":Object 和 "key!=":Object 这两种写法,直接用更简单的 "key":Object 和 "key!":Object 替代。 | ① ["id<=":90000](http://apijson.cn:8080/get/{"[]":{"User":{"id<=":90000}}}),对应SQL是`id<=90000`,查询符合id<=90000的一个User数组

② ["id>@":{
   "from":"Comment",
   "Comment":{
      "@column":"min(userId)"
   }
}](http://apijson.cn:8080/get/{"User":{"id>@":{"from":"Comment","Comment":{"@column":"min(userId)"}}}})
WHERE id>(SELECT min(userId) FROM Comment) 逻辑运算 | &, \|, ! 逻辑运算符,对应数据库 SQL 中的 AND, OR, NOT。
横或纵与:同一键值对的值内条件默认 \| 或连接,不同键值对的条件默认 & 与连接。

① & 可用于"key&{}":"条件"等

② \| 可用于"key\|{}":"条件", "key\|{}":[]等,一般可省略

③ ! 可单独使用,如"key!":Object,也可像&,\|一样配合其他功能符使用
"key!":null 无效,null 值会导致整个键值对被忽略解析,可以用 "key{}":"!=null" 替代,
"key":null 同理,用 "key{}":"=null" 替代。 | ① ["id&{}":">80000,<=90000"](http://apijson.cn:8080/head/{"User":{"id&{}":">80000,<=90000"}}),对应SQL是`id>80000 AND id<=90000`,即id满足id>80000 & id<=90000

② ["id\|{}":">90000,<=80000"](http://apijson.cn:8080/head/{"User":{"id\|{}":">90000,<=80000"}}),同"id{}":">90000,<=80000",对应SQL是`id>80000 OR id<=90000`,即id满足id>90000 \| id<=80000

③ ["id!{}":[82001,38710]](http://apijson.cn:8080/head/{"User":{"id!{}":[82001,38710]}}),对应SQL是`id NOT IN(82001,38710)`,即id满足 ! (id=82001 \| id=38710),可过滤黑名单的消息 数组关键词,可自定义 | "key":Object,key为 "[]":{} 中{}内的关键词,Object的类型由key指定

① "count":Integer,查询数量,0 表示最大值,默认最大值为100

② "page":Integer,查询页码,从0开始,默认最大值为100,一般和count一起用

③ "query":Integer,查询内容
0-对象,1-总数和分页详情,2-以上全部
总数关键词为 total,分页详情关键词为 info,
它们都和 query 同级,通过引用赋值得到,例如
"total@":"/[]/total", "info@":"/[]/info"
这里query及total仅为GET类型的请求提供方便,
一般可直接用HEAD类型的请求获取总数

④ "join":"&/Table0/key0@,\多表连接方式:
"\<" - LEFT JOIN
">" - RIGHT JOIN
"&" - INNER JOIN
"\|" - FULL JOIN
"!" - OUTTER JOIN
"@" - APP JOIN
其中 @ APP JOIN 为应用层连表,会从已查出的主表里取得所有副表 key@ 关联的主表内的 refKey 作为一个数组 refKeys: [value0, value1...],然后把原来副表 count 次查询 key=$refKey 的 SQL 用 key IN($refKeys) 的方式合并为一条 SQL 来优化性能;
其它 JOIN 都是 SQL JOIN,具体功能和 MySQL,PostgreSQL 等数据库的 JOIN 一一对应
`"join":"`"MainTable":{},`
`"ViceTable":{"key@":"/MainTable/refKey"}`
会对应生成
`MainTable LEFT JOIN ViceTable`
`ON ViceTable.key=MainTable.refKey`

⑤ "otherKey":Object,自定义关键词,名称和以上系统关键词不一样,且原样返回上传的值 | ① 查询User数组,最多5个:
["count":5](http://apijson.cn:8080/get/{"[]":{"count":5,"User":{}}})
对应SQL是`LIMIT 5`

② 查询第3页的User数组,每页5个:
["count":5,
"page":3](http://apijson.cn:8080/get/{"[]":{"count":5,"page":3,"User":{}}})
对应SQL是`LIMIT 5 OFFSET 15`

③ 查询User数组和对应的User总数:
["[]":{
   "query":2,
   "User":{}
},
"total@":"/[]/total",
"info@":"/[]/info"](http://apijson.cn:8080/get/{"[]":{"query":2,"count":5,"User":{}},"total@":"%252F[]%252Ftotal","info@":"%252F[]%252Finfo"})
返回的数据中,总数及分页详情结构为:
"total":139, //总数
"info":{ //分页详情
   "total":139, //总数
   "count":5, //每页数量
   "page":0, //当前页码
   "max":27, //最大页码
   "more":true, //是否还有更多
   "first":true, //是否为首页
   "last":false //是否为尾页
}

④ Moment INNER JOIN User LEFT JOIN Comment:
["[]":{
   "join":"&/User/id@,\    "Moment":{},
   "User":{
     "name~":"t",
     "id@":"/Moment/userId"
   },
   "Comment":{
     "momentId@":"/Moment/id"
   }
}](http://apijson.cn:8080/get/{"[]":{"count":5,"join":"&%252FUser%252Fid@,\<%252FComment%252FmomentId@","Moment":{"@column":"id,userId,content"},"User":{"name~":"t","id@":"%252FMoment%252FuserId","@column":"id,name,head"},"Comment":{"momentId@":"%252FMoment%252Fid","@column":"id,momentId,content"}}})

⑤ 每一层都加当前用户名:
["User":{},
"[]":{
   "name@":"User/name", //自定义关键词
   "Moment":{}
}](http://apijson.cn:8080/get/{"User":{},"[]":{"name@":"User%252Fname","Moment":{}}}) - 对象关键词,可自定义 | "@key":Object,@key为 Table:{} 中{}内的关键词,Object的类型由@key指定

① "@combine":"&key0,&key1,\|key2,key3,
!key4,!key5,&key6,key7...",条件组合方式,\| 可省略。会自动把同类的合并,外层按照 & \| ! 顺序,内层的按传参顺序组合成
(key0 & key1 & key6 & 其它key) & (key2 \| key3 \| key7) & !(key4 \| key5)
这种连接方式,其中 "其它key" 是指与 @combine 在同一对象,且未被它声明的条件 key,默认都是 & 连接

② "@column":"column;function(arg)...",返回字段

③ "@order":"column0+,column1-...",排序方式

④ "@group":"column0,column1...",分组方式。如果@column里声明了Table的id,则id也必须在@group中声明;其它情况下必须满足至少一个条件:
1.分组的key在@column里声明
2.Table主键在@group中声明

⑤ "@having":"function0(...)?value0;function1(...)?value1;function2(...)?value2...",SQL函数条件,一般和@group一起用,函数一般在@column里声明

⑥ "@schema":"sys",集合空间(模式),非默认的值可通过它来指定,可以在最外层作为全局默认配置

⑦ "@database":"POSTGRESQL",跨数据库,非默认的值可通过它来指定,可以在最外层作为全局默认配置

⑧ "@json":"key0,key1...",转为 JSON 格式返回,符合 JSONObject 则转为 {...},符合 JSONArray 则转为 \[...]

⑨ "@role":"OWNER",来访角色,包括
UNKNOWN,LOGIN,CONTACT,CIRCLE,OWNER,ADMIN,
可以在最外层作为全局默认配置,
可自定义其它角色并重写 Verifier.verify 等相关方法来自定义校验

⑩ "@explain":true,性能分析,可以在最外层作为全局默认配置

⑪ "@raw":"SQL片段或SQL片段的别名",自定义原始SQL片段,可扩展嵌套SQL函数等复杂语句,必须是后端已配置的,谨慎使用,其它功能符做不到才考虑

⑫ "@otherKey":Object,自定义关键词,名称和以上系统关键词不一样,且原样返回上传的值 | ① 搜索name或tag任何一个字段包含字符a的User列表:
["name~":"a",
"tag~":"a",
"@combine":"name~,tag~"](http://apijson.cn:8080/get/{"User[]":{"count":10,"User":{"@column":"id,name,tag","name~":"a","tag~":"a","@combine":"name~,tag~"}}})
对应SQL是`name REGEXP 'a' OR tag REGEXP 'a'`

② 只查询id,sex,name这几列并且请求结果也按照这个顺序:
["@column":"id,sex,name"](http://apijson.cn:8080/get/{"User":{"@column":"id,sex,name","id":38710}})
对应SQL是`SELECT id,sex,name`

③ 查询按 name降序、id默认顺序 排序的User数组:
["@order":"name-,id"](http://apijson.cn:8080/get/{"[]":{"count":10,"User":{"@column":"name,id","@order":"name-,id"}}})
对应SQL是`ORDER BY name DESC,id`

④ 查询按userId分组的Moment数组:
["@group":"userId,id"](http://apijson.cn:8080/get/{"[]":{"count":10,"Moment":%7B"@column":"userId,id","@group":"userId,id"}}})
对应SQL是`GROUP BY userId,id`

⑤ 查询 按userId分组、id最大值>=100 的Moment数组:
["@column":"userId;max(id)",
"@group":"userId",
"@having":"max(id)>=100"](http://apijson.cn:8080/get/{"[]":{"count":10,"Moment":{"@column":"userId%253Bmax(id)","@group":"userId","@having":"max(id)>=100"}}})
对应SQL是`SELECT userId,max(id) GROUP BY userId HAVING max(id)>=100`
还可以指定函数返回名:
["@column":"userId;max(id):maxId",
"@group":"userId",
"@having":"maxId>=100"](http://apijson.cn:8080/get/{"[]":{"count":10,"Moment":{"@column":"userId%253Bmax(id):maxId","@group":"userId","@having":"maxId>=100"}}})
对应SQL是`SELECT userId,max(id) AS maxId GROUP BY userId HAVING maxId>=100`

⑥ 查询 sys 内的 User 表:
["@schema":"sys"](http://apijson.cn:8080/get/{"User":{"@schema":"sys"}})
对应SQL是`FROM sys.User`

⑦ 查询 PostgreSQL 数据库的 User 表:
["@database":"POSTGRESQL"](http://apijson.cn:8080/get/{"User":{"@database":"POSTGRESQL","@explain":true}})

⑧ 将 VARCHAR 字符串字段 get 转为 JSONArray 返回:
["@json":"get"](http://apijson.cn:8080/get/{"Access":{"@json":"get"}})

⑨ 查询当前用户的动态:
["@role":"OWNER"](http://apijson.cn:8080/get/{"[]":{"Moment":{"@role":"OWNER"}}})

⑩ 开启性能分析:
["@explain":true](http://apijson.cn:8080/get/{"[]":{"Moment":{"@explain":true}}})
对应SQL是`EXPLAIN`

⑪ 统计最近一周偶数userId数量
["@column":"sum(if(userId%2=0,1,0))",
"@having":"to_days(now())-to_days(\`date\`)<=7"](http://apijson.cn:8080/get/{"Comment":{"@column":"sum(if(userId%252=0,1,0))","@having":"to_days(now())-to_days(\`date\`)<=7"}})
对应SQL是`SELECT sum(if(userId%2=0,1,0)) ... HAVING to_days(now())-to_days(\` `date` `\`)<=7`

⑫ 从pictureList获取第0张图片:
["@position":0, //自定义关键词
"firstPicture()":"getFromArray(pictureList,@position)"](http://apijson.cn:8080/get/{"User":{"id":38710,"@position":0,"firstPicture()":"getFromArray(pictureList,@position)"}}) + 对象关键词,可自定义 | "@key":Object,@key为 Table:{} 中{}内的关键词,Object的类型由@key指定

① "@combine":"&key0,&key1,\|key2,key3,
!key4,!key5,&key6,key7...",条件组合方式,\| 可省略。会自动把同类的合并,外层按照 & \| ! 顺序,内层的按传参顺序组合成
(key0 & key1 & key6 & 其它key) & (key2 \| key3 \| key7) & !(key4 \| key5)
这种连接方式,其中 "其它key" 是指与 @combine 在同一对象,且未被它声明的条件 key,默认都是 & 连接

② "@column":"column;function(arg)...",返回字段

③ "@order":"column0+,column1-...",排序方式

④ "@group":"column0,column1...",分组方式。如果@column里声明了Table的id,则id也必须在@group中声明;其它情况下必须满足至少一个条件:
1.分组的key在@column里声明
2.Table主键在@group中声明

⑤ "@having":"function0(...)?value0;function1(...)?value1;function2(...)?value2...",SQL函数条件,一般和@group一起用,函数一般在@column里声明

⑥ "@schema":"sys",集合空间(模式),非默认的值可通过它来指定,可以在最外层作为全局默认配置

⑦ "@database":"POSTGRESQL",跨数据库,非默认的值可通过它来指定,可以在最外层作为全局默认配置

⑧ "@json":"key0,key1...",转为 JSON 格式返回,符合 JSONObject 则转为 {...},符合 JSONArray 则转为 \[...]

⑨ "@role":"OWNER",来访角色,包括
UNKNOWN,LOGIN,CONTACT,CIRCLE,OWNER,ADMIN,
可以在最外层作为全局默认配置,
可自定义其它角色并重写 Verifier.verify 等相关方法来自定义校验

⑩ "@explain":true,性能分析,可以在最外层作为全局默认配置

⑪ "@raw":"SQL片段或SQL片段的别名",自定义原始SQL片段,可扩展嵌套SQL函数等复杂语句,必须是后端已配置的,谨慎使用,其它功能符做不到才考虑

⑫ "@otherKey":Object,自定义关键词,名称和以上系统关键词不一样,且原样返回上传的值 | ① 搜索name或tag任何一个字段包含字符a的User列表:
["name~":"a",
"tag~":"a",
"@combine":"name~,tag~"](http://apijson.cn:8080/get/{"User[]":{"count":10,"User":{"@column":"id,name,tag","name~":"a","tag~":"a","@combine":"name~,tag~"}}})
对应SQL是`name REGEXP 'a' OR tag REGEXP 'a'`

② 只查询id,sex,name这几列并且请求结果也按照这个顺序:
["@column":"id,sex,name"](http://apijson.cn:8080/get/{"User":{"@column":"id,sex,name","id":38710}})
对应SQL是`SELECT id,sex,name`

③ 查询按 name降序、id默认顺序 排序的User数组:
["@order":"name-,id"](http://apijson.cn:8080/get/{"[]":{"count":10,"User":{"@column":"name,id","@order":"name-,id"}}})
对应SQL是`ORDER BY name DESC,id`

④ 查询按userId分组的Moment数组:
["@group":"userId,id"](http://apijson.cn:8080/get/{"[]":{"count":10,"Moment":%7B"@column":"userId,id","@group":"userId,id"}}})
对应SQL是`GROUP BY userId,id`

⑤ 查询 按userId分组、id最大值>=100 的Moment数组:
["@column":"userId;max(id)",
"@group":"userId",
"@having":"max(id)>=100"](http://apijson.cn:8080/get/{"[]":{"count":10,"Moment":{"@column":"userId%253Bmax(id)","@group":"userId","@having":"max(id)>=100"}}})
对应SQL是`SELECT userId,max(id) GROUP BY userId HAVING max(id)>=100`
还可以指定函数返回名:
["@column":"userId;max(id):maxId",
"@group":"userId",
"@having":"maxId>=100"](http://apijson.cn:8080/get/{"[]":{"count":10,"Moment":{"@column":"userId%253Bmax(id):maxId","@group":"userId","@having":"maxId>=100"}}})
对应SQL是`SELECT userId,max(id) AS maxId GROUP BY userId HAVING maxId>=100`

⑥ 查询 sys 内的 User 表:
["@schema":"sys"](http://apijson.cn:8080/get/{"User":{"@schema":"sys"}})
对应SQL是`FROM sys.User`

⑦ 查询 PostgreSQL 数据库的 User 表:
["@database":"POSTGRESQL"](http://apijson.cn:8080/get/{"User":{"@database":"POSTGRESQL","@explain":true}})

⑧ 将 VARCHAR 字符串字段 get 转为 JSONArray 返回:
["@json":"get"](http://apijson.cn:8080/get/{"Access":{"@json":"get"}})

⑨ 查询当前用户的动态:
["@role":"OWNER"](http://apijson.cn:8080/get/{"[]":{"Moment":{"@role":"OWNER"}}})

⑩ 开启性能分析:
["@explain":true](http://apijson.cn:8080/get/{"[]":{"Moment":{"@explain":true}}})
对应SQL是`EXPLAIN`

⑪ 统计最近一周偶数userId数量
["@column":"sum(if(userId%2=0,1,0))",
"@having":"to_days(now())-to_days(\`date\`)<=7"](http://apijson.cn:8080/get/{"Comment":{"@column":"sum(if(userId%252=0,1,0))","@having":"to_days(now())-to_days(\`date\`)<=7"}})
对应SQL是`SELECT sum(if(userId%2=0,1,0)) ... HAVING to_days(now())-to_days(\`` `date` `\`)<=7`

⑫ 从pictureList获取第0张图片:
["@position":0, //自定义关键词
"firstPicture()":"getFromArray(pictureList,@position)"](http://apijson.cn:8080/get/{"User":{"id":38710,"@position":0,"firstPicture()":"getFromArray(pictureList,@position)"}})
From 0469f4aff904ca777f7ec828d7fe1f90c3ec5b54 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Tue, 22 Dec 2020 00:35:24 +0800 Subject: [PATCH 0116/1181] Update Document.md --- Document.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Document.md b/Document.md index 17d6e24c4..82a7703b1 100644 --- a/Document.md +++ b/Document.md @@ -370,6 +370,6 @@ DELETE:
删除数据 | base_url/delete/ | {
   TableName:{< 比较运算 | >, <, >=, <= 比较运算符,用于
① 提供 "id{}":"<=90000" 这种条件范围的简化写法

② 实现子查询相关比较运算

不支持 "key=":Object 和 "key!=":Object 这两种写法,直接用更简单的 "key":Object 和 "key!":Object 替代。 | ① ["id<=":90000](http://apijson.cn:8080/get/{"[]":{"User":{"id<=":90000}}}),对应SQL是`id<=90000`,查询符合id<=90000的一个User数组

② ["id>@":{
   "from":"Comment",
   "Comment":{
      "@column":"min(userId)"
   }
}](http://apijson.cn:8080/get/{"User":{"id>@":{"from":"Comment","Comment":{"@column":"min(userId)"}}}})
WHERE id>(SELECT min(userId) FROM Comment) 逻辑运算 | &, \|, ! 逻辑运算符,对应数据库 SQL 中的 AND, OR, NOT。
横或纵与:同一键值对的值内条件默认 \| 或连接,不同键值对的条件默认 & 与连接。

① & 可用于"key&{}":"条件"等

② \| 可用于"key\|{}":"条件", "key\|{}":[]等,一般可省略

③ ! 可单独使用,如"key!":Object,也可像&,\|一样配合其他功能符使用
"key!":null 无效,null 值会导致整个键值对被忽略解析,可以用 "key{}":"!=null" 替代,
"key":null 同理,用 "key{}":"=null" 替代。 | ① ["id&{}":">80000,<=90000"](http://apijson.cn:8080/head/{"User":{"id&{}":">80000,<=90000"}}),对应SQL是`id>80000 AND id<=90000`,即id满足id>80000 & id<=90000

② ["id\|{}":">90000,<=80000"](http://apijson.cn:8080/head/{"User":{"id\|{}":">90000,<=80000"}}),同"id{}":">90000,<=80000",对应SQL是`id>80000 OR id<=90000`,即id满足id>90000 \| id<=80000

③ ["id!{}":[82001,38710]](http://apijson.cn:8080/head/{"User":{"id!{}":[82001,38710]}}),对应SQL是`id NOT IN(82001,38710)`,即id满足 ! (id=82001 \| id=38710),可过滤黑名单的消息 数组关键词,可自定义 | "key":Object,key为 "[]":{} 中{}内的关键词,Object的类型由key指定

① "count":Integer,查询数量,0 表示最大值,默认最大值为100

② "page":Integer,查询页码,从0开始,默认最大值为100,一般和count一起用

③ "query":Integer,查询内容
0-对象,1-总数和分页详情,2-以上全部
总数关键词为 total,分页详情关键词为 info,
它们都和 query 同级,通过引用赋值得到,例如
"total@":"/[]/total", "info@":"/[]/info"
这里query及total仅为GET类型的请求提供方便,
一般可直接用HEAD类型的请求获取总数

④ "join":"&/Table0/key0@,\多表连接方式:
"\<" - LEFT JOIN
">" - RIGHT JOIN
"&" - INNER JOIN
"\|" - FULL JOIN
"!" - OUTTER JOIN
"@" - APP JOIN
其中 @ APP JOIN 为应用层连表,会从已查出的主表里取得所有副表 key@ 关联的主表内的 refKey 作为一个数组 refKeys: [value0, value1...],然后把原来副表 count 次查询 key=$refKey 的 SQL 用 key IN($refKeys) 的方式合并为一条 SQL 来优化性能;
其它 JOIN 都是 SQL JOIN,具体功能和 MySQL,PostgreSQL 等数据库的 JOIN 一一对应
`"join":"`"MainTable":{},`
`"ViceTable":{"key@":"/MainTable/refKey"}`
会对应生成
`MainTable LEFT JOIN ViceTable`
`ON ViceTable.key=MainTable.refKey`

⑤ "otherKey":Object,自定义关键词,名称和以上系统关键词不一样,且原样返回上传的值 | ① 查询User数组,最多5个:
["count":5](http://apijson.cn:8080/get/{"[]":{"count":5,"User":{}}})
对应SQL是`LIMIT 5`

② 查询第3页的User数组,每页5个:
["count":5,
"page":3](http://apijson.cn:8080/get/{"[]":{"count":5,"page":3,"User":{}}})
对应SQL是`LIMIT 5 OFFSET 15`

③ 查询User数组和对应的User总数:
["[]":{
   "query":2,
   "User":{}
},
"total@":"/[]/total",
"info@":"/[]/info"](http://apijson.cn:8080/get/{"[]":{"query":2,"count":5,"User":{}},"total@":"%252F[]%252Ftotal","info@":"%252F[]%252Finfo"})
返回的数据中,总数及分页详情结构为:
"total":139, //总数
"info":{ //分页详情
   "total":139, //总数
   "count":5, //每页数量
   "page":0, //当前页码
   "max":27, //最大页码
   "more":true, //是否还有更多
   "first":true, //是否为首页
   "last":false //是否为尾页
}

④ Moment INNER JOIN User LEFT JOIN Comment:
["[]":{
   "join":"&/User/id@,\    "Moment":{},
   "User":{
     "name~":"t",
     "id@":"/Moment/userId"
   },
   "Comment":{
     "momentId@":"/Moment/id"
   }
}](http://apijson.cn:8080/get/{"[]":{"count":5,"join":"&%252FUser%252Fid@,\<%252FComment%252FmomentId@","Moment":{"@column":"id,userId,content"},"User":{"name~":"t","id@":"%252FMoment%252FuserId","@column":"id,name,head"},"Comment":{"momentId@":"%252FMoment%252Fid","@column":"id,momentId,content"}}})

⑤ 每一层都加当前用户名:
["User":{},
"[]":{
   "name@":"User/name", //自定义关键词
   "Moment":{}
}](http://apijson.cn:8080/get/{"User":{},"[]":{"name@":"User%252Fname","Moment":{}}}) - 对象关键词,可自定义 | "@key":Object,@key为 Table:{} 中{}内的关键词,Object的类型由@key指定

① "@combine":"&key0,&key1,\|key2,key3,
!key4,!key5,&key6,key7...",条件组合方式,\| 可省略。会自动把同类的合并,外层按照 & \| ! 顺序,内层的按传参顺序组合成
(key0 & key1 & key6 & 其它key) & (key2 \| key3 \| key7) & !(key4 \| key5)
这种连接方式,其中 "其它key" 是指与 @combine 在同一对象,且未被它声明的条件 key,默认都是 & 连接

② "@column":"column;function(arg)...",返回字段

③ "@order":"column0+,column1-...",排序方式

④ "@group":"column0,column1...",分组方式。如果@column里声明了Table的id,则id也必须在@group中声明;其它情况下必须满足至少一个条件:
1.分组的key在@column里声明
2.Table主键在@group中声明

⑤ "@having":"function0(...)?value0;function1(...)?value1;function2(...)?value2...",SQL函数条件,一般和@group一起用,函数一般在@column里声明

⑥ "@schema":"sys",集合空间(模式),非默认的值可通过它来指定,可以在最外层作为全局默认配置

⑦ "@database":"POSTGRESQL",跨数据库,非默认的值可通过它来指定,可以在最外层作为全局默认配置

⑧ "@json":"key0,key1...",转为 JSON 格式返回,符合 JSONObject 则转为 {...},符合 JSONArray 则转为 \[...]

⑨ "@role":"OWNER",来访角色,包括
UNKNOWN,LOGIN,CONTACT,CIRCLE,OWNER,ADMIN,
可以在最外层作为全局默认配置,
可自定义其它角色并重写 Verifier.verify 等相关方法来自定义校验

⑩ "@explain":true,性能分析,可以在最外层作为全局默认配置

⑪ "@raw":"SQL片段或SQL片段的别名",自定义原始SQL片段,可扩展嵌套SQL函数等复杂语句,必须是后端已配置的,谨慎使用,其它功能符做不到才考虑

⑫ "@otherKey":Object,自定义关键词,名称和以上系统关键词不一样,且原样返回上传的值 | ① 搜索name或tag任何一个字段包含字符a的User列表:
["name~":"a",
"tag~":"a",
"@combine":"name~,tag~"](http://apijson.cn:8080/get/{"User[]":{"count":10,"User":{"@column":"id,name,tag","name~":"a","tag~":"a","@combine":"name~,tag~"}}})
对应SQL是`name REGEXP 'a' OR tag REGEXP 'a'`

② 只查询id,sex,name这几列并且请求结果也按照这个顺序:
["@column":"id,sex,name"](http://apijson.cn:8080/get/{"User":{"@column":"id,sex,name","id":38710}})
对应SQL是`SELECT id,sex,name`

③ 查询按 name降序、id默认顺序 排序的User数组:
["@order":"name-,id"](http://apijson.cn:8080/get/{"[]":{"count":10,"User":{"@column":"name,id","@order":"name-,id"}}})
对应SQL是`ORDER BY name DESC,id`

④ 查询按userId分组的Moment数组:
["@group":"userId,id"](http://apijson.cn:8080/get/{"[]":{"count":10,"Moment":%7B"@column":"userId,id","@group":"userId,id"}}})
对应SQL是`GROUP BY userId,id`

⑤ 查询 按userId分组、id最大值>=100 的Moment数组:
["@column":"userId;max(id)",
"@group":"userId",
"@having":"max(id)>=100"](http://apijson.cn:8080/get/{"[]":{"count":10,"Moment":{"@column":"userId%253Bmax(id)","@group":"userId","@having":"max(id)>=100"}}})
对应SQL是`SELECT userId,max(id) GROUP BY userId HAVING max(id)>=100`
还可以指定函数返回名:
["@column":"userId;max(id):maxId",
"@group":"userId",
"@having":"maxId>=100"](http://apijson.cn:8080/get/{"[]":{"count":10,"Moment":{"@column":"userId%253Bmax(id):maxId","@group":"userId","@having":"maxId>=100"}}})
对应SQL是`SELECT userId,max(id) AS maxId GROUP BY userId HAVING maxId>=100`

⑥ 查询 sys 内的 User 表:
["@schema":"sys"](http://apijson.cn:8080/get/{"User":{"@schema":"sys"}})
对应SQL是`FROM sys.User`

⑦ 查询 PostgreSQL 数据库的 User 表:
["@database":"POSTGRESQL"](http://apijson.cn:8080/get/{"User":{"@database":"POSTGRESQL","@explain":true}})

⑧ 将 VARCHAR 字符串字段 get 转为 JSONArray 返回:
["@json":"get"](http://apijson.cn:8080/get/{"Access":{"@json":"get"}})

⑨ 查询当前用户的动态:
["@role":"OWNER"](http://apijson.cn:8080/get/{"[]":{"Moment":{"@role":"OWNER"}}})

⑩ 开启性能分析:
["@explain":true](http://apijson.cn:8080/get/{"[]":{"Moment":{"@explain":true}}})
对应SQL是`EXPLAIN`

⑪ 统计最近一周偶数userId数量
["@column":"sum(if(userId%2=0,1,0))",
"@having":"to_days(now())-to_days(\`date\`)<=7"](http://apijson.cn:8080/get/{"Comment":{"@column":"sum(if(userId%252=0,1,0))","@having":"to_days(now())-to_days(\`date\`)<=7"}})
对应SQL是`SELECT sum(if(userId%2=0,1,0)) ... HAVING to_days(now())-to_days(\`` `date` `\`)<=7`

⑫ 从pictureList获取第0张图片:
["@position":0, //自定义关键词
"firstPicture()":"getFromArray(pictureList,@position)"](http://apijson.cn:8080/get/{"User":{"id":38710,"@position":0,"firstPicture()":"getFromArray(pictureList,@position)"}}) + 对象关键词,可自定义 | "@key":Object,@key为 Table:{} 中{}内的关键词,Object的类型由@key指定

① "@combine":"&key0,&key1,\|key2,key3,
!key4,!key5,&key6,key7...",条件组合方式,\| 可省略。会自动把同类的合并,外层按照 & \| ! 顺序,内层的按传参顺序组合成
(key0 & key1 & key6 & 其它key) & (key2 \| key3 \| key7) & !(key4 \| key5)
这种连接方式,其中 "其它key" 是指与 @combine 在同一对象,且未被它声明的条件 key,默认都是 & 连接

② "@column":"column;function(arg)...",返回字段

③ "@order":"column0+,column1-...",排序方式

④ "@group":"column0,column1...",分组方式。如果@column里声明了Table的id,则id也必须在@group中声明;其它情况下必须满足至少一个条件:
1.分组的key在@column里声明
2.Table主键在@group中声明

⑤ "@having":"function0(...)?value0;function1(...)?value1;function2(...)?value2...",SQL函数条件,一般和@group一起用,函数一般在@column里声明

⑥ "@schema":"sys",集合空间(模式),非默认的值可通过它来指定,可以在最外层作为全局默认配置

⑦ "@database":"POSTGRESQL",跨数据库,非默认的值可通过它来指定,可以在最外层作为全局默认配置

⑧ "@json":"key0,key1...",转为 JSON 格式返回,符合 JSONObject 则转为 {...},符合 JSONArray 则转为 \[...]

⑨ "@role":"OWNER",来访角色,包括
UNKNOWN,LOGIN,CONTACT,CIRCLE,OWNER,ADMIN,
可以在最外层作为全局默认配置,
可自定义其它角色并重写 Verifier.verify 等相关方法来自定义校验

⑩ "@explain":true,性能分析,可以在最外层作为全局默认配置

⑪ "@raw":"SQL片段或SQL片段的别名",自定义原始SQL片段,可扩展嵌套SQL函数等复杂语句,必须是后端已配置的,谨慎使用,其它功能符做不到才考虑

⑫ "@otherKey":Object,自定义关键词,名称和以上系统关键词不一样,且原样返回上传的值 | ① 搜索name或tag任何一个字段包含字符a的User列表:
["name~":"a",
"tag~":"a",
"@combine":"name~,tag~"](http://apijson.cn:8080/get/{"User[]":{"count":10,"User":{"@column":"id,name,tag","name~":"a","tag~":"a","@combine":"name~,tag~"}}})
对应SQL是`name REGEXP 'a' OR tag REGEXP 'a'`

② 只查询id,sex,name这几列并且请求结果也按照这个顺序:
["@column":"id,sex,name"](http://apijson.cn:8080/get/{"User":{"@column":"id,sex,name","id":38710}})
对应SQL是`SELECT id,sex,name`

③ 查询按 name降序、id默认顺序 排序的User数组:
["@order":"name-,id"](http://apijson.cn:8080/get/{"[]":{"count":10,"User":{"@column":"name,id","@order":"name-,id"}}})
对应SQL是`ORDER BY name DESC,id`

④ 查询按userId分组的Moment数组:
["@group":"userId,id"](http://apijson.cn:8080/get/{"[]":{"count":10,"Moment":%7B"@column":"userId,id","@group":"userId,id"}}})
对应SQL是`GROUP BY userId,id`

⑤ 查询 按userId分组、id最大值>=100 的Moment数组:
["@column":"userId;max(id)",
"@group":"userId",
"@having":"max(id)>=100"](http://apijson.cn:8080/get/{"[]":{"count":10,"Moment":{"@column":"userId%253Bmax(id)","@group":"userId","@having":"max(id)>=100"}}})
对应SQL是`SELECT userId,max(id) GROUP BY userId HAVING max(id)>=100`
还可以指定函数返回名:
["@column":"userId;max(id):maxId",
"@group":"userId",
"@having":"maxId>=100"](http://apijson.cn:8080/get/{"[]":{"count":10,"Moment":{"@column":"userId%253Bmax(id):maxId","@group":"userId","@having":"maxId>=100"}}})
对应SQL是`SELECT userId,max(id) AS maxId GROUP BY userId HAVING maxId>=100`

⑥ 查询 sys 内的 User 表:
["@schema":"sys"](http://apijson.cn:8080/get/{"User":{"@schema":"sys"}})
对应SQL是`FROM sys.User`

⑦ 查询 PostgreSQL 数据库的 User 表:
["@database":"POSTGRESQL"](http://apijson.cn:8080/get/{"User":{"@database":"POSTGRESQL","@explain":true}})

⑧ 将 VARCHAR 字符串字段 get 转为 JSONArray 返回:
["@json":"get"](http://apijson.cn:8080/get/{"Access":{"@json":"get"}})

⑨ 查询当前用户的动态:
["@role":"OWNER"](http://apijson.cn:8080/get/{"[]":{"Moment":{"@role":"OWNER"}}})

⑩ 开启性能分析:
["@explain":true](http://apijson.cn:8080/get/{"[]":{"Moment":{"@explain":true}}})
对应SQL是`EXPLAIN`

⑪ 统计最近一周偶数userId数量
["@column":"sum(if(userId%2=0,1,0))",
"@having":"to_days(now())-to_days(\`date\`)<=7"](http://apijson.cn:8080/get/{"Comment":{"@column":"sum(if(userId%252=0,1,0))","@having":"to_days(now())-to_days(\`date\`)<=7"}})
对应SQL是``SELECT sum(if(userId%2=0,1,0)) ... HAVING to_days(now())-to_days(`date`)<=7``

⑫ 从pictureList获取第0张图片:
["@position":0, //自定义关键词
"firstPicture()":"getFromArray(pictureList,@position)"](http://apijson.cn:8080/get/{"User":{"id":38710,"@position":0,"firstPicture()":"getFromArray(pictureList,@position)"}})
From bc455f72334bdd789317fddd23697a47cf664fa9 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Tue, 22 Dec 2020 00:36:21 +0800 Subject: [PATCH 0117/1181] Update Document.md --- Document.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Document.md b/Document.md index 82a7703b1..3d3f4c5ac 100644 --- a/Document.md +++ b/Document.md @@ -370,6 +370,6 @@ DELETE:
删除数据 | base_url/delete/ | {
   TableName:{< 比较运算 | >, <, >=, <= 比较运算符,用于
① 提供 "id{}":"<=90000" 这种条件范围的简化写法

② 实现子查询相关比较运算

不支持 "key=":Object 和 "key!=":Object 这两种写法,直接用更简单的 "key":Object 和 "key!":Object 替代。 | ① ["id<=":90000](http://apijson.cn:8080/get/{"[]":{"User":{"id<=":90000}}}),对应SQL是`id<=90000`,查询符合id<=90000的一个User数组

② ["id>@":{
   "from":"Comment",
   "Comment":{
      "@column":"min(userId)"
   }
}](http://apijson.cn:8080/get/{"User":{"id>@":{"from":"Comment","Comment":{"@column":"min(userId)"}}}})
WHERE id>(SELECT min(userId) FROM Comment) 逻辑运算 | &, \|, ! 逻辑运算符,对应数据库 SQL 中的 AND, OR, NOT。
横或纵与:同一键值对的值内条件默认 \| 或连接,不同键值对的条件默认 & 与连接。

① & 可用于"key&{}":"条件"等

② \| 可用于"key\|{}":"条件", "key\|{}":[]等,一般可省略

③ ! 可单独使用,如"key!":Object,也可像&,\|一样配合其他功能符使用
"key!":null 无效,null 值会导致整个键值对被忽略解析,可以用 "key{}":"!=null" 替代,
"key":null 同理,用 "key{}":"=null" 替代。 | ① ["id&{}":">80000,<=90000"](http://apijson.cn:8080/head/{"User":{"id&{}":">80000,<=90000"}}),对应SQL是`id>80000 AND id<=90000`,即id满足id>80000 & id<=90000

② ["id\|{}":">90000,<=80000"](http://apijson.cn:8080/head/{"User":{"id\|{}":">90000,<=80000"}}),同"id{}":">90000,<=80000",对应SQL是`id>80000 OR id<=90000`,即id满足id>90000 \| id<=80000

③ ["id!{}":[82001,38710]](http://apijson.cn:8080/head/{"User":{"id!{}":[82001,38710]}}),对应SQL是`id NOT IN(82001,38710)`,即id满足 ! (id=82001 \| id=38710),可过滤黑名单的消息 数组关键词,可自定义 | "key":Object,key为 "[]":{} 中{}内的关键词,Object的类型由key指定

① "count":Integer,查询数量,0 表示最大值,默认最大值为100

② "page":Integer,查询页码,从0开始,默认最大值为100,一般和count一起用

③ "query":Integer,查询内容
0-对象,1-总数和分页详情,2-以上全部
总数关键词为 total,分页详情关键词为 info,
它们都和 query 同级,通过引用赋值得到,例如
"total@":"/[]/total", "info@":"/[]/info"
这里query及total仅为GET类型的请求提供方便,
一般可直接用HEAD类型的请求获取总数

④ "join":"&/Table0/key0@,\多表连接方式:
"\<" - LEFT JOIN
">" - RIGHT JOIN
"&" - INNER JOIN
"\|" - FULL JOIN
"!" - OUTTER JOIN
"@" - APP JOIN
其中 @ APP JOIN 为应用层连表,会从已查出的主表里取得所有副表 key@ 关联的主表内的 refKey 作为一个数组 refKeys: [value0, value1...],然后把原来副表 count 次查询 key=$refKey 的 SQL 用 key IN($refKeys) 的方式合并为一条 SQL 来优化性能;
其它 JOIN 都是 SQL JOIN,具体功能和 MySQL,PostgreSQL 等数据库的 JOIN 一一对应
`"join":"`"MainTable":{},`
`"ViceTable":{"key@":"/MainTable/refKey"}`
会对应生成
`MainTable LEFT JOIN ViceTable`
`ON ViceTable.key=MainTable.refKey`

⑤ "otherKey":Object,自定义关键词,名称和以上系统关键词不一样,且原样返回上传的值 | ① 查询User数组,最多5个:
["count":5](http://apijson.cn:8080/get/{"[]":{"count":5,"User":{}}})
对应SQL是`LIMIT 5`

② 查询第3页的User数组,每页5个:
["count":5,
"page":3](http://apijson.cn:8080/get/{"[]":{"count":5,"page":3,"User":{}}})
对应SQL是`LIMIT 5 OFFSET 15`

③ 查询User数组和对应的User总数:
["[]":{
   "query":2,
   "User":{}
},
"total@":"/[]/total",
"info@":"/[]/info"](http://apijson.cn:8080/get/{"[]":{"query":2,"count":5,"User":{}},"total@":"%252F[]%252Ftotal","info@":"%252F[]%252Finfo"})
返回的数据中,总数及分页详情结构为:
"total":139, //总数
"info":{ //分页详情
   "total":139, //总数
   "count":5, //每页数量
   "page":0, //当前页码
   "max":27, //最大页码
   "more":true, //是否还有更多
   "first":true, //是否为首页
   "last":false //是否为尾页
}

④ Moment INNER JOIN User LEFT JOIN Comment:
["[]":{
   "join":"&/User/id@,\    "Moment":{},
   "User":{
     "name~":"t",
     "id@":"/Moment/userId"
   },
   "Comment":{
     "momentId@":"/Moment/id"
   }
}](http://apijson.cn:8080/get/{"[]":{"count":5,"join":"&%252FUser%252Fid@,\<%252FComment%252FmomentId@","Moment":{"@column":"id,userId,content"},"User":{"name~":"t","id@":"%252FMoment%252FuserId","@column":"id,name,head"},"Comment":{"momentId@":"%252FMoment%252Fid","@column":"id,momentId,content"}}})

⑤ 每一层都加当前用户名:
["User":{},
"[]":{
   "name@":"User/name", //自定义关键词
   "Moment":{}
}](http://apijson.cn:8080/get/{"User":{},"[]":{"name@":"User%252Fname","Moment":{}}}) - 对象关键词,可自定义 | "@key":Object,@key为 Table:{} 中{}内的关键词,Object的类型由@key指定

① "@combine":"&key0,&key1,\|key2,key3,
!key4,!key5,&key6,key7...",条件组合方式,\| 可省略。会自动把同类的合并,外层按照 & \| ! 顺序,内层的按传参顺序组合成
(key0 & key1 & key6 & 其它key) & (key2 \| key3 \| key7) & !(key4 \| key5)
这种连接方式,其中 "其它key" 是指与 @combine 在同一对象,且未被它声明的条件 key,默认都是 & 连接

② "@column":"column;function(arg)...",返回字段

③ "@order":"column0+,column1-...",排序方式

④ "@group":"column0,column1...",分组方式。如果@column里声明了Table的id,则id也必须在@group中声明;其它情况下必须满足至少一个条件:
1.分组的key在@column里声明
2.Table主键在@group中声明

⑤ "@having":"function0(...)?value0;function1(...)?value1;function2(...)?value2...",SQL函数条件,一般和@group一起用,函数一般在@column里声明

⑥ "@schema":"sys",集合空间(模式),非默认的值可通过它来指定,可以在最外层作为全局默认配置

⑦ "@database":"POSTGRESQL",跨数据库,非默认的值可通过它来指定,可以在最外层作为全局默认配置

⑧ "@json":"key0,key1...",转为 JSON 格式返回,符合 JSONObject 则转为 {...},符合 JSONArray 则转为 \[...]

⑨ "@role":"OWNER",来访角色,包括
UNKNOWN,LOGIN,CONTACT,CIRCLE,OWNER,ADMIN,
可以在最外层作为全局默认配置,
可自定义其它角色并重写 Verifier.verify 等相关方法来自定义校验

⑩ "@explain":true,性能分析,可以在最外层作为全局默认配置

⑪ "@raw":"SQL片段或SQL片段的别名",自定义原始SQL片段,可扩展嵌套SQL函数等复杂语句,必须是后端已配置的,谨慎使用,其它功能符做不到才考虑

⑫ "@otherKey":Object,自定义关键词,名称和以上系统关键词不一样,且原样返回上传的值 | ① 搜索name或tag任何一个字段包含字符a的User列表:
["name~":"a",
"tag~":"a",
"@combine":"name~,tag~"](http://apijson.cn:8080/get/{"User[]":{"count":10,"User":{"@column":"id,name,tag","name~":"a","tag~":"a","@combine":"name~,tag~"}}})
对应SQL是`name REGEXP 'a' OR tag REGEXP 'a'`

② 只查询id,sex,name这几列并且请求结果也按照这个顺序:
["@column":"id,sex,name"](http://apijson.cn:8080/get/{"User":{"@column":"id,sex,name","id":38710}})
对应SQL是`SELECT id,sex,name`

③ 查询按 name降序、id默认顺序 排序的User数组:
["@order":"name-,id"](http://apijson.cn:8080/get/{"[]":{"count":10,"User":{"@column":"name,id","@order":"name-,id"}}})
对应SQL是`ORDER BY name DESC,id`

④ 查询按userId分组的Moment数组:
["@group":"userId,id"](http://apijson.cn:8080/get/{"[]":{"count":10,"Moment":%7B"@column":"userId,id","@group":"userId,id"}}})
对应SQL是`GROUP BY userId,id`

⑤ 查询 按userId分组、id最大值>=100 的Moment数组:
["@column":"userId;max(id)",
"@group":"userId",
"@having":"max(id)>=100"](http://apijson.cn:8080/get/{"[]":{"count":10,"Moment":{"@column":"userId%253Bmax(id)","@group":"userId","@having":"max(id)>=100"}}})
对应SQL是`SELECT userId,max(id) GROUP BY userId HAVING max(id)>=100`
还可以指定函数返回名:
["@column":"userId;max(id):maxId",
"@group":"userId",
"@having":"maxId>=100"](http://apijson.cn:8080/get/{"[]":{"count":10,"Moment":{"@column":"userId%253Bmax(id):maxId","@group":"userId","@having":"maxId>=100"}}})
对应SQL是`SELECT userId,max(id) AS maxId GROUP BY userId HAVING maxId>=100`

⑥ 查询 sys 内的 User 表:
["@schema":"sys"](http://apijson.cn:8080/get/{"User":{"@schema":"sys"}})
对应SQL是`FROM sys.User`

⑦ 查询 PostgreSQL 数据库的 User 表:
["@database":"POSTGRESQL"](http://apijson.cn:8080/get/{"User":{"@database":"POSTGRESQL","@explain":true}})

⑧ 将 VARCHAR 字符串字段 get 转为 JSONArray 返回:
["@json":"get"](http://apijson.cn:8080/get/{"Access":{"@json":"get"}})

⑨ 查询当前用户的动态:
["@role":"OWNER"](http://apijson.cn:8080/get/{"[]":{"Moment":{"@role":"OWNER"}}})

⑩ 开启性能分析:
["@explain":true](http://apijson.cn:8080/get/{"[]":{"Moment":{"@explain":true}}})
对应SQL是`EXPLAIN`

⑪ 统计最近一周偶数userId数量
["@column":"sum(if(userId%2=0,1,0))",
"@having":"to_days(now())-to_days(\`date\`)<=7"](http://apijson.cn:8080/get/{"Comment":{"@column":"sum(if(userId%252=0,1,0))","@having":"to_days(now())-to_days(\`date\`)<=7"}})
对应SQL是``SELECT sum(if(userId%2=0,1,0)) ... HAVING to_days(now())-to_days(`date`)<=7``

⑫ 从pictureList获取第0张图片:
["@position":0, //自定义关键词
"firstPicture()":"getFromArray(pictureList,@position)"](http://apijson.cn:8080/get/{"User":{"id":38710,"@position":0,"firstPicture()":"getFromArray(pictureList,@position)"}}) + 对象关键词,可自定义 | "@key":Object,@key为 Table:{} 中{}内的关键词,Object的类型由@key指定

① "@combine":"&key0,&key1,\|key2,key3,
!key4,!key5,&key6,key7...",条件组合方式,\| 可省略。会自动把同类的合并,外层按照 & \| ! 顺序,内层的按传参顺序组合成
(key0 & key1 & key6 & 其它key) & (key2 \| key3 \| key7) & !(key4 \| key5)
这种连接方式,其中 "其它key" 是指与 @combine 在同一对象,且未被它声明的条件 key,默认都是 & 连接

② "@column":"column;function(arg)...",返回字段

③ "@order":"column0+,column1-...",排序方式

④ "@group":"column0,column1...",分组方式。如果@column里声明了Table的id,则id也必须在@group中声明;其它情况下必须满足至少一个条件:
1.分组的key在@column里声明
2.Table主键在@group中声明

⑤ "@having":"function0(...)?value0;function1(...)?value1;function2(...)?value2...",SQL函数条件,一般和@group一起用,函数一般在@column里声明

⑥ "@schema":"sys",集合空间(模式),非默认的值可通过它来指定,可以在最外层作为全局默认配置

⑦ "@database":"POSTGRESQL",跨数据库,非默认的值可通过它来指定,可以在最外层作为全局默认配置

⑧ "@json":"key0,key1...",转为 JSON 格式返回,符合 JSONObject 则转为 {...},符合 JSONArray 则转为 \[...]

⑨ "@role":"OWNER",来访角色,包括
UNKNOWN,LOGIN,CONTACT,CIRCLE,OWNER,ADMIN,
可以在最外层作为全局默认配置,
可自定义其它角色并重写 Verifier.verify 等相关方法来自定义校验

⑩ "@explain":true,性能分析,可以在最外层作为全局默认配置

⑪ "@raw":"SQL片段或SQL片段的别名",自定义原始SQL片段,可扩展嵌套SQL函数等复杂语句,必须是后端已配置的,谨慎使用,其它功能符做不到才考虑

⑫ "@otherKey":Object,自定义关键词,名称和以上系统关键词不一样,且原样返回上传的值 | ① 搜索name或tag任何一个字段包含字符a的User列表:
["name~":"a",
"tag~":"a",
"@combine":"name~,tag~"](http://apijson.cn:8080/get/{"User[]":{"count":10,"User":{"@column":"id,name,tag","name~":"a","tag~":"a","@combine":"name~,tag~"}}})
对应SQL是`name REGEXP 'a' OR tag REGEXP 'a'`

② 只查询id,sex,name这几列并且请求结果也按照这个顺序:
["@column":"id,sex,name"](http://apijson.cn:8080/get/{"User":{"@column":"id,sex,name","id":38710}})
对应SQL是`SELECT id,sex,name`

③ 查询按 name降序、id默认顺序 排序的User数组:
["@order":"name-,id"](http://apijson.cn:8080/get/{"[]":{"count":10,"User":{"@column":"name,id","@order":"name-,id"}}})
对应SQL是`ORDER BY name DESC,id`

④ 查询按userId分组的Moment数组:
["@group":"userId,id"](http://apijson.cn:8080/get/{"[]":{"count":10,"Moment":%7B"@column":"userId,id","@group":"userId,id"}}})
对应SQL是`GROUP BY userId,id`

⑤ 查询 按userId分组、id最大值>=100 的Moment数组:
["@column":"userId;max(id)",
"@group":"userId",
"@having":"max(id)>=100"](http://apijson.cn:8080/get/{"[]":{"count":10,"Moment":{"@column":"userId%253Bmax(id)","@group":"userId","@having":"max(id)>=100"}}})
对应SQL是`SELECT userId,max(id) GROUP BY userId HAVING max(id)>=100`
还可以指定函数返回名:
["@column":"userId;max(id):maxId",
"@group":"userId",
"@having":"maxId>=100"](http://apijson.cn:8080/get/{"[]":{"count":10,"Moment":{"@column":"userId%253Bmax(id):maxId","@group":"userId","@having":"maxId>=100"}}})
对应SQL是`SELECT userId,max(id) AS maxId GROUP BY userId HAVING maxId>=100`

⑥ 查询 sys 内的 User 表:
["@schema":"sys"](http://apijson.cn:8080/get/{"User":{"@schema":"sys"}})
对应SQL是`FROM sys.`

⑦ 查询 PostgreSQL 数据库的 User 表:
["@database":"POSTGRESQL"](http://apijson.cn:8080/get/{"User":{"@database":"POSTGRESQL","@explain":true}})

⑧ 将 VARCHAR 字符串字段 get 转为 JSONArray 返回:
["@json":"get"](http://apijson.cn:8080/get/{"Access":{"@json":"get"}})

⑨ 查询当前用户的动态:
["@role":"OWNER"](http://apijson.cn:8080/get/{"[]":{"Moment":{"@role":"OWNER"}}})

⑩ 开启性能分析:
["@explain":true](http://apijson.cn:8080/get/{"[]":{"Moment":{"@explain":true}}})
对应SQL是`EXPLAIN`

⑪ 统计最近一周偶数userId数量
["@column":"sum(if(userId%2=0,1,0))",
"@having":"to_days(now())-to_days(\`date\`)<=7"](http://apijson.cn:8080/get/{"Comment":{"@column":"sum(if(userId%252=0,1,0))","@having":"to_days(now())-to_days(\`date\`)<=7"}})
对应SQL是``SELECT sum(if(userId%2=0,1,0)) ... HAVING to_days(now())-to_days(`date`)<=7``

⑫ 从pictureList获取第0张图片:
["@position":0, //自定义关键词
"firstPicture()":"getFromArray(pictureList,@position)"](http://apijson.cn:8080/get/{"User":{"id":38710,"@position":0,"firstPicture()":"getFromArray(pictureList,@position)"}})
From a94a1a48a08dff33475b08bb3c3b1b3111b4a5ef Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Tue, 22 Dec 2020 00:37:20 +0800 Subject: [PATCH 0118/1181] Update Document.md --- Document.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Document.md b/Document.md index 3d3f4c5ac..289a918aa 100644 --- a/Document.md +++ b/Document.md @@ -370,6 +370,6 @@ DELETE:
删除数据 | base_url/delete/ | {
   TableName:{< 比较运算 | >, <, >=, <= 比较运算符,用于
① 提供 "id{}":"<=90000" 这种条件范围的简化写法

② 实现子查询相关比较运算

不支持 "key=":Object 和 "key!=":Object 这两种写法,直接用更简单的 "key":Object 和 "key!":Object 替代。 | ① ["id<=":90000](http://apijson.cn:8080/get/{"[]":{"User":{"id<=":90000}}}),对应SQL是`id<=90000`,查询符合id<=90000的一个User数组

② ["id>@":{
   "from":"Comment",
   "Comment":{
      "@column":"min(userId)"
   }
}](http://apijson.cn:8080/get/{"User":{"id>@":{"from":"Comment","Comment":{"@column":"min(userId)"}}}})
WHERE id>(SELECT min(userId) FROM Comment) 逻辑运算 | &, \|, ! 逻辑运算符,对应数据库 SQL 中的 AND, OR, NOT。
横或纵与:同一键值对的值内条件默认 \| 或连接,不同键值对的条件默认 & 与连接。

① & 可用于"key&{}":"条件"等

② \| 可用于"key\|{}":"条件", "key\|{}":[]等,一般可省略

③ ! 可单独使用,如"key!":Object,也可像&,\|一样配合其他功能符使用
"key!":null 无效,null 值会导致整个键值对被忽略解析,可以用 "key{}":"!=null" 替代,
"key":null 同理,用 "key{}":"=null" 替代。 | ① ["id&{}":">80000,<=90000"](http://apijson.cn:8080/head/{"User":{"id&{}":">80000,<=90000"}}),对应SQL是`id>80000 AND id<=90000`,即id满足id>80000 & id<=90000

② ["id\|{}":">90000,<=80000"](http://apijson.cn:8080/head/{"User":{"id\|{}":">90000,<=80000"}}),同"id{}":">90000,<=80000",对应SQL是`id>80000 OR id<=90000`,即id满足id>90000 \| id<=80000

③ ["id!{}":[82001,38710]](http://apijson.cn:8080/head/{"User":{"id!{}":[82001,38710]}}),对应SQL是`id NOT IN(82001,38710)`,即id满足 ! (id=82001 \| id=38710),可过滤黑名单的消息 数组关键词,可自定义 | "key":Object,key为 "[]":{} 中{}内的关键词,Object的类型由key指定

① "count":Integer,查询数量,0 表示最大值,默认最大值为100

② "page":Integer,查询页码,从0开始,默认最大值为100,一般和count一起用

③ "query":Integer,查询内容
0-对象,1-总数和分页详情,2-以上全部
总数关键词为 total,分页详情关键词为 info,
它们都和 query 同级,通过引用赋值得到,例如
"total@":"/[]/total", "info@":"/[]/info"
这里query及total仅为GET类型的请求提供方便,
一般可直接用HEAD类型的请求获取总数

④ "join":"&/Table0/key0@,\多表连接方式:
"\<" - LEFT JOIN
">" - RIGHT JOIN
"&" - INNER JOIN
"\|" - FULL JOIN
"!" - OUTTER JOIN
"@" - APP JOIN
其中 @ APP JOIN 为应用层连表,会从已查出的主表里取得所有副表 key@ 关联的主表内的 refKey 作为一个数组 refKeys: [value0, value1...],然后把原来副表 count 次查询 key=$refKey 的 SQL 用 key IN($refKeys) 的方式合并为一条 SQL 来优化性能;
其它 JOIN 都是 SQL JOIN,具体功能和 MySQL,PostgreSQL 等数据库的 JOIN 一一对应
`"join":"`"MainTable":{},`
`"ViceTable":{"key@":"/MainTable/refKey"}`
会对应生成
`MainTable LEFT JOIN ViceTable`
`ON ViceTable.key=MainTable.refKey`

⑤ "otherKey":Object,自定义关键词,名称和以上系统关键词不一样,且原样返回上传的值 | ① 查询User数组,最多5个:
["count":5](http://apijson.cn:8080/get/{"[]":{"count":5,"User":{}}})
对应SQL是`LIMIT 5`

② 查询第3页的User数组,每页5个:
["count":5,
"page":3](http://apijson.cn:8080/get/{"[]":{"count":5,"page":3,"User":{}}})
对应SQL是`LIMIT 5 OFFSET 15`

③ 查询User数组和对应的User总数:
["[]":{
   "query":2,
   "User":{}
},
"total@":"/[]/total",
"info@":"/[]/info"](http://apijson.cn:8080/get/{"[]":{"query":2,"count":5,"User":{}},"total@":"%252F[]%252Ftotal","info@":"%252F[]%252Finfo"})
返回的数据中,总数及分页详情结构为:
"total":139, //总数
"info":{ //分页详情
   "total":139, //总数
   "count":5, //每页数量
   "page":0, //当前页码
   "max":27, //最大页码
   "more":true, //是否还有更多
   "first":true, //是否为首页
   "last":false //是否为尾页
}

④ Moment INNER JOIN User LEFT JOIN Comment:
["[]":{
   "join":"&/User/id@,\    "Moment":{},
   "User":{
     "name~":"t",
     "id@":"/Moment/userId"
   },
   "Comment":{
     "momentId@":"/Moment/id"
   }
}](http://apijson.cn:8080/get/{"[]":{"count":5,"join":"&%252FUser%252Fid@,\<%252FComment%252FmomentId@","Moment":{"@column":"id,userId,content"},"User":{"name~":"t","id@":"%252FMoment%252FuserId","@column":"id,name,head"},"Comment":{"momentId@":"%252FMoment%252Fid","@column":"id,momentId,content"}}})

⑤ 每一层都加当前用户名:
["User":{},
"[]":{
   "name@":"User/name", //自定义关键词
   "Moment":{}
}](http://apijson.cn:8080/get/{"User":{},"[]":{"name@":"User%252Fname","Moment":{}}}) - 对象关键词,可自定义 | "@key":Object,@key为 Table:{} 中{}内的关键词,Object的类型由@key指定

① "@combine":"&key0,&key1,\|key2,key3,
!key4,!key5,&key6,key7...",条件组合方式,\| 可省略。会自动把同类的合并,外层按照 & \| ! 顺序,内层的按传参顺序组合成
(key0 & key1 & key6 & 其它key) & (key2 \| key3 \| key7) & !(key4 \| key5)
这种连接方式,其中 "其它key" 是指与 @combine 在同一对象,且未被它声明的条件 key,默认都是 & 连接

② "@column":"column;function(arg)...",返回字段

③ "@order":"column0+,column1-...",排序方式

④ "@group":"column0,column1...",分组方式。如果@column里声明了Table的id,则id也必须在@group中声明;其它情况下必须满足至少一个条件:
1.分组的key在@column里声明
2.Table主键在@group中声明

⑤ "@having":"function0(...)?value0;function1(...)?value1;function2(...)?value2...",SQL函数条件,一般和@group一起用,函数一般在@column里声明

⑥ "@schema":"sys",集合空间(模式),非默认的值可通过它来指定,可以在最外层作为全局默认配置

⑦ "@database":"POSTGRESQL",跨数据库,非默认的值可通过它来指定,可以在最外层作为全局默认配置

⑧ "@json":"key0,key1...",转为 JSON 格式返回,符合 JSONObject 则转为 {...},符合 JSONArray 则转为 \[...]

⑨ "@role":"OWNER",来访角色,包括
UNKNOWN,LOGIN,CONTACT,CIRCLE,OWNER,ADMIN,
可以在最外层作为全局默认配置,
可自定义其它角色并重写 Verifier.verify 等相关方法来自定义校验

⑩ "@explain":true,性能分析,可以在最外层作为全局默认配置

⑪ "@raw":"SQL片段或SQL片段的别名",自定义原始SQL片段,可扩展嵌套SQL函数等复杂语句,必须是后端已配置的,谨慎使用,其它功能符做不到才考虑

⑫ "@otherKey":Object,自定义关键词,名称和以上系统关键词不一样,且原样返回上传的值 | ① 搜索name或tag任何一个字段包含字符a的User列表:
["name~":"a",
"tag~":"a",
"@combine":"name~,tag~"](http://apijson.cn:8080/get/{"User[]":{"count":10,"User":{"@column":"id,name,tag","name~":"a","tag~":"a","@combine":"name~,tag~"}}})
对应SQL是`name REGEXP 'a' OR tag REGEXP 'a'`

② 只查询id,sex,name这几列并且请求结果也按照这个顺序:
["@column":"id,sex,name"](http://apijson.cn:8080/get/{"User":{"@column":"id,sex,name","id":38710}})
对应SQL是`SELECT id,sex,name`

③ 查询按 name降序、id默认顺序 排序的User数组:
["@order":"name-,id"](http://apijson.cn:8080/get/{"[]":{"count":10,"User":{"@column":"name,id","@order":"name-,id"}}})
对应SQL是`ORDER BY name DESC,id`

④ 查询按userId分组的Moment数组:
["@group":"userId,id"](http://apijson.cn:8080/get/{"[]":{"count":10,"Moment":%7B"@column":"userId,id","@group":"userId,id"}}})
对应SQL是`GROUP BY userId,id`

⑤ 查询 按userId分组、id最大值>=100 的Moment数组:
["@column":"userId;max(id)",
"@group":"userId",
"@having":"max(id)>=100"](http://apijson.cn:8080/get/{"[]":{"count":10,"Moment":{"@column":"userId%253Bmax(id)","@group":"userId","@having":"max(id)>=100"}}})
对应SQL是`SELECT userId,max(id) GROUP BY userId HAVING max(id)>=100`
还可以指定函数返回名:
["@column":"userId;max(id):maxId",
"@group":"userId",
"@having":"maxId>=100"](http://apijson.cn:8080/get/{"[]":{"count":10,"Moment":{"@column":"userId%253Bmax(id):maxId","@group":"userId","@having":"maxId>=100"}}})
对应SQL是`SELECT userId,max(id) AS maxId GROUP BY userId HAVING maxId>=100`

⑥ 查询 sys 内的 User 表:
["@schema":"sys"](http://apijson.cn:8080/get/{"User":{"@schema":"sys"}})
对应SQL是`FROM sys.`

⑦ 查询 PostgreSQL 数据库的 User 表:
["@database":"POSTGRESQL"](http://apijson.cn:8080/get/{"User":{"@database":"POSTGRESQL","@explain":true}})

⑧ 将 VARCHAR 字符串字段 get 转为 JSONArray 返回:
["@json":"get"](http://apijson.cn:8080/get/{"Access":{"@json":"get"}})

⑨ 查询当前用户的动态:
["@role":"OWNER"](http://apijson.cn:8080/get/{"[]":{"Moment":{"@role":"OWNER"}}})

⑩ 开启性能分析:
["@explain":true](http://apijson.cn:8080/get/{"[]":{"Moment":{"@explain":true}}})
对应SQL是`EXPLAIN`

⑪ 统计最近一周偶数userId数量
["@column":"sum(if(userId%2=0,1,0))",
"@having":"to_days(now())-to_days(\`date\`)<=7"](http://apijson.cn:8080/get/{"Comment":{"@column":"sum(if(userId%252=0,1,0))","@having":"to_days(now())-to_days(\`date\`)<=7"}})
对应SQL是``SELECT sum(if(userId%2=0,1,0)) ... HAVING to_days(now())-to_days(`date`)<=7``

⑫ 从pictureList获取第0张图片:
["@position":0, //自定义关键词
"firstPicture()":"getFromArray(pictureList,@position)"](http://apijson.cn:8080/get/{"User":{"id":38710,"@position":0,"firstPicture()":"getFromArray(pictureList,@position)"}}) + 对象关键词,可自定义 | "@key":Object,@key为 Table:{} 中{}内的关键词,Object的类型由@key指定

① "@combine":"&key0,&key1,\|key2,key3,
!key4,!key5,&key6,key7...",条件组合方式,\| 可省略。会自动把同类的合并,外层按照 & \| ! 顺序,内层的按传参顺序组合成
(key0 & key1 & key6 & 其它key) & (key2 \| key3 \| key7) & !(key4 \| key5)
这种连接方式,其中 "其它key" 是指与 @combine 在同一对象,且未被它声明的条件 key,默认都是 & 连接

② "@column":"column;function(arg)...",返回字段

③ "@order":"column0+,column1-...",排序方式

④ "@group":"column0,column1...",分组方式。如果@column里声明了Table的id,则id也必须在@group中声明;其它情况下必须满足至少一个条件:
1.分组的key在@column里声明
2.Table主键在@group中声明

⑤ "@having":"function0(...)?value0;function1(...)?value1;function2(...)?value2...",SQL函数条件,一般和@group一起用,函数一般在@column里声明

⑥ "@schema":"sys",集合空间(模式),非默认的值可通过它来指定,可以在最外层作为全局默认配置

⑦ "@database":"POSTGRESQL",跨数据库,非默认的值可通过它来指定,可以在最外层作为全局默认配置

⑧ "@json":"key0,key1...",转为 JSON 格式返回,符合 JSONObject 则转为 {...},符合 JSONArray 则转为 \[...]

⑨ "@role":"OWNER",来访角色,包括
UNKNOWN,LOGIN,CONTACT,CIRCLE,OWNER,ADMIN,
可以在最外层作为全局默认配置,
可自定义其它角色并重写 Verifier.verify 等相关方法来自定义校验

⑩ "@explain":true,性能分析,可以在最外层作为全局默认配置

⑪ "@raw":"SQL片段或SQL片段的别名",自定义原始SQL片段,可扩展嵌套SQL函数等复杂语句,必须是后端已配置的,谨慎使用,其它功能符做不到才考虑

⑫ "@otherKey":Object,自定义关键词,名称和以上系统关键词不一样,且原样返回上传的值 | ① 搜索name或tag任何一个字段包含字符a的User列表:
["name~":"a",
"tag~":"a",
"@combine":"name~,tag~"](http://apijson.cn:8080/get/{"User[]":{"count":10,"User":{"@column":"id,name,tag","name~":"a","tag~":"a","@combine":"name~,tag~"}}})
对应SQL是`name REGEXP 'a' OR tag REGEXP 'a'`

② 只查询id,sex,name这几列并且请求结果也按照这个顺序:
["@column":"id,sex,name"](http://apijson.cn:8080/get/{"User":{"@column":"id,sex,name","id":38710}})
对应SQL是`SELECT id,sex,name`

③ 查询按 name降序、id默认顺序 排序的User数组:
["@order":"name-,id"](http://apijson.cn:8080/get/{"[]":{"count":10,"User":{"@column":"name,id","@order":"name-,id"}}})
对应SQL是`ORDER BY name DESC,id`

④ 查询按userId分组的Moment数组:
["@group":"userId,id"](http://apijson.cn:8080/get/{"[]":{"count":10,"Moment":%7B"@column":"userId,id","@group":"userId,id"}}})
对应SQL是`GROUP BY userId,id`

⑤ 查询 按userId分组、id最大值>=100 的Moment数组:
["@column":"userId;max(id)",
"@group":"userId",
"@having":"max(id)>=100"](http://apijson.cn:8080/get/{"[]":{"count":10,"Moment":{"@column":"userId%253Bmax(id)","@group":"userId","@having":"max(id)>=100"}}})
对应SQL是`SELECT userId,max(id) ... GROUP BY userId HAVING max(id)>=100`
还可以指定函数返回名:
["@column":"userId;max(id):maxId",
"@group":"userId",
"@having":"maxId>=100"](http://apijson.cn:8080/get/{"[]":{"count":10,"Moment":{"@column":"userId%253Bmax(id):maxId","@group":"userId","@having":"maxId>=100"}}})
对应SQL是`SELECT userId,max(id) AS maxId ... GROUP BY userId HAVING maxId>=100`

⑥ 查询 sys 内的 User 表:
["@schema":"sys"](http://apijson.cn:8080/get/{"User":{"@schema":"sys"}})
对应SQL是`FROM sys.`

⑦ 查询 PostgreSQL 数据库的 User 表:
["@database":"POSTGRESQL"](http://apijson.cn:8080/get/{"User":{"@database":"POSTGRESQL","@explain":true}})

⑧ 将 VARCHAR 字符串字段 get 转为 JSONArray 返回:
["@json":"get"](http://apijson.cn:8080/get/{"Access":{"@json":"get"}})

⑨ 查询当前用户的动态:
["@role":"OWNER"](http://apijson.cn:8080/get/{"[]":{"Moment":{"@role":"OWNER"}}})

⑩ 开启性能分析:
["@explain":true](http://apijson.cn:8080/get/{"[]":{"Moment":{"@explain":true}}})
对应SQL是`EXPLAIN`

⑪ 统计最近一周偶数userId数量
["@column":"sum(if(userId%2=0,1,0))",
"@having":"to_days(now())-to_days(\`date\`)<=7"](http://apijson.cn:8080/get/{"Comment":{"@column":"sum(if(userId%252=0,1,0))","@having":"to_days(now())-to_days(\`date\`)<=7"}})
对应SQL是``SELECT sum(if(userId%2=0,1,0)) ... HAVING to_days(now())-to_days(`date`)<=7``

⑫ 从pictureList获取第0张图片:
["@position":0, //自定义关键词
"firstPicture()":"getFromArray(pictureList,@position)"](http://apijson.cn:8080/get/{"User":{"id":38710,"@position":0,"firstPicture()":"getFromArray(pictureList,@position)"}})
From fd8bc562147c293b5e90f334e71c3132c4a53a80 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Tue, 22 Dec 2020 00:37:51 +0800 Subject: [PATCH 0119/1181] Update Document.md --- Document.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Document.md b/Document.md index 289a918aa..57bb43c7a 100644 --- a/Document.md +++ b/Document.md @@ -370,6 +370,6 @@ DELETE:
删除数据 | base_url/delete/ | {
   TableName:{< 比较运算 | >, <, >=, <= 比较运算符,用于
① 提供 "id{}":"<=90000" 这种条件范围的简化写法

② 实现子查询相关比较运算

不支持 "key=":Object 和 "key!=":Object 这两种写法,直接用更简单的 "key":Object 和 "key!":Object 替代。 | ① ["id<=":90000](http://apijson.cn:8080/get/{"[]":{"User":{"id<=":90000}}}),对应SQL是`id<=90000`,查询符合id<=90000的一个User数组

② ["id>@":{
   "from":"Comment",
   "Comment":{
      "@column":"min(userId)"
   }
}](http://apijson.cn:8080/get/{"User":{"id>@":{"from":"Comment","Comment":{"@column":"min(userId)"}}}})
WHERE id>(SELECT min(userId) FROM Comment) 逻辑运算 | &, \|, ! 逻辑运算符,对应数据库 SQL 中的 AND, OR, NOT。
横或纵与:同一键值对的值内条件默认 \| 或连接,不同键值对的条件默认 & 与连接。

① & 可用于"key&{}":"条件"等

② \| 可用于"key\|{}":"条件", "key\|{}":[]等,一般可省略

③ ! 可单独使用,如"key!":Object,也可像&,\|一样配合其他功能符使用
"key!":null 无效,null 值会导致整个键值对被忽略解析,可以用 "key{}":"!=null" 替代,
"key":null 同理,用 "key{}":"=null" 替代。 | ① ["id&{}":">80000,<=90000"](http://apijson.cn:8080/head/{"User":{"id&{}":">80000,<=90000"}}),对应SQL是`id>80000 AND id<=90000`,即id满足id>80000 & id<=90000

② ["id\|{}":">90000,<=80000"](http://apijson.cn:8080/head/{"User":{"id\|{}":">90000,<=80000"}}),同"id{}":">90000,<=80000",对应SQL是`id>80000 OR id<=90000`,即id满足id>90000 \| id<=80000

③ ["id!{}":[82001,38710]](http://apijson.cn:8080/head/{"User":{"id!{}":[82001,38710]}}),对应SQL是`id NOT IN(82001,38710)`,即id满足 ! (id=82001 \| id=38710),可过滤黑名单的消息 数组关键词,可自定义 | "key":Object,key为 "[]":{} 中{}内的关键词,Object的类型由key指定

① "count":Integer,查询数量,0 表示最大值,默认最大值为100

② "page":Integer,查询页码,从0开始,默认最大值为100,一般和count一起用

③ "query":Integer,查询内容
0-对象,1-总数和分页详情,2-以上全部
总数关键词为 total,分页详情关键词为 info,
它们都和 query 同级,通过引用赋值得到,例如
"total@":"/[]/total", "info@":"/[]/info"
这里query及total仅为GET类型的请求提供方便,
一般可直接用HEAD类型的请求获取总数

④ "join":"&/Table0/key0@,\多表连接方式:
"\<" - LEFT JOIN
">" - RIGHT JOIN
"&" - INNER JOIN
"\|" - FULL JOIN
"!" - OUTTER JOIN
"@" - APP JOIN
其中 @ APP JOIN 为应用层连表,会从已查出的主表里取得所有副表 key@ 关联的主表内的 refKey 作为一个数组 refKeys: [value0, value1...],然后把原来副表 count 次查询 key=$refKey 的 SQL 用 key IN($refKeys) 的方式合并为一条 SQL 来优化性能;
其它 JOIN 都是 SQL JOIN,具体功能和 MySQL,PostgreSQL 等数据库的 JOIN 一一对应
`"join":"`"MainTable":{},`
`"ViceTable":{"key@":"/MainTable/refKey"}`
会对应生成
`MainTable LEFT JOIN ViceTable`
`ON ViceTable.key=MainTable.refKey`

⑤ "otherKey":Object,自定义关键词,名称和以上系统关键词不一样,且原样返回上传的值 | ① 查询User数组,最多5个:
["count":5](http://apijson.cn:8080/get/{"[]":{"count":5,"User":{}}})
对应SQL是`LIMIT 5`

② 查询第3页的User数组,每页5个:
["count":5,
"page":3](http://apijson.cn:8080/get/{"[]":{"count":5,"page":3,"User":{}}})
对应SQL是`LIMIT 5 OFFSET 15`

③ 查询User数组和对应的User总数:
["[]":{
   "query":2,
   "User":{}
},
"total@":"/[]/total",
"info@":"/[]/info"](http://apijson.cn:8080/get/{"[]":{"query":2,"count":5,"User":{}},"total@":"%252F[]%252Ftotal","info@":"%252F[]%252Finfo"})
返回的数据中,总数及分页详情结构为:
"total":139, //总数
"info":{ //分页详情
   "total":139, //总数
   "count":5, //每页数量
   "page":0, //当前页码
   "max":27, //最大页码
   "more":true, //是否还有更多
   "first":true, //是否为首页
   "last":false //是否为尾页
}

④ Moment INNER JOIN User LEFT JOIN Comment:
["[]":{
   "join":"&/User/id@,\    "Moment":{},
   "User":{
     "name~":"t",
     "id@":"/Moment/userId"
   },
   "Comment":{
     "momentId@":"/Moment/id"
   }
}](http://apijson.cn:8080/get/{"[]":{"count":5,"join":"&%252FUser%252Fid@,\<%252FComment%252FmomentId@","Moment":{"@column":"id,userId,content"},"User":{"name~":"t","id@":"%252FMoment%252FuserId","@column":"id,name,head"},"Comment":{"momentId@":"%252FMoment%252Fid","@column":"id,momentId,content"}}})

⑤ 每一层都加当前用户名:
["User":{},
"[]":{
   "name@":"User/name", //自定义关键词
   "Moment":{}
}](http://apijson.cn:8080/get/{"User":{},"[]":{"name@":"User%252Fname","Moment":{}}}) - 对象关键词,可自定义 | "@key":Object,@key为 Table:{} 中{}内的关键词,Object的类型由@key指定

① "@combine":"&key0,&key1,\|key2,key3,
!key4,!key5,&key6,key7...",条件组合方式,\| 可省略。会自动把同类的合并,外层按照 & \| ! 顺序,内层的按传参顺序组合成
(key0 & key1 & key6 & 其它key) & (key2 \| key3 \| key7) & !(key4 \| key5)
这种连接方式,其中 "其它key" 是指与 @combine 在同一对象,且未被它声明的条件 key,默认都是 & 连接

② "@column":"column;function(arg)...",返回字段

③ "@order":"column0+,column1-...",排序方式

④ "@group":"column0,column1...",分组方式。如果@column里声明了Table的id,则id也必须在@group中声明;其它情况下必须满足至少一个条件:
1.分组的key在@column里声明
2.Table主键在@group中声明

⑤ "@having":"function0(...)?value0;function1(...)?value1;function2(...)?value2...",SQL函数条件,一般和@group一起用,函数一般在@column里声明

⑥ "@schema":"sys",集合空间(模式),非默认的值可通过它来指定,可以在最外层作为全局默认配置

⑦ "@database":"POSTGRESQL",跨数据库,非默认的值可通过它来指定,可以在最外层作为全局默认配置

⑧ "@json":"key0,key1...",转为 JSON 格式返回,符合 JSONObject 则转为 {...},符合 JSONArray 则转为 \[...]

⑨ "@role":"OWNER",来访角色,包括
UNKNOWN,LOGIN,CONTACT,CIRCLE,OWNER,ADMIN,
可以在最外层作为全局默认配置,
可自定义其它角色并重写 Verifier.verify 等相关方法来自定义校验

⑩ "@explain":true,性能分析,可以在最外层作为全局默认配置

⑪ "@raw":"SQL片段或SQL片段的别名",自定义原始SQL片段,可扩展嵌套SQL函数等复杂语句,必须是后端已配置的,谨慎使用,其它功能符做不到才考虑

⑫ "@otherKey":Object,自定义关键词,名称和以上系统关键词不一样,且原样返回上传的值 | ① 搜索name或tag任何一个字段包含字符a的User列表:
["name~":"a",
"tag~":"a",
"@combine":"name~,tag~"](http://apijson.cn:8080/get/{"User[]":{"count":10,"User":{"@column":"id,name,tag","name~":"a","tag~":"a","@combine":"name~,tag~"}}})
对应SQL是`name REGEXP 'a' OR tag REGEXP 'a'`

② 只查询id,sex,name这几列并且请求结果也按照这个顺序:
["@column":"id,sex,name"](http://apijson.cn:8080/get/{"User":{"@column":"id,sex,name","id":38710}})
对应SQL是`SELECT id,sex,name`

③ 查询按 name降序、id默认顺序 排序的User数组:
["@order":"name-,id"](http://apijson.cn:8080/get/{"[]":{"count":10,"User":{"@column":"name,id","@order":"name-,id"}}})
对应SQL是`ORDER BY name DESC,id`

④ 查询按userId分组的Moment数组:
["@group":"userId,id"](http://apijson.cn:8080/get/{"[]":{"count":10,"Moment":%7B"@column":"userId,id","@group":"userId,id"}}})
对应SQL是`GROUP BY userId,id`

⑤ 查询 按userId分组、id最大值>=100 的Moment数组:
["@column":"userId;max(id)",
"@group":"userId",
"@having":"max(id)>=100"](http://apijson.cn:8080/get/{"[]":{"count":10,"Moment":{"@column":"userId%253Bmax(id)","@group":"userId","@having":"max(id)>=100"}}})
对应SQL是`SELECT userId,max(id) ... GROUP BY userId HAVING max(id)>=100`
还可以指定函数返回名:
["@column":"userId;max(id):maxId",
"@group":"userId",
"@having":"maxId>=100"](http://apijson.cn:8080/get/{"[]":{"count":10,"Moment":{"@column":"userId%253Bmax(id):maxId","@group":"userId","@having":"maxId>=100"}}})
对应SQL是`SELECT userId,max(id) AS maxId ... GROUP BY userId HAVING maxId>=100`

⑥ 查询 sys 内的 User 表:
["@schema":"sys"](http://apijson.cn:8080/get/{"User":{"@schema":"sys"}})
对应SQL是`FROM sys.`

⑦ 查询 PostgreSQL 数据库的 User 表:
["@database":"POSTGRESQL"](http://apijson.cn:8080/get/{"User":{"@database":"POSTGRESQL","@explain":true}})

⑧ 将 VARCHAR 字符串字段 get 转为 JSONArray 返回:
["@json":"get"](http://apijson.cn:8080/get/{"Access":{"@json":"get"}})

⑨ 查询当前用户的动态:
["@role":"OWNER"](http://apijson.cn:8080/get/{"[]":{"Moment":{"@role":"OWNER"}}})

⑩ 开启性能分析:
["@explain":true](http://apijson.cn:8080/get/{"[]":{"Moment":{"@explain":true}}})
对应SQL是`EXPLAIN`

⑪ 统计最近一周偶数userId数量
["@column":"sum(if(userId%2=0,1,0))",
"@having":"to_days(now())-to_days(\`date\`)<=7"](http://apijson.cn:8080/get/{"Comment":{"@column":"sum(if(userId%252=0,1,0))","@having":"to_days(now())-to_days(\`date\`)<=7"}})
对应SQL是``SELECT sum(if(userId%2=0,1,0)) ... HAVING to_days(now())-to_days(`date`)<=7``

⑫ 从pictureList获取第0张图片:
["@position":0, //自定义关键词
"firstPicture()":"getFromArray(pictureList,@position)"](http://apijson.cn:8080/get/{"User":{"id":38710,"@position":0,"firstPicture()":"getFromArray(pictureList,@position)"}}) + 对象关键词,可自定义 | "@key":Object,@key为 Table:{} 中{}内的关键词,Object的类型由@key指定

① "@combine":"&key0,&key1,\|key2,key3,
!key4,!key5,&key6,key7...",条件组合方式,\| 可省略。会自动把同类的合并,外层按照 & \| ! 顺序,内层的按传参顺序组合成
(key0 & key1 & key6 & 其它key) & (key2 \| key3 \| key7) & !(key4 \| key5)
这种连接方式,其中 "其它key" 是指与 @combine 在同一对象,且未被它声明的条件 key,默认都是 & 连接

② "@column":"column;function(arg)...",返回字段

③ "@order":"column0+,column1-...",排序方式

④ "@group":"column0,column1...",分组方式。如果@column里声明了Table的id,则id也必须在@group中声明;其它情况下必须满足至少一个条件:
1.分组的key在@column里声明
2.Table主键在@group中声明

⑤ "@having":"function0(...)?value0;function1(...)?value1;function2(...)?value2...",SQL函数条件,一般和@group一起用,函数一般在@column里声明

⑥ "@schema":"sys",集合空间(模式),非默认的值可通过它来指定,可以在最外层作为全局默认配置

⑦ "@database":"POSTGRESQL",跨数据库,非默认的值可通过它来指定,可以在最外层作为全局默认配置

⑧ "@json":"key0,key1...",转为 JSON 格式返回,符合 JSONObject 则转为 {...},符合 JSONArray 则转为 \[...]

⑨ "@role":"OWNER",来访角色,包括
UNKNOWN,LOGIN,CONTACT,CIRCLE,OWNER,ADMIN,
可以在最外层作为全局默认配置,
可自定义其它角色并重写 Verifier.verify 等相关方法来自定义校验

⑩ "@explain":true,性能分析,可以在最外层作为全局默认配置

⑪ "@raw":"SQL片段或SQL片段的别名",自定义原始SQL片段,可扩展嵌套SQL函数等复杂语句,必须是后端已配置的,谨慎使用,其它功能符做不到才考虑

⑫ "@otherKey":Object,自定义关键词,名称和以上系统关键词不一样,且原样返回上传的值 | ① 搜索name或tag任何一个字段包含字符a的User列表:
["name~":"a",
"tag~":"a",
"@combine":"name~,tag~"](http://apijson.cn:8080/get/{"User[]":{"count":10,"User":{"@column":"id,name,tag","name~":"a","tag~":"a","@combine":"name~,tag~"}}})
对应SQL是`name REGEXP 'a' OR tag REGEXP 'a'`

② 只查询id,sex,name这几列并且请求结果也按照这个顺序:
["@column":"id,sex,name"](http://apijson.cn:8080/get/{"User":{"@column":"id,sex,name","id":38710}})
对应SQL是`SELECT id,sex,name`

③ 查询按 name降序、id默认顺序 排序的User数组:
["@order":"name-,id"](http://apijson.cn:8080/get/{"[]":{"count":10,"User":{"@column":"name,id","@order":"name-,id"}}})
对应SQL是`ORDER BY name DESC,id`

④ 查询按userId分组的Moment数组:
["@group":"userId,id"](http://apijson.cn:8080/get/{"[]":{"count":10,"Moment":%7B"@column":"userId,id","@group":"userId,id"}}})
对应SQL是`GROUP BY userId,id`

⑤ 查询 按userId分组、id最大值>=100 的Moment数组:
["@column":"userId;max(id)",
"@group":"userId",
"@having":"max(id)>=100"](http://apijson.cn:8080/get/{"[]":{"count":10,"Moment":{"@column":"userId%253Bmax(id)","@group":"userId","@having":"max(id)>=100"}}})
对应SQL是`SELECT userId,max(id) ... GROUP BY userId HAVING max(id)>=100`
还可以指定函数返回名:
["@column":"userId;max(id):maxId",
"@group":"userId",
"@having":"maxId>=100"](http://apijson.cn:8080/get/{"[]":{"count":10,"Moment":{"@column":"userId%253Bmax(id):maxId","@group":"userId","@having":"maxId>=100"}}})
对应SQL是`SELECT userId,max(id) AS maxId ... GROUP BY userId HAVING maxId>=100`

⑥ 查询 sys 内的 User 表:
["@schema":"sys"](http://apijson.cn:8080/get/{"User":{"@schema":"sys"}})
对应SQL是`FROM sys.User`

⑦ 查询 PostgreSQL 数据库的 User 表:
["@database":"POSTGRESQL"](http://apijson.cn:8080/get/{"User":{"@database":"POSTGRESQL","@explain":true}})

⑧ 将 VARCHAR 字符串字段 get 转为 JSONArray 返回:
["@json":"get"](http://apijson.cn:8080/get/{"Access":{"@json":"get"}})

⑨ 查询当前用户的动态:
["@role":"OWNER"](http://apijson.cn:8080/get/{"[]":{"Moment":{"@role":"OWNER"}}})

⑩ 开启性能分析:
["@explain":true](http://apijson.cn:8080/get/{"[]":{"Moment":{"@explain":true}}})
对应SQL是`EXPLAIN`

⑪ 统计最近一周偶数userId数量
["@column":"sum(if(userId%2=0,1,0))",
"@having":"to_days(now())-to_days(\`date\`)<=7"](http://apijson.cn:8080/get/{"Comment":{"@column":"sum(if(userId%252=0,1,0))","@having":"to_days(now())-to_days(\`date\`)<=7"}})
对应SQL是``SELECT sum(if(userId%2=0,1,0)) ... HAVING to_days(now())-to_days(`date`)<=7``

⑫ 从pictureList获取第0张图片:
["@position":0, //自定义关键词
"firstPicture()":"getFromArray(pictureList,@position)"](http://apijson.cn:8080/get/{"User":{"id":38710,"@position":0,"firstPicture()":"getFromArray(pictureList,@position)"}})
From 66c05129d7f4cab026550efc81ba01a0e5a67766 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Tue, 22 Dec 2020 11:30:13 +0800 Subject: [PATCH 0120/1181] Update Document.md --- Document.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Document.md b/Document.md index 57bb43c7a..aebb4227b 100644 --- a/Document.md +++ b/Document.md @@ -370,6 +370,6 @@ DELETE:
删除数据 | base_url/delete/ | {
   TableName:{< 比较运算 | >, <, >=, <= 比较运算符,用于
① 提供 "id{}":"<=90000" 这种条件范围的简化写法

② 实现子查询相关比较运算

不支持 "key=":Object 和 "key!=":Object 这两种写法,直接用更简单的 "key":Object 和 "key!":Object 替代。 | ① ["id<=":90000](http://apijson.cn:8080/get/{"[]":{"User":{"id<=":90000}}}),对应SQL是`id<=90000`,查询符合id<=90000的一个User数组

② ["id>@":{
   "from":"Comment",
   "Comment":{
      "@column":"min(userId)"
   }
}](http://apijson.cn:8080/get/{"User":{"id>@":{"from":"Comment","Comment":{"@column":"min(userId)"}}}})
WHERE id>(SELECT min(userId) FROM Comment) 逻辑运算 | &, \|, ! 逻辑运算符,对应数据库 SQL 中的 AND, OR, NOT。
横或纵与:同一键值对的值内条件默认 \| 或连接,不同键值对的条件默认 & 与连接。

① & 可用于"key&{}":"条件"等

② \| 可用于"key\|{}":"条件", "key\|{}":[]等,一般可省略

③ ! 可单独使用,如"key!":Object,也可像&,\|一样配合其他功能符使用
"key!":null 无效,null 值会导致整个键值对被忽略解析,可以用 "key{}":"!=null" 替代,
"key":null 同理,用 "key{}":"=null" 替代。 | ① ["id&{}":">80000,<=90000"](http://apijson.cn:8080/head/{"User":{"id&{}":">80000,<=90000"}}),对应SQL是`id>80000 AND id<=90000`,即id满足id>80000 & id<=90000

② ["id\|{}":">90000,<=80000"](http://apijson.cn:8080/head/{"User":{"id\|{}":">90000,<=80000"}}),同"id{}":">90000,<=80000",对应SQL是`id>80000 OR id<=90000`,即id满足id>90000 \| id<=80000

③ ["id!{}":[82001,38710]](http://apijson.cn:8080/head/{"User":{"id!{}":[82001,38710]}}),对应SQL是`id NOT IN(82001,38710)`,即id满足 ! (id=82001 \| id=38710),可过滤黑名单的消息 数组关键词,可自定义 | "key":Object,key为 "[]":{} 中{}内的关键词,Object的类型由key指定

① "count":Integer,查询数量,0 表示最大值,默认最大值为100

② "page":Integer,查询页码,从0开始,默认最大值为100,一般和count一起用

③ "query":Integer,查询内容
0-对象,1-总数和分页详情,2-以上全部
总数关键词为 total,分页详情关键词为 info,
它们都和 query 同级,通过引用赋值得到,例如
"total@":"/[]/total", "info@":"/[]/info"
这里query及total仅为GET类型的请求提供方便,
一般可直接用HEAD类型的请求获取总数

④ "join":"&/Table0/key0@,\多表连接方式:
"\<" - LEFT JOIN
">" - RIGHT JOIN
"&" - INNER JOIN
"\|" - FULL JOIN
"!" - OUTTER JOIN
"@" - APP JOIN
其中 @ APP JOIN 为应用层连表,会从已查出的主表里取得所有副表 key@ 关联的主表内的 refKey 作为一个数组 refKeys: [value0, value1...],然后把原来副表 count 次查询 key=$refKey 的 SQL 用 key IN($refKeys) 的方式合并为一条 SQL 来优化性能;
其它 JOIN 都是 SQL JOIN,具体功能和 MySQL,PostgreSQL 等数据库的 JOIN 一一对应
`"join":"`"MainTable":{},`
`"ViceTable":{"key@":"/MainTable/refKey"}`
会对应生成
`MainTable LEFT JOIN ViceTable`
`ON ViceTable.key=MainTable.refKey`

⑤ "otherKey":Object,自定义关键词,名称和以上系统关键词不一样,且原样返回上传的值 | ① 查询User数组,最多5个:
["count":5](http://apijson.cn:8080/get/{"[]":{"count":5,"User":{}}})
对应SQL是`LIMIT 5`

② 查询第3页的User数组,每页5个:
["count":5,
"page":3](http://apijson.cn:8080/get/{"[]":{"count":5,"page":3,"User":{}}})
对应SQL是`LIMIT 5 OFFSET 15`

③ 查询User数组和对应的User总数:
["[]":{
   "query":2,
   "User":{}
},
"total@":"/[]/total",
"info@":"/[]/info"](http://apijson.cn:8080/get/{"[]":{"query":2,"count":5,"User":{}},"total@":"%252F[]%252Ftotal","info@":"%252F[]%252Finfo"})
返回的数据中,总数及分页详情结构为:
"total":139, //总数
"info":{ //分页详情
   "total":139, //总数
   "count":5, //每页数量
   "page":0, //当前页码
   "max":27, //最大页码
   "more":true, //是否还有更多
   "first":true, //是否为首页
   "last":false //是否为尾页
}

④ Moment INNER JOIN User LEFT JOIN Comment:
["[]":{
   "join":"&/User/id@,\    "Moment":{},
   "User":{
     "name~":"t",
     "id@":"/Moment/userId"
   },
   "Comment":{
     "momentId@":"/Moment/id"
   }
}](http://apijson.cn:8080/get/{"[]":{"count":5,"join":"&%252FUser%252Fid@,\<%252FComment%252FmomentId@","Moment":{"@column":"id,userId,content"},"User":{"name~":"t","id@":"%252FMoment%252FuserId","@column":"id,name,head"},"Comment":{"momentId@":"%252FMoment%252Fid","@column":"id,momentId,content"}}})

⑤ 每一层都加当前用户名:
["User":{},
"[]":{
   "name@":"User/name", //自定义关键词
   "Moment":{}
}](http://apijson.cn:8080/get/{"User":{},"[]":{"name@":"User%252Fname","Moment":{}}}) - 对象关键词,可自定义 | "@key":Object,@key为 Table:{} 中{}内的关键词,Object的类型由@key指定

① "@combine":"&key0,&key1,\|key2,key3,
!key4,!key5,&key6,key7...",条件组合方式,\| 可省略。会自动把同类的合并,外层按照 & \| ! 顺序,内层的按传参顺序组合成
(key0 & key1 & key6 & 其它key) & (key2 \| key3 \| key7) & !(key4 \| key5)
这种连接方式,其中 "其它key" 是指与 @combine 在同一对象,且未被它声明的条件 key,默认都是 & 连接

② "@column":"column;function(arg)...",返回字段

③ "@order":"column0+,column1-...",排序方式

④ "@group":"column0,column1...",分组方式。如果@column里声明了Table的id,则id也必须在@group中声明;其它情况下必须满足至少一个条件:
1.分组的key在@column里声明
2.Table主键在@group中声明

⑤ "@having":"function0(...)?value0;function1(...)?value1;function2(...)?value2...",SQL函数条件,一般和@group一起用,函数一般在@column里声明

⑥ "@schema":"sys",集合空间(模式),非默认的值可通过它来指定,可以在最外层作为全局默认配置

⑦ "@database":"POSTGRESQL",跨数据库,非默认的值可通过它来指定,可以在最外层作为全局默认配置

⑧ "@json":"key0,key1...",转为 JSON 格式返回,符合 JSONObject 则转为 {...},符合 JSONArray 则转为 \[...]

⑨ "@role":"OWNER",来访角色,包括
UNKNOWN,LOGIN,CONTACT,CIRCLE,OWNER,ADMIN,
可以在最外层作为全局默认配置,
可自定义其它角色并重写 Verifier.verify 等相关方法来自定义校验

⑩ "@explain":true,性能分析,可以在最外层作为全局默认配置

⑪ "@raw":"SQL片段或SQL片段的别名",自定义原始SQL片段,可扩展嵌套SQL函数等复杂语句,必须是后端已配置的,谨慎使用,其它功能符做不到才考虑

⑫ "@otherKey":Object,自定义关键词,名称和以上系统关键词不一样,且原样返回上传的值 | ① 搜索name或tag任何一个字段包含字符a的User列表:
["name~":"a",
"tag~":"a",
"@combine":"name~,tag~"](http://apijson.cn:8080/get/{"User[]":{"count":10,"User":{"@column":"id,name,tag","name~":"a","tag~":"a","@combine":"name~,tag~"}}})
对应SQL是`name REGEXP 'a' OR tag REGEXP 'a'`

② 只查询id,sex,name这几列并且请求结果也按照这个顺序:
["@column":"id,sex,name"](http://apijson.cn:8080/get/{"User":{"@column":"id,sex,name","id":38710}})
对应SQL是`SELECT id,sex,name`

③ 查询按 name降序、id默认顺序 排序的User数组:
["@order":"name-,id"](http://apijson.cn:8080/get/{"[]":{"count":10,"User":{"@column":"name,id","@order":"name-,id"}}})
对应SQL是`ORDER BY name DESC,id`

④ 查询按userId分组的Moment数组:
["@group":"userId,id"](http://apijson.cn:8080/get/{"[]":{"count":10,"Moment":%7B"@column":"userId,id","@group":"userId,id"}}})
对应SQL是`GROUP BY userId,id`

⑤ 查询 按userId分组、id最大值>=100 的Moment数组:
["@column":"userId;max(id)",
"@group":"userId",
"@having":"max(id)>=100"](http://apijson.cn:8080/get/{"[]":{"count":10,"Moment":{"@column":"userId%253Bmax(id)","@group":"userId","@having":"max(id)>=100"}}})
对应SQL是`SELECT userId,max(id) ... GROUP BY userId HAVING max(id)>=100`
还可以指定函数返回名:
["@column":"userId;max(id):maxId",
"@group":"userId",
"@having":"maxId>=100"](http://apijson.cn:8080/get/{"[]":{"count":10,"Moment":{"@column":"userId%253Bmax(id):maxId","@group":"userId","@having":"maxId>=100"}}})
对应SQL是`SELECT userId,max(id) AS maxId ... GROUP BY userId HAVING maxId>=100`

⑥ 查询 sys 内的 User 表:
["@schema":"sys"](http://apijson.cn:8080/get/{"User":{"@schema":"sys"}})
对应SQL是`FROM sys.User`

⑦ 查询 PostgreSQL 数据库的 User 表:
["@database":"POSTGRESQL"](http://apijson.cn:8080/get/{"User":{"@database":"POSTGRESQL","@explain":true}})

⑧ 将 VARCHAR 字符串字段 get 转为 JSONArray 返回:
["@json":"get"](http://apijson.cn:8080/get/{"Access":{"@json":"get"}})

⑨ 查询当前用户的动态:
["@role":"OWNER"](http://apijson.cn:8080/get/{"[]":{"Moment":{"@role":"OWNER"}}})

⑩ 开启性能分析:
["@explain":true](http://apijson.cn:8080/get/{"[]":{"Moment":{"@explain":true}}})
对应SQL是`EXPLAIN`

⑪ 统计最近一周偶数userId数量
["@column":"sum(if(userId%2=0,1,0))",
"@having":"to_days(now())-to_days(\`date\`)<=7"](http://apijson.cn:8080/get/{"Comment":{"@column":"sum(if(userId%252=0,1,0))","@having":"to_days(now())-to_days(\`date\`)<=7"}})
对应SQL是``SELECT sum(if(userId%2=0,1,0)) ... HAVING to_days(now())-to_days(`date`)<=7``

⑫ 从pictureList获取第0张图片:
["@position":0, //自定义关键词
"firstPicture()":"getFromArray(pictureList,@position)"](http://apijson.cn:8080/get/{"User":{"id":38710,"@position":0,"firstPicture()":"getFromArray(pictureList,@position)"}}) + 对象关键词,可自定义 | "@key":Object,@key为 Table:{} 中{}内的关键词,Object的类型由@key指定

① "@combine":"&key0,&key1,\|key2,key3,
!key4,!key5,&key6,key7...",条件组合方式,\| 可省略。会自动把同类的合并,外层按照 & \| ! 顺序,内层的按传参顺序组合成
(key0 & key1 & key6 & 其它key) & (key2 \| key3 \| key7) & !(key4 \| key5)
这种连接方式,其中 "其它key" 是指与 @combine 在同一对象,且未被它声明的条件 key,默认都是 & 连接

② "@column":"column;function(arg)...",返回字段

③ "@order":"column0+,column1-...",排序方式

④ "@group":"column0,column1...",分组方式。如果@column里声明了Table的id,则id也必须在@group中声明;其它情况下必须满足至少一个条件:
1.分组的key在@column里声明
2.Table主键在@group中声明

⑤ "@having":"function0(...)?value0;function1(...)?value1;function2(...)?value2...",SQL函数条件,一般和@group一起用,函数一般在@column里声明

⑥ "@schema":"sys",集合空间(模式),非默认的值可通过它来指定,可以在最外层作为全局默认配置

⑦ "@database":"POSTGRESQL",跨数据库,非默认的值可通过它来指定,可以在最外层作为全局默认配置

⑧ "@json":"key0,key1...",转为 JSON 格式返回,符合 JSONObject 则转为 {...},符合 JSONArray 则转为 \[...]

⑨ "@role":"OWNER",来访角色,包括
UNKNOWN,LOGIN,CONTACT,CIRCLE,OWNER,ADMIN,
可以在最外层作为全局默认配置,
可自定义其它角色并重写 Verifier.verify 等相关方法来自定义校验

⑩ "@explain":true,性能分析,可以在最外层作为全局默认配置

⑪ "@raw":"key0,key1...",其中 key0, key1 都对应有键值对
"key0":"SQL片段或SQL片段的别名",
"key1":"SQL片段或SQL片段的别名"
自定义原始SQL片段,可扩展嵌套SQL函数等复杂语句,必须是后端已配置的,谨慎使用,其它功能符做不到才考虑

⑫ "@otherKey":Object,自定义关键词,名称和以上系统关键词不一样,且原样返回上传的值 | ① 搜索name或tag任何一个字段包含字符a的User列表:
["name~":"a",
"tag~":"a",
"@combine":"name~,tag~"](http://apijson.cn:8080/get/{"User[]":{"count":10,"User":{"@column":"id,name,tag","name~":"a","tag~":"a","@combine":"name~,tag~"}}})
对应SQL是`name REGEXP 'a' OR tag REGEXP 'a'`

② 只查询id,sex,name这几列并且请求结果也按照这个顺序:
["@column":"id,sex,name"](http://apijson.cn:8080/get/{"User":{"@column":"id,sex,name","id":38710}})
对应SQL是`SELECT id,sex,name`

③ 查询按 name降序、id默认顺序 排序的User数组:
["@order":"name-,id"](http://apijson.cn:8080/get/{"[]":{"count":10,"User":{"@column":"name,id","@order":"name-,id"}}})
对应SQL是`ORDER BY name DESC,id`

④ 查询按userId分组的Moment数组:
["@group":"userId,id"](http://apijson.cn:8080/get/{"[]":{"count":10,"Moment":%7B"@column":"userId,id","@group":"userId,id"}}})
对应SQL是`GROUP BY userId,id`

⑤ 查询 按userId分组、id最大值>=100 的Moment数组:
["@column":"userId;max(id)",
"@group":"userId",
"@having":"max(id)>=100"](http://apijson.cn:8080/get/{"[]":{"count":10,"Moment":{"@column":"userId%253Bmax(id)","@group":"userId","@having":"max(id)>=100"}}})
对应SQL是`SELECT userId,max(id) ... GROUP BY userId HAVING max(id)>=100`
还可以指定函数返回名:
["@column":"userId;max(id):maxId",
"@group":"userId",
"@having":"maxId>=100"](http://apijson.cn:8080/get/{"[]":{"count":10,"Moment":{"@column":"userId%253Bmax(id):maxId","@group":"userId","@having":"maxId>=100"}}})
对应SQL是`SELECT userId,max(id) AS maxId ... GROUP BY userId HAVING maxId>=100`

⑥ 查询 sys 内的 User 表:
["@schema":"sys"](http://apijson.cn:8080/get/{"User":{"@schema":"sys"}})
对应SQL是`FROM sys.User`

⑦ 查询 PostgreSQL 数据库的 User 表:
["@database":"POSTGRESQL"](http://apijson.cn:8080/get/{"User":{"@database":"POSTGRESQL","@explain":true}})

⑧ 将 VARCHAR 字符串字段 get 转为 JSONArray 返回:
["@json":"get"](http://apijson.cn:8080/get/{"Access":{"@json":"get"}})

⑨ 查询当前用户的动态:
["@role":"OWNER"](http://apijson.cn:8080/get/{"[]":{"Moment":{"@role":"OWNER"}}})

⑩ 开启性能分析:
["@explain":true](http://apijson.cn:8080/get/{"[]":{"Moment":{"@explain":true}}})
对应SQL是`EXPLAIN`

⑪ 统计最近一周偶数userId数量
["@column":"sum(if(userId%2=0,1,0))",
"@having":"to_days(now())-to_days(\`date\`)<=7",
"@raw":"@column,@having"](http://apijson.cn:8080/get/{"Comment":{"@column":"sum(if(userId%252=0,1,0))","@having":"to_days(now())-to_days(\`date\`)<=7","@raw":"@column,@having"}})
对应SQL是``SELECT sum(if(userId%2=0,1,0)) ... HAVING to_days(now())-to_days(`date`)<=7``

⑫ 从pictureList获取第0张图片:
["@position":0, //自定义关键词
"firstPicture()":"getFromArray(pictureList,@position)"](http://apijson.cn:8080/get/{"User":{"id":38710,"@position":0,"firstPicture()":"getFromArray(pictureList,@position)"}})
From 36decb91f063ae6fcadf1d682a74ad455182656c Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Tue, 22 Dec 2020 11:31:05 +0800 Subject: [PATCH 0121/1181] Update Document.md --- Document.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Document.md b/Document.md index aebb4227b..d95072db0 100644 --- a/Document.md +++ b/Document.md @@ -370,6 +370,6 @@ DELETE:
删除数据 | base_url/delete/ | {
   TableName:{< 比较运算 | >, <, >=, <= 比较运算符,用于
① 提供 "id{}":"<=90000" 这种条件范围的简化写法

② 实现子查询相关比较运算

不支持 "key=":Object 和 "key!=":Object 这两种写法,直接用更简单的 "key":Object 和 "key!":Object 替代。 | ① ["id<=":90000](http://apijson.cn:8080/get/{"[]":{"User":{"id<=":90000}}}),对应SQL是`id<=90000`,查询符合id<=90000的一个User数组

② ["id>@":{
   "from":"Comment",
   "Comment":{
      "@column":"min(userId)"
   }
}](http://apijson.cn:8080/get/{"User":{"id>@":{"from":"Comment","Comment":{"@column":"min(userId)"}}}})
WHERE id>(SELECT min(userId) FROM Comment) 逻辑运算 | &, \|, ! 逻辑运算符,对应数据库 SQL 中的 AND, OR, NOT。
横或纵与:同一键值对的值内条件默认 \| 或连接,不同键值对的条件默认 & 与连接。

① & 可用于"key&{}":"条件"等

② \| 可用于"key\|{}":"条件", "key\|{}":[]等,一般可省略

③ ! 可单独使用,如"key!":Object,也可像&,\|一样配合其他功能符使用
"key!":null 无效,null 值会导致整个键值对被忽略解析,可以用 "key{}":"!=null" 替代,
"key":null 同理,用 "key{}":"=null" 替代。 | ① ["id&{}":">80000,<=90000"](http://apijson.cn:8080/head/{"User":{"id&{}":">80000,<=90000"}}),对应SQL是`id>80000 AND id<=90000`,即id满足id>80000 & id<=90000

② ["id\|{}":">90000,<=80000"](http://apijson.cn:8080/head/{"User":{"id\|{}":">90000,<=80000"}}),同"id{}":">90000,<=80000",对应SQL是`id>80000 OR id<=90000`,即id满足id>90000 \| id<=80000

③ ["id!{}":[82001,38710]](http://apijson.cn:8080/head/{"User":{"id!{}":[82001,38710]}}),对应SQL是`id NOT IN(82001,38710)`,即id满足 ! (id=82001 \| id=38710),可过滤黑名单的消息 数组关键词,可自定义 | "key":Object,key为 "[]":{} 中{}内的关键词,Object的类型由key指定

① "count":Integer,查询数量,0 表示最大值,默认最大值为100

② "page":Integer,查询页码,从0开始,默认最大值为100,一般和count一起用

③ "query":Integer,查询内容
0-对象,1-总数和分页详情,2-以上全部
总数关键词为 total,分页详情关键词为 info,
它们都和 query 同级,通过引用赋值得到,例如
"total@":"/[]/total", "info@":"/[]/info"
这里query及total仅为GET类型的请求提供方便,
一般可直接用HEAD类型的请求获取总数

④ "join":"&/Table0/key0@,\多表连接方式:
"\<" - LEFT JOIN
">" - RIGHT JOIN
"&" - INNER JOIN
"\|" - FULL JOIN
"!" - OUTTER JOIN
"@" - APP JOIN
其中 @ APP JOIN 为应用层连表,会从已查出的主表里取得所有副表 key@ 关联的主表内的 refKey 作为一个数组 refKeys: [value0, value1...],然后把原来副表 count 次查询 key=$refKey 的 SQL 用 key IN($refKeys) 的方式合并为一条 SQL 来优化性能;
其它 JOIN 都是 SQL JOIN,具体功能和 MySQL,PostgreSQL 等数据库的 JOIN 一一对应
`"join":"`"MainTable":{},`
`"ViceTable":{"key@":"/MainTable/refKey"}`
会对应生成
`MainTable LEFT JOIN ViceTable`
`ON ViceTable.key=MainTable.refKey`

⑤ "otherKey":Object,自定义关键词,名称和以上系统关键词不一样,且原样返回上传的值 | ① 查询User数组,最多5个:
["count":5](http://apijson.cn:8080/get/{"[]":{"count":5,"User":{}}})
对应SQL是`LIMIT 5`

② 查询第3页的User数组,每页5个:
["count":5,
"page":3](http://apijson.cn:8080/get/{"[]":{"count":5,"page":3,"User":{}}})
对应SQL是`LIMIT 5 OFFSET 15`

③ 查询User数组和对应的User总数:
["[]":{
   "query":2,
   "User":{}
},
"total@":"/[]/total",
"info@":"/[]/info"](http://apijson.cn:8080/get/{"[]":{"query":2,"count":5,"User":{}},"total@":"%252F[]%252Ftotal","info@":"%252F[]%252Finfo"})
返回的数据中,总数及分页详情结构为:
"total":139, //总数
"info":{ //分页详情
   "total":139, //总数
   "count":5, //每页数量
   "page":0, //当前页码
   "max":27, //最大页码
   "more":true, //是否还有更多
   "first":true, //是否为首页
   "last":false //是否为尾页
}

④ Moment INNER JOIN User LEFT JOIN Comment:
["[]":{
   "join":"&/User/id@,\    "Moment":{},
   "User":{
     "name~":"t",
     "id@":"/Moment/userId"
   },
   "Comment":{
     "momentId@":"/Moment/id"
   }
}](http://apijson.cn:8080/get/{"[]":{"count":5,"join":"&%252FUser%252Fid@,\<%252FComment%252FmomentId@","Moment":{"@column":"id,userId,content"},"User":{"name~":"t","id@":"%252FMoment%252FuserId","@column":"id,name,head"},"Comment":{"momentId@":"%252FMoment%252Fid","@column":"id,momentId,content"}}})

⑤ 每一层都加当前用户名:
["User":{},
"[]":{
   "name@":"User/name", //自定义关键词
   "Moment":{}
}](http://apijson.cn:8080/get/{"User":{},"[]":{"name@":"User%252Fname","Moment":{}}}) - 对象关键词,可自定义 | "@key":Object,@key为 Table:{} 中{}内的关键词,Object的类型由@key指定

① "@combine":"&key0,&key1,\|key2,key3,
!key4,!key5,&key6,key7...",条件组合方式,\| 可省略。会自动把同类的合并,外层按照 & \| ! 顺序,内层的按传参顺序组合成
(key0 & key1 & key6 & 其它key) & (key2 \| key3 \| key7) & !(key4 \| key5)
这种连接方式,其中 "其它key" 是指与 @combine 在同一对象,且未被它声明的条件 key,默认都是 & 连接

② "@column":"column;function(arg)...",返回字段

③ "@order":"column0+,column1-...",排序方式

④ "@group":"column0,column1...",分组方式。如果@column里声明了Table的id,则id也必须在@group中声明;其它情况下必须满足至少一个条件:
1.分组的key在@column里声明
2.Table主键在@group中声明

⑤ "@having":"function0(...)?value0;function1(...)?value1;function2(...)?value2...",SQL函数条件,一般和@group一起用,函数一般在@column里声明

⑥ "@schema":"sys",集合空间(模式),非默认的值可通过它来指定,可以在最外层作为全局默认配置

⑦ "@database":"POSTGRESQL",跨数据库,非默认的值可通过它来指定,可以在最外层作为全局默认配置

⑧ "@json":"key0,key1...",转为 JSON 格式返回,符合 JSONObject 则转为 {...},符合 JSONArray 则转为 \[...]

⑨ "@role":"OWNER",来访角色,包括
UNKNOWN,LOGIN,CONTACT,CIRCLE,OWNER,ADMIN,
可以在最外层作为全局默认配置,
可自定义其它角色并重写 Verifier.verify 等相关方法来自定义校验

⑩ "@explain":true,性能分析,可以在最外层作为全局默认配置

⑪ "@raw":"key0,key1...",其中 key0, key1 都对应有键值对
"key0":"SQL片段或SQL片段的别名",
"key1":"SQL片段或SQL片段的别名"
自定义原始SQL片段,可扩展嵌套SQL函数等复杂语句,必须是后端已配置的,谨慎使用,其它功能符做不到才考虑

⑫ "@otherKey":Object,自定义关键词,名称和以上系统关键词不一样,且原样返回上传的值 | ① 搜索name或tag任何一个字段包含字符a的User列表:
["name~":"a",
"tag~":"a",
"@combine":"name~,tag~"](http://apijson.cn:8080/get/{"User[]":{"count":10,"User":{"@column":"id,name,tag","name~":"a","tag~":"a","@combine":"name~,tag~"}}})
对应SQL是`name REGEXP 'a' OR tag REGEXP 'a'`

② 只查询id,sex,name这几列并且请求结果也按照这个顺序:
["@column":"id,sex,name"](http://apijson.cn:8080/get/{"User":{"@column":"id,sex,name","id":38710}})
对应SQL是`SELECT id,sex,name`

③ 查询按 name降序、id默认顺序 排序的User数组:
["@order":"name-,id"](http://apijson.cn:8080/get/{"[]":{"count":10,"User":{"@column":"name,id","@order":"name-,id"}}})
对应SQL是`ORDER BY name DESC,id`

④ 查询按userId分组的Moment数组:
["@group":"userId,id"](http://apijson.cn:8080/get/{"[]":{"count":10,"Moment":%7B"@column":"userId,id","@group":"userId,id"}}})
对应SQL是`GROUP BY userId,id`

⑤ 查询 按userId分组、id最大值>=100 的Moment数组:
["@column":"userId;max(id)",
"@group":"userId",
"@having":"max(id)>=100"](http://apijson.cn:8080/get/{"[]":{"count":10,"Moment":{"@column":"userId%253Bmax(id)","@group":"userId","@having":"max(id)>=100"}}})
对应SQL是`SELECT userId,max(id) ... GROUP BY userId HAVING max(id)>=100`
还可以指定函数返回名:
["@column":"userId;max(id):maxId",
"@group":"userId",
"@having":"maxId>=100"](http://apijson.cn:8080/get/{"[]":{"count":10,"Moment":{"@column":"userId%253Bmax(id):maxId","@group":"userId","@having":"maxId>=100"}}})
对应SQL是`SELECT userId,max(id) AS maxId ... GROUP BY userId HAVING maxId>=100`

⑥ 查询 sys 内的 User 表:
["@schema":"sys"](http://apijson.cn:8080/get/{"User":{"@schema":"sys"}})
对应SQL是`FROM sys.User`

⑦ 查询 PostgreSQL 数据库的 User 表:
["@database":"POSTGRESQL"](http://apijson.cn:8080/get/{"User":{"@database":"POSTGRESQL","@explain":true}})

⑧ 将 VARCHAR 字符串字段 get 转为 JSONArray 返回:
["@json":"get"](http://apijson.cn:8080/get/{"Access":{"@json":"get"}})

⑨ 查询当前用户的动态:
["@role":"OWNER"](http://apijson.cn:8080/get/{"[]":{"Moment":{"@role":"OWNER"}}})

⑩ 开启性能分析:
["@explain":true](http://apijson.cn:8080/get/{"[]":{"Moment":{"@explain":true}}})
对应SQL是`EXPLAIN`

⑪ 统计最近一周偶数userId数量
["@column":"sum(if(userId%2=0,1,0))",
"@having":"to_days(now())-to_days(\`date\`)<=7",
"@raw":"@column,@having"](http://apijson.cn:8080/get/{"Comment":{"@column":"sum(if(userId%252=0,1,0))","@having":"to_days(now())-to_days(\`date\`)<=7","@raw":"@column,@having"}})
对应SQL是``SELECT sum(if(userId%2=0,1,0)) ... HAVING to_days(now())-to_days(`date`)<=7``

⑫ 从pictureList获取第0张图片:
["@position":0, //自定义关键词
"firstPicture()":"getFromArray(pictureList,@position)"](http://apijson.cn:8080/get/{"User":{"id":38710,"@position":0,"firstPicture()":"getFromArray(pictureList,@position)"}}) + 对象关键词,可自定义 | "@key":Object,@key为 Table:{} 中{}内的关键词,Object的类型由@key指定

① "@combine":"&key0,&key1,\|key2,key3,
!key4,!key5,&key6,key7...",条件组合方式,\| 可省略。会自动把同类的合并,外层按照 & \| ! 顺序,内层的按传参顺序组合成
(key0 & key1 & key6 & 其它key) & (key2 \| key3 \| key7) & !(key4 \| key5)
这种连接方式,其中 "其它key" 是指与 @combine 在同一对象,且未被它声明的条件 key,默认都是 & 连接

② "@column":"column;function(arg)...",返回字段

③ "@order":"column0+,column1-...",排序方式

④ "@group":"column0,column1...",分组方式。如果@column里声明了Table的id,则id也必须在@group中声明;其它情况下必须满足至少一个条件:
1.分组的key在@column里声明
2.Table主键在@group中声明

⑤ "@having":"function0(...)?value0;function1(...)?value1;function2(...)?value2...",SQL函数条件,一般和@group一起用,函数一般在@column里声明

⑥ "@schema":"sys",集合空间(模式),非默认的值可通过它来指定,可以在最外层作为全局默认配置

⑦ "@database":"POSTGRESQL",跨数据库,非默认的值可通过它来指定,可以在最外层作为全局默认配置

⑧ "@json":"key0,key1...",转为 JSON 格式返回,符合 JSONObject 则转为 {...},符合 JSONArray 则转为 \[...]

⑨ "@role":"OWNER",来访角色,包括
UNKNOWN,LOGIN,CONTACT,CIRCLE,OWNER,ADMIN,
可以在最外层作为全局默认配置,
可自定义其它角色并重写 Verifier.verify 等相关方法来自定义校验

⑩ "@explain":true,性能分析,可以在最外层作为全局默认配置

⑪ "@raw":"key0,key1...",其中 key0, key1 都对应有键值对
"key0":"SQL片段或SQL片段的别名",
"key1":"SQL片段或SQL片段的别名"
自定义原始SQL片段,可扩展嵌套SQL函数等复杂语句,必须是后端已配置的,谨慎使用,其它功能符做不到才考虑

⑫ "@otherKey":Object,自定义关键词,名称和以上系统关键词不一样,且原样返回上传的值 | ① 搜索name或tag任何一个字段包含字符a的User列表:
["name~":"a",
"tag~":"a",
"@combine":"name~,tag~"](http://apijson.cn:8080/get/{"User[]":{"count":10,"User":{"@column":"id,name,tag","name~":"a","tag~":"a","@combine":"name~,tag~"}}})
对应SQL是`name REGEXP 'a' OR tag REGEXP 'a'`

② 只查询id,sex,name这几列并且请求结果也按照这个顺序:
["@column":"id,sex,name"](http://apijson.cn:8080/get/{"User":{"@column":"id,sex,name","id":38710}})
对应SQL是`SELECT id,sex,name`

③ 查询按 name降序、id默认顺序 排序的User数组:
["@order":"name-,id"](http://apijson.cn:8080/get/{"[]":{"count":10,"User":{"@column":"name,id","@order":"name-,id"}}})
对应SQL是`ORDER BY name DESC,id`

④ 查询按userId分组的Moment数组:
["@group":"userId,id"](http://apijson.cn:8080/get/{"[]":{"count":10,"Moment":%7B"@column":"userId,id","@group":"userId,id"}}})
对应SQL是`GROUP BY userId,id`

⑤ 查询 按userId分组、id最大值>=100 的Moment数组:
["@column":"userId;max(id)",
"@group":"userId",
"@having":"max(id)>=100"](http://apijson.cn:8080/get/{"[]":{"count":10,"Moment":{"@column":"userId%253Bmax(id)","@group":"userId","@having":"max(id)>=100"}}})
对应SQL是`SELECT userId,max(id) ... GROUP BY userId HAVING max(id)>=100`
还可以指定函数返回名:
["@column":"userId;max(id):maxId",
"@group":"userId",
"@having":"maxId>=100"](http://apijson.cn:8080/get/{"[]":{"count":10,"Moment":{"@column":"userId%253Bmax(id):maxId","@group":"userId","@having":"maxId>=100"}}})
对应SQL是`SELECT userId,max(id) AS maxId ... GROUP BY userId HAVING maxId>=100`

⑥ 查询 sys 内的 User 表:
["@schema":"sys"](http://apijson.cn:8080/get/{"User":{"@schema":"sys"}})
对应SQL是`FROM sys.User`

⑦ 查询 PostgreSQL 数据库的 User 表:
["@database":"POSTGRESQL"](http://apijson.cn:8080/get/{"User":{"@database":"POSTGRESQL","@explain":true}})

⑧ 将 VARCHAR 字符串字段 get 转为 JSONArray 返回:
["@json":"get"](http://apijson.cn:8080/get/{"Access":{"@json":"get"}})

⑨ 查询当前用户的动态:
["@role":"OWNER"](http://apijson.cn:8080/get/{"[]":{"Moment":{"@role":"OWNER"}}})

⑩ 开启性能分析:
["@explain":true](http://apijson.cn:8080/get/{"[]":{"Moment":{"@explain":true}}})
对应SQL是`EXPLAIN`

⑪ 统计最近一周偶数userId的数量
["@column":"sum(if(userId%2=0,1,0))",
"@having":"to_days(now())-to_days(\`date\`)<=7",
"@raw":"@column,@having"](http://apijson.cn:8080/get/{"Comment":{"@column":"sum(if(userId%252=0,1,0))","@having":"to_days(now())-to_days(\`date\`)<=7","@raw":"@column,@having"}})
对应SQL是``SELECT sum(if(userId%2=0,1,0)) ... HAVING to_days(now())-to_days(`date`)<=7``

⑫ 从pictureList获取第0张图片:
["@position":0, //自定义关键词
"firstPicture()":"getFromArray(pictureList,@position)"](http://apijson.cn:8080/get/{"User":{"id":38710,"@position":0,"firstPicture()":"getFromArray(pictureList,@position)"}})
From c63ae27148587b0a239df964404adb0ba607636d Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Tue, 22 Dec 2020 11:32:48 +0800 Subject: [PATCH 0122/1181] Update Document.md --- Document.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Document.md b/Document.md index d95072db0..c7c773dbd 100644 --- a/Document.md +++ b/Document.md @@ -370,6 +370,6 @@ DELETE:
删除数据 | base_url/delete/ | {
   TableName:{< 比较运算 | >, <, >=, <= 比较运算符,用于
① 提供 "id{}":"<=90000" 这种条件范围的简化写法

② 实现子查询相关比较运算

不支持 "key=":Object 和 "key!=":Object 这两种写法,直接用更简单的 "key":Object 和 "key!":Object 替代。 | ① ["id<=":90000](http://apijson.cn:8080/get/{"[]":{"User":{"id<=":90000}}}),对应SQL是`id<=90000`,查询符合id<=90000的一个User数组

② ["id>@":{
   "from":"Comment",
   "Comment":{
      "@column":"min(userId)"
   }
}](http://apijson.cn:8080/get/{"User":{"id>@":{"from":"Comment","Comment":{"@column":"min(userId)"}}}})
WHERE id>(SELECT min(userId) FROM Comment) 逻辑运算 | &, \|, ! 逻辑运算符,对应数据库 SQL 中的 AND, OR, NOT。
横或纵与:同一键值对的值内条件默认 \| 或连接,不同键值对的条件默认 & 与连接。

① & 可用于"key&{}":"条件"等

② \| 可用于"key\|{}":"条件", "key\|{}":[]等,一般可省略

③ ! 可单独使用,如"key!":Object,也可像&,\|一样配合其他功能符使用
"key!":null 无效,null 值会导致整个键值对被忽略解析,可以用 "key{}":"!=null" 替代,
"key":null 同理,用 "key{}":"=null" 替代。 | ① ["id&{}":">80000,<=90000"](http://apijson.cn:8080/head/{"User":{"id&{}":">80000,<=90000"}}),对应SQL是`id>80000 AND id<=90000`,即id满足id>80000 & id<=90000

② ["id\|{}":">90000,<=80000"](http://apijson.cn:8080/head/{"User":{"id\|{}":">90000,<=80000"}}),同"id{}":">90000,<=80000",对应SQL是`id>80000 OR id<=90000`,即id满足id>90000 \| id<=80000

③ ["id!{}":[82001,38710]](http://apijson.cn:8080/head/{"User":{"id!{}":[82001,38710]}}),对应SQL是`id NOT IN(82001,38710)`,即id满足 ! (id=82001 \| id=38710),可过滤黑名单的消息 数组关键词,可自定义 | "key":Object,key为 "[]":{} 中{}内的关键词,Object的类型由key指定

① "count":Integer,查询数量,0 表示最大值,默认最大值为100

② "page":Integer,查询页码,从0开始,默认最大值为100,一般和count一起用

③ "query":Integer,查询内容
0-对象,1-总数和分页详情,2-以上全部
总数关键词为 total,分页详情关键词为 info,
它们都和 query 同级,通过引用赋值得到,例如
"total@":"/[]/total", "info@":"/[]/info"
这里query及total仅为GET类型的请求提供方便,
一般可直接用HEAD类型的请求获取总数

④ "join":"&/Table0/key0@,\多表连接方式:
"\<" - LEFT JOIN
">" - RIGHT JOIN
"&" - INNER JOIN
"\|" - FULL JOIN
"!" - OUTTER JOIN
"@" - APP JOIN
其中 @ APP JOIN 为应用层连表,会从已查出的主表里取得所有副表 key@ 关联的主表内的 refKey 作为一个数组 refKeys: [value0, value1...],然后把原来副表 count 次查询 key=$refKey 的 SQL 用 key IN($refKeys) 的方式合并为一条 SQL 来优化性能;
其它 JOIN 都是 SQL JOIN,具体功能和 MySQL,PostgreSQL 等数据库的 JOIN 一一对应
`"join":"`"MainTable":{},`
`"ViceTable":{"key@":"/MainTable/refKey"}`
会对应生成
`MainTable LEFT JOIN ViceTable`
`ON ViceTable.key=MainTable.refKey`

⑤ "otherKey":Object,自定义关键词,名称和以上系统关键词不一样,且原样返回上传的值 | ① 查询User数组,最多5个:
["count":5](http://apijson.cn:8080/get/{"[]":{"count":5,"User":{}}})
对应SQL是`LIMIT 5`

② 查询第3页的User数组,每页5个:
["count":5,
"page":3](http://apijson.cn:8080/get/{"[]":{"count":5,"page":3,"User":{}}})
对应SQL是`LIMIT 5 OFFSET 15`

③ 查询User数组和对应的User总数:
["[]":{
   "query":2,
   "User":{}
},
"total@":"/[]/total",
"info@":"/[]/info"](http://apijson.cn:8080/get/{"[]":{"query":2,"count":5,"User":{}},"total@":"%252F[]%252Ftotal","info@":"%252F[]%252Finfo"})
返回的数据中,总数及分页详情结构为:
"total":139, //总数
"info":{ //分页详情
   "total":139, //总数
   "count":5, //每页数量
   "page":0, //当前页码
   "max":27, //最大页码
   "more":true, //是否还有更多
   "first":true, //是否为首页
   "last":false //是否为尾页
}

④ Moment INNER JOIN User LEFT JOIN Comment:
["[]":{
   "join":"&/User/id@,\    "Moment":{},
   "User":{
     "name~":"t",
     "id@":"/Moment/userId"
   },
   "Comment":{
     "momentId@":"/Moment/id"
   }
}](http://apijson.cn:8080/get/{"[]":{"count":5,"join":"&%252FUser%252Fid@,\<%252FComment%252FmomentId@","Moment":{"@column":"id,userId,content"},"User":{"name~":"t","id@":"%252FMoment%252FuserId","@column":"id,name,head"},"Comment":{"momentId@":"%252FMoment%252Fid","@column":"id,momentId,content"}}})

⑤ 每一层都加当前用户名:
["User":{},
"[]":{
   "name@":"User/name", //自定义关键词
   "Moment":{}
}](http://apijson.cn:8080/get/{"User":{},"[]":{"name@":"User%252Fname","Moment":{}}}) - 对象关键词,可自定义 | "@key":Object,@key为 Table:{} 中{}内的关键词,Object的类型由@key指定

① "@combine":"&key0,&key1,\|key2,key3,
!key4,!key5,&key6,key7...",条件组合方式,\| 可省略。会自动把同类的合并,外层按照 & \| ! 顺序,内层的按传参顺序组合成
(key0 & key1 & key6 & 其它key) & (key2 \| key3 \| key7) & !(key4 \| key5)
这种连接方式,其中 "其它key" 是指与 @combine 在同一对象,且未被它声明的条件 key,默认都是 & 连接

② "@column":"column;function(arg)...",返回字段

③ "@order":"column0+,column1-...",排序方式

④ "@group":"column0,column1...",分组方式。如果@column里声明了Table的id,则id也必须在@group中声明;其它情况下必须满足至少一个条件:
1.分组的key在@column里声明
2.Table主键在@group中声明

⑤ "@having":"function0(...)?value0;function1(...)?value1;function2(...)?value2...",SQL函数条件,一般和@group一起用,函数一般在@column里声明

⑥ "@schema":"sys",集合空间(模式),非默认的值可通过它来指定,可以在最外层作为全局默认配置

⑦ "@database":"POSTGRESQL",跨数据库,非默认的值可通过它来指定,可以在最外层作为全局默认配置

⑧ "@json":"key0,key1...",转为 JSON 格式返回,符合 JSONObject 则转为 {...},符合 JSONArray 则转为 \[...]

⑨ "@role":"OWNER",来访角色,包括
UNKNOWN,LOGIN,CONTACT,CIRCLE,OWNER,ADMIN,
可以在最外层作为全局默认配置,
可自定义其它角色并重写 Verifier.verify 等相关方法来自定义校验

⑩ "@explain":true,性能分析,可以在最外层作为全局默认配置

⑪ "@raw":"key0,key1...",其中 key0, key1 都对应有键值对
"key0":"SQL片段或SQL片段的别名",
"key1":"SQL片段或SQL片段的别名"
自定义原始SQL片段,可扩展嵌套SQL函数等复杂语句,必须是后端已配置的,谨慎使用,其它功能符做不到才考虑

⑫ "@otherKey":Object,自定义关键词,名称和以上系统关键词不一样,且原样返回上传的值 | ① 搜索name或tag任何一个字段包含字符a的User列表:
["name~":"a",
"tag~":"a",
"@combine":"name~,tag~"](http://apijson.cn:8080/get/{"User[]":{"count":10,"User":{"@column":"id,name,tag","name~":"a","tag~":"a","@combine":"name~,tag~"}}})
对应SQL是`name REGEXP 'a' OR tag REGEXP 'a'`

② 只查询id,sex,name这几列并且请求结果也按照这个顺序:
["@column":"id,sex,name"](http://apijson.cn:8080/get/{"User":{"@column":"id,sex,name","id":38710}})
对应SQL是`SELECT id,sex,name`

③ 查询按 name降序、id默认顺序 排序的User数组:
["@order":"name-,id"](http://apijson.cn:8080/get/{"[]":{"count":10,"User":{"@column":"name,id","@order":"name-,id"}}})
对应SQL是`ORDER BY name DESC,id`

④ 查询按userId分组的Moment数组:
["@group":"userId,id"](http://apijson.cn:8080/get/{"[]":{"count":10,"Moment":%7B"@column":"userId,id","@group":"userId,id"}}})
对应SQL是`GROUP BY userId,id`

⑤ 查询 按userId分组、id最大值>=100 的Moment数组:
["@column":"userId;max(id)",
"@group":"userId",
"@having":"max(id)>=100"](http://apijson.cn:8080/get/{"[]":{"count":10,"Moment":{"@column":"userId%253Bmax(id)","@group":"userId","@having":"max(id)>=100"}}})
对应SQL是`SELECT userId,max(id) ... GROUP BY userId HAVING max(id)>=100`
还可以指定函数返回名:
["@column":"userId;max(id):maxId",
"@group":"userId",
"@having":"maxId>=100"](http://apijson.cn:8080/get/{"[]":{"count":10,"Moment":{"@column":"userId%253Bmax(id):maxId","@group":"userId","@having":"maxId>=100"}}})
对应SQL是`SELECT userId,max(id) AS maxId ... GROUP BY userId HAVING maxId>=100`

⑥ 查询 sys 内的 User 表:
["@schema":"sys"](http://apijson.cn:8080/get/{"User":{"@schema":"sys"}})
对应SQL是`FROM sys.User`

⑦ 查询 PostgreSQL 数据库的 User 表:
["@database":"POSTGRESQL"](http://apijson.cn:8080/get/{"User":{"@database":"POSTGRESQL","@explain":true}})

⑧ 将 VARCHAR 字符串字段 get 转为 JSONArray 返回:
["@json":"get"](http://apijson.cn:8080/get/{"Access":{"@json":"get"}})

⑨ 查询当前用户的动态:
["@role":"OWNER"](http://apijson.cn:8080/get/{"[]":{"Moment":{"@role":"OWNER"}}})

⑩ 开启性能分析:
["@explain":true](http://apijson.cn:8080/get/{"[]":{"Moment":{"@explain":true}}})
对应SQL是`EXPLAIN`

⑪ 统计最近一周偶数userId的数量
["@column":"sum(if(userId%2=0,1,0))",
"@having":"to_days(now())-to_days(\`date\`)<=7",
"@raw":"@column,@having"](http://apijson.cn:8080/get/{"Comment":{"@column":"sum(if(userId%252=0,1,0))","@having":"to_days(now())-to_days(\`date\`)<=7","@raw":"@column,@having"}})
对应SQL是``SELECT sum(if(userId%2=0,1,0)) ... HAVING to_days(now())-to_days(`date`)<=7``

⑫ 从pictureList获取第0张图片:
["@position":0, //自定义关键词
"firstPicture()":"getFromArray(pictureList,@position)"](http://apijson.cn:8080/get/{"User":{"id":38710,"@position":0,"firstPicture()":"getFromArray(pictureList,@position)"}}) + 对象关键词,可自定义 | "@key":Object,@key为 Table:{} 中{}内的关键词,Object的类型由@key指定

① "@combine":"&key0,&key1,\|key2,key3,
!key4,!key5,&key6,key7...",条件组合方式,\| 可省略。会自动把同类的合并,外层按照 & \| ! 顺序,内层的按传参顺序组合成
(key0 & key1 & key6 & 其它key) & (key2 \| key3 \| key7) & !(key4 \| key5)
这种连接方式,其中 "其它key" 是指与 @combine 在同一对象,且未被它声明的条件 key,默认都是 & 连接

② "@column":"column;function(arg)...",返回字段

③ "@order":"column0+,column1-...",排序方式

④ "@group":"column0,column1...",分组方式。如果@column里声明了Table的id,则id也必须在@group中声明;其它情况下必须满足至少一个条件:
1.分组的key在@column里声明
2.Table主键在@group中声明

⑤ "@having":"function0(...)?value0;function1(...)?value1;function2(...)?value2...",SQL函数条件,一般和@group一起用,函数一般在@column里声明

⑥ "@schema":"sys",集合空间(模式),非默认的值可通过它来指定,可以在最外层作为全局默认配置

⑦ "@database":"POSTGRESQL",跨数据库,非默认的值可通过它来指定,可以在最外层作为全局默认配置

⑧ "@json":"key0,key1...",转为 JSON 格式返回,符合 JSONObject 则转为 {...},符合 JSONArray 则转为 \[...]

⑨ "@role":"OWNER",来访角色,包括
UNKNOWN,LOGIN,CONTACT,CIRCLE,OWNER,ADMIN,
可以在最外层作为全局默认配置,
可自定义其它角色并重写 Verifier.verify 等相关方法来自定义校验

⑩ "@explain":true,性能分析,可以在最外层作为全局默认配置

⑪ "@raw":"key0,key1...",其中 key0, key1 都对应有键值对
"key0":"SQL片段或SQL片段的别名",
"key1":"SQL片段或SQL片段的别名"
自定义原始SQL片段,可扩展嵌套SQL函数等复杂语句,必须是后端已配置的,谨慎使用,只有其它功能符都做不到才考虑

⑫ "@otherKey":Object,自定义关键词,名称和以上系统关键词不一样,且原样返回上传的值 | ① 搜索name或tag任何一个字段包含字符a的User列表:
["name~":"a",
"tag~":"a",
"@combine":"name~,tag~"](http://apijson.cn:8080/get/{"User[]":{"count":10,"User":{"@column":"id,name,tag","name~":"a","tag~":"a","@combine":"name~,tag~"}}})
对应SQL是`name REGEXP 'a' OR tag REGEXP 'a'`

② 只查询id,sex,name这几列并且请求结果也按照这个顺序:
["@column":"id,sex,name"](http://apijson.cn:8080/get/{"User":{"@column":"id,sex,name","id":38710}})
对应SQL是`SELECT id,sex,name`

③ 查询按 name降序、id默认顺序 排序的User数组:
["@order":"name-,id"](http://apijson.cn:8080/get/{"[]":{"count":10,"User":{"@column":"name,id","@order":"name-,id"}}})
对应SQL是`ORDER BY name DESC,id`

④ 查询按userId分组的Moment数组:
["@group":"userId,id"](http://apijson.cn:8080/get/{"[]":{"count":10,"Moment":%7B"@column":"userId,id","@group":"userId,id"}}})
对应SQL是`GROUP BY userId,id`

⑤ 查询 按userId分组、id最大值>=100 的Moment数组:
["@column":"userId;max(id)",
"@group":"userId",
"@having":"max(id)>=100"](http://apijson.cn:8080/get/{"[]":{"count":10,"Moment":{"@column":"userId%253Bmax(id)","@group":"userId","@having":"max(id)>=100"}}})
对应SQL是`SELECT userId,max(id) ... GROUP BY userId HAVING max(id)>=100`
还可以指定函数返回名:
["@column":"userId;max(id):maxId",
"@group":"userId",
"@having":"maxId>=100"](http://apijson.cn:8080/get/{"[]":{"count":10,"Moment":{"@column":"userId%253Bmax(id):maxId","@group":"userId","@having":"maxId>=100"}}})
对应SQL是`SELECT userId,max(id) AS maxId ... GROUP BY userId HAVING maxId>=100`

⑥ 查询 sys 内的 User 表:
["@schema":"sys"](http://apijson.cn:8080/get/{"User":{"@schema":"sys"}})
对应SQL是`FROM sys.User`

⑦ 查询 PostgreSQL 数据库的 User 表:
["@database":"POSTGRESQL"](http://apijson.cn:8080/get/{"User":{"@database":"POSTGRESQL","@explain":true}})

⑧ 将 VARCHAR 字符串字段 get 转为 JSONArray 返回:
["@json":"get"](http://apijson.cn:8080/get/{"Access":{"@json":"get"}})

⑨ 查询当前用户的动态:
["@role":"OWNER"](http://apijson.cn:8080/get/{"[]":{"Moment":{"@role":"OWNER"}}})

⑩ 开启性能分析:
["@explain":true](http://apijson.cn:8080/get/{"[]":{"Moment":{"@explain":true}}})
对应SQL是`EXPLAIN`

⑪ 统计最近一周偶数userId的数量
["@column":"sum(if(userId%2=0,1,0))",
"@having":"to_days(now())-to_days(\`date\`)<=7",
"@raw":"@column,@having"](http://apijson.cn:8080/get/{"Comment":{"@column":"sum(if(userId%252=0,1,0))","@having":"to_days(now())-to_days(\`date\`)<=7","@raw":"@column,@having"}})
对应SQL是``SELECT sum(if(userId%2=0,1,0)) ... HAVING to_days(now())-to_days(`date`)<=7``

⑫ 从pictureList获取第0张图片:
["@position":0, //自定义关键词
"firstPicture()":"getFromArray(pictureList,@position)"](http://apijson.cn:8080/get/{"User":{"id":38710,"@position":0,"firstPicture()":"getFromArray(pictureList,@position)"}})
From 980df582f50340934d8a8382e35c856b6a024d73 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Tue, 22 Dec 2020 11:33:59 +0800 Subject: [PATCH 0123/1181] Update Document.md --- Document.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Document.md b/Document.md index c7c773dbd..251533667 100644 --- a/Document.md +++ b/Document.md @@ -370,6 +370,6 @@ DELETE:
删除数据 | base_url/delete/ | {
   TableName:{< 比较运算 | >, <, >=, <= 比较运算符,用于
① 提供 "id{}":"<=90000" 这种条件范围的简化写法

② 实现子查询相关比较运算

不支持 "key=":Object 和 "key!=":Object 这两种写法,直接用更简单的 "key":Object 和 "key!":Object 替代。 | ① ["id<=":90000](http://apijson.cn:8080/get/{"[]":{"User":{"id<=":90000}}}),对应SQL是`id<=90000`,查询符合id<=90000的一个User数组

② ["id>@":{
   "from":"Comment",
   "Comment":{
      "@column":"min(userId)"
   }
}](http://apijson.cn:8080/get/{"User":{"id>@":{"from":"Comment","Comment":{"@column":"min(userId)"}}}})
WHERE id>(SELECT min(userId) FROM Comment) 逻辑运算 | &, \|, ! 逻辑运算符,对应数据库 SQL 中的 AND, OR, NOT。
横或纵与:同一键值对的值内条件默认 \| 或连接,不同键值对的条件默认 & 与连接。

① & 可用于"key&{}":"条件"等

② \| 可用于"key\|{}":"条件", "key\|{}":[]等,一般可省略

③ ! 可单独使用,如"key!":Object,也可像&,\|一样配合其他功能符使用
"key!":null 无效,null 值会导致整个键值对被忽略解析,可以用 "key{}":"!=null" 替代,
"key":null 同理,用 "key{}":"=null" 替代。 | ① ["id&{}":">80000,<=90000"](http://apijson.cn:8080/head/{"User":{"id&{}":">80000,<=90000"}}),对应SQL是`id>80000 AND id<=90000`,即id满足id>80000 & id<=90000

② ["id\|{}":">90000,<=80000"](http://apijson.cn:8080/head/{"User":{"id\|{}":">90000,<=80000"}}),同"id{}":">90000,<=80000",对应SQL是`id>80000 OR id<=90000`,即id满足id>90000 \| id<=80000

③ ["id!{}":[82001,38710]](http://apijson.cn:8080/head/{"User":{"id!{}":[82001,38710]}}),对应SQL是`id NOT IN(82001,38710)`,即id满足 ! (id=82001 \| id=38710),可过滤黑名单的消息 数组关键词,可自定义 | "key":Object,key为 "[]":{} 中{}内的关键词,Object的类型由key指定

① "count":Integer,查询数量,0 表示最大值,默认最大值为100

② "page":Integer,查询页码,从0开始,默认最大值为100,一般和count一起用

③ "query":Integer,查询内容
0-对象,1-总数和分页详情,2-以上全部
总数关键词为 total,分页详情关键词为 info,
它们都和 query 同级,通过引用赋值得到,例如
"total@":"/[]/total", "info@":"/[]/info"
这里query及total仅为GET类型的请求提供方便,
一般可直接用HEAD类型的请求获取总数

④ "join":"&/Table0/key0@,\多表连接方式:
"\<" - LEFT JOIN
">" - RIGHT JOIN
"&" - INNER JOIN
"\|" - FULL JOIN
"!" - OUTTER JOIN
"@" - APP JOIN
其中 @ APP JOIN 为应用层连表,会从已查出的主表里取得所有副表 key@ 关联的主表内的 refKey 作为一个数组 refKeys: [value0, value1...],然后把原来副表 count 次查询 key=$refKey 的 SQL 用 key IN($refKeys) 的方式合并为一条 SQL 来优化性能;
其它 JOIN 都是 SQL JOIN,具体功能和 MySQL,PostgreSQL 等数据库的 JOIN 一一对应
`"join":"`"MainTable":{},`
`"ViceTable":{"key@":"/MainTable/refKey"}`
会对应生成
`MainTable LEFT JOIN ViceTable`
`ON ViceTable.key=MainTable.refKey`

⑤ "otherKey":Object,自定义关键词,名称和以上系统关键词不一样,且原样返回上传的值 | ① 查询User数组,最多5个:
["count":5](http://apijson.cn:8080/get/{"[]":{"count":5,"User":{}}})
对应SQL是`LIMIT 5`

② 查询第3页的User数组,每页5个:
["count":5,
"page":3](http://apijson.cn:8080/get/{"[]":{"count":5,"page":3,"User":{}}})
对应SQL是`LIMIT 5 OFFSET 15`

③ 查询User数组和对应的User总数:
["[]":{
   "query":2,
   "User":{}
},
"total@":"/[]/total",
"info@":"/[]/info"](http://apijson.cn:8080/get/{"[]":{"query":2,"count":5,"User":{}},"total@":"%252F[]%252Ftotal","info@":"%252F[]%252Finfo"})
返回的数据中,总数及分页详情结构为:
"total":139, //总数
"info":{ //分页详情
   "total":139, //总数
   "count":5, //每页数量
   "page":0, //当前页码
   "max":27, //最大页码
   "more":true, //是否还有更多
   "first":true, //是否为首页
   "last":false //是否为尾页
}

④ Moment INNER JOIN User LEFT JOIN Comment:
["[]":{
   "join":"&/User/id@,\    "Moment":{},
   "User":{
     "name~":"t",
     "id@":"/Moment/userId"
   },
   "Comment":{
     "momentId@":"/Moment/id"
   }
}](http://apijson.cn:8080/get/{"[]":{"count":5,"join":"&%252FUser%252Fid@,\<%252FComment%252FmomentId@","Moment":{"@column":"id,userId,content"},"User":{"name~":"t","id@":"%252FMoment%252FuserId","@column":"id,name,head"},"Comment":{"momentId@":"%252FMoment%252Fid","@column":"id,momentId,content"}}})

⑤ 每一层都加当前用户名:
["User":{},
"[]":{
   "name@":"User/name", //自定义关键词
   "Moment":{}
}](http://apijson.cn:8080/get/{"User":{},"[]":{"name@":"User%252Fname","Moment":{}}}) - 对象关键词,可自定义 | "@key":Object,@key为 Table:{} 中{}内的关键词,Object的类型由@key指定

① "@combine":"&key0,&key1,\|key2,key3,
!key4,!key5,&key6,key7...",条件组合方式,\| 可省略。会自动把同类的合并,外层按照 & \| ! 顺序,内层的按传参顺序组合成
(key0 & key1 & key6 & 其它key) & (key2 \| key3 \| key7) & !(key4 \| key5)
这种连接方式,其中 "其它key" 是指与 @combine 在同一对象,且未被它声明的条件 key,默认都是 & 连接

② "@column":"column;function(arg)...",返回字段

③ "@order":"column0+,column1-...",排序方式

④ "@group":"column0,column1...",分组方式。如果@column里声明了Table的id,则id也必须在@group中声明;其它情况下必须满足至少一个条件:
1.分组的key在@column里声明
2.Table主键在@group中声明

⑤ "@having":"function0(...)?value0;function1(...)?value1;function2(...)?value2...",SQL函数条件,一般和@group一起用,函数一般在@column里声明

⑥ "@schema":"sys",集合空间(模式),非默认的值可通过它来指定,可以在最外层作为全局默认配置

⑦ "@database":"POSTGRESQL",跨数据库,非默认的值可通过它来指定,可以在最外层作为全局默认配置

⑧ "@json":"key0,key1...",转为 JSON 格式返回,符合 JSONObject 则转为 {...},符合 JSONArray 则转为 \[...]

⑨ "@role":"OWNER",来访角色,包括
UNKNOWN,LOGIN,CONTACT,CIRCLE,OWNER,ADMIN,
可以在最外层作为全局默认配置,
可自定义其它角色并重写 Verifier.verify 等相关方法来自定义校验

⑩ "@explain":true,性能分析,可以在最外层作为全局默认配置

⑪ "@raw":"key0,key1...",其中 key0, key1 都对应有键值对
"key0":"SQL片段或SQL片段的别名",
"key1":"SQL片段或SQL片段的别名"
自定义原始SQL片段,可扩展嵌套SQL函数等复杂语句,必须是后端已配置的,谨慎使用,只有其它功能符都做不到才考虑

⑫ "@otherKey":Object,自定义关键词,名称和以上系统关键词不一样,且原样返回上传的值 | ① 搜索name或tag任何一个字段包含字符a的User列表:
["name~":"a",
"tag~":"a",
"@combine":"name~,tag~"](http://apijson.cn:8080/get/{"User[]":{"count":10,"User":{"@column":"id,name,tag","name~":"a","tag~":"a","@combine":"name~,tag~"}}})
对应SQL是`name REGEXP 'a' OR tag REGEXP 'a'`

② 只查询id,sex,name这几列并且请求结果也按照这个顺序:
["@column":"id,sex,name"](http://apijson.cn:8080/get/{"User":{"@column":"id,sex,name","id":38710}})
对应SQL是`SELECT id,sex,name`

③ 查询按 name降序、id默认顺序 排序的User数组:
["@order":"name-,id"](http://apijson.cn:8080/get/{"[]":{"count":10,"User":{"@column":"name,id","@order":"name-,id"}}})
对应SQL是`ORDER BY name DESC,id`

④ 查询按userId分组的Moment数组:
["@group":"userId,id"](http://apijson.cn:8080/get/{"[]":{"count":10,"Moment":%7B"@column":"userId,id","@group":"userId,id"}}})
对应SQL是`GROUP BY userId,id`

⑤ 查询 按userId分组、id最大值>=100 的Moment数组:
["@column":"userId;max(id)",
"@group":"userId",
"@having":"max(id)>=100"](http://apijson.cn:8080/get/{"[]":{"count":10,"Moment":{"@column":"userId%253Bmax(id)","@group":"userId","@having":"max(id)>=100"}}})
对应SQL是`SELECT userId,max(id) ... GROUP BY userId HAVING max(id)>=100`
还可以指定函数返回名:
["@column":"userId;max(id):maxId",
"@group":"userId",
"@having":"maxId>=100"](http://apijson.cn:8080/get/{"[]":{"count":10,"Moment":{"@column":"userId%253Bmax(id):maxId","@group":"userId","@having":"maxId>=100"}}})
对应SQL是`SELECT userId,max(id) AS maxId ... GROUP BY userId HAVING maxId>=100`

⑥ 查询 sys 内的 User 表:
["@schema":"sys"](http://apijson.cn:8080/get/{"User":{"@schema":"sys"}})
对应SQL是`FROM sys.User`

⑦ 查询 PostgreSQL 数据库的 User 表:
["@database":"POSTGRESQL"](http://apijson.cn:8080/get/{"User":{"@database":"POSTGRESQL","@explain":true}})

⑧ 将 VARCHAR 字符串字段 get 转为 JSONArray 返回:
["@json":"get"](http://apijson.cn:8080/get/{"Access":{"@json":"get"}})

⑨ 查询当前用户的动态:
["@role":"OWNER"](http://apijson.cn:8080/get/{"[]":{"Moment":{"@role":"OWNER"}}})

⑩ 开启性能分析:
["@explain":true](http://apijson.cn:8080/get/{"[]":{"Moment":{"@explain":true}}})
对应SQL是`EXPLAIN`

⑪ 统计最近一周偶数userId的数量
["@column":"sum(if(userId%2=0,1,0))",
"@having":"to_days(now())-to_days(\`date\`)<=7",
"@raw":"@column,@having"](http://apijson.cn:8080/get/{"Comment":{"@column":"sum(if(userId%252=0,1,0))","@having":"to_days(now())-to_days(\`date\`)<=7","@raw":"@column,@having"}})
对应SQL是``SELECT sum(if(userId%2=0,1,0)) ... HAVING to_days(now())-to_days(`date`)<=7``

⑫ 从pictureList获取第0张图片:
["@position":0, //自定义关键词
"firstPicture()":"getFromArray(pictureList,@position)"](http://apijson.cn:8080/get/{"User":{"id":38710,"@position":0,"firstPicture()":"getFromArray(pictureList,@position)"}}) + 对象关键词,可自定义 | "@key":Object,@key为 Table:{} 中{}内的关键词,Object的类型由@key指定

① "@combine":"&key0,&key1,\|key2,key3,
!key4,!key5,&key6,key7...",条件组合方式,\| 可省略。会自动把同类的合并,外层按照 & \| ! 顺序,内层的按传参顺序组合成
(key0 & key1 & key6 & 其它key) & (key2 \| key3 \| key7) & !(key4 \| key5)
这种连接方式,其中 "其它key" 是指与 @combine 在同一对象,且未被它声明的条件 key,默认都是 & 连接

② "@column":"column;function(arg)...",返回字段

③ "@order":"column0+,column1-...",排序方式

④ "@group":"column0,column1...",分组方式。如果@column里声明了Table的id,则id也必须在@group中声明;其它情况下必须满足至少一个条件:
1.分组的key在@column里声明
2.Table主键在@group中声明

⑤ "@having":"function0(...)?value0;function1(...)?value1;function2(...)?value2...",SQL函数条件,一般和@group一起用,函数一般在@column里声明

⑥ "@schema":"sys",集合空间(模式),非默认的值可通过它来指定,可以在最外层作为全局默认配置

⑦ "@database":"POSTGRESQL",跨数据库,非默认的值可通过它来指定,可以在最外层作为全局默认配置

⑧ "@json":"key0,key1...",转为 JSON 格式返回,符合 JSONObject 则转为 {...},符合 JSONArray 则转为 \[...]

⑨ "@role":"OWNER",来访角色,包括
UNKNOWN,LOGIN,CONTACT,CIRCLE,OWNER,ADMIN,
可以在最外层作为全局默认配置,
可自定义其它角色并重写 Verifier.verify 等相关方法来自定义校验

⑩ "@explain":true,性能分析,可以在最外层作为全局默认配置

⑪ "@raw":"key0,key1...",其中 key0, key1 都对应有键值对
"key0":"SQL片段或SQL片段的别名",
"key1":"SQL片段或SQL片段的别名"
自定义原始SQL片段,可扩展嵌套SQL函数等复杂语句,必须是后端已配置的,只有其它功能符都做不到才考虑,谨慎使用,注意防SQL注入

⑫ "@otherKey":Object,自定义关键词,名称和以上系统关键词不一样,且原样返回上传的值 | ① 搜索name或tag任何一个字段包含字符a的User列表:
["name~":"a",
"tag~":"a",
"@combine":"name~,tag~"](http://apijson.cn:8080/get/{"User[]":{"count":10,"User":{"@column":"id,name,tag","name~":"a","tag~":"a","@combine":"name~,tag~"}}})
对应SQL是`name REGEXP 'a' OR tag REGEXP 'a'`

② 只查询id,sex,name这几列并且请求结果也按照这个顺序:
["@column":"id,sex,name"](http://apijson.cn:8080/get/{"User":{"@column":"id,sex,name","id":38710}})
对应SQL是`SELECT id,sex,name`

③ 查询按 name降序、id默认顺序 排序的User数组:
["@order":"name-,id"](http://apijson.cn:8080/get/{"[]":{"count":10,"User":{"@column":"name,id","@order":"name-,id"}}})
对应SQL是`ORDER BY name DESC,id`

④ 查询按userId分组的Moment数组:
["@group":"userId,id"](http://apijson.cn:8080/get/{"[]":{"count":10,"Moment":%7B"@column":"userId,id","@group":"userId,id"}}})
对应SQL是`GROUP BY userId,id`

⑤ 查询 按userId分组、id最大值>=100 的Moment数组:
["@column":"userId;max(id)",
"@group":"userId",
"@having":"max(id)>=100"](http://apijson.cn:8080/get/{"[]":{"count":10,"Moment":{"@column":"userId%253Bmax(id)","@group":"userId","@having":"max(id)>=100"}}})
对应SQL是`SELECT userId,max(id) ... GROUP BY userId HAVING max(id)>=100`
还可以指定函数返回名:
["@column":"userId;max(id):maxId",
"@group":"userId",
"@having":"maxId>=100"](http://apijson.cn:8080/get/{"[]":{"count":10,"Moment":{"@column":"userId%253Bmax(id):maxId","@group":"userId","@having":"maxId>=100"}}})
对应SQL是`SELECT userId,max(id) AS maxId ... GROUP BY userId HAVING maxId>=100`

⑥ 查询 sys 内的 User 表:
["@schema":"sys"](http://apijson.cn:8080/get/{"User":{"@schema":"sys"}})
对应SQL是`FROM sys.User`

⑦ 查询 PostgreSQL 数据库的 User 表:
["@database":"POSTGRESQL"](http://apijson.cn:8080/get/{"User":{"@database":"POSTGRESQL","@explain":true}})

⑧ 将 VARCHAR 字符串字段 get 转为 JSONArray 返回:
["@json":"get"](http://apijson.cn:8080/get/{"Access":{"@json":"get"}})

⑨ 查询当前用户的动态:
["@role":"OWNER"](http://apijson.cn:8080/get/{"[]":{"Moment":{"@role":"OWNER"}}})

⑩ 开启性能分析:
["@explain":true](http://apijson.cn:8080/get/{"[]":{"Moment":{"@explain":true}}})
对应SQL是`EXPLAIN`

⑪ 统计最近一周偶数userId的数量
["@column":"sum(if(userId%2=0,1,0))",
"@having":"to_days(now())-to_days(\`date\`)<=7",
"@raw":"@column,@having"](http://apijson.cn:8080/get/{"Comment":{"@column":"sum(if(userId%252=0,1,0))","@having":"to_days(now())-to_days(\`date\`)<=7","@raw":"@column,@having"}})
对应SQL是``SELECT sum(if(userId%2=0,1,0)) ... HAVING to_days(now())-to_days(`date`)<=7``

⑫ 从pictureList获取第0张图片:
["@position":0, //自定义关键词
"firstPicture()":"getFromArray(pictureList,@position)"](http://apijson.cn:8080/get/{"User":{"id":38710,"@position":0,"firstPicture()":"getFromArray(pictureList,@position)"}})
From c567c9cd9ed1bcbb45d670658a8af42d2b65aa57 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Tue, 22 Dec 2020 23:41:56 +0800 Subject: [PATCH 0124/1181] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E4=BD=BF=E7=94=A8?= =?UTF-8?q?=20apijson-framework=20=E7=9A=84=E5=BC=80=E5=8F=91=E6=B5=81?= =?UTF-8?q?=E7=A8=8B=E5=9B=BE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- assets/1543975563776.png | Bin 0 -> 29002 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 assets/1543975563776.png diff --git a/assets/1543975563776.png b/assets/1543975563776.png new file mode 100644 index 0000000000000000000000000000000000000000..58e2d933cdbabc8bb2f7d069d4f195f9d6288f88 GIT binary patch literal 29002 zcmeFZ2Ut_vwl*A^D7|+G0t!+@=}L*HG!YPxULpe01XMs;APUl(fPjJ$s(=XDNRt}r zf+$i$k&@7Rf|LLu`Ih^hd(PeNeeT)kw(t4x|4mptteKU$=9p`a_KqIf$B-hVA5e&13Aw4zwcoIpiKE=N=cmT+za5(vK69 zzx^QW1RWPQ4=>-T)8Z0mBo!2ulvOTVymD1bTSr&#n(0k5a|=r=Yey&NJ1(wn?moVL z5B&oIgTkLgL`Fr&#HOS^OH0pq{vtCkzo77SQE|zeca>GuHMQ^S>Ra2|J36~QcJ~Yn z4h@f>N5{r7*x9-Hg~g@i75vus?Va5{!v4Vzx+p+Y|3Vh<_g^Uci7r-vE=p=@Dr(vv zbWu?H{~(-|n&#yBV{Dqnv<~;#MdThI=eV4lThVetRNe%~dHX>>9haB_<`n)1X}?nT z=Lie?4^j44!v03r1n3Hg^4CU1Nl8URMMXt(j0R}OjvxEcj?*3gwbA{hG5p$?el+I4 zHZm{>1uzCRH8m~p%R+yGp5>oz!t)=nN?AHqM}_wU@lkhidnp3YQ@S;i>L6`g?{)FRXKd7-Uy#h;#P_ygDwgBkU=y9@C08n=+13O z^7EsI8Q+CaT7k60v!2|fM;8}K^aW%P9|Ys$irS_?LvVKDg#M!!(IjoKT_Ldx*>A5& z2GM6}kwH03b!1T3GNdz!U^^~*1oG=6a_f;4;6H|qLc77wuWtW?Ie^EWC(SwRj6-{a zjyUJQxVkFn0caILIGajzC4(Y9=aE5QX>G}%7GK1y&h8!2eV&m2*4d_85gl>Y2Rl4V zx_$EW&eKI1SEmp2COL>AGY|yMpxAfyQI~lwomf8#qZRL}Ukfsn;WsViC7zuv&WmmT zDv=ip`op8#zr9%oHyyr~aRYyfXnwA4JnM_SHfcrp(}mp|2KPMih_u+A*y=pz)HUwVEGbo$Nt2M=Q#I0{l4l$MYp-ti;>$NE0~k+WE}7?HuE z(Y7IPI$h!!)W2mikIG-4!@`dzrt^OgC0Tp@RPwNG$4&q3w`>MAzQ;jhXK@^r3iYQ;IVzVOy6@Da z2=ID_v!`Sk61s4Y+FncLltb)KL%0ZhH!+>%78-q?=Nt&JreUuxXs)~L73SWYn^hDG zIjO~A@ezC#;0w^t2WQq>_nIKC)Zj9A#&_ebyyDLe#M~`OVO6C)B<1ta2o158&Q;H% zr7t;2?R?XWdWHqJ4yxi;jnbL)^VBxmGCjXkK2QtBrm=fi$I`}t(<*91O^!c4EMVYv z_@w01#rf^#eUQkdi!|V#0%Ym4^1f_cK)NUYeeV1V*9uDHJyKC!ghpA{6Bf0Fw4Mxf z301hi)RHziXH*rjbSnC(9yss;vm?|X>^QYRI8=$Kt(E#gNYsKCJhmsk6`yQ>l+&P} zyF75=lX{gH!sLdtm19opH$gup zt)a%y1JzBvE%5&NbRHpA5iW{m7}c}@`Xf}RsxOAmW&IQxbQ~S6rah&myA56c#N+2l zZ!#>c&~`uYz9-+r@;JW^PrAfc&-!{R=)-HADpY+Wt|bVIFDenj9Lh_OZ#N;(g+Q-0 zMC>riY7SQ+t6xWX@;q7L%Ix=0-RC`*Abnp)?vdP#CuMQLx*uvem;{}>7`G2EOBxCB zo^=U_$>UA05%jUn^#gu_p<_h@n_doMMNoaAS(&%Ws?o~lbEX_`73B78v{1DYPXeY5 z^-(I(`1Q9HZWN0Gin*z2eKIH9xFPmTfsIw>$&1Bzj=r_{cN(5BIYIfJ3`(%4#V5ry z%UKa6@D@oss?#*Y%U_9C?%lmzQ&E$ppVHxJ#`k4i`1{a1Bk8+VF%oNa3(Z^v$649v z^yy=3L`d0mKrEDD6?~j%9K35_-=!CBAm_GwLyY#ihf2y!XUxe3pA=pr@zc5X=q>LJ z6d5Ffevua+yraKayH8A-qNhJpH*cLV9eiyQr~h%l)%i+V$^{O&ciA&x$4mVdp~oR} zDFkuShfHr$s*Zh}_05EW{ysyCSO+bRn_NHG!E{Krtg3%_g0%bY3HS7z|Z?RaAMAFx<`5{GYl3bM5p9@f)zF_ihA@q zDx3lQb%ZhH$-7!J9(pzkf&?f9X2m_b!F+DRbcbw-2Y<*1+ajV zMSyn)n$6g=ozgfzEr}ERns;YU>cNN5C+KE$-@>zJ9vb#F z@W8cOcP14>0Vc3``S9<~<`32Azc4D8Jox8@?INQnNe3xN290V@lRuK&@ z2W#z%E(&c9lkWZy6@QzR$xLDu=$WyOVbx!ysD&UFyw3+?CW9OvlR*XFEgWtAqv3OI zILJ{XEh+W0nIIyc_}tIF^t(oKUaZutQ30S@9{ujvLMD(wTi0MZWRNX48Kf-}R2FE_ zNe1no(hwzs%IV3Ve6D&NxT|OdCC^}iD_R2fU_5+f@Ti|yvi*?^8ZJ7_-#1-det|hA zuLV1S?`xS73GwNgX4g<6S`KUQ2hi)*gfY{1<+^va$sPAMP;Yn5EkDcWP@`hFh6Du# zlR;~BSZK7+`6EOlE7m*KxF=g2Rl)pluwzbNf=#YNIvHPG7%iIoxckd74?e%ZEOQbH zx$0y6tj@$=ed}4+#dwQM;%!3mYoai)5fL%<<$TOcqXxdkhH7JJ_ZhBC>CxpJpxYo7 zQPH6yunTx}v<4ILE?%sDst_x~)YiFc`mJ~1xyC@2AO8)jEPC(L=Sn|-Ui#>bT!SGG zCP{jzjOUUvZCMA)2i1Fs!flJTZ0@7d=2HPR(^m;m1+Y`~bp#u%pqavifjR%QvKfp1 zna7GJ6o#K9yW2jv8WBe9igoT24rE%AD8MCd@iw>W?t9N_dXYf zUFb8kwEe_j;uM1GrBI<#;S6Yi+tseue^ZY~ArbrX8}3pVBqs?ybt=jn-_h6RO>qSa zr-!Lz>)Y9rW-X%(>$9kb)UPUeB z0&cLQ9+^mFEg+QI;MNzP?C>x3jVmFcg%}-2*wD{6#)?fRh91g}}96 zW3*m$^H(tEGF`FwQZtg?U-j6r1S(RO7SSFYiQM1)+gMF zbWJ48C3MdFIkcElvuL((5^@hsr(jZ97WgkkZiQluZ}>}H3x|9+dE3+uo-K9HclN&- zmDf|cDjd2VDMz=Ebtq$i>lsWDh9gxV>ofTFsaN2M-`EZ_!N>tsg ztaO>K!gxG9mGe>~byli^t02u!n+HGCTAhm^UC%FV zQKzRdwxebdW&LK@R`&I064%yuQaML)iS=f1s4^KOy4{RD==?Ih?w@g9lNQ{VJj}kZ z{GE+fz4QXi08?};=B<_dc@TvUt626U#$h~jyQlVGE27f74e?}34HrTxDP9>jPjxCe zN^mQim{>7?+CfTEEStmSf0?iq2{WRJx<;VyvS*0gXtNq<6vvI@70I9zwr)WY#2YZx zfo-dGnGxT5>AecOh7Ur=Up=N;0F?-{xl7)@>FgC=6!jqC0MR^Tfq=;pl8T5{IOmKc zg%H^r1HMPs1Jk=pu0H)NElnlXQ_(H*e2!LPqKvPaXN`GtFo!K*8yi$Qxr;dlZ1n zBN%F%uWBV*3cQxDioX@Ux5F{`t`<3qno9U30KeLtWC}c zO3ILr0wdKN#%Gi^y07(o1a#t+9|}zE(=3FTf?_9w(9hCh+{hrIr*O0afc0p35?#ok z6bmf;U@N6U1~NOqe6WBxIF8K!1bO@&o}vbW<29fQmViF2n*E(BJabRS`r2O+m{wd7 zG2I4v2ofiy@$HS{Wa;4D&h@}!ql7OTBSVnvDkRBmow5uvh6-OgeqdNvX~ki{jEMw99sa5-Vr) zl-@qauI*?z9;JuM6B=fdo)FEjM!c@8n8PN~_VMgwE=1C_ji$?dL?R|!uByi}%DFZ5 z>+R+&&MoG(+Pr|&79l=DwI5c7Yj;GC=!dntHbVP2#iqeZQ0R=XIaN>(H|sB$q$|*C z1pfA|0CcOhQS4m*W)q|eEhaT&gSvG`2>;&Nx;W`y9v4w z`rs2j$sQu8A#F)8#XigrEDW%34fY%Ghh74kv>?pCZlG!a$d|A88wvv8cwf*iBSpn&D@9&t5 z(hBsoB(W}O@DU=g30{ZT+9x@&P_{NdaMrA+Uiy|o*f(z7w%T!yv@==pY&|y;9$CDI z$nptC86DNRecxZ63sQ+LwGKh_&y4uXtkFCe=#jelP4x+&$aGwTMMOQ9FeJg92*X&$(rgPO@tGW9_tuWu_eE zdWBC9ACGZR)&Kk*RH!Y#&@4i@k85ZhpGU?us$<-pzH!U%dclRa3-l@<0Rh z+w7$VWvUZI$RKme4Vkl8tC<&SF^1@b7IR+EOk&;}Rfx|1xs@i1g?|f6Z7oXbXNfiJyl~TL5sJpXOlv`+0)?ch9L4QQf!Rf<=ybV40&} z&X}=nL^PaHO&j6dG`L=#%YERtrj`-u8V5_&`Dn!&Id_ZKpKd#We|PK%0dYe-v0kqQ z)Fn$C@VMJ4HHX6nitY4OVk$lf+i;+X@;$3rj3jvHx-bo0RJFHYCa zb}ugJ3tWK6c9d5Q9T&GFyeDzs5J|ohKGYgKQPAPD`353WV*FF8S8D^VZgr>23HGXh z&?EIUdtINh04xR)t58PS^TpNMD0*OI1sf>*(q-*-$14uqvaU=gDuH#RV_rp}e#RZQ zi!UdcGvRKK@MdAcs^iQwD6&?Fo^QT67aK62mSR)NFYJ^!qG+c2 zw>dn$MN}X{@4p#<`k^3Q(2oQ#CZlzF9F5tG1nf5D>q83a$}J?0A$I?mvJJr_5p9jyA8_kY?48YB>jV+RVKHh9#k2;@1Vk?9aXWJ zP-VnPykvBOsD^_imJz*6zPZ+xQ`r^PSROXlONO;svvH%<5XQh z$VJV@8M8^zRxo+>z;1Ym4DuLL_ymhb-KljYjos}p=+XO5_X|%+sUd5`tHY3P5<+#N zEa8md1OEgRW}MUjOX6!fdZ8h=Y)^+X3L~0f`WRqd{R$z+N9`~Db$1Ca`kdCsZwZKJ zte-=S4zH1h%n`?7jCm`eg51iP+0`1UVix9^&Ygut{5DzbvGZEJl3(}Bz|Tbt4qw!) zQzyT=h2igOMg$W>f<5!)U)~u=s5#=^`Tkukzmr5wn8e7AOSLp#7?$?yOVoFBcw@x) zUSCFDXTWC+nl!tj%_o>r3|9mAOxB%jY6T!#vg6bW5ea187MZo2J&rY0(X`QgRk z#wn3hlR;#`yTQuFs(s%xt8Y9VUo}M@cKlu-g)H)kuNE z8pBGjoIM!RhH=k$GA|e|m9=y)HzZpP#&b8;LxxGE{^i+PdfkUL*2n?CWJbP7n{CqR z>cd9?Fb{{U8U=FYAoOv*(aa{pxaEY7SgpTu>2BP_F0%-AjlZRXUf@US*RCf70yWHn&wZ3}$soh0|D5++R59|$b2axp! zeBSP&K?PnB9Zo!{xEtkM)`_lvSo`p-2r7I2%$w&fvS`czTT6Ns z!57M-98bI~ZM~sxVKPYkeDMONI#ZQDGU9ZiDq>pV>De_69v5n=4vuHeL4f_QS49T# zLYC?B?^lhJsyuMeh5M)~=I~ItfT-&PlTaort5?Bo2UY&2cJ9|{UL(M}zPp`nj?`*} ze70}hUiE_1*gR4Ro@Fp|g6vzEX1^aGLhOa{!S19}4Z}0T3dy%*khkk1Ij7icipP~) z5@N2y>N-6(B&FW?Zi*}HkwK41+UHALi*39KkOdffd4SRGV?q+{iY~!rHi>3*HFGiU z_0ZWJ{`JrW@oL;0^TRExq73(|gOi$6A{}$@FwZugQuKYkNV`@zi-)xJuPQEp4)RwW%#-X+>@giWRnr Z z5oERKN?$Ygs?e#CMqXz~984I-SYqQ<6Cz>6*lR2e7I`ifK6maHa0ABho3mCBBZq~- z7w;=cgeU;kIGqr3 zOXndJ|EXW75)eW*82=-ti^?Ph85Ch2jH_!F~y7De6r!2 z6b)h8`8K)| zL4NS2AoyVVBDQ;{JwnCw>4RM|t>lueCkpFNyxtm;f3NB&-9tZr{}Nb};JrI@l#K!m zxMq%)PaPBazJL}#1e{H5IDol zvG2RC1jq7#r0avz9YW`--z8^Q4^uWi?T+`VeiFLD)W|}=JLR$NVMN#tC)E{^!lahA z`etC$b4mPyM5B!TbO4rEzrg%LLnZ(@yG91FR`o?q)2>&2rq#DoGB7g5D>Zp2ehyQ9 zF|XxJ*&%kRw`(!vbL#{34bmqRH&Gvtz%)%Y&C=UQ_$BaL3@LcTYzV1S3sNFY#u(l_44M6a{Dk0v8R5&5Fw-Zd#XAxlZE<6`sp>aw=w1Js zZ2pEJk+6fQsgLgL?r!sq`270rD$yRD9(unc_;+gR9=&$ht2%M2Y|tDU6V-NT6gSQ8 z`&qsvlpXiFDBr{W7_MHY9cN=VY2{wO%`J4B|0&nFp63kWla)3$N9mlFV+^Xw*v^!Q zSaH)lt39oaQalRj8h}@9c7Qnu(^#V?e0!aI&6O@lg3*m;)L=E^oi@uz{?)+knH252 zPZhv2ZAqtn7K#sNgqi`QF1Gmskltc}o#uxmhKgUos>;BPs~&G38p&TuJYM~za49EU*NsYy6**Pl@&( zlD-+6Z@jdHr;+3ax8V2RT$3r93^gQ!GS!io+Bn5H@VMv)64wCH2+u?Y>5tr1?3VRx zbo1DC74y)|b!BY!%#rS~i@WubAyh>aTie;Bj0Jb4;qT%DxMVcYSCul%(Mia&h=;qg zYs)mLR!^4$m|Xng1Fvog5Zhi4I`(_QJ;)#s!PJmYSOU`=QSgMcmlTgpeY5U6onp=J zY_jUfpndx3%iGr%UAs_`uFWhsF%5~MTo^PLX8EGJ>@=eg+7Ms3+a%af_^sydrkl*R zYkz^p1Ib1=q4i?+k;-S&Qk9!WvIIEFVKBD2w003q;#nZk!~F4kMDb7triWlN0m>ycdfK;|8DQ#9w zqygMsWMf5v8+YdGzB8M9fq4TL)EYPV{T=;PzA$G%I z{O-ASrcRVvs7h%66eWHyil~V30t zIot*0*S*8_9mRfai-mP~zd)M#n4hp&}3ecO#vD3D&Z9F`U=_+QC=gW%(|GLeDW>%vUTmb;UhtS1I8o8RRnILV90B zFi+#K^q2fLYB{AQXK$l*Z})SvsEE|_37&5ekH_zK>L{x9clq$QJqWX$r?Im=W^UR- zEp%KX5~0P#GITYDOX8V`Fx{Jc5XA=&(<9U3zeHAN8?OLS4I>~RyLJw;o$bEnk%(s| zCTP$CxbX)vXdv)Se&seim8o<%)MY@ySPSO1{UZy5;^)KO438q|836{_DiB>+>Oh(y zB?&;_SgfcWzS9EVzKyo0B5WU7EI7oHIF7(`#z{xk@Ons^#tZNPU<>=gB|}2w)r9k6 z&-@xMwwRUeP*?4+MkJ7w+_^IERN-AS+Xt3wgC4%3nSASWh1!A};x0d>S!eZ7ViYAz z{i-YWC=;0t(>W9;eaSvIqS(h8Q28OqtD-a@)+KvuYeimqK_|iSl)%dzx_nn#P@n+% z^yw!>Z-W*0Mwi=$a}-c1Lr+QqF`v?77x;sNqfO0+9KseT6I!}C(S0em!QT%Kn zucEUjnh0+6ebXtUJcexjv;h&rDhaU;!NgrKylkHp!CbD(_c?qDK1oimu*T)|zq)-W za^v&+K(Pmb_SL(P_1PIX2LY-3wTeE@d7znhiF;fuG5y^>b4Uqv<^Gs7zi3OWl+DAG zOWN{c(V8?;OZ1zg(=*3P*lboZ4NC(#9*Fg%65_1W%q^cQcv0Cu<>jG#djUv;Kw1uk zvJv>raf(rGue1k(6X*>x2e8NI8{TIc%RF1XPp`1m(0k}~!*K7yQb2Jc??l?VFBv4e zOkFBf(*VwdR6nyWYNPL?CtPU@l|$i~W0RtgzRWz+r?TvzHUzoAVN{h@JMDb>5LXKBuW-z3aViiC9TEZr6yJHY=)D# z0;oRtH=i6L%I0@@24g?f$@1@RbA5vhswf)Htr=20ek(G- zPE(m%DOon00ex>D#0pIg(=vBJ`88FJp)28K5& z73u@`DzDa^593Un@jzF5KSI$el0nfZTn$VUqbg(3@mM%P7RmBW;qrurag3OZz}>o! z&NqC%_}GD;koXAT51F;mim2mm=a(()&(zsW>U7^>HF!{3)g#iEp4gTlksmn!F0e>D zF8(C~u?QzTa33~FlH4Me5qwoMCGzSG@BKyEQvJ?NYnpAF-U1CmR!6``Ad7G!AbgVP z+u^%RK%A=Iz#fm;+y}WKc2=25kEHrOqav6s_C*_H3G+({u@ZUBu${12i5$v5IGE)K z0ER7w1abp+=1<+WhIbW!cRoY5S#99FWYAn10YPdkc&q<=ix>?4t3|BRL7f=I@eIh7 zxm>fmtd^nACjFg;O_ziA-ivYG`loXDvtOu`6z4F!_f=;a-&i3{8`i|G5N=~{MsVpp zUKgbR0&9=itwkiB+-8Xty#cai%wc-O^zva*|0UbUGM=|zZRCY1WE(Y! zX5dhfr>Z@8eS^ho0@E>h9R_5dE66htpu|AtUd5u*d z`t74m|C4muas`y?%p$KX^YyYZtmalAh8a*Vw+i)+u+!|I!hr-!cYY#9ts#Q#^%aK? z4rv!?Y2T|QT?_dav3-m?0y6h*5fHN-G|0au_57XBD99uKl=8O34;bTXdWQV4|pE1u}=x z-30+nJ1g{!Rxe2kddp&=Uvcg`JF<-I-d;ZC*OZ(Qv z{xcG-Cc!{Lg%lHOV~Sn>wmOF~R49b!kBGKNBYK%k)4}u-A5M5dwEV=kIaktNl({Jd z?Is83xzpE=lX!)n!Yf45RQ6iRaKjvu+TQuszf@hwl_)RXT1vcqS+$sCcIqC9cc^^( zMaGA0DF>#yBH;wy_clxHPqNuUceRy^=WQ- zZ2X3G(`H#MCB6;f^|hNn8`iJIIg_39M{5K0Q7k}tA4BMf#!375XQay&()*RQknJBF zYM+l~=E}{Pf)@{Y&e!@Zy(=++GoT+6WN+4|cQmS&g|6Cut?&qBokt#%fxsv%| z^t14fYz^`ZKv=?bs;QpI_RnPM3ByGrz!m*=GVjnZ$V*2AnNm29=A_p0?X8^{Plf>X(F+eUu7=pBnyqA}> z9(08MhWez8aWyAi5R}Z(Ew>8gMI|q ze@k`#Pt#T0;4nTyLhBDR_GyRCJcI|6LGm5>1haB?kQNrf+2?mRl&EwL$d2}Bz!ap? zHjhBdf|`@^mxGRF9ZSj^FMO5uZaq<=lT&9$yhJ=DP_aMX~FdJKEFv@D_;k| z>b9Lf*!*|aIM7E6!9wT){5xtcx3xcHoxU9Pz?;X(ds8%C4(un4Mu5W(=wLOOBh{WP z%Y5!^a>!rgydY)4jGTiyyyltnPcpR?&*4%V^8)sWXI@E>B4zx^AgUw)E7JMt+`diA z^Qb7A^iXH01b$|A8u!{4dB9UWO=N-)wZOkQ3ufeiyI~=(qW*`j3;?hr{&kz(L_E56 z0>$b+J(?}_wMzXm+H;IO@Zd>pjM83L@B7C}@%Q`6LK>i=0sJH*Q!MHzHk%8uMh?I0RsYmu@k_tap%E)_4Q^6MEJ}j6z8F9) z-A2Q4Dr69zpb3WmtIhL$) zMoLczP_s;GaNaSuSqba&U;AE*b`zduDvFaR&mexwXMl8RK>da9MY8~Q43+&;A_=kE z6uPa^B(74JX@dknv<`-OvELJa z`I|}b_e_O9?Ek-B;fqXeWCSp|5H81Oyh4RH0uX zG*t%dE!n~&cY*9)AS!HkQU7X9liYHxuIKJ&AJ#kJl>Xm2gw1-=?{Q^Lp41yt3PEeZ zFVw;XfYfp9MEkCFQAeq#Jc%owVQ)8m)wS6+UDFd2+dYK~NuHB5S*m|cxu4@u39Xr~ zWKL9^j-I9>s$!?vrma_kBHHXf?3o)!mlfIQcbDa-D45^lOi>hN&{22yLVBzo*}ks2 zzSkpxOalV@Kzw(U;DLFVCAb$0ZZ%$+68U=9sWIvX=`&(&aD}DpYvPQY9BDT2HgnIT ziWzoO6WE*Cc&`v+txyJ-7~wD^1S@;O9Zd#td!OBWTR3o+Yuq!qHxdIRMZ_;)CkrMN zvVE0u8cI&qFk?YStA{6nJhJBVcyxjvf_+6EI%!XE`QV?jpYY^RYbrx5wO;#Nw3AO= zm<09in|Nff??d9*3zxt20YM)QlaAXg zOVL!>&hXn#eSPQe3r%U)6K{XFHa8J)qT&5Gz_MTgMR&lDPLtwI1`oE$AXRW~m9f^} zDYg7dG1tGTA7MyQnb(drV@f<{7O6>F!MO!l2h6xr9=iZKQ}Jby47wha5aJIc^a1A5 zmN|e^LJtKt;M-lrUuj3MD(#Usa#TTu72Q-heL3Al{eSG1{gJEokNV*MR5AJA^#uNq zz5k7;@b^FeJ-huK7DUE}Y7pSFdvT=mFf{~5Hqw`{uEweUCI56?nLSO#o#v|S?p%Ev z*rcAAhxl`!3g#Z=2fBwBW8KDH3-UoIy*8>|U9lm=-G5vC(*L4&Orw^YSDvXW8@+zF z2@Q*8&SY1F8HoLYOnePv=u@I3Aq$J33GIY1HlG?LSbOsrDyX0RZdN^b)f_D{Wl5dk zue7@ooAU8V#+HQx?4{r+?fUVuna2$GIxF(IZgYT60Jt;L+)IS@$uNFblc_1iScwm2 zfY7|O1W3&@QpHE$x9$ydDYX53YE5@{ch}nDMe)=pDHX}& zqwLB4xLbk*HFl`O6$T}|7}rvT4iDX%9LZuq5Y;0Zwl;<($5&ieg~NC#kBicVuN#`z zd(^{y-fLFxg+_f}KGbl9GplI^VorKH6lw*RcpTV86ut0Z`z$`DkYK&A;76m(le+$} z_scOkmc^4Eo6MtIE91=UFyo{T=w18s0Nwy^n_?Wut*?9;$3JnTd@Wr$Q8nx>WyUzY z=P9ZO@*SbEar~zO=4=AmE-U38m&=KFFjV20O&k{E8rdzGpSb!o_d=_LF~XMa3(LF# zG&q#5qsQNE-v*D`7yTGYM|2(_onPWcUooh|?M3_2zaFe^;@Shd4|+d6cD@3de%1LT zHZ9bOB~tLi2JB&PHZ7jM;KB7zMRre0!mO^zuTFKzeF)%L6=)fno`q)+W3k zL0rN#)Y*$5reM01HwA&I_E!fpq*K>IjfjGTX&)SNhPkbbAKTR`HA&l6@0H+I(5+O$ z03RQ3ev{rHQZ=ysJl!TH)0}oM_4y^+Y;9ftwVRpf53+*xL=D1T0hAEj4rjStDa4k& z+cs%B^(V&0wjfE=z#5GcJEDe<5nerA?e=p~y(K>pK`WGW8T z3{e%8PMzEq#nM-Ea|dJ)6uw*(yy)$xQ@gVl=#S2;T*`~>&H1x>7?}(Q_Rzi*yb^ho z=npw?9P(Iwv(LQ?I3+2yhZliDwM8E?=(W1`BaQk$JLaFb?w_ep{!_x^-@RuYgp)Ac zW=~fgyV$NA#`WIwm)6(;BYF5km8V>e+XDVbjnrAr)mI}zCq{~%Z2Q{;0Eo?_#)&8_ z#cp}M3STw1f@9Inx7OE^F9i#B)N5jYxVE-@=%eF7sG~hVyLJ@={%}hf{A*Mx88rP6 zMGUGT^&p9;Sim=hAWb$2^4}zoKivD@Tm!uB;x}Xb>sQVl>;U1xaP`(dai65MH)PN! z0`fcafAltzp#xLL@aQBB77uec-A)67Ibiy_(K;jB*2oq9keX30UE2ppfz^6(3x@Z1 zlb=fMx8r2j`wtz6x`a06NErVvBLu75E>VSKFoFxFkL7W(eYcM*#T5dNf`8RR~c7Pvha_-+{ zs()hv{9h9yf4=heKP5!omib#&;&$WU4dL2u!#z7bn_R-#E35i0sw7PnrT zG^vg1G4PkzhIfRhyyd-e=A&9TR2ima1_uQsSUSO7zLAovgBKpHpN=R}eOYO9S#M85 z>hl-jmnSvqkv(0ZzrGxx45)|>3(Ea_Sj5l$e_*hp(Pmq+zZ$pRyMBK&Zb2b_KY#f@ zaZdYBR8IY;Ho?!4_c!P?m^Yv$O|g)J8Pvgf4OP zpt#NwO=qD(Ds{yEy)dLpII%y{FNhh1f5Sewz)&(6;5V`AC1a_>st-;KQEkkURw(P2?#`wew& z*E{XwAzM73)K)p1hhSk(n8(NI5-JZhS0PNII8qa5d1{f3M|%BTsFR0o!5zyDk*CQE zY;pz6os}ha+Sk-K2{yzo!wk8*M>q3O%Aqu*fp-LbOj0|9GoZ3PGoyW)ekuEm9ewM? zln+TI9%UDW6Gvq76koJ|uF}0k?X(SI_U}izhd_)*6gL@RF1W}d+$W2#ReFtA9f!nf z8zNV)*Q(x0Ik9fnUyu-O9=A!U#oFFdun7c-Bgh~b&Hl7?uNtD~KEkXY%CQ8mky&4i zR--5k$j4YjtH~D;jhe#-HHXg5R|Pohbou<;DOt;BE$t_=mWg`mVL)$$}kn*XTRq^w#K0Q-n)|El4>sQ233?Svx+vps6paS#}C7jttFLv+rg+kbIGTt9$fI zCOo&=>3eVNWqn=>-k1sKHPTn>&4@;IT#-#Z-mCI-gp1tUbKly6{faKsi^Evs8cqdb zn7XT9a1iULS?=kWJ*Zw0|)%8aD9Rf^9yi+UR#uFd%TK~LyMJhE-jLnx$Foh;^(0eSk z{h|F?93*xd8fCA{Jgap6<2!rz@uJxEyR#oH3p)$alcH3I^R=lWZcn~BbDLu$+3X&% zM5(VXx30F1$+J)osGzMm=ULu#b2eA7W$xziK0U6eV_Kk@r{(QYen-6>DGi&%OIR4O z%pGX%yXXB@dgHA9)`^7n$2UHM+O_z-h3k-B1aTzM>?_e3cQ9**Z*G`ff;r<>Hrp$g zGhTnz*^M_SdUvWMk>}y!I#b-j1%deN?IjB{HaA~rd>~48nXZt@eE0y>GVvX987t!O zT=(e%4QSTWdqF`$jMw_F@bwwz=avxo_=d6Fv zek%J-H^;iqZH0Yn&0R#bw!K1z!!6zGnQ@q|C(Zm;pr!v`e)ZQf!Fu9R5)a^)G)(Ww1rfX5=XW(b@g)BY(R1_K$p==K9}TGOkps zZ+xyADBb=fiGna>nmJn)IMyJEv;vhVRCvZz-{cN&Cx24F3 z4|ER#kps{F`g)Kh=WE{=P&-Fn0X;lN?s=>u>M{nQS ziUK`R>9eN|Po$iCtAk4oA81s?Kw5%d@ngL^AsmFwOBHohc<<#w6gzNpQ^;Pn2 zYcli`SF}f3HsgJaSEji_)d@h762~Ahcm~BZv7~VZeXE~s)o7%=+$+!HN&3~NEvMqo zBvc3_$%pA!&uOTT22rd4(|xkVZAPduWgfjw^>9#NOG70io$4RjM9+HYyX9SR2e~lK zJiT|!{gi~A!8VW`JSUJcB2|vc1l-V8nA@QcVbvVB2PE?_65ws==iTzVYBc0<8=2@h zZ^sfFF-p}e`8lOSAQe^V1RWJ?80ceVVQBYwU!ntkvMHmDQ5ACx@ixGIQ>!Al?3#h& z&Gdt{QeGk5#dID|3$KA3Ztte9YilsxLstOE>g1bDam!|TH>5y6X=C&*%DBqzrk0QM z!y85+?0wDx`kXZj45$3Wa=UoMml5MsvxXJ8;IV?l^b|O;vJkb)rDkz30QrO-UR* zZ@e&LBUMU|WU+H+4S2j07l5rBd_APKm1m`c_szbW?y$`Mj9$Pyf`{) z+n(-ow&eAS{M9|C0O0ML#e}N1ytX zlr~FZcPKZIa#f#AgO}fR#xq8BdPfVXK%abtS^k3W8{72VkE|KLUhlkZzDoUcQq+Ja z*x#8+=v*)DrzE{b<3lAvzCP|pQ7e5w>dZzAp8oRxY46IT*<9DX-NCHrWUiX3v>G*3 zt0_9DjT$yeYsywbLZq~)Af%g`+XmfRwosJTSk#nIA=_MS?NCE1N+fEY5-LI@?$>qK zIqRNt*IoPUv+n8n=lqwo-gmv<@IK#{=lKoK*I(yfkPGOF6R!@2KJtv9M##TiX3#yB z+)c)sRuyHsRp9smG@|^GLwB3;Qc<$T6ebCE8STfW#^gmum7s5Z9EqU8t;5Xy{MC=Q z7D~!rnY=TW6HYro3PkVXz}XQ*&IsN6jv|3gvB_^Qn@qoqm=Wsg+$s*4LsOrM$~M&#MRv?>ESb5l8CZWH~=WYt+Bji@tB}kMVO=S|6uM9>;OWH%5`=qQS%7h@_-w2@Jr z#>E|vM0|z}uj0;u7VF_zrXhi$$wz`cn>~K=g))$ta=5}dx9QyH0LD;9?TuOl+CtT>Hasm#Uyk22U z2RqV*qCnEKkv@8!p455WGd=soLf>a}gSOttLQ&9j>WW@gr>E+C`(vyj9#yL(YY$5k zxFKadGBPOW>1!GzTC**3M}Q4!uZmZ@Gz^Nolpi#29i?C*gS_$f~RThzi;j`LSQvsU@Fim%-@3jGoCmA^rig@6>I! zHD7p$v>G07=C8!_fAB8v^}~$z$#!DBEJbl;rK1(=X3ORAue z7xBDsrCHB1_{=h_t5a;vXDZ?l{V)wr>uf}+z$dd%hv@ayxm^R!aP#3R+k9Nubb&!f zw%04E%(b7-yiHYFz&*oCaWpd9Bf>lryo#vE}b8BN+6Ccey$ny7P zjpSBhk0}x`#4S+ci_N;c?X&Z3)>($MRnq555*?F4g6V)djfY=MqLcB)$I6*l^T98p zOM+YTfWCMb?4oytK|SpS(@YsZ*fW@+eylh(X?ioauHwQpIxl zp5HkT+IwWys;ZP@v2KIMHFE zqKFjh@Im*P0NJIvVel4YQJ;G(z}2_tSrl{BHj^RGlN-~tw#4%7 zbo^HJ|`R6URb)ky@ zdw%5|5T7joUpH9~7!aeq*rTSKE6P0faV{cndjrGg9$H2Juc>wDzg^5s`9?IJ%#Z2@ z(8&~&QRt4q1waPV(uv9g2v9k$3ZR(L8$ho#0)X?9GP5pJWvAn>R)gNdlcm0WR$v+`X{+3nEUqQHQe=)|iWaUbq zG+ij%5kR7i`H{B~_2*1P7UU;CdbENge_5*0hZ(JX>?u;HI@&OJeZO##eh{KFSNH5W z@9r8Y3xRPOeTSO{?=GV2hfy(hNAsdpN zvwy}0aWp+d=XLKyyYkTQSA+J#aJHrN^FdUxNl1tC{&80%w9EJ*epoWNX6_XkQTE9n ze-ZKFfQo3P%*v7HUn-T^J^f%%5emj!PQ%^K_7X}8Mc8iGKUu_E8uku`OI@u2uerUQ ziaozicp>Mr%TNzj4W%ug@u>&RkgMw)dQ9M9j@5@!Ek+;p5XS>Igx@BUvj|209w%`mNTzSp zt{)rQ5h$l^t(zLgNO6K%ap7Hg4}fxFv5sft$YdAaiS`eNhXr||d=kdHZr+km!QZS= zy78O$$7w4%Puq~Oa~gyQsjel3O3}3dU?9O^%&DQ}pglD9<3Fb`2rybC>V zF-WT?JXj}7A|>ghSmMP(Sg?{S)lPy+edOt8Dn2LkWHZ)!Fom(t-PKnW7l964-lb-w z`2|8JwPF&`A`UE~NP&fKM5z>ou-Y^R8P{F86Mi22A!jWiaNlpvb{(YqG)cbhe5MRq zI+J^2z+l}SMvl<-KVbFP!?&UCQmKCFS%uYx-6Pk|mzATSsCI^}}A!Mj-~7E?8%MtCUvgttv%W%C7|I_4dzE*rm(# zAlbRRSL}Euxz$0ZJK-cs8;cum++BEu>7U86J}4~^VMGvrDeEKL&rOGTBGK&Y98G%A zfw^&wegl86L<@LwU}$j@4x#!xWA2jWh31D3`#M#wm3+(>=)Et}8{o$O9nc^yw(RF5 zPCQB+{gH`HY8k8uIF-fkmKfPPQF}_EC*IcT=o|MON4DPQpCN8B%F{rYeOJbEeApBN zVIEFwk2%rAN#&{sP(e#_tlAE-N#60f@LZXMEBNbqcJ)X4x(JECL&qt1O{*?xf>h@r~?96lIQKMcQ zcNdVV1^{h5C6!Du+d+sB2DhPbH85~JJ1(kduK(AL+d6?c5y#!dYxQbr@V-L>m5f>2D;>tylmsf{(1X71T zbA5eU01-7Uwr{(@P7vZb7x`gf{NM+$_e&3Wu1bGJ)w+)A_173j;sB-No;Ew=o$6>E zR^KK_dop57)_c`uIIp%FDCKq|i=8-689{=u1!ns-e4x3qvr5P}h!SGNRsP|IFIEZ6t$r*&~xX#YhMD=$1W8 zqB|X*aEgR3K4BJLW>S)gTF=JeZ094VQjqlmhD|-!@&eL*8^YF}w)LEr%`h|4tKAi0 zc)Eb`6l-$$FfBy~W!A>>Vib@1QsbbN`8^N05#f_2I?*0q`Y~R7F=)Xrk31 z@mx^3+~+}^lq;qKH{vbT{Pt!Imh3VU0)YBzb0YNxDG?lXPsVz8d$@@#?k($qQN>HQ zfRK43Yen^p{+A(fX?Jz+=8J9(bkJ&P?RGK{q)q*(qU!W$Wdadl}DUL!H@A(93(MH=eTK-GGAwCy~?VOhAVP^GS#IIOl>xm=<1M(g1F zW(6UkdZ~Oy#cLPBCoBU?t8&xO?xDR{LIWEYw{5EYuEMl!s~rYmmdwN>hzS3Qs(rJx z5q;}}E})XU{nllMHA2DSJy7Lbd&R}08**D+!)^R-20FfXy$;_tGigFH@C?w4NH<9h zs~-qfNSmN13RPDXOaQNC(N5<08vXfepAF}&c*Z=|IESKV`O>;fOB~e8GJ!Sh37$9G ztu6TB;`|C!9GU;R7gdLJ+aTv=*-~*^A09ih+qV+?`>uWVxPBP4|&}0f@QJd zd56>`fQQgB?R48tElqKlUL)}{7$m{G&D?%~>ho&51GKyXAcjjUWnNq*>5HQa5gEld zlZWymfnduQWulYNt13tgzm)>n(kdSZUjPgO68ijRFQDV%-iS>C^BKUYtB3pU2q-p7 zR|89ubH6%i{>aqyuNeRCPxwCmzw(jguN>eI{8v_SVqz*-qnr~+$_!Ju>E+B{sCin< z43wX3bFwcNx$p#%m8p8lY3k6Be%xQOlExsR1w;j`pI0F+{|jcz@QXxydx%tf>5;@0 zty!no6QxyUIj;Ah|JK( z6%&+j4J#aTzHTeG`dIuuAxlZBm*#vSQu(KDNZ06me?PLU@lnXS;fj+3 z|6_SSsKK;$ZZiiIN8J5_V_pgB_%QF<%-l7-^*PSqr%2WNyPkX39N$gR#4VQ;5a;WC z&xP2GJq<1@XuMSi@JO{M0ov-Xy=K4V?|--2_aD_3edBrGR+#udG*sXCI{)my-tL`| F{{k_0^A-RA literal 0 HcmV?d00001 From 37a329afca4299d075cffdf1f73e215b32982164 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Tue, 22 Dec 2020 23:44:22 +0800 Subject: [PATCH 0125/1181] =?UTF-8?q?Update=20=E8=AF=A6=E7=BB=86=E7=9A=84?= =?UTF-8?q?=E8=AF=B4=E6=98=8E=E6=96=87=E6=A1=A3.md?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...2\204\350\257\264\346\230\216\346\226\207\346\241\243.md" | 5 +++++ 1 file changed, 5 insertions(+) diff --git "a/\350\257\246\347\273\206\347\232\204\350\257\264\346\230\216\346\226\207\346\241\243.md" "b/\350\257\246\347\273\206\347\232\204\350\257\264\346\230\216\346\226\207\346\241\243.md" index e2af8cba3..4e1761275 100644 --- "a/\350\257\246\347\273\206\347\232\204\350\257\264\346\230\216\346\226\207\346\241\243.md" +++ "b/\350\257\246\347\273\206\347\232\204\350\257\264\346\230\216\346\226\207\346\241\243.md" @@ -22,6 +22,11 @@ ![1545468361962](assets/1545468361962.png) +如果使用 apijson-framework,还可进一步简化流程 + +![1543975563776](assets/1543975563776.png) + + 换句话说,使用这个项目作为后端的支持的话,是不需要对每个表写增删改查等接口的,只需在该项目连接的数据里进行表的创建,以及配置接口权限即可。无需进行过多的开发,哪怕是要改结构也仅仅只需要修改表字段而已。想想仅仅是部署一个后端项目,现在需要些的接口就基本写好了,直接调用就行了,是不是挺爽的。 说这么多,咱们直接开干吧! From c46766c2bbce9b4ebc70d8393b9fd15fda5fe72a Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Tue, 22 Dec 2020 23:44:53 +0800 Subject: [PATCH 0126/1181] =?UTF-8?q?Update=20=E8=AF=A6=E7=BB=86=E7=9A=84?= =?UTF-8?q?=E8=AF=B4=E6=98=8E=E6=96=87=E6=A1=A3.md?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...\232\204\350\257\264\346\230\216\346\226\207\346\241\243.md" | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git "a/\350\257\246\347\273\206\347\232\204\350\257\264\346\230\216\346\226\207\346\241\243.md" "b/\350\257\246\347\273\206\347\232\204\350\257\264\346\230\216\346\226\207\346\241\243.md" index 4e1761275..8a4d7f7c6 100644 --- "a/\350\257\246\347\273\206\347\232\204\350\257\264\346\230\216\346\226\207\346\241\243.md" +++ "b/\350\257\246\347\273\206\347\232\204\350\257\264\346\230\216\346\226\207\346\241\243.md" @@ -22,7 +22,7 @@ ![1545468361962](assets/1545468361962.png) -如果使用 apijson-framework,还可进一步简化流程 +如果使用 [apijson-framework](https://github.com/APIJSON/apijson-framework),还可进一步简化流程 ![1543975563776](assets/1543975563776.png) From 6022ec3eaee431f26fe7b372d44d75fc48d089b2 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Wed, 23 Dec 2020 15:23:44 +0800 Subject: [PATCH 0127/1181] Update README.md --- README.md | 4 ---- 1 file changed, 4 deletions(-) diff --git a/README.md b/README.md index 347b3108c..bde5a0061 100644 --- a/README.md +++ b/README.md @@ -314,10 +314,6 @@ QQ 技术群: 734652054(新)、607020115(旧) [uliweb-apijson](https://github.com/zhangchunlin/uliweb-apijson) Python 版 APIJSON,支持 MySQL, PostgreSQL, SQL Server, Oracle, SQLite 等 -[APIJSON](https://github.com/crazytaxi824/APIJSON) Go 版 APIJSON,功能开发中...([不可用且长期未更新,期待热心开发者帮助完善或新增项目](https://github.com/Tencent/APIJSON/issues/111)) - -[APIJSONKOTLIN](https://github.com/luckyxiaomo/APIJSONKOTLIN) Kotlin 版 APIJSON,基础框架搭建中...(不可用且长期未更新,期待热心开发者帮助完善或新增项目) - [APIJSONParser](https://github.com/Zerounary/APIJSONParser) 第三方 APIJSON 解析器,将 JSON 动态解析成 SQL [ApiJsonByJFinal](https://gitee.com/zhiyuexin/ApiJsonByJFinal) 整合 APIJSON 和 JFinal 的 Demo From 2bc5682642a8e9597af8cb181fb1632b8023f719 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Wed, 23 Dec 2020 20:03:10 +0800 Subject: [PATCH 0128/1181] Update README.md --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index bde5a0061..4da4a8484 100644 --- a/README.md +++ b/README.md @@ -41,6 +41,8 @@ This source code is licensed under the Apache License Version 2.0
--- +* ### [项目简介](#--apijson) +* ### [使用文档](#%E5%BF%AB%E9%80%9F%E4%B8%8A%E6%89%8B) APIJSON 是一种专为 API 而生的 JSON 网络传输协议 以及 基于这套协议实现的 ORM 库。
为 简单的增删改查、复杂的查询、简单的事务操作 提供了完全自动化的万能 API。
From 206e334f4ba103f27e957c138199921e73a9dddd Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Wed, 23 Dec 2020 20:06:50 +0800 Subject: [PATCH 0129/1181] Update README.md --- README.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 4da4a8484..7f8e36f45 100644 --- a/README.md +++ b/README.md @@ -41,8 +41,10 @@ This source code is licensed under the Apache License Version 2.0
--- -* ### [项目简介](#--apijson) -* ### [使用文档](#%E5%BF%AB%E9%80%9F%E4%B8%8A%E6%89%8B) +* #### [项目简介](#--apijson) +* #### [上手使用](#%E5%BF%AB%E9%80%9F%E4%B8%8A%E6%89%8B) +* #### [周边生态](#%E6%8A%80%E6%9C%AF%E4%BA%A4%E6%B5%81) + APIJSON 是一种专为 API 而生的 JSON 网络传输协议 以及 基于这套协议实现的 ORM 库。
为 简单的增删改查、复杂的查询、简单的事务操作 提供了完全自动化的万能 API。
From 4e20c6000e2d4426d93114215019ac177805d99d Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Wed, 23 Dec 2020 20:07:23 +0800 Subject: [PATCH 0130/1181] Update README.md --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 7f8e36f45..42e7b1c73 100644 --- a/README.md +++ b/README.md @@ -41,9 +41,9 @@ This source code is licensed under the Apache License Version 2.0
--- -* #### [项目简介](#--apijson) -* #### [上手使用](#%E5%BF%AB%E9%80%9F%E4%B8%8A%E6%89%8B) -* #### [周边生态](#%E6%8A%80%E6%9C%AF%E4%BA%A4%E6%B5%81) +* [项目简介](#--apijson) +* [上手使用](#%E5%BF%AB%E9%80%9F%E4%B8%8A%E6%89%8B) +* [周边生态](#%E6%8A%80%E6%9C%AF%E4%BA%A4%E6%B5%81) APIJSON 是一种专为 API 而生的 JSON 网络传输协议 以及 基于这套协议实现的 ORM 库。
From 5b2ffcfd8699770c7361dc6079e0a5a12a766b48 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Wed, 23 Dec 2020 20:08:37 +0800 Subject: [PATCH 0131/1181] Update README.md --- README.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 42e7b1c73..b5679910e 100644 --- a/README.md +++ b/README.md @@ -41,9 +41,10 @@ This source code is licensed under the Apache License Version 2.0
--- -* [项目简介](#--apijson) -* [上手使用](#%E5%BF%AB%E9%80%9F%E4%B8%8A%E6%89%8B) -* [周边生态](#%E6%8A%80%E6%9C%AF%E4%BA%A4%E6%B5%81) +#### 导航目录 +[项目简介](#--apijson) +[上手使用](#%E5%BF%AB%E9%80%9F%E4%B8%8A%E6%89%8B) +[周边生态](#%E6%8A%80%E6%9C%AF%E4%BA%A4%E6%B5%81) APIJSON 是一种专为 API 而生的 JSON 网络传输协议 以及 基于这套协议实现的 ORM 库。
From 35c50eae63b11e3d60b6a5b7e37645ec9b04af81 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Wed, 23 Dec 2020 20:10:51 +0800 Subject: [PATCH 0132/1181] Update README.md --- README.md | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/README.md b/README.md index b5679910e..1948fff93 100644 --- a/README.md +++ b/README.md @@ -41,11 +41,7 @@ This source code is licensed under the Apache License Version 2.0
--- -#### 导航目录 -[项目简介](#--apijson) -[上手使用](#%E5%BF%AB%E9%80%9F%E4%B8%8A%E6%89%8B) -[周边生态](#%E6%8A%80%E6%9C%AF%E4%BA%A4%E6%B5%81) - +导航目录: [项目简介](#--apijson) [上手使用](#%E5%BF%AB%E9%80%9F%E4%B8%8A%E6%89%8B) [周边生态](#%E6%8A%80%E6%9C%AF%E4%BA%A4%E6%B5%81) APIJSON 是一种专为 API 而生的 JSON 网络传输协议 以及 基于这套协议实现的 ORM 库。
为 简单的增删改查、复杂的查询、简单的事务操作 提供了完全自动化的万能 API。
From 70f643f8c0420d8eec8e25b0c342bc2d2755069c Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Wed, 23 Dec 2020 20:11:23 +0800 Subject: [PATCH 0133/1181] Update README.md --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 1948fff93..ff65aafa8 100644 --- a/README.md +++ b/README.md @@ -43,6 +43,7 @@ This source code is licensed under the Apache License Version 2.0
导航目录: [项目简介](#--apijson) [上手使用](#%E5%BF%AB%E9%80%9F%E4%B8%8A%E6%89%8B) [周边生态](#%E6%8A%80%E6%9C%AF%E4%BA%A4%E6%B5%81) + APIJSON 是一种专为 API 而生的 JSON 网络传输协议 以及 基于这套协议实现的 ORM 库。
为 简单的增删改查、复杂的查询、简单的事务操作 提供了完全自动化的万能 API。
能大幅降低开发和沟通成本,简化开发流程,缩短开发周期。
From dc4211564a5b26681cdc236ea08fb083c3b67e4c Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Wed, 23 Dec 2020 20:11:55 +0800 Subject: [PATCH 0134/1181] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index ff65aafa8..59f21f4cb 100644 --- a/README.md +++ b/README.md @@ -41,7 +41,7 @@ This source code is licensed under the Apache License Version 2.0
--- -导航目录: [项目简介](#--apijson) [上手使用](#%E5%BF%AB%E9%80%9F%E4%B8%8A%E6%89%8B) [周边生态](#%E6%8A%80%E6%9C%AF%E4%BA%A4%E6%B5%81) +导航目录: [项目简介](#--apijson) [上手使用](#%E5%BF%AB%E9%80%9F%E4%B8%8A%E6%89%8B) [周边生态](#%E6%8A%80%E6%9C%AF%E4%BA%A4%E6%B5%81)
APIJSON 是一种专为 API 而生的 JSON 网络传输协议 以及 基于这套协议实现的 ORM 库。
From fbddafbb0adc58d6d7f262b8a1757cebf1ecb92a Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Wed, 23 Dec 2020 20:12:15 +0800 Subject: [PATCH 0135/1181] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 59f21f4cb..06f74cd4f 100644 --- a/README.md +++ b/README.md @@ -41,7 +41,7 @@ This source code is licensed under the Apache License Version 2.0
--- -导航目录: [项目简介](#--apijson) [上手使用](#%E5%BF%AB%E9%80%9F%E4%B8%8A%E6%89%8B) [周边生态](#%E6%8A%80%E6%9C%AF%E4%BA%A4%E6%B5%81)
+导航目录: [项目简介](#--apijson) [上手使用](#%E5%BF%AB%E9%80%9F%E4%B8%8A%E6%89%8B) [周边生态](#%E6%8A%80%E6%9C%AF%E4%BA%A4%E6%B5%81)
APIJSON 是一种专为 API 而生的 JSON 网络传输协议 以及 基于这套协议实现的 ORM 库。
From fddb96e0d1332a230b23070084ee4c695e672ceb Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Wed, 23 Dec 2020 20:13:26 +0800 Subject: [PATCH 0136/1181] Update README.md --- README.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 06f74cd4f..22118eaab 100644 --- a/README.md +++ b/README.md @@ -41,7 +41,7 @@ This source code is licensed under the Apache License Version 2.0
--- -导航目录: [项目简介](#--apijson) [上手使用](#%E5%BF%AB%E9%80%9F%E4%B8%8A%E6%89%8B) [周边生态](#%E6%8A%80%E6%9C%AF%E4%BA%A4%E6%B5%81)
+导航目录: [项目简介](#--apijson) [上手使用](#%E5%BF%AB%E9%80%9F%E4%B8%8A%E6%89%8B) [社区生态](#%E6%8A%80%E6%9C%AF%E4%BA%A4%E6%B5%81)
APIJSON 是一种专为 API 而生的 JSON 网络传输协议 以及 基于这套协议实现的 ORM 库。
@@ -170,7 +170,9 @@ https://github.com/Tencent/APIJSON/wiki#%E5%AE%9E%E7%8E%B0%E5%8E%9F%E7%90%86
其它问题见 Closed Issues
https://github.com/Tencent/APIJSON/issues?q=is%3Aissue+is%3Aclosed - +
+
+
### 快速上手 From c0ed8c61f85be7e28c9370d2c5f85b6a6ebd9e15 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Wed, 23 Dec 2020 20:14:32 +0800 Subject: [PATCH 0137/1181] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 22118eaab..e1919aa2a 100644 --- a/README.md +++ b/README.md @@ -169,7 +169,7 @@ https://github.com/Tencent/APIJSON/wiki#%E5%AE%9E%E7%8E%B0%E5%8E%9F%E7%90%86
其它问题见 Closed Issues
-https://github.com/Tencent/APIJSON/issues?q=is%3Aissue+is%3Aclosed +https://github.com/Tencent/APIJSON/issues?q=is%3Aissue+is%3Aclosed



From 3267195adc657b65685c80dca9fb92c47f3fe20e Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Wed, 23 Dec 2020 20:20:26 +0800 Subject: [PATCH 0138/1181] Create Navigation.md --- Navigation.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 Navigation.md diff --git a/Navigation.md b/Navigation.md new file mode 100644 index 000000000..0f1d8d0e6 --- /dev/null +++ b/Navigation.md @@ -0,0 +1,12 @@ +APIJSON 导航目录 +* ### [项目简介](/README.md#--apijson) +* ### [特点功能](https://github.com/Tencent/APIJSON/blob/master/README.md#%E7%89%B9%E7%82%B9%E5%8A%9F%E8%83%BD) +* [2.1 开发流程](#2.1) +* [2.2 前端请求](#2.2) +* [2.3 后端操作](#2.3) +* [2.4 前端解析](#2.4) +* [2.5 对应不同需求的请求](#2.5) +* [2.6 对应不同请求的结果](#2.6) +* ### [3.设计规范](#3) +* [3.1 操作方法](#3.1) +* [3.2 功能符](#3.2) From 2a940526f9de8fc1fea1412763e78386d52ee4c3 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Wed, 23 Dec 2020 20:21:48 +0800 Subject: [PATCH 0139/1181] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index e1919aa2a..b5f07e741 100644 --- a/README.md +++ b/README.md @@ -41,7 +41,7 @@ This source code is licensed under the Apache License Version 2.0
--- -导航目录: [项目简介](#--apijson) [上手使用](#%E5%BF%AB%E9%80%9F%E4%B8%8A%E6%89%8B) [社区生态](#%E6%8A%80%E6%9C%AF%E4%BA%A4%E6%B5%81)
+导航目录: [项目简介](#--apijson) [上手使用](#%E5%BF%AB%E9%80%9F%E4%B8%8A%E6%89%8B) [社区生态](#%E6%8A%80%E6%9C%AF%E4%BA%A4%E6%B5%81) 详细导航目录 [点这里](/Navigation.md)
APIJSON 是一种专为 API 而生的 JSON 网络传输协议 以及 基于这套协议实现的 ORM 库。
From cf51cb0ec9c93b0a9fa6b432303ca6590ca7fe36 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Wed, 23 Dec 2020 20:23:21 +0800 Subject: [PATCH 0140/1181] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index b5f07e741..d9e8a4016 100644 --- a/README.md +++ b/README.md @@ -41,7 +41,7 @@ This source code is licensed under the Apache License Version 2.0
--- -导航目录: [项目简介](#--apijson) [上手使用](#%E5%BF%AB%E9%80%9F%E4%B8%8A%E6%89%8B) [社区生态](#%E6%8A%80%E6%9C%AF%E4%BA%A4%E6%B5%81) 详细导航目录 [点这里](/Navigation.md)
+导航目录: [项目简介](#--apijson) [上手使用](#%E5%BF%AB%E9%80%9F%E4%B8%8A%E6%89%8B) [社区生态](#%E6%8A%80%E6%9C%AF%E4%BA%A4%E6%B5%81) 完整详细的导航目录 [点这里查看](/Navigation.md)
APIJSON 是一种专为 API 而生的 JSON 网络传输协议 以及 基于这套协议实现的 ORM 库。
From 55e98d437deac571eeaaacf484b2990f01669f39 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Wed, 23 Dec 2020 20:24:23 +0800 Subject: [PATCH 0141/1181] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index d9e8a4016..d7dd70211 100644 --- a/README.md +++ b/README.md @@ -41,7 +41,7 @@ This source code is licensed under the Apache License Version 2.0
--- -导航目录: [项目简介](#--apijson) [上手使用](#%E5%BF%AB%E9%80%9F%E4%B8%8A%E6%89%8B) [社区生态](#%E6%8A%80%E6%9C%AF%E4%BA%A4%E6%B5%81) 完整详细的导航目录 [点这里查看](/Navigation.md)
+导航目录: [项目简介](#--apijson) [上手使用](#%E5%BF%AB%E9%80%9F%E4%B8%8A%E6%89%8B) [社区生态](#%E6%8A%80%E6%9C%AF%E4%BA%A4%E6%B5%81)      完整详细的导航目录 [点这里查看](/Navigation.md)
APIJSON 是一种专为 API 而生的 JSON 网络传输协议 以及 基于这套协议实现的 ORM 库。
From 9b8b97e3e25358cca8fe5162b4a0b431540d3130 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Wed, 23 Dec 2020 20:35:38 +0800 Subject: [PATCH 0142/1181] Update Navigation.md --- Navigation.md | 57 +++++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 46 insertions(+), 11 deletions(-) diff --git a/Navigation.md b/Navigation.md index 0f1d8d0e6..efa5577d9 100644 --- a/Navigation.md +++ b/Navigation.md @@ -1,12 +1,47 @@ APIJSON 导航目录 -* ### [项目简介](/README.md#--apijson) -* ### [特点功能](https://github.com/Tencent/APIJSON/blob/master/README.md#%E7%89%B9%E7%82%B9%E5%8A%9F%E8%83%BD) -* [2.1 开发流程](#2.1) -* [2.2 前端请求](#2.2) -* [2.3 后端操作](#2.3) -* [2.4 前端解析](#2.4) -* [2.5 对应不同需求的请求](#2.5) -* [2.6 对应不同请求的结果](#2.6) -* ### [3.设计规范](#3) -* [3.1 操作方法](#3.1) -* [3.2 功能符](#3.2) +### [项目简介](/README.md#--apijson) + +### [特点功能](/README.md#%E7%89%B9%E7%82%B9%E5%8A%9F%E8%83%BD) +#### [对于前端](/README.md#%E7%89%B9%E7%82%B9%E5%8A%9F%E8%83%BD) +#### [对于后端](https://github.com/Tencent/APIJSON#%E5%AF%B9%E4%BA%8E%E5%90%8E%E7%AB%AF) + +### [APIJSON 接口展示](https://github.com/Tencent/APIJSON#apijson-%E6%8E%A5%E5%8F%A3%E5%B1%95%E7%A4%BA) +#### [Postman 展示 APIJSON](https://github.com/Tencent/APIJSON#postman-%E5%B1%95%E7%A4%BA-apijson) +#### [APIAuto 展示 APIJSON](https://github.com/Tencent/APIJSON#apiauto-%E5%B1%95%E7%A4%BA-apijson) + +### [APIJSON App 演示](https://github.com/Tencent/APIJSON#apijson-app-%E6%BC%94%E7%A4%BA) + +### [为什么选择 APIJSON?](/README.md#%E7%89%B9%E7%82%B9%E5%8A%9F%E8%83%BD) + +### [常见问题](https://github.com/Tencent/APIJSON#%E4%B8%BA%E4%BB%80%E4%B9%88%E9%80%89%E6%8B%A9-apijson) +#### [1.如何定制业务逻辑?](https://github.com/Tencent/APIJSON#1%E5%A6%82%E4%BD%95%E5%AE%9A%E5%88%B6%E4%B8%9A%E5%8A%A1%E9%80%BB%E8%BE%91) +#### [2.如何控制权限?](https://github.com/Tencent/APIJSON#2%E5%A6%82%E4%BD%95%E6%8E%A7%E5%88%B6%E6%9D%83%E9%99%90) +#### [3.如何校验参数?](https://github.com/Tencent/APIJSON#3%E5%A6%82%E4%BD%95%E6%A0%A1%E9%AA%8C%E5%8F%82%E6%95%B0) + + +### [快速上手](https://github.com/Tencent/APIJSON#%E5%BF%AB%E9%80%9F%E4%B8%8A%E6%89%8B) +#### [1.后端上手](https://github.com/Tencent/APIJSON#1%E5%90%8E%E7%AB%AF%E4%B8%8A%E6%89%8B) +#### [2.前端上手](https://github.com/Tencent/APIJSON#2%E5%89%8D%E7%AB%AF%E4%B8%8A%E6%89%8B) + +### [下载客户端 App](https://github.com/Tencent/APIJSON#%E4%B8%8B%E8%BD%BD%E5%AE%A2%E6%88%B7%E7%AB%AF-app) + +### [使用登记](https://github.com/Tencent/APIJSON#%E4%BD%BF%E7%94%A8%E7%99%BB%E8%AE%B0) + +### [贡献者们](https://github.com/Tencent/APIJSON#%E8%B4%A1%E7%8C%AE%E8%80%85%E4%BB%AC) + +### [规划及路线图](https://github.com/Tencent/APIJSON#%E8%A7%84%E5%88%92%E5%8F%8A%E8%B7%AF%E7%BA%BF%E5%9B%BE) + +### [我要赞赏](https://github.com/Tencent/APIJSON#%E6%88%91%E8%A6%81%E8%B5%9E%E8%B5%8F) + + +### [技术交流](https://github.com/Tencent/APIJSON#%E6%8A%80%E6%9C%AF%E4%BA%A4%E6%B5%81) + +### [相关推荐](https://github.com/Tencent/APIJSON#%E7%9B%B8%E5%85%B3%E6%8E%A8%E8%8D%90) + +### [生态项目](https://github.com/Tencent/APIJSON#%E7%94%9F%E6%80%81%E9%A1%B9%E7%9B%AED) + +### [持续更新](https://github.com/Tencent/APIJSON#%E6%8C%81%E7%BB%AD%E6%9B%B4%E6%96%B0) + +### [工蜂主页](https://github.com/Tencent/APIJSON#%E5%B7%A5%E8%9C%82%E4%B8%BB%E9%A1%B5) + +### [码云主页](https://github.com/Tencent/APIJSON#%E7%A0%81%E4%BA%91%E4%B8%BB%E9%A1%B5) From 4779781df1f334f9113d16c5a22f80c939a250da Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Wed, 23 Dec 2020 20:36:03 +0800 Subject: [PATCH 0143/1181] Update Navigation.md --- Navigation.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Navigation.md b/Navigation.md index efa5577d9..a582e60f1 100644 --- a/Navigation.md +++ b/Navigation.md @@ -1,4 +1,4 @@ -APIJSON 导航目录 +# APIJSON 导航目录 ### [项目简介](/README.md#--apijson) ### [特点功能](/README.md#%E7%89%B9%E7%82%B9%E5%8A%9F%E8%83%BD) From 7b6cbbd28fcf68153636f85c086387def2103a4d Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Wed, 23 Dec 2020 20:36:41 +0800 Subject: [PATCH 0144/1181] Update Navigation.md --- Navigation.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Navigation.md b/Navigation.md index a582e60f1..a12874535 100644 --- a/Navigation.md +++ b/Navigation.md @@ -1,5 +1,5 @@ # APIJSON 导航目录 -### [项目简介](/README.md#--apijson) +## [项目简介](/README.md#--apijson) ### [特点功能](/README.md#%E7%89%B9%E7%82%B9%E5%8A%9F%E8%83%BD) #### [对于前端](/README.md#%E7%89%B9%E7%82%B9%E5%8A%9F%E8%83%BD) @@ -18,6 +18,7 @@ #### [2.如何控制权限?](https://github.com/Tencent/APIJSON#2%E5%A6%82%E4%BD%95%E6%8E%A7%E5%88%B6%E6%9D%83%E9%99%90) #### [3.如何校验参数?](https://github.com/Tencent/APIJSON#3%E5%A6%82%E4%BD%95%E6%A0%A1%E9%AA%8C%E5%8F%82%E6%95%B0) +



### [快速上手](https://github.com/Tencent/APIJSON#%E5%BF%AB%E9%80%9F%E4%B8%8A%E6%89%8B) #### [1.后端上手](https://github.com/Tencent/APIJSON#1%E5%90%8E%E7%AB%AF%E4%B8%8A%E6%89%8B) @@ -33,6 +34,7 @@ ### [我要赞赏](https://github.com/Tencent/APIJSON#%E6%88%91%E8%A6%81%E8%B5%9E%E8%B5%8F) +



### [技术交流](https://github.com/Tencent/APIJSON#%E6%8A%80%E6%9C%AF%E4%BA%A4%E6%B5%81) From 63c7ef2acf25ea8094a8c72b797c26cbc55346ca Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Wed, 23 Dec 2020 20:37:07 +0800 Subject: [PATCH 0145/1181] Update Navigation.md --- Navigation.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Navigation.md b/Navigation.md index a12874535..a5c0d5403 100644 --- a/Navigation.md +++ b/Navigation.md @@ -18,7 +18,7 @@ #### [2.如何控制权限?](https://github.com/Tencent/APIJSON#2%E5%A6%82%E4%BD%95%E6%8E%A7%E5%88%B6%E6%9D%83%E9%99%90) #### [3.如何校验参数?](https://github.com/Tencent/APIJSON#3%E5%A6%82%E4%BD%95%E6%A0%A1%E9%AA%8C%E5%8F%82%E6%95%B0) -



+

### [快速上手](https://github.com/Tencent/APIJSON#%E5%BF%AB%E9%80%9F%E4%B8%8A%E6%89%8B) #### [1.后端上手](https://github.com/Tencent/APIJSON#1%E5%90%8E%E7%AB%AF%E4%B8%8A%E6%89%8B) @@ -34,7 +34,7 @@ ### [我要赞赏](https://github.com/Tencent/APIJSON#%E6%88%91%E8%A6%81%E8%B5%9E%E8%B5%8F) -



+

### [技术交流](https://github.com/Tencent/APIJSON#%E6%8A%80%E6%9C%AF%E4%BA%A4%E6%B5%81) From 1ebc06540edbfca67093677114cde7092f835147 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Wed, 23 Dec 2020 20:39:19 +0800 Subject: [PATCH 0146/1181] Update Navigation.md --- Navigation.md | 46 +++++++++++++++++++++++----------------------- 1 file changed, 23 insertions(+), 23 deletions(-) diff --git a/Navigation.md b/Navigation.md index a5c0d5403..5f4bae329 100644 --- a/Navigation.md +++ b/Navigation.md @@ -3,47 +3,47 @@ ### [特点功能](/README.md#%E7%89%B9%E7%82%B9%E5%8A%9F%E8%83%BD) #### [对于前端](/README.md#%E7%89%B9%E7%82%B9%E5%8A%9F%E8%83%BD) -#### [对于后端](https://github.com/Tencent/APIJSON#%E5%AF%B9%E4%BA%8E%E5%90%8E%E7%AB%AF) +#### [对于后端]/README.md#%E5%AF%B9%E4%BA%8E%E5%90%8E%E7%AB%AF) -### [APIJSON 接口展示](https://github.com/Tencent/APIJSON#apijson-%E6%8E%A5%E5%8F%A3%E5%B1%95%E7%A4%BA) -#### [Postman 展示 APIJSON](https://github.com/Tencent/APIJSON#postman-%E5%B1%95%E7%A4%BA-apijson) -#### [APIAuto 展示 APIJSON](https://github.com/Tencent/APIJSON#apiauto-%E5%B1%95%E7%A4%BA-apijson) +### [APIJSON 接口展示](/README.md#apijson-%E6%8E%A5%E5%8F%A3%E5%B1%95%E7%A4%BA) +#### [Postman 展示 APIJSON](/README.md#postman-%E5%B1%95%E7%A4%BA-apijson) +#### [APIAuto 展示 APIJSON](/README.md#apiauto-%E5%B1%95%E7%A4%BA-apijson) -### [APIJSON App 演示](https://github.com/Tencent/APIJSON#apijson-app-%E6%BC%94%E7%A4%BA) +### [APIJSON App 演示](/README.md#apijson-app-%E6%BC%94%E7%A4%BA) ### [为什么选择 APIJSON?](/README.md#%E7%89%B9%E7%82%B9%E5%8A%9F%E8%83%BD) -### [常见问题](https://github.com/Tencent/APIJSON#%E4%B8%BA%E4%BB%80%E4%B9%88%E9%80%89%E6%8B%A9-apijson) -#### [1.如何定制业务逻辑?](https://github.com/Tencent/APIJSON#1%E5%A6%82%E4%BD%95%E5%AE%9A%E5%88%B6%E4%B8%9A%E5%8A%A1%E9%80%BB%E8%BE%91) -#### [2.如何控制权限?](https://github.com/Tencent/APIJSON#2%E5%A6%82%E4%BD%95%E6%8E%A7%E5%88%B6%E6%9D%83%E9%99%90) -#### [3.如何校验参数?](https://github.com/Tencent/APIJSON#3%E5%A6%82%E4%BD%95%E6%A0%A1%E9%AA%8C%E5%8F%82%E6%95%B0) +### [常见问题](/README.md#%E4%B8%BA%E4%BB%80%E4%B9%88%E9%80%89%E6%8B%A9-apijson) +#### [1.如何定制业务逻辑?](/README.md#1%E5%A6%82%E4%BD%95%E5%AE%9A%E5%88%B6%E4%B8%9A%E5%8A%A1%E9%80%BB%E8%BE%91) +#### [2.如何控制权限?](/README.md#2%E5%A6%82%E4%BD%95%E6%8E%A7%E5%88%B6%E6%9D%83%E9%99%90) +#### [3.如何校验参数?](/README.md#3%E5%A6%82%E4%BD%95%E6%A0%A1%E9%AA%8C%E5%8F%82%E6%95%B0)

-### [快速上手](https://github.com/Tencent/APIJSON#%E5%BF%AB%E9%80%9F%E4%B8%8A%E6%89%8B) -#### [1.后端上手](https://github.com/Tencent/APIJSON#1%E5%90%8E%E7%AB%AF%E4%B8%8A%E6%89%8B) -#### [2.前端上手](https://github.com/Tencent/APIJSON#2%E5%89%8D%E7%AB%AF%E4%B8%8A%E6%89%8B) +### [快速上手](/README.md#%E5%BF%AB%E9%80%9F%E4%B8%8A%E6%89%8B) +#### [1.后端上手](/README.md#1%E5%90%8E%E7%AB%AF%E4%B8%8A%E6%89%8B) +#### [2.前端上手](/README.md#2%E5%89%8D%E7%AB%AF%E4%B8%8A%E6%89%8B) -### [下载客户端 App](https://github.com/Tencent/APIJSON#%E4%B8%8B%E8%BD%BD%E5%AE%A2%E6%88%B7%E7%AB%AF-app) +### [下载客户端 App](/README.md#%E4%B8%8B%E8%BD%BD%E5%AE%A2%E6%88%B7%E7%AB%AF-app) -### [使用登记](https://github.com/Tencent/APIJSON#%E4%BD%BF%E7%94%A8%E7%99%BB%E8%AE%B0) +### [使用登记](/README.md#%E4%BD%BF%E7%94%A8%E7%99%BB%E8%AE%B0) -### [贡献者们](https://github.com/Tencent/APIJSON#%E8%B4%A1%E7%8C%AE%E8%80%85%E4%BB%AC) +### [贡献者们](/README.md#%E8%B4%A1%E7%8C%AE%E8%80%85%E4%BB%AC) -### [规划及路线图](https://github.com/Tencent/APIJSON#%E8%A7%84%E5%88%92%E5%8F%8A%E8%B7%AF%E7%BA%BF%E5%9B%BE) +### [规划及路线图](/README.md#%E8%A7%84%E5%88%92%E5%8F%8A%E8%B7%AF%E7%BA%BF%E5%9B%BE) -### [我要赞赏](https://github.com/Tencent/APIJSON#%E6%88%91%E8%A6%81%E8%B5%9E%E8%B5%8F) +### [我要赞赏](/README.md#%E6%88%91%E8%A6%81%E8%B5%9E%E8%B5%8F)

-### [技术交流](https://github.com/Tencent/APIJSON#%E6%8A%80%E6%9C%AF%E4%BA%A4%E6%B5%81) +### [技术交流](/README.md#%E6%8A%80%E6%9C%AF%E4%BA%A4%E6%B5%81) -### [相关推荐](https://github.com/Tencent/APIJSON#%E7%9B%B8%E5%85%B3%E6%8E%A8%E8%8D%90) +### [相关推荐](/README.md#%E7%9B%B8%E5%85%B3%E6%8E%A8%E8%8D%90) -### [生态项目](https://github.com/Tencent/APIJSON#%E7%94%9F%E6%80%81%E9%A1%B9%E7%9B%AED) +### [生态项目](/README.md#%E7%94%9F%E6%80%81%E9%A1%B9%E7%9B%AED) -### [持续更新](https://github.com/Tencent/APIJSON#%E6%8C%81%E7%BB%AD%E6%9B%B4%E6%96%B0) +### [持续更新](/README.md#%E6%8C%81%E7%BB%AD%E6%9B%B4%E6%96%B0) -### [工蜂主页](https://github.com/Tencent/APIJSON#%E5%B7%A5%E8%9C%82%E4%B8%BB%E9%A1%B5) +### [工蜂主页](/README.md#%E5%B7%A5%E8%9C%82%E4%B8%BB%E9%A1%B5) -### [码云主页](https://github.com/Tencent/APIJSON#%E7%A0%81%E4%BA%91%E4%B8%BB%E9%A1%B5) +### [码云主页](/README.md#%E7%A0%81%E4%BA%91%E4%B8%BB%E9%A1%B5) From bc8e61a33e199d04dbbe918c2835cdccbe06177d Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Wed, 23 Dec 2020 20:39:52 +0800 Subject: [PATCH 0147/1181] Update Navigation.md --- Navigation.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Navigation.md b/Navigation.md index 5f4bae329..477653838 100644 --- a/Navigation.md +++ b/Navigation.md @@ -3,7 +3,7 @@ ### [特点功能](/README.md#%E7%89%B9%E7%82%B9%E5%8A%9F%E8%83%BD) #### [对于前端](/README.md#%E7%89%B9%E7%82%B9%E5%8A%9F%E8%83%BD) -#### [对于后端]/README.md#%E5%AF%B9%E4%BA%8E%E5%90%8E%E7%AB%AF) +#### [对于后端](/README.md#%E5%AF%B9%E4%BA%8E%E5%90%8E%E7%AB%AF) ### [APIJSON 接口展示](/README.md#apijson-%E6%8E%A5%E5%8F%A3%E5%B1%95%E7%A4%BA) #### [Postman 展示 APIJSON](/README.md#postman-%E5%B1%95%E7%A4%BA-apijson) @@ -11,7 +11,7 @@ ### [APIJSON App 演示](/README.md#apijson-app-%E6%BC%94%E7%A4%BA) -### [为什么选择 APIJSON?](/README.md#%E7%89%B9%E7%82%B9%E5%8A%9F%E8%83%BD) +### [为什么选择 APIJSON?](/README.md#%E4%B8%BA%E4%BB%80%E4%B9%88%E9%80%89%E6%8B%A9-apijson) ### [常见问题](/README.md#%E4%B8%BA%E4%BB%80%E4%B9%88%E9%80%89%E6%8B%A9-apijson) #### [1.如何定制业务逻辑?](/README.md#1%E5%A6%82%E4%BD%95%E5%AE%9A%E5%88%B6%E4%B8%9A%E5%8A%A1%E9%80%BB%E8%BE%91) From 18e5f6e8980171ef054a6ff5303ba70fad1a966b Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Wed, 23 Dec 2020 20:40:58 +0800 Subject: [PATCH 0148/1181] Update Navigation.md --- Navigation.md | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/Navigation.md b/Navigation.md index 477653838..9b87e3358 100644 --- a/Navigation.md +++ b/Navigation.md @@ -4,14 +4,18 @@ ### [特点功能](/README.md#%E7%89%B9%E7%82%B9%E5%8A%9F%E8%83%BD) #### [对于前端](/README.md#%E7%89%B9%E7%82%B9%E5%8A%9F%E8%83%BD) #### [对于后端](/README.md#%E5%AF%B9%E4%BA%8E%E5%90%8E%E7%AB%AF) +
### [APIJSON 接口展示](/README.md#apijson-%E6%8E%A5%E5%8F%A3%E5%B1%95%E7%A4%BA) #### [Postman 展示 APIJSON](/README.md#postman-%E5%B1%95%E7%A4%BA-apijson) #### [APIAuto 展示 APIJSON](/README.md#apiauto-%E5%B1%95%E7%A4%BA-apijson) +
### [APIJSON App 演示](/README.md#apijson-app-%E6%BC%94%E7%A4%BA) +
### [为什么选择 APIJSON?](/README.md#%E4%B8%BA%E4%BB%80%E4%B9%88%E9%80%89%E6%8B%A9-apijson) +
### [常见问题](/README.md#%E4%B8%BA%E4%BB%80%E4%B9%88%E9%80%89%E6%8B%A9-apijson) #### [1.如何定制业务逻辑?](/README.md#1%E5%A6%82%E4%BD%95%E5%AE%9A%E5%88%B6%E4%B8%9A%E5%8A%A1%E9%80%BB%E8%BE%91) @@ -23,27 +27,37 @@ ### [快速上手](/README.md#%E5%BF%AB%E9%80%9F%E4%B8%8A%E6%89%8B) #### [1.后端上手](/README.md#1%E5%90%8E%E7%AB%AF%E4%B8%8A%E6%89%8B) #### [2.前端上手](/README.md#2%E5%89%8D%E7%AB%AF%E4%B8%8A%E6%89%8B) +
### [下载客户端 App](/README.md#%E4%B8%8B%E8%BD%BD%E5%AE%A2%E6%88%B7%E7%AB%AF-app) +
### [使用登记](/README.md#%E4%BD%BF%E7%94%A8%E7%99%BB%E8%AE%B0) +
### [贡献者们](/README.md#%E8%B4%A1%E7%8C%AE%E8%80%85%E4%BB%AC) +
### [规划及路线图](/README.md#%E8%A7%84%E5%88%92%E5%8F%8A%E8%B7%AF%E7%BA%BF%E5%9B%BE) +
### [我要赞赏](/README.md#%E6%88%91%E8%A6%81%E8%B5%9E%E8%B5%8F)

### [技术交流](/README.md#%E6%8A%80%E6%9C%AF%E4%BA%A4%E6%B5%81) +
### [相关推荐](/README.md#%E7%9B%B8%E5%85%B3%E6%8E%A8%E8%8D%90) +
### [生态项目](/README.md#%E7%94%9F%E6%80%81%E9%A1%B9%E7%9B%AED) +
### [持续更新](/README.md#%E6%8C%81%E7%BB%AD%E6%9B%B4%E6%96%B0) +
### [工蜂主页](/README.md#%E5%B7%A5%E8%9C%82%E4%B8%BB%E9%A1%B5) +
### [码云主页](/README.md#%E7%A0%81%E4%BA%91%E4%B8%BB%E9%A1%B5) From 0ccc5b4cfd8ff585280778bc1eaf382066040397 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Wed, 23 Dec 2020 20:41:40 +0800 Subject: [PATCH 0149/1181] Update Navigation.md --- Navigation.md | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/Navigation.md b/Navigation.md index 9b87e3358..30c187190 100644 --- a/Navigation.md +++ b/Navigation.md @@ -12,7 +12,6 @@
### [APIJSON App 演示](/README.md#apijson-app-%E6%BC%94%E7%A4%BA) -
### [为什么选择 APIJSON?](/README.md#%E4%B8%BA%E4%BB%80%E4%B9%88%E9%80%89%E6%8B%A9-apijson)
@@ -30,34 +29,25 @@
### [下载客户端 App](/README.md#%E4%B8%8B%E8%BD%BD%E5%AE%A2%E6%88%B7%E7%AB%AF-app) -
### [使用登记](/README.md#%E4%BD%BF%E7%94%A8%E7%99%BB%E8%AE%B0) -
### [贡献者们](/README.md#%E8%B4%A1%E7%8C%AE%E8%80%85%E4%BB%AC) -
### [规划及路线图](/README.md#%E8%A7%84%E5%88%92%E5%8F%8A%E8%B7%AF%E7%BA%BF%E5%9B%BE) -
### [我要赞赏](/README.md#%E6%88%91%E8%A6%81%E8%B5%9E%E8%B5%8F)

### [技术交流](/README.md#%E6%8A%80%E6%9C%AF%E4%BA%A4%E6%B5%81) -
### [相关推荐](/README.md#%E7%9B%B8%E5%85%B3%E6%8E%A8%E8%8D%90) -
### [生态项目](/README.md#%E7%94%9F%E6%80%81%E9%A1%B9%E7%9B%AED) -
### [持续更新](/README.md#%E6%8C%81%E7%BB%AD%E6%9B%B4%E6%96%B0) -
### [工蜂主页](/README.md#%E5%B7%A5%E8%9C%82%E4%B8%BB%E9%A1%B5) -
### [码云主页](/README.md#%E7%A0%81%E4%BA%91%E4%B8%BB%E9%A1%B5) From 9b2924814056070e9f51d58d80035ef57a721c17 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Wed, 23 Dec 2020 20:42:47 +0800 Subject: [PATCH 0150/1181] Update Navigation.md --- Navigation.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Navigation.md b/Navigation.md index 30c187190..1a46f34ae 100644 --- a/Navigation.md +++ b/Navigation.md @@ -16,7 +16,7 @@ ### [为什么选择 APIJSON?](/README.md#%E4%B8%BA%E4%BB%80%E4%B9%88%E9%80%89%E6%8B%A9-apijson)
-### [常见问题](/README.md#%E4%B8%BA%E4%BB%80%E4%B9%88%E9%80%89%E6%8B%A9-apijson) +### [常见问题](/README.md#%E5%B8%B8%E8%A7%81%E9%97%AE%E9%A2%98) #### [1.如何定制业务逻辑?](/README.md#1%E5%A6%82%E4%BD%95%E5%AE%9A%E5%88%B6%E4%B8%9A%E5%8A%A1%E9%80%BB%E8%BE%91) #### [2.如何控制权限?](/README.md#2%E5%A6%82%E4%BD%95%E6%8E%A7%E5%88%B6%E6%9D%83%E9%99%90) #### [3.如何校验参数?](/README.md#3%E5%A6%82%E4%BD%95%E6%A0%A1%E9%AA%8C%E5%8F%82%E6%95%B0) From 5914141fbc2a6c6a386d346be9d6a709e7450301 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Wed, 23 Dec 2020 20:46:19 +0800 Subject: [PATCH 0151/1181] Update README.md --- README.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index d7dd70211..b7efacc80 100644 --- a/README.md +++ b/README.md @@ -41,7 +41,7 @@ This source code is licensed under the Apache License Version 2.0
--- -导航目录: [项目简介](#--apijson) [上手使用](#%E5%BF%AB%E9%80%9F%E4%B8%8A%E6%89%8B) [社区生态](#%E6%8A%80%E6%9C%AF%E4%BA%A4%E6%B5%81)      完整详细的导航目录 [点这里查看](/Navigation.md)
+导航目录: 项目简介 [上手使用](#%E5%BF%AB%E9%80%9F%E4%B8%8A%E6%89%8B) [社区生态](#%E6%8A%80%E6%9C%AF%E4%BA%A4%E6%B5%81)      完整详细的导航目录 [点这里查看](/Navigation.md)
APIJSON 是一种专为 API 而生的 JSON 网络传输协议 以及 基于这套协议实现的 ORM 库。
@@ -174,6 +174,8 @@ https://github.com/Tencent/APIJSON/issues?q=is%3Aissue+is%3Aclosed


+导航目录: [项目简介](#--apijson) 上手使用 [社区生态](#%E6%8A%80%E6%9C%AF%E4%BA%A4%E6%B5%81)      完整详细的导航目录 [点这里查看](/Navigation.md)
+ ### 快速上手 #### 1.后端上手 @@ -262,6 +264,8 @@ https://github.com/Tencent/APIJSON/blob/master/Roadmap.md

+导航目录: [项目简介](#--apijson) [上手使用](#%E5%BF%AB%E9%80%9F%E4%B8%8A%E6%89%8B) 社区生态      完整详细的导航目录 [点这里查看](/Navigation.md)
+ ### 技术交流 如果有什么问题或建议可以 [提ISSUE](https://github.com/Tencent/APIJSON/issues) 或 加群,交流技术,分享经验。
如果你解决了某些bug,或者新增了一些功能,欢迎 [贡献代码](https://github.com/Tencent/APIJSON/pulls),感激不尽~
From 9f724003e5114c96e581d57305b43c905ba9a00e Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Thu, 24 Dec 2020 01:15:44 +0800 Subject: [PATCH 0152/1181] Update Document.md --- Document.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Document.md b/Document.md index 251533667..820f5f20c 100644 --- a/Document.md +++ b/Document.md @@ -333,7 +333,7 @@ GET:
普通获取数据,
可用浏览器调试 | base_url/get/ | {< HEAD:
普通获取数量,
可用浏览器调试 | base_url/head/ | {
   TableName:{
     …
   }
}
{…}内为限制条件

例如获取一个 id = 38710 的 User 所发布的 Moment 总数:
{
   "Moment":{
     "userId":38710
   }
} | {
   TableName:{
     "code":200,
     "msg":"success",
     "count":10
   },
   "code":200,
   "msg":"success"
}
例如
{
   "Moment":{
     "code":200,
     "msg":"success",
     "count":10
   },
   "code":200,
   "msg":"success"
} GETS:
安全/私密获取数据,
用于获取钱包等
对安全性要求高的数据 | base_url/gets/ | 最外层加一个 "tag":tag,其它同GET | 同GET HEADS:
安全/私密获取数量,
用于获取银行卡数量等
对安全性要求高的数据总数 | base_url/heads/ | 最外层加一个 "tag":tag,其它同HEAD | 同HEAD -POST:
新增数据 | base_url/post/ | 单个:
{
   TableName:{
     …
   },
   "tag":tag
}
{…}中id由后端生成,不能传

例如一个 id = 38710 的 User 发布一个新 Moment:
{
   "Moment":{
     "userId":38710,
     "content":"APIJSON,let interfaces and documents go to hell !"
   },
   "tag":"Moment"
}

批量:
{
   TableName\[]:\[{
       …
     }, {
       …
     }
     …
   ],
   "tag":tag
}
{…}中id由后端生成,不能传

例如当前登录用户 82001 发布 2 个 Comment:
{
   "Comment\[]":\[{
     "momentId":12,
     "content":"APIJSON,let interfaces and documents go to hell !"
     }, {
     "momentId":15,
     "content":"APIJSON is a JSON transmision protocol."
   }],
   "tag":"Comment[]"
}
| 单个:
{
   TableName:{
     "code":200,
     "msg":"success",
     "id":38710
   },
   "code":200,
   "msg":"success"
}
例如
{
   "Moment":{
     "code":200,
     "msg":"success",
     "id":120
   },
   "code":200,
   "msg":"success"
}

批量:
{
   TableName\[]:{
     "code":200,
     "msg":"success",
     "count":5,
     "id[]":[1, 2, 3, 4, 5]
   },
   "code":200,
   "msg":"success"
}
例如
{
   "Comment\[]":{
     "code":200,
     "msg":"success",
     "count":2,
     "id[]":\[1, 2]
   },
   "code":200,
   "msg":"success"
} +POST:
新增数据 | base_url/post/ | 单个:
{
   TableName:{
     …
   },
   "tag":tag
}
{…}中id由后端生成,不能传

例如一个 id = 38710 的 User 发布一个新 Moment:
{
   "Moment":{
     "userId":38710,
     "content":"APIJSON,let interfaces and documents go to hell !"
   },
   "tag":"Moment"
}

批量:
{
   TableName\[]:\[{
       …
     }, {
       …
     }
     …
   ],
   "tag":tag
}
{…}中id由后端生成,不能传

例如当前登录用户 82001 发布 2 个 Comment:
{
   "Comment\[]":\[{
     "momentId":12,
     "content":"APIJSON,let interfaces and documents go to hell !"
     }, {
     "momentId":15,
     "content":"APIJSON is a JSON transmision protocol."
   }],
   "tag":"Comment[]"
}
| 单个:
{
   TableName:{
     "code":200,
     "msg":"success",
     "id":38710
   },
   "code":200,
   "msg":"success"
}
例如
{
   "Moment":{
     "code":200,
     "msg":"success",
     "id":120
   },
   "code":200,
   "msg":"success"
}

批量:
{
   TableName:{
     "code":200,
     "msg":"success",
     "count":5,
     "id[]":[1, 2, 3, 4, 5]
   },
   "code":200,
   "msg":"success"
}
例如
{
   "Comment":{
     "code":200,
     "msg":"success",
     "count":2,
     "id[]":\[1, 2]
   },
   "code":200,
   "msg":"success"
} PUT:
修改数据,
只修改所传的字段 | base_url/put/ | {
   TableName:{
     "id":id,
     …
   },
   "tag":tag
}
{…} 中 id 或 id{} 至少传一个

例如修改 id = 235 的 Moment 的 content:
{
   "Moment":{
     "id":235,
     "content":"APIJSON,let interfaces and documents go to hell !"
   },
   "tag":"Moment"
}

批量除了 id{}:\[] 也可类似批量 POST,只是每个 {...} 里面都必须有 id | 同POST DELETE:
删除数据 | base_url/delete/ | {
   TableName:{
     "id":id
   },
   "tag":tag
}
{…} 中 id 或 id{} 至少传一个,一般只传 id 或 id{}

例如批量删除 id = 100,110,120 的 Comment:
{
   "Comment":{
     "id{}":[100,110,120]
   },
   "tag":"Comment[]"
} | {
   TableName:{
     "code":200,
     "msg":"success",
     "id[]":[100,110,120]
      "count":3
   },
   "code":200,
   "msg":"success"
}
例如
{
   "Comment":{
      "code":200,
      "msg":"success",
      "id[]":[100,110,120],
      "count":3
   },
   "code":200,
   "msg":"success"
} From 852b43e784fe6d87148223a072fe8d0de79a61b3 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Thu, 24 Dec 2020 22:27:30 +0800 Subject: [PATCH 0153/1181] Update Document.md --- Document.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Document.md b/Document.md index 820f5f20c..86869c255 100644 --- a/Document.md +++ b/Document.md @@ -368,7 +368,7 @@ DELETE:
删除数据 | base_url/delete/ | {
   TableName:{< 增加 或 扩展 | "key+":Object,Object的类型由key指定,且类型为Number,String,JSONArray中的一种。如 82001,"apijson",["url0","url1"] 等。只用于PUT请求 | "praiseUserIdList+":[82001],对应SQL是`json_insert(praiseUserIdList,82001)`,添加一个点赞用户id,即这个用户点了赞 减少 或 去除 | "key-":Object,与"key+"相反 | "balance-":100.00,对应SQL是`balance = balance - 100.00`,余额减少100.00,即花费了100元 比较运算 | >, <, >=, <= 比较运算符,用于
① 提供 "id{}":"<=90000" 这种条件范围的简化写法

② 实现子查询相关比较运算

不支持 "key=":Object 和 "key!=":Object 这两种写法,直接用更简单的 "key":Object 和 "key!":Object 替代。 | ① ["id<=":90000](http://apijson.cn:8080/get/{"[]":{"User":{"id<=":90000}}}),对应SQL是`id<=90000`,查询符合id<=90000的一个User数组

② ["id>@":{
   "from":"Comment",
   "Comment":{
      "@column":"min(userId)"
   }
}](http://apijson.cn:8080/get/{"User":{"id>@":{"from":"Comment","Comment":{"@column":"min(userId)"}}}})
WHERE id>(SELECT min(userId) FROM Comment) - 逻辑运算 | &, \|, ! 逻辑运算符,对应数据库 SQL 中的 AND, OR, NOT。
横或纵与:同一键值对的值内条件默认 \| 或连接,不同键值对的条件默认 & 与连接。

① & 可用于"key&{}":"条件"等

② \| 可用于"key\|{}":"条件", "key\|{}":[]等,一般可省略

③ ! 可单独使用,如"key!":Object,也可像&,\|一样配合其他功能符使用
"key!":null 无效,null 值会导致整个键值对被忽略解析,可以用 "key{}":"!=null" 替代,
"key":null 同理,用 "key{}":"=null" 替代。 | ① ["id&{}":">80000,<=90000"](http://apijson.cn:8080/head/{"User":{"id&{}":">80000,<=90000"}}),对应SQL是`id>80000 AND id<=90000`,即id满足id>80000 & id<=90000

② ["id\|{}":">90000,<=80000"](http://apijson.cn:8080/head/{"User":{"id\|{}":">90000,<=80000"}}),同"id{}":">90000,<=80000",对应SQL是`id>80000 OR id<=90000`,即id满足id>90000 \| id<=80000

③ ["id!{}":[82001,38710]](http://apijson.cn:8080/head/{"User":{"id!{}":[82001,38710]}}),对应SQL是`id NOT IN(82001,38710)`,即id满足 ! (id=82001 \| id=38710),可过滤黑名单的消息 + 逻辑运算 | &, \|, ! 逻辑运算符,对应数据库 SQL 中的 AND, OR, NOT。
横或纵与:同一键值对的值内条件默认 \| 或连接,可以在 key 后加逻辑运算符来具体指定;不同键值对的条件默认 & 与连接,可以用下面说明的对象关键词 @combine 来具体指定。

① & 可用于"key&{}":"条件"等

② \| 可用于"key\|{}":"条件", "key\|{}":[]等,一般可省略

③ ! 可单独使用,如"key!":Object,也可像&,\|一样配合其他功能符使用
"key!":null 无效,null 值会导致整个键值对被忽略解析,可以用 "key{}":"!=null" 替代,
"key":null 同理,用 "key{}":"=null" 替代。 | ① ["id&{}":">80000,<=90000"](http://apijson.cn:8080/head/{"User":{"id&{}":">80000,<=90000"}}),对应SQL是`id>80000 AND id<=90000`,即id满足id>80000 & id<=90000

② ["id\|{}":">90000,<=80000"](http://apijson.cn:8080/head/{"User":{"id\|{}":">90000,<=80000"}}),同"id{}":">90000,<=80000",对应SQL是`id>80000 OR id<=90000`,即id满足id>90000 \| id<=80000

③ ["id!{}":[82001,38710]](http://apijson.cn:8080/head/{"User":{"id!{}":[82001,38710]}}),对应SQL是`id NOT IN(82001,38710)`,即id满足 ! (id=82001 \| id=38710),可过滤黑名单的消息 数组关键词,可自定义 | "key":Object,key为 "[]":{} 中{}内的关键词,Object的类型由key指定

① "count":Integer,查询数量,0 表示最大值,默认最大值为100

② "page":Integer,查询页码,从0开始,默认最大值为100,一般和count一起用

③ "query":Integer,查询内容
0-对象,1-总数和分页详情,2-以上全部
总数关键词为 total,分页详情关键词为 info,
它们都和 query 同级,通过引用赋值得到,例如
"total@":"/[]/total", "info@":"/[]/info"
这里query及total仅为GET类型的请求提供方便,
一般可直接用HEAD类型的请求获取总数

④ "join":"&/Table0/key0@,\多表连接方式:
"\<" - LEFT JOIN
">" - RIGHT JOIN
"&" - INNER JOIN
"\|" - FULL JOIN
"!" - OUTTER JOIN
"@" - APP JOIN
其中 @ APP JOIN 为应用层连表,会从已查出的主表里取得所有副表 key@ 关联的主表内的 refKey 作为一个数组 refKeys: [value0, value1...],然后把原来副表 count 次查询 key=$refKey 的 SQL 用 key IN($refKeys) 的方式合并为一条 SQL 来优化性能;
其它 JOIN 都是 SQL JOIN,具体功能和 MySQL,PostgreSQL 等数据库的 JOIN 一一对应
`"join":"`"MainTable":{},`
`"ViceTable":{"key@":"/MainTable/refKey"}`
会对应生成
`MainTable LEFT JOIN ViceTable`
`ON ViceTable.key=MainTable.refKey`

⑤ "otherKey":Object,自定义关键词,名称和以上系统关键词不一样,且原样返回上传的值 | ① 查询User数组,最多5个:
["count":5](http://apijson.cn:8080/get/{"[]":{"count":5,"User":{}}})
对应SQL是`LIMIT 5`

② 查询第3页的User数组,每页5个:
["count":5,
"page":3](http://apijson.cn:8080/get/{"[]":{"count":5,"page":3,"User":{}}})
对应SQL是`LIMIT 5 OFFSET 15`

③ 查询User数组和对应的User总数:
["[]":{
   "query":2,
   "User":{}
},
"total@":"/[]/total",
"info@":"/[]/info"](http://apijson.cn:8080/get/{"[]":{"query":2,"count":5,"User":{}},"total@":"%252F[]%252Ftotal","info@":"%252F[]%252Finfo"})
返回的数据中,总数及分页详情结构为:
"total":139, //总数
"info":{ //分页详情
   "total":139, //总数
   "count":5, //每页数量
   "page":0, //当前页码
   "max":27, //最大页码
   "more":true, //是否还有更多
   "first":true, //是否为首页
   "last":false //是否为尾页
}

④ Moment INNER JOIN User LEFT JOIN Comment:
["[]":{
   "join":"&/User/id@,\    "Moment":{},
   "User":{
     "name~":"t",
     "id@":"/Moment/userId"
   },
   "Comment":{
     "momentId@":"/Moment/id"
   }
}](http://apijson.cn:8080/get/{"[]":{"count":5,"join":"&%252FUser%252Fid@,\<%252FComment%252FmomentId@","Moment":{"@column":"id,userId,content"},"User":{"name~":"t","id@":"%252FMoment%252FuserId","@column":"id,name,head"},"Comment":{"momentId@":"%252FMoment%252Fid","@column":"id,momentId,content"}}})

⑤ 每一层都加当前用户名:
["User":{},
"[]":{
   "name@":"User/name", //自定义关键词
   "Moment":{}
}](http://apijson.cn:8080/get/{"User":{},"[]":{"name@":"User%252Fname","Moment":{}}}) 对象关键词,可自定义 | "@key":Object,@key为 Table:{} 中{}内的关键词,Object的类型由@key指定

① "@combine":"&key0,&key1,\|key2,key3,
!key4,!key5,&key6,key7...",条件组合方式,\| 可省略。会自动把同类的合并,外层按照 & \| ! 顺序,内层的按传参顺序组合成
(key0 & key1 & key6 & 其它key) & (key2 \| key3 \| key7) & !(key4 \| key5)
这种连接方式,其中 "其它key" 是指与 @combine 在同一对象,且未被它声明的条件 key,默认都是 & 连接

② "@column":"column;function(arg)...",返回字段

③ "@order":"column0+,column1-...",排序方式

④ "@group":"column0,column1...",分组方式。如果@column里声明了Table的id,则id也必须在@group中声明;其它情况下必须满足至少一个条件:
1.分组的key在@column里声明
2.Table主键在@group中声明

⑤ "@having":"function0(...)?value0;function1(...)?value1;function2(...)?value2...",SQL函数条件,一般和@group一起用,函数一般在@column里声明

⑥ "@schema":"sys",集合空间(模式),非默认的值可通过它来指定,可以在最外层作为全局默认配置

⑦ "@database":"POSTGRESQL",跨数据库,非默认的值可通过它来指定,可以在最外层作为全局默认配置

⑧ "@json":"key0,key1...",转为 JSON 格式返回,符合 JSONObject 则转为 {...},符合 JSONArray 则转为 \[...]

⑨ "@role":"OWNER",来访角色,包括
UNKNOWN,LOGIN,CONTACT,CIRCLE,OWNER,ADMIN,
可以在最外层作为全局默认配置,
可自定义其它角色并重写 Verifier.verify 等相关方法来自定义校验

⑩ "@explain":true,性能分析,可以在最外层作为全局默认配置

⑪ "@raw":"key0,key1...",其中 key0, key1 都对应有键值对
"key0":"SQL片段或SQL片段的别名",
"key1":"SQL片段或SQL片段的别名"
自定义原始SQL片段,可扩展嵌套SQL函数等复杂语句,必须是后端已配置的,只有其它功能符都做不到才考虑,谨慎使用,注意防SQL注入

⑫ "@otherKey":Object,自定义关键词,名称和以上系统关键词不一样,且原样返回上传的值 | ① 搜索name或tag任何一个字段包含字符a的User列表:
["name~":"a",
"tag~":"a",
"@combine":"name~,tag~"](http://apijson.cn:8080/get/{"User[]":{"count":10,"User":{"@column":"id,name,tag","name~":"a","tag~":"a","@combine":"name~,tag~"}}})
对应SQL是`name REGEXP 'a' OR tag REGEXP 'a'`

② 只查询id,sex,name这几列并且请求结果也按照这个顺序:
["@column":"id,sex,name"](http://apijson.cn:8080/get/{"User":{"@column":"id,sex,name","id":38710}})
对应SQL是`SELECT id,sex,name`

③ 查询按 name降序、id默认顺序 排序的User数组:
["@order":"name-,id"](http://apijson.cn:8080/get/{"[]":{"count":10,"User":{"@column":"name,id","@order":"name-,id"}}})
对应SQL是`ORDER BY name DESC,id`

④ 查询按userId分组的Moment数组:
["@group":"userId,id"](http://apijson.cn:8080/get/{"[]":{"count":10,"Moment":%7B"@column":"userId,id","@group":"userId,id"}}})
对应SQL是`GROUP BY userId,id`

⑤ 查询 按userId分组、id最大值>=100 的Moment数组:
["@column":"userId;max(id)",
"@group":"userId",
"@having":"max(id)>=100"](http://apijson.cn:8080/get/{"[]":{"count":10,"Moment":{"@column":"userId%253Bmax(id)","@group":"userId","@having":"max(id)>=100"}}})
对应SQL是`SELECT userId,max(id) ... GROUP BY userId HAVING max(id)>=100`
还可以指定函数返回名:
["@column":"userId;max(id):maxId",
"@group":"userId",
"@having":"maxId>=100"](http://apijson.cn:8080/get/{"[]":{"count":10,"Moment":{"@column":"userId%253Bmax(id):maxId","@group":"userId","@having":"maxId>=100"}}})
对应SQL是`SELECT userId,max(id) AS maxId ... GROUP BY userId HAVING maxId>=100`

⑥ 查询 sys 内的 User 表:
["@schema":"sys"](http://apijson.cn:8080/get/{"User":{"@schema":"sys"}})
对应SQL是`FROM sys.User`

⑦ 查询 PostgreSQL 数据库的 User 表:
["@database":"POSTGRESQL"](http://apijson.cn:8080/get/{"User":{"@database":"POSTGRESQL","@explain":true}})

⑧ 将 VARCHAR 字符串字段 get 转为 JSONArray 返回:
["@json":"get"](http://apijson.cn:8080/get/{"Access":{"@json":"get"}})

⑨ 查询当前用户的动态:
["@role":"OWNER"](http://apijson.cn:8080/get/{"[]":{"Moment":{"@role":"OWNER"}}})

⑩ 开启性能分析:
["@explain":true](http://apijson.cn:8080/get/{"[]":{"Moment":{"@explain":true}}})
对应SQL是`EXPLAIN`

⑪ 统计最近一周偶数userId的数量
["@column":"sum(if(userId%2=0,1,0))",
"@having":"to_days(now())-to_days(\`date\`)<=7",
"@raw":"@column,@having"](http://apijson.cn:8080/get/{"Comment":{"@column":"sum(if(userId%252=0,1,0))","@having":"to_days(now())-to_days(\`date\`)<=7","@raw":"@column,@having"}})
对应SQL是``SELECT sum(if(userId%2=0,1,0)) ... HAVING to_days(now())-to_days(`date`)<=7``

⑫ 从pictureList获取第0张图片:
["@position":0, //自定义关键词
"firstPicture()":"getFromArray(pictureList,@position)"](http://apijson.cn:8080/get/{"User":{"id":38710,"@position":0,"firstPicture()":"getFromArray(pictureList,@position)"}}) From 1e0f361922a9ae8c2fca37f7f156f1405ac50813 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Sat, 26 Dec 2020 15:56:58 +0800 Subject: [PATCH 0154/1181] Update README.md --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index b7efacc80..7da066ef7 100644 --- a/README.md +++ b/README.md @@ -284,6 +284,8 @@ QQ 技术群: 734652054(新)、607020115(旧) [3步创建APIJSON后端新表及配置](https://my.oschina.net/tommylemon/blog/889074) +[APIJSON对接HTAP数据库TiDB](https://asktug.com/t/htap-tidb/395) + [APIJSON简单部署和使用](https://blog.csdn.net/m450744192/article/details/108462611) [学习自动化接口APIJSON](https://www.jianshu.com/p/981a2a630c7b) From 686cd459a1697d1fcd5095e5c235cdb4444f8db6 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Sat, 26 Dec 2020 15:58:02 +0800 Subject: [PATCH 0155/1181] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 7da066ef7..a77f891ab 100644 --- a/README.md +++ b/README.md @@ -284,7 +284,7 @@ QQ 技术群: 734652054(新)、607020115(旧) [3步创建APIJSON后端新表及配置](https://my.oschina.net/tommylemon/blog/889074) -[APIJSON对接HTAP数据库TiDB](https://asktug.com/t/htap-tidb/395) +[APIJSON对接分布式HTAP数据库TiDB](https://asktug.com/t/htap-tidb/395) [APIJSON简单部署和使用](https://blog.csdn.net/m450744192/article/details/108462611) From 22ed7cc9e801f35158e85cf6adfa5c1243eb09c9 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Sat, 26 Dec 2020 19:41:51 +0800 Subject: [PATCH 0156/1181] =?UTF-8?q?=E9=80=9A=E8=BF=87=E7=BC=93=E5=AD=98?= =?UTF-8?q?=20Request=20=E6=A0=A1=E9=AA=8C=E8=A7=84=E5=88=99=E6=9D=A5?= =?UTF-8?q?=E5=A4=A7=E5=B9=85=E6=8F=90=E5=8D=87=E5=A2=9E=E5=88=A0=E6=94=B9?= =?UTF-8?q?=E7=AD=89=E9=9D=9E=E5=BC=80=E6=94=BE=E8=AF=B7=E6=B1=82=E7=9A=84?= =?UTF-8?q?=E6=80=A7=E8=83=BD=EF=BC=9B=E8=A7=A3=E5=86=B3=20=E5=85=B3?= =?UTF-8?q?=E9=97=AD=E6=9D=83=E9=99=90=E9=AA=8C=E8=AF=81=E6=83=85=E5=86=B5?= =?UTF-8?q?=E4=B8=8B=E6=89=B9=E9=87=8F=E6=96=B0=E5=A2=9E=E3=80=81=E6=89=B9?= =?UTF-8?q?=E9=87=8F=E4=BF=AE=E6=94=B9=E4=BE=9D=E7=84=B6=E4=BC=9A=E9=AA=8C?= =?UTF-8?q?=E8=AF=81=E6=9D=83=E9=99=90=20=20https://github.com/Tencent/API?= =?UTF-8?q?JSON/issues/164?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../apijson/orm/AbstractObjectParser.java | 6 +- .../main/java/apijson/orm/AbstractParser.java | 108 ++++++++++++++---- .../java/apijson/orm/AbstractVerifier.java | 18 ++- .../src/main/java/apijson/orm/Parser.java | 2 +- 4 files changed, 106 insertions(+), 28 deletions(-) diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractObjectParser.java b/APIJSONORM/src/main/java/apijson/orm/AbstractObjectParser.java index 2a59b731c..6bcc06e7a 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractObjectParser.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractObjectParser.java @@ -568,6 +568,8 @@ public void onTableArrayParse(String key, JSONArray value) throws Exception { int maxUpdateCount = parser.getMaxUpdateCount(); String idKey = parser.createSQLConfig().getIdKey(); //Table[]: [{}] arrayConfig 为 null + boolean isNeedVerifyContent = parser.isNeedVerifyContent(); + for (int i = 0; i < valueArray.size(); i++) { //只要有一条失败,则抛出异常,全部失败 //TODO 改成一条多 VALUES 的 SQL 性能更高,报错也更会更好处理,更人性化 JSONObject item; @@ -580,13 +582,13 @@ public void onTableArrayParse(String key, JSONArray value) throws Exception { JSONRequest req = new JSONRequest(childKey, item); //parser.getMaxSQLCount() ? 可能恶意调用接口,把数据库拖死 - JSONObject result = (JSONObject) onChildParse(0, "" + i, parser.parseCorrectRequest(method, childKey, version, "", req, maxUpdateCount, parser)); + JSONObject result = (JSONObject) onChildParse(0, "" + i, isNeedVerifyContent == false ? req : parser.parseCorrectRequest(method, childKey, version, "", req, maxUpdateCount, parser)); result = result.getJSONObject(childKey); // boolean success = JSONResponse.isSuccess(result); int count = result == null ? null : result.getIntValue(JSONResponse.KEY_COUNT); - if (success == false || count != 1) { //如果 code = 200 但 count != 1,不能算成功,掩盖了错误为不好排查问题 + if (success == false || count != 1) { //如果 code = 200 但 count != 1,不能算成功,掩盖了错误不好排查问题 throw new ServerException("批量新增/修改失败!" + key + "/" + i + ":" + (success ? "成功但 count != 1 !" : (result == null ? "null" : result.getString(JSONResponse.KEY_MSG)))); } diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java b/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java index d8230e850..3ab9225b3 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java @@ -14,12 +14,15 @@ import java.sql.Savepoint; import java.util.ArrayList; import java.util.Arrays; +import java.util.Comparator; import java.util.HashMap; import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Set; +import java.util.SortedMap; +import java.util.TreeMap; import java.util.concurrent.TimeoutException; import javax.activation.UnsupportedDataTypeException; @@ -472,7 +475,7 @@ public JSONObject parseCorrectRequest(RequestMethod method, String tag, int vers JSONObject object = null; String error = ""; try { - object = getStructure("Request", JSONRequest.KEY_TAG, tag, version); + object = getStructure("Request", method.name(), tag, version); } catch (Exception e) { error = e.getMessage(); } @@ -665,34 +668,91 @@ public JSONObject parseCorrectResponse(String table, JSONObject response) throws /**获取Request或Response内指定JSON结构 * @param table - * @param key - * @param value + * @param method + * @param tag * @param version * @return * @throws Exception */ @Override - public JSONObject getStructure(@NotNull String table, String key, String value, int version) throws Exception { - //获取指定的JSON结构 <<<<<<<<<<<<<< - SQLConfig config = createSQLConfig().setMethod(GET).setTable(table); - config.setPrepared(false); - config.setColumn(Arrays.asList("structure")); - - Map where = new HashMap(); - where.put("method", requestMethod.name()); - if (key != null) { - where.put(key, value); - } - if (version > 0) { - where.put(JSONRequest.KEY_VERSION + "{}", ">=" + version); - } - config.setWhere(where); - config.setOrder(JSONRequest.KEY_VERSION + (version > 0 ? "+" : "-")); - config.setCount(1); - - //too many connections error: 不try-catch,可以让客户端看到是服务器内部异常 - JSONObject result = getSQLExecutor().execute(config, false); - return getJSONObject(result, "structure");//解决返回值套了一层 "structure":{} + public JSONObject getStructure(@NotNull String table, String method, String tag, int version) throws Exception { + // TODO 目前只使用 Request 而不使用 Response,所以这里写死用 REQUEST_MAP,以后可能 Response 表也会与 Request 表合并,用字段来区分 + String cacheKey = AbstractVerifier.getCacheKeyForRequest(method, tag); + SortedMap versionedMap = AbstractVerifier.REQUEST_MAP.get(cacheKey); + + JSONObject result = versionedMap == null ? null : versionedMap.get(Integer.valueOf(version)); + if (result == null) { // version <= 0 时使用最新,version > 0 时使用 > version 的最接近版本(最小版本) + Set> set = versionedMap == null ? null : versionedMap.entrySet(); + + if (set != null && set.isEmpty() == false) { + Entry maxEntry = null; + + for (Entry entry : set) { + if (entry == null || entry.getKey() == null || entry.getValue() == null) { + continue; + } + + if (version <= 0 || version == entry.getKey()) { // 这里应该不会出现相等,因为上面 versionedMap.get(Integer.valueOf(version)) + maxEntry = entry; + break; + } + + if (entry.getKey() < version) { + break; + } + + maxEntry = entry; + } + + result = maxEntry == null ? null : maxEntry.getValue(); + } + + if (result != null) { // 加快下次查询,查到值的话组合情况其实是有限的,不属于恶意请求 + if (versionedMap == null) { + versionedMap = new TreeMap<>(new Comparator() { + + @Override + public int compare(Integer o1, Integer o2) { + return o2 == null ? -1 : o2.compareTo(o1); // 降序 + } + }); + } + + versionedMap.put(Integer.valueOf(version), result); + AbstractVerifier.REQUEST_MAP.put(cacheKey, versionedMap); + } + } + + if (result == null) { + if (AbstractVerifier.REQUEST_MAP.isEmpty() == false) { + return null; // 已使用 REQUEST_MAP 缓存全部,但没查到 + } + + //获取指定的JSON结构 <<<<<<<<<<<<<< + SQLConfig config = createSQLConfig().setMethod(GET).setTable(table); + config.setPrepared(false); + config.setColumn(Arrays.asList("structure")); + + Map where = new HashMap(); + where.put("method", method); + where.put(JSONRequest.KEY_TAG, tag); + + if (version > 0) { + where.put(JSONRequest.KEY_VERSION + "{}", ">=" + version); + } + config.setWhere(where); + config.setOrder(JSONRequest.KEY_VERSION + (version > 0 ? "+" : "-")); + config.setCount(1); + + //too many connections error: 不try-catch,可以让客户端看到是服务器内部异常 + result = getSQLExecutor().execute(config, false); + + // version, method, tag 组合情况太多了,JDK 里又没有 LRUCache,所以要么启动时一次性缓存全部后面只用缓存,要么每次都查数据库 + // versionedMap.put(Integer.valueOf(version), result); + // AbstractVerifier.REQUEST_MAP.put(cacheKey, versionedMap); + } + + return getJSONObject(result, "structure"); //解决返回值套了一层 "structure":{} } diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractVerifier.java b/APIJSONORM/src/main/java/apijson/orm/AbstractVerifier.java index 9783918f6..8d133a16d 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractVerifier.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractVerifier.java @@ -34,11 +34,13 @@ import java.util.Collection; import java.util.HashMap; import java.util.HashSet; +import java.util.LinkedHashMap; import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Set; +import java.util.SortedMap; import java.util.regex.Pattern; import javax.activation.UnsupportedDataTypeException; @@ -83,9 +85,17 @@ public abstract class AbstractVerifier implements Verifier, IdCallback { // > // > + @NotNull public static final Map> SYSTEM_ACCESS_MAP; + @NotNull public static final Map> ACCESS_MAP; + + // > + // > + @NotNull + public static final Map> REQUEST_MAP; + @NotNull public static final Map COMPILE_MAP; static { SYSTEM_ACCESS_MAP = new HashMap>(); @@ -110,6 +120,8 @@ public abstract class AbstractVerifier implements Verifier, IdCallback { } ACCESS_MAP = new HashMap<>(SYSTEM_ACCESS_MAP); + + REQUEST_MAP = new HashMap<>(ACCESS_MAP.size()*6); // 单个与批量增删改 COMPILE_MAP = new HashMap(); } @@ -689,7 +701,7 @@ public static JSONObject verifyResponse(@NotNull final RequestMethod method, fin + "\n response = \n" + JSON.toJSONString(response)); if (target == null || response == null) {// || target.isEmpty() { - Log.i(TAG, "verifyRequest target == null || response == null >> return response;"); + Log.i(TAG, "verifyResponse target == null || response == null >> return response;"); return response; } @@ -1424,5 +1436,9 @@ public static void verifyRepeat(String table, String key, Object value, long exc } } + public static String getCacheKeyForRequest(String method, String tag) { + return method + " " + tag; + } + } diff --git a/APIJSONORM/src/main/java/apijson/orm/Parser.java b/APIJSONORM/src/main/java/apijson/orm/Parser.java index e8c285872..4e09c5738 100755 --- a/APIJSONORM/src/main/java/apijson/orm/Parser.java +++ b/APIJSONORM/src/main/java/apijson/orm/Parser.java @@ -74,7 +74,7 @@ JSONObject parseCorrectRequest(RequestMethod method, String tag, int version, St JSONObject parseCorrectResponse(String table, JSONObject response) throws Exception; - JSONObject getStructure(String table, String key, String value, int version) throws Exception; + JSONObject getStructure(String table, String method, String tag, int version) throws Exception; From 71f904313d04459745c1e1588397688da6b44c30 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Sat, 26 Dec 2020 19:44:21 +0800 Subject: [PATCH 0157/1181] =?UTF-8?q?=E5=8D=87=E7=BA=A7=E7=89=88=E6=9C=AC?= =?UTF-8?q?=E5=8F=B7=E8=87=B3=204.4.5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- APIJSONORM/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/APIJSONORM/pom.xml b/APIJSONORM/pom.xml index 3756b076f..5342770e1 100755 --- a/APIJSONORM/pom.xml +++ b/APIJSONORM/pom.xml @@ -5,7 +5,7 @@ apijson.orm apijson-orm - 4.4.0 + 4.4.5 jar APIJSONORM From 2b19a123d46b4e4939e78d6139dc36368fc0c203 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Sat, 26 Dec 2020 23:20:44 +0800 Subject: [PATCH 0158/1181] =?UTF-8?q?=E8=BF=94=E5=9B=9E=E5=AD=97=E6=AE=B5?= =?UTF-8?q?=20@column=20=E6=94=AF=E6=8C=81=20(balance)*100=20=E5=92=8C=20l?= =?UTF-8?q?ength(content)%2=3D0=20=E8=BF=99=E7=A7=8D=20=E5=AD=97=E6=AE=B5?= =?UTF-8?q?=20=E6=88=96=20SQL=20=E5=87=BD=E6=95=B0=20=E5=90=8E=E6=8B=BC?= =?UTF-8?q?=E6=8E=A5=E6=95=B0=E5=AD=97=E6=AF=94=E8=BE=83=E8=A1=A8=E8=BE=BE?= =?UTF-8?q?=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/apijson/orm/AbstractSQLConfig.java | 35 ++++++++++--------- 1 file changed, 19 insertions(+), 16 deletions(-) diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java index d621f17e8..a8d50dd2e 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java @@ -957,11 +957,14 @@ public String getColumnString(boolean inSQLJoin) throws Exception { method = expression.substring(0, start); boolean distinct = i <= 0 && method.startsWith(PREFFIX_DISTINCT); - if (StringUtil.isName(distinct ? method.substring(PREFFIX_DISTINCT.length()) : method) == false) { + String fun = distinct ? method.substring(PREFFIX_DISTINCT.length()) : method; + + if (fun.isEmpty() == false && StringUtil.isName(fun) == false) { throw new IllegalArgumentException("字符 " + method + " 不合法!" + "预编译模式下 @column:\"column0,column1:alias;function0(arg0,arg1,...);function1(...):alias...\"" + " 中SQL函数名 function 必须符合正则表达式 ^[0-9a-zA-Z_]+$ !"); } + } boolean isColumn = start < 0; @@ -1043,23 +1046,23 @@ else if (StringUtil.isName(origin)) { } else { String suffix = expression.substring(end + 1, expression.length()); //:contactCount - String alias = suffix.startsWith(":") ? suffix.substring(1) : null; //contactCount - - if (StringUtil.isEmpty(alias, true)) { - if (suffix.isEmpty() == false) { - throw new IllegalArgumentException("GET请求: 预编译模式下 @column:value 中 value里面用 ; 分割的每一项" - + " function(arg0,arg1,...):alias 中 alias 如果有就必须是1个单词!并且不要有多余的空格!"); - } - } - else { - if (StringUtil.isEmpty(alias, true) == false && StringUtil.isName(alias) == false) { - throw new IllegalArgumentException("GET请求: 预编译模式下 @column:value 中 value里面用 ; 分割的每一项" - + " function(arg0,arg1,...):alias 中 alias 必须是1个单词!并且不要有多余的空格!"); - } + int index = suffix.lastIndexOf(":"); + String alias = index < 0 ? "" : suffix.substring(index + 1); //contactCount + suffix = index < 0 ? suffix : suffix.substring(0, index); + + if (alias.isEmpty() == false && StringUtil.isName(alias) == false) { + throw new IllegalArgumentException("字符串 " + alias + " 不合法!" + + "预编译模式下 @column:value 中 value里面用 ; 分割的每一项" + + " function(arg0,arg1,...):alias 中 alias 必须是1个单词!并且不要有多余的空格!"); + } + + if (suffix.isEmpty() == false && (((String) suffix).contains("--") || PATTERN_RANGE.matcher((String) suffix).matches() == false)) { + throw new UnsupportedOperationException("字符串 " + suffix + " 不合法!" + + "预编译模式下 @column:\"column?value;function(arg0,arg1,...)?value...\"" + + " 中 ?value 必须符合正则表达式 " + PATTERN_RANGE + " 且不包含连续减号 -- !不允许多余的空格!"); } - - String origin = method + "(" + StringUtil.getString(ckeys) + ")"; + String origin = method + "(" + StringUtil.getString(ckeys) + ")" + suffix; // if (isKeyPrefix()) { // keys[i] = origin + " AS " + quote + (isMain() ? "" : tableAlias + ".") + (StringUtil.isEmpty(alias, true) ? method : alias) + quote; // } From d7905fff56967787c0401bb4c4936b1846f3846e Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Sat, 26 Dec 2020 23:27:52 +0800 Subject: [PATCH 0159/1181] =?UTF-8?q?@column=20=E5=92=8C=20@having=20?= =?UTF-8?q?=E4=B8=8D=E5=85=81=E8=AE=B8=E6=B3=A8=E9=87=8A=E7=AC=A6=20/*?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/main/java/apijson/orm/AbstractSQLConfig.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java index a8d50dd2e..e6f3fd9cc 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java @@ -592,10 +592,10 @@ public String getHavingString(boolean hasPrefix) { suffix = expression.substring(end + 1, expression.length()); - if (isPrepared() && (((String) suffix).contains("--") || PATTERN_RANGE.matcher((String) suffix).matches() == false)) { + if (isPrepared() && (((String) suffix).contains("--") || ((String) suffix).contains("/*") || PATTERN_RANGE.matcher((String) suffix).matches() == false)) { throw new UnsupportedOperationException("字符串 " + suffix + " 不合法!" + "预编译模式下 @having:\"column?value;function(arg0,arg1,...)?value...\"" - + " 中 ?value 必须符合正则表达式 " + PATTERN_RANGE + " 且不包含连续减号 -- !不允许多余的空格!"); + + " 中 ?value 必须符合正则表达式 " + PATTERN_RANGE + " 且不包含连续减号 -- 或注释符 /* !不允许多余的空格!"); } String[] ckeys = StringUtil.split(expression.substring(start + 1, end)); @@ -1056,10 +1056,10 @@ else if (StringUtil.isName(origin)) { + " function(arg0,arg1,...):alias 中 alias 必须是1个单词!并且不要有多余的空格!"); } - if (suffix.isEmpty() == false && (((String) suffix).contains("--") || PATTERN_RANGE.matcher((String) suffix).matches() == false)) { + if (suffix.isEmpty() == false && (((String) suffix).contains("--") || ((String) suffix).contains("/*") || PATTERN_RANGE.matcher((String) suffix).matches() == false)) { throw new UnsupportedOperationException("字符串 " + suffix + " 不合法!" + "预编译模式下 @column:\"column?value;function(arg0,arg1,...)?value...\"" - + " 中 ?value 必须符合正则表达式 " + PATTERN_RANGE + " 且不包含连续减号 -- !不允许多余的空格!"); + + " 中 ?value 必须符合正则表达式 " + PATTERN_RANGE + " 且不包含连续减号 -- 或注释符 /* !不允许多余的空格!"); } String origin = method + "(" + StringUtil.getString(ckeys) + ")" + suffix; From 3e86b0921069a5fad7358cb45932beae781c2f87 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Sun, 27 Dec 2020 00:00:39 +0800 Subject: [PATCH 0160/1181] =?UTF-8?q?=E8=A7=A3=E5=86=B3=20PUT=20=E8=AF=B7?= =?UTF-8?q?=E6=B1=82=E4=BC=A0=20@key:[]=20=E8=A2=AB=E5=BD=93=E6=88=90?= =?UTF-8?q?=E8=A1=A8=E5=AD=97=E6=AE=B5=E7=84=B6=E5=90=8E=E8=A7=A3=E6=9E=90?= =?UTF-8?q?=E6=8A=A5=E9=94=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../apijson/orm/AbstractObjectParser.java | 24 ++++++++++++------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractObjectParser.java b/APIJSONORM/src/main/java/apijson/orm/AbstractObjectParser.java index 6bcc06e7a..f33534d19 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractObjectParser.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractObjectParser.java @@ -222,36 +222,42 @@ public AbstractObjectParser parse() throws Exception { key = entry.getKey(); try { - if (value instanceof JSONObject && key.startsWith("@") == false && key.endsWith("@") == false) { //JSONObject,往下一级提取 - if (childMap != null) {//添加到childMap,最后再解析 + if (key.startsWith("@") || key.endsWith("@")) { + if (onParse(key, value) == false) { + invalidate(); + } + } + else if (value instanceof JSONObject) { // JSONObject,往下一级提取 + if (childMap != null) { // 添加到childMap,最后再解析 childMap.put(key, (JSONObject)value); } - else { //直接解析并替换原来的,[]:{} 内必须直接解析,否则会因为丢掉count等属性,并且total@:"/[]/total"必须在[]:{} 后! + else { // 直接解析并替换原来的,[]:{} 内必须直接解析,否则会因为丢掉count等属性,并且total@:"/[]/total"必须在[]:{} 后! response.put(key, onChildParse(index, key, (JSONObject)value)); index ++; } } - else if ((method == POST || method == PUT) && value instanceof JSONArray && JSONRequest.isTableArray(key)) { //JSONArray,批量新增或修改,往下一级提取 + else if ((method == POST || method == PUT) && value instanceof JSONArray + && JSONRequest.isTableArray(key)) { // JSONArray,批量新增或修改,往下一级提取 onTableArrayParse(key, (JSONArray) value); } else if (method == PUT && value instanceof JSONArray - && (whereList == null || whereList.contains(key) == false)) { //PUT JSONArray + && (whereList == null || whereList.contains(key) == false)) { // PUT JSONArray onPUTArrayParse(key, (JSONArray) value); } - else {//JSONArray或其它Object,直接填充 + else { // JSONArray或其它Object,直接填充 if (onParse(key, value) == false) { invalidate(); } } } catch (Exception e) { if (tri == false) { - throw e;//不忽略错误,抛异常 + throw e; // 不忽略错误,抛异常 } - invalidate();//忽略错误,还原request + invalidate(); // 忽略错误,还原request } } - //非Table内的函数会被滞后在onChildParse后调用! onFunctionResponse("-"); + // 非Table内的函数会被滞后在onChildParse后调用! onFunctionResponse("-"); } if (isTable) { From 5cf01bc44f0dd392f43197dc33386849b12cc52b Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Sun, 27 Dec 2020 01:33:42 +0800 Subject: [PATCH 0161/1181] =?UTF-8?q?=E8=A7=A3=E5=86=B3=20REQUEST=5FMAP=20?= =?UTF-8?q?=E7=BC=93=E5=AD=98=E4=B8=AD=E7=9A=84=20strcuture=20=E5=9C=A8?= =?UTF-8?q?=E6=A0=A1=E9=AA=8C=E5=8F=82=E6=95=B0=E6=8A=9B=E5=BC=82=E5=B8=B8?= =?UTF-8?q?=E5=90=8E=E6=9C=AA=E8=A2=AB=E8=BF=98=E5=8E=9F=EF=BC=8C=E5=AF=BC?= =?UTF-8?q?=E8=87=B4=E5=90=8E=E7=BB=AD=E8=A7=A3=E6=9E=90=E4=B8=A2=E4=BA=86?= =?UTF-8?q?=E9=83=A8=E5=88=86=E9=85=8D=E7=BD=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/apijson/orm/AbstractVerifier.java | 350 +++++++++--------- 1 file changed, 176 insertions(+), 174 deletions(-) diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractVerifier.java b/APIJSONORM/src/main/java/apijson/orm/AbstractVerifier.java index 8d133a16d..c19c94acf 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractVerifier.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractVerifier.java @@ -34,7 +34,6 @@ import java.util.Collection; import java.util.HashMap; import java.util.HashSet; -import java.util.LinkedHashMap; import java.util.LinkedHashSet; import java.util.List; import java.util.Map; @@ -89,7 +88,7 @@ public abstract class AbstractVerifier implements Verifier, IdCallback { public static final Map> SYSTEM_ACCESS_MAP; @NotNull public static final Map> ACCESS_MAP; - + // > // > @NotNull @@ -120,7 +119,7 @@ public abstract class AbstractVerifier implements Verifier, IdCallback { } ACCESS_MAP = new HashMap<>(SYSTEM_ACCESS_MAP); - + REQUEST_MAP = new HashMap<>(ACCESS_MAP.size()*6); // 单个与批量增删改 COMPILE_MAP = new HashMap(); @@ -773,220 +772,223 @@ public static JSONObject parse(@NotNull final RequestMethod method, String name, target.remove(DISALLOW.name()); + try { // 避免中间抛异常导致 target 未 put 进 remove 掉的键值对 - //移除字段<<<<<<<<<<<<<<<<<<< - String[] removes = StringUtil.split(remove); - if (removes != null && removes.length > 0) { - for (String r : removes) { - real.remove(r); + //移除字段<<<<<<<<<<<<<<<<<<< + String[] removes = StringUtil.split(remove); + if (removes != null && removes.length > 0) { + for (String r : removes) { + real.remove(r); + } } - } - //移除字段>>>>>>>>>>>>>>>>>>> - - //判断必要字段是否都有<<<<<<<<<<<<<<<<<<< - String[] musts = StringUtil.split(must); - List mustList = musts == null ? new ArrayList() : Arrays.asList(musts); - for (String s : mustList) { - if (real.get(s) == null) {//可能传null进来,这里还会通过 real.containsKey(s) == false) { - throw new IllegalArgumentException(method + "请求," + name - + " 里面不能缺少 " + s + " 等[" + must + "]内的任何字段!"); + //移除字段>>>>>>>>>>>>>>>>>>> + + //判断必要字段是否都有<<<<<<<<<<<<<<<<<<< + String[] musts = StringUtil.split(must); + List mustList = musts == null ? new ArrayList() : Arrays.asList(musts); + for (String s : mustList) { + if (real.get(s) == null) {//可能传null进来,这里还会通过 real.containsKey(s) == false) { + throw new IllegalArgumentException(method + "请求," + name + + " 里面不能缺少 " + s + " 等[" + must + "]内的任何字段!"); + } } - } - String[] necessarys = StringUtil.split(necessary); - List necessaryList = necessarys == null ? new ArrayList() : Arrays.asList(necessarys); - for (String s : necessaryList) { - if (real.get(s) == null) {//可能传null进来,这里还会通过 real.containsKey(s) == false) { - throw new IllegalArgumentException(method + "请求," + name - + " 里面不能缺少 " + s + " 等[" + necessary + "]内的任何字段!"); + String[] necessarys = StringUtil.split(necessary); + List necessaryList = necessarys == null ? new ArrayList() : Arrays.asList(necessarys); + for (String s : necessaryList) { + if (real.get(s) == null) {//可能传null进来,这里还会通过 real.containsKey(s) == false) { + throw new IllegalArgumentException(method + "请求," + name + + " 里面不能缺少 " + s + " 等[" + necessary + "]内的任何字段!"); + } } - } - //判断必要字段是否都有>>>>>>>>>>>>>>>>>>> + //判断必要字段是否都有>>>>>>>>>>>>>>>>>>> - Set objKeySet = new HashSet(); //不能用tableKeySet,仅判断 Table:{} 会导致 key:{ Table:{} } 绕过判断 + Set objKeySet = new HashSet(); //不能用tableKeySet,仅判断 Table:{} 会导致 key:{ Table:{} } 绕过判断 - //解析内容<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< + //解析内容<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< - Set> set = new LinkedHashSet<>(target.entrySet()); - if (set.isEmpty() == false) { + Set> set = new LinkedHashSet<>(target.entrySet()); + if (set.isEmpty() == false) { - String key; - Object tvalue; - Object rvalue; - for (Entry entry : set) { - key = entry == null ? null : entry.getKey(); - if (key == null) { - continue; - } - tvalue = entry.getValue(); - rvalue = real.get(key); - if (callback.onParse(key, tvalue, rvalue) == false) { - continue; - } - - if (tvalue instanceof JSONObject) { //JSONObject,往下一级提取 - if (rvalue != null && rvalue instanceof JSONObject == false) { - throw new UnsupportedDataTypeException(key + ":value 的value不合法!类型必须是 OBJECT ,结构为 {} !"); + String key; + Object tvalue; + Object rvalue; + for (Entry entry : set) { + key = entry == null ? null : entry.getKey(); + if (key == null) { + continue; } - tvalue = callback.onParseJSONObject(key, (JSONObject) tvalue, (JSONObject) rvalue); - - objKeySet.add(key); - } else if (tvalue instanceof JSONArray) { //JSONArray - if (rvalue != null && rvalue instanceof JSONArray == false) { - throw new UnsupportedDataTypeException(key + ":value 的value不合法!类型必须是 ARRAY ,结构为 [] !"); + tvalue = entry.getValue(); + rvalue = real.get(key); + if (callback.onParse(key, tvalue, rvalue) == false) { + continue; } - tvalue = callback.onParseJSONArray(key, (JSONArray) tvalue, (JSONArray) rvalue); - if ((method == RequestMethod.POST || method == RequestMethod.PUT) && JSONRequest.isArrayKey(key)) { + if (tvalue instanceof JSONObject) { //JSONObject,往下一级提取 + if (rvalue != null && rvalue instanceof JSONObject == false) { + throw new UnsupportedDataTypeException(key + ":value 的value不合法!类型必须是 OBJECT ,结构为 {} !"); + } + tvalue = callback.onParseJSONObject(key, (JSONObject) tvalue, (JSONObject) rvalue); + objKeySet.add(key); + } else if (tvalue instanceof JSONArray) { //JSONArray + if (rvalue != null && rvalue instanceof JSONArray == false) { + throw new UnsupportedDataTypeException(key + ":value 的value不合法!类型必须是 ARRAY ,结构为 [] !"); + } + tvalue = callback.onParseJSONArray(key, (JSONArray) tvalue, (JSONArray) rvalue); + + if ((method == RequestMethod.POST || method == RequestMethod.PUT) && JSONRequest.isArrayKey(key)) { + objKeySet.add(key); + } + } else {//其它Object + tvalue = callback.onParseObject(key, tvalue, rvalue); } - } else {//其它Object - tvalue = callback.onParseObject(key, tvalue, rvalue); - } - if (tvalue != null) {//可以在target中加上一些不需要客户端传的键值对 - real.put(key, tvalue); + if (tvalue != null) {//可以在target中加上一些不需要客户端传的键值对 + real.put(key, tvalue); + } } - } - } + } - //解析内容>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> + //解析内容>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> - Set rkset = real.keySet(); //解析内容并没有改变rkset + Set rkset = real.keySet(); //解析内容并没有改变rkset - //解析不允许的字段<<<<<<<<<<<<<<<<<<< - List refuseList = new ArrayList(); - if ("!".equals(refuse)) {//所有非 must,改成 !must 更好 - for (String key : rkset) {//对@key放行,@role,@column,自定义@position等 - if (key != null && key.startsWith("@") == false - && necessaryList.contains(key) == false && objKeySet.contains(key) == false) { - refuseList.add(key); + //解析不允许的字段<<<<<<<<<<<<<<<<<<< + List refuseList = new ArrayList(); + if ("!".equals(refuse)) {//所有非 must,改成 !must 更好 + for (String key : rkset) {//对@key放行,@role,@column,自定义@position等 + if (key != null && key.startsWith("@") == false + && necessaryList.contains(key) == false && objKeySet.contains(key) == false) { + refuseList.add(key); + } + } + } else { + String[] refuses = StringUtil.split(refuse); + if (refuses != null && refuses.length > 0) { + refuseList.addAll(Arrays.asList(refuses)); } } - } else { - String[] refuses = StringUtil.split(refuse); - if (refuses != null && refuses.length > 0) { - refuseList.addAll(Arrays.asList(refuses)); - } - } - List disallowList = new ArrayList(); - if ("!".equals(disallow)) {//所有非necessary,改成 !necessary 更好 - for (String key : rkset) {//对@key放行,@role,@column,自定义@position等 - if (key != null && key.startsWith("@") == false - && necessaryList.contains(key) == false && objKeySet.contains(key) == false) { - disallowList.add(key); + List disallowList = new ArrayList(); + if ("!".equals(disallow)) {//所有非necessary,改成 !necessary 更好 + for (String key : rkset) {//对@key放行,@role,@column,自定义@position等 + if (key != null && key.startsWith("@") == false + && necessaryList.contains(key) == false && objKeySet.contains(key) == false) { + disallowList.add(key); + } + } + } else { + String[] disallows = StringUtil.split(disallow); + if (disallows != null && disallows.length > 0) { + disallowList.addAll(Arrays.asList(disallows)); } } - } else { - String[] disallows = StringUtil.split(disallow); - if (disallows != null && disallows.length > 0) { - disallowList.addAll(Arrays.asList(disallows)); - } - } - //解析不允许的字段>>>>>>>>>>>>>>>>>>> - + //解析不允许的字段>>>>>>>>>>>>>>>>>>> - //判断不允许传的key<<<<<<<<<<<<<<<<<<<<<<<<< - for (String rk : rkset) { - if (refuseList.contains(rk)) { //不允许的字段 - throw new IllegalArgumentException(method + "请求," + name - + " 里面不允许传 " + rk + " 等" + StringUtil.getString(refuseList) + "内的任何字段!"); - } - if (disallowList.contains(rk)) { //不允许的字段 - throw new IllegalArgumentException(method + "请求," + name - + " 里面不允许传 " + rk + " 等" + StringUtil.getString(disallowList) + "内的任何字段!"); - } - if (rk == null) { //无效的key - real.remove(rk); - continue; - } + //判断不允许传的key<<<<<<<<<<<<<<<<<<<<<<<<< + for (String rk : rkset) { + if (refuseList.contains(rk)) { //不允许的字段 + throw new IllegalArgumentException(method + "请求," + name + + " 里面不允许传 " + rk + " 等" + StringUtil.getString(refuseList) + "内的任何字段!"); + } + if (disallowList.contains(rk)) { //不允许的字段 + throw new IllegalArgumentException(method + "请求," + name + + " 里面不允许传 " + rk + " 等" + StringUtil.getString(disallowList) + "内的任何字段!"); + } - Object rv = real.get(rk); + if (rk == null) { //无效的key + real.remove(rk); + continue; + } - //不允许传远程函数,只能后端配置 - if (rk.endsWith("()") && rv instanceof String) { - throw new UnsupportedOperationException(method + " 请求," + rk + " 不合法!非开放请求不允许传远程函数 key():\"fun()\" !"); - } + Object rv = real.get(rk); - //不在target内的 key:{} - if (rk.startsWith("@") == false && objKeySet.contains(rk) == false) { - if (rv instanceof JSONObject) { - throw new UnsupportedOperationException(method + " 请求," +name + " 里面不允许传 " + rk + ":{} !"); + //不允许传远程函数,只能后端配置 + if (rk.endsWith("()") && rv instanceof String) { + throw new UnsupportedOperationException(method + " 请求," + rk + " 不合法!非开放请求不允许传远程函数 key():\"fun()\" !"); } - if ((method == RequestMethod.POST || method == RequestMethod.PUT) && rv instanceof JSONArray && JSONRequest.isArrayKey(rk)) { - throw new UnsupportedOperationException(method + " 请求," + name + " 里面不允许 " + rk + ":[] 等未定义的 Table[]:[{}] 批量操作键值对!"); + + //不在target内的 key:{} + if (rk.startsWith("@") == false && objKeySet.contains(rk) == false) { + if (rv instanceof JSONObject) { + throw new UnsupportedOperationException(method + " 请求," +name + " 里面不允许传 " + rk + ":{} !"); + } + if ((method == RequestMethod.POST || method == RequestMethod.PUT) && rv instanceof JSONArray && JSONRequest.isArrayKey(rk)) { + throw new UnsupportedOperationException(method + " 请求," + name + " 里面不允许 " + rk + ":[] 等未定义的 Table[]:[{}] 批量操作键值对!"); + } } } - } - //判断不允许传的key>>>>>>>>>>>>>>>>>>>>>>>>> - + //判断不允许传的key>>>>>>>>>>>>>>>>>>>>>>>>> - //校验与修改Request<<<<<<<<<<<<<<<<< - //在tableKeySet校验后操作,避免 导致put/add进去的Table 被当成原Request的内容 - real = operate(TYPE, type, real, creator); - real = operate(VERIFY, verify, real, creator); - real = operate(INSERT, insert, real, creator); - real = operate(UPDATE, update, real, creator); - real = operate(REPLACE, replace, real, creator); - //校验与修改Request>>>>>>>>>>>>>>>>> + //校验与修改Request<<<<<<<<<<<<<<<<< + //在tableKeySet校验后操作,避免 导致put/add进去的Table 被当成原Request的内容 + real = operate(TYPE, type, real, creator); + real = operate(VERIFY, verify, real, creator); + real = operate(INSERT, insert, real, creator); + real = operate(UPDATE, update, real, creator); + real = operate(REPLACE, replace, real, creator); + //校验与修改Request>>>>>>>>>>>>>>>>> - String db = real.getString(apijson.JSONObject.KEY_DATABASE); - String sh = real.getString(apijson.JSONObject.KEY_SCHEMA); - if (StringUtil.isEmpty(db, false)) { - db = database; - } - if (StringUtil.isEmpty(sh, false)) { - sh = schema; - } - String idKey = idCallback == null ? null : idCallback.getIdKey(db, sh, name); - String finalIdKey = StringUtil.isEmpty(idKey, false) ? apijson.JSONObject.KEY_ID : idKey; - //TODO放在operate前?考虑性能、operate修改后再验证的值是否和原来一样 - //校验存在<<<<<<<<<<<<<<<<<<< TODO 格式改为 id;version,tag 兼容多个字段联合主键 - String[] exists = StringUtil.split(exist); - if (exists != null && exists.length > 0) { - long exceptId = real.getLongValue(finalIdKey); - for (String e : exists) { - verifyExist(name, e, real.get(e), exceptId, creator); + String db = real.getString(apijson.JSONObject.KEY_DATABASE); + String sh = real.getString(apijson.JSONObject.KEY_SCHEMA); + if (StringUtil.isEmpty(db, false)) { + db = database; } - } - //校验存在>>>>>>>>>>>>>>>>>>> - - //TODO放在operate前?考虑性能、operate修改后再验证的值是否和原来一样 - //校验重复<<<<<<<<<<<<<<<<<<< TODO 格式改为 id;version,tag 兼容多个字段联合主键 - String[] uniques = StringUtil.split(unique); - if (uniques != null && uniques.length > 0) { - long exceptId = real.getLongValue(finalIdKey); - for (String u : uniques) { - verifyRepeat(name, u, real.get(u), exceptId, finalIdKey, creator); + if (StringUtil.isEmpty(sh, false)) { + sh = schema; } - } - //校验重复>>>>>>>>>>>>>>>>>>> - - - //还原 <<<<<<<<<< - target.put(TYPE.name(), type); - target.put(VERIFY.name(), verify); - target.put(INSERT.name(), insert); - target.put(UPDATE.name(), update); - target.put(REPLACE.name(), replace); + String idKey = idCallback == null ? null : idCallback.getIdKey(db, sh, name); + String finalIdKey = StringUtil.isEmpty(idKey, false) ? apijson.JSONObject.KEY_ID : idKey; + + //TODO放在operate前?考虑性能、operate修改后再验证的值是否和原来一样 + //校验存在<<<<<<<<<<<<<<<<<<< TODO 格式改为 id;version,tag 兼容多个字段联合主键 + String[] exists = StringUtil.split(exist); + if (exists != null && exists.length > 0) { + long exceptId = real.getLongValue(finalIdKey); + for (String e : exists) { + verifyExist(name, e, real.get(e), exceptId, creator); + } + } + //校验存在>>>>>>>>>>>>>>>>>>> + + //TODO放在operate前?考虑性能、operate修改后再验证的值是否和原来一样 + //校验重复<<<<<<<<<<<<<<<<<<< TODO 格式改为 id;version,tag 兼容多个字段联合主键 + String[] uniques = StringUtil.split(unique); + if (uniques != null && uniques.length > 0) { + long exceptId = real.getLongValue(finalIdKey); + for (String u : uniques) { + verifyRepeat(name, u, real.get(u), exceptId, finalIdKey, creator); + } + } + //校验重复>>>>>>>>>>>>>>>>>>> - target.put(EXIST.name(), exist); - target.put(UNIQUE.name(), unique); - target.put(REMOVE.name(), remove); - target.put(MUST.name(), must); - target.put(REFUSE.name(), refuse); - target.put(NECESSARY.name(), necessary); - target.put(DISALLOW.name(), disallow); - //还原 >>>>>>>>>> + } + finally { + //还原 <<<<<<<<<< + target.put(TYPE.name(), type); + target.put(VERIFY.name(), verify); + target.put(INSERT.name(), insert); + target.put(UPDATE.name(), update); + target.put(REPLACE.name(), replace); + + target.put(EXIST.name(), exist); + target.put(UNIQUE.name(), unique); + target.put(REMOVE.name(), remove); + target.put(MUST.name(), must); + target.put(REFUSE.name(), refuse); + target.put(NECESSARY.name(), necessary); + target.put(DISALLOW.name(), disallow); + //还原 >>>>>>>>>> + } Log.i(TAG, "parse return real = " + JSON.toJSONString(real)); return real; @@ -1439,6 +1441,6 @@ public static void verifyRepeat(String table, String key, Object value, long exc public static String getCacheKeyForRequest(String method, String tag) { return method + " " + tag; } - + } From 991b5fbbccf3fb28e9817e697c87220d4ab357a4 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Sun, 27 Dec 2020 01:45:05 +0800 Subject: [PATCH 0162/1181] =?UTF-8?q?=E8=A7=A3=E5=86=B3=20REFUSE=20?= =?UTF-8?q?=E5=80=BC=E4=B8=BA=20"!"=20=E6=97=B6=E5=9B=A0=E4=B8=BA=E6=B2=A1?= =?UTF-8?q?=E6=9C=89=E5=8C=B9=E9=85=8D=20MUST=EF=BC=8C=E8=80=8C=E6=98=AF?= =?UTF-8?q?=E5=8C=B9=E9=85=8D=E4=BA=86=20NECESSARY=20=E5=AF=BC=E8=87=B4?= =?UTF-8?q?=E5=88=A4=E6=96=AD=E5=87=BA=E9=94=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- APIJSONORM/src/main/java/apijson/orm/AbstractVerifier.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractVerifier.java b/APIJSONORM/src/main/java/apijson/orm/AbstractVerifier.java index c19c94acf..d7c796e33 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractVerifier.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractVerifier.java @@ -863,7 +863,7 @@ public static JSONObject parse(@NotNull final RequestMethod method, String name, if ("!".equals(refuse)) {//所有非 must,改成 !must 更好 for (String key : rkset) {//对@key放行,@role,@column,自定义@position等 if (key != null && key.startsWith("@") == false - && necessaryList.contains(key) == false && objKeySet.contains(key) == false) { + && mustList.contains(key) == false && objKeySet.contains(key) == false) { refuseList.add(key); } } From 945f6e4b0a0e18351c8e752e7d501782b04dbbcf Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Sun, 27 Dec 2020 01:46:08 +0800 Subject: [PATCH 0163/1181] =?UTF-8?q?=E5=8D=87=E7=BA=A7=E7=89=88=E6=9C=AC?= =?UTF-8?q?=E5=8F=B7=E4=B8=BA=204.4.6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- APIJSONORM/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/APIJSONORM/pom.xml b/APIJSONORM/pom.xml index 5342770e1..c0505510c 100755 --- a/APIJSONORM/pom.xml +++ b/APIJSONORM/pom.xml @@ -5,7 +5,7 @@ apijson.orm apijson-orm - 4.4.5 + 4.4.6 jar APIJSONORM From 5a51f8825d284bc4e58b9bf79d3cc6de100881f8 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Sun, 27 Dec 2020 02:02:45 +0800 Subject: [PATCH 0164/1181] Update pom.xml --- APIJSONORM/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/APIJSONORM/pom.xml b/APIJSONORM/pom.xml index c0505510c..09a9fcf4e 100755 --- a/APIJSONORM/pom.xml +++ b/APIJSONORM/pom.xml @@ -5,7 +5,7 @@ apijson.orm apijson-orm - 4.4.6 + 4.4.7 jar APIJSONORM From f4f887cd7ae3b1cfeaad8a40e84bd41887dadf18 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Mon, 28 Dec 2020 00:37:29 +0800 Subject: [PATCH 0165/1181] Update README.md --- README.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/README.md b/README.md index a77f891ab..bb212a895 100644 --- a/README.md +++ b/README.md @@ -131,6 +131,11 @@ APIJSON 是一种专为 API 而生的 JSON 网络传输协议 以及 基于这
+### APIJSON 分享演讲 +https://github.com/TommyLemon/StaticResources/tree/master/APIJSON/Share/GiteeGVPMeetup2020 + + +
### 为什么选择 APIJSON? 前后端 关于接口的 开发、文档、联调 等 10 大痛点解析
From 23f2c15cde109ff804202c763dc8305537bb18d8 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Mon, 28 Dec 2020 00:39:07 +0800 Subject: [PATCH 0166/1181] Update Navigation.md --- Navigation.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Navigation.md b/Navigation.md index 1a46f34ae..31bde26f9 100644 --- a/Navigation.md +++ b/Navigation.md @@ -13,6 +13,8 @@ ### [APIJSON App 演示](/README.md#apijson-app-%E6%BC%94%E7%A4%BA) +### [APIJSON 分享演讲](/README.md#apijson-%E5%88%86%E4%BA%AB%E6%BC%94%E8%AE%B2) + ### [为什么选择 APIJSON?](/README.md#%E4%B8%BA%E4%BB%80%E4%B9%88%E9%80%89%E6%8B%A9-apijson)
From 2e7c93b4978a8335c8634528ca2f7750e12afbe9 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Mon, 28 Dec 2020 00:40:24 +0800 Subject: [PATCH 0167/1181] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index bb212a895..52a5672cf 100644 --- a/README.md +++ b/README.md @@ -133,7 +133,7 @@ APIJSON 是一种专为 API 而生的 JSON 网络传输协议 以及 基于这 ### APIJSON 分享演讲 https://github.com/TommyLemon/StaticResources/tree/master/APIJSON/Share/GiteeGVPMeetup2020 - +
From ad4f90a9df20c1df9850d6c4f9083dead158755c Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Mon, 28 Dec 2020 00:46:58 +0800 Subject: [PATCH 0168/1181] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 52a5672cf..f32ac238e 100644 --- a/README.md +++ b/README.md @@ -132,7 +132,7 @@ APIJSON 是一种专为 API 而生的 JSON 网络传输协议 以及 基于这
### APIJSON 分享演讲 -https://github.com/TommyLemon/StaticResources/tree/master/APIJSON/Share/GiteeGVPMeetup2020 +https://github.com/TommyLemon/StaticResources/tree/master/APIJSON/Share/GiteeGVPMeetup2020

From 26351ab9cf6517c8ae80d0dba51c01e799a780e5 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Mon, 28 Dec 2020 19:57:56 +0800 Subject: [PATCH 0169/1181] Update README.md --- README.md | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index f32ac238e..59a1dfcb9 100644 --- a/README.md +++ b/README.md @@ -174,7 +174,13 @@ https://github.com/Tencent/APIJSON/wiki#%E5%AE%9E%E7%8E%B0%E5%8E%9F%E7%90%86
其它问题见 Closed Issues
-https://github.com/Tencent/APIJSON/issues?q=is%3Aissue+is%3Aclosed
+https://github.com/Tencent/APIJSON/issues?q=is%3Aissue+is%3Aclosed +
+ +### 注意事项 +APIJSON 表名、字段名、关键词及对应的值等全都是大小写敏感、空格敏感、换行敏感,大部分情况都不允许空格和换行,表名以大写字母开头,不要想当然,请严格按照 [设计规范](https://github.com/Tencent/APIJSON/blob/master/Document.md#3) 操作 +[#181](https://github.com/Tencent/APIJSON/issues/181) [#181](https://github.com/Tencent/APIJSON/issues/181) +



From da61b4a586087b1ba4be52a07acedf5a0d19be14 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Mon, 28 Dec 2020 19:58:45 +0800 Subject: [PATCH 0170/1181] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 59a1dfcb9..e23092e05 100644 --- a/README.md +++ b/README.md @@ -179,7 +179,7 @@ https://github.com/Tencent/APIJSON/issues?q=is%3Aissue+is%3Aclosed ### 注意事项 APIJSON 表名、字段名、关键词及对应的值等全都是大小写敏感、空格敏感、换行敏感,大部分情况都不允许空格和换行,表名以大写字母开头,不要想当然,请严格按照 [设计规范](https://github.com/Tencent/APIJSON/blob/master/Document.md#3) 操作 -[#181](https://github.com/Tencent/APIJSON/issues/181) [#181](https://github.com/Tencent/APIJSON/issues/181) +[#181](https://github.com/Tencent/APIJSON/issues/181)


From 69847c805a9b77ed5335705287c0d48aea2615d2 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Mon, 28 Dec 2020 19:59:11 +0800 Subject: [PATCH 0171/1181] Update README.md --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index e23092e05..f8a2a29fb 100644 --- a/README.md +++ b/README.md @@ -178,7 +178,8 @@ https://github.com/Tencent/APIJSON/issues?q=is%3Aissue+is%3Aclosed
### 注意事项 -APIJSON 表名、字段名、关键词及对应的值等全都是大小写敏感、空格敏感、换行敏感,大部分情况都不允许空格和换行,表名以大写字母开头,不要想当然,请严格按照 [设计规范](https://github.com/Tencent/APIJSON/blob/master/Document.md#3) 操作 +APIJSON 表名、字段名、关键词及对应的值等全都是大小写敏感、空格敏感、换行敏感,大部分情况都不允许空格和换行,
+表名以大写字母开头,不要想当然,请严格按照 [设计规范](https://github.com/Tencent/APIJSON/blob/master/Document.md#3) 操作 [#181](https://github.com/Tencent/APIJSON/issues/181)

From f871d4fdb26c14977e54a73e00de4c80c4e3b5a5 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Mon, 28 Dec 2020 20:00:49 +0800 Subject: [PATCH 0172/1181] Update README.md --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index f8a2a29fb..4318724bc 100644 --- a/README.md +++ b/README.md @@ -178,8 +178,8 @@ https://github.com/Tencent/APIJSON/issues?q=is%3Aissue+is%3Aclosed
### 注意事项 -APIJSON 表名、字段名、关键词及对应的值等全都是大小写敏感、空格敏感、换行敏感,大部分情况都不允许空格和换行,
-表名以大写字母开头,不要想当然,请严格按照 [设计规范](https://github.com/Tencent/APIJSON/blob/master/Document.md#3) 操作 +APIJSON 表名、字段名、关键词及对应的值等基本都是大小写敏感、逗号敏感、分号敏感、空格敏感、换行敏感,
+大部分情况都不允许空格和换行,表名以大写字母开头,不要想当然,请严格按照 [设计规范](https://github.com/Tencent/APIJSON/blob/master/Document.md#3) 操作 [#181](https://github.com/Tencent/APIJSON/issues/181)

From f417184d0691b0adc974f8e9fdb855aa574122ef Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Mon, 28 Dec 2020 20:02:52 +0800 Subject: [PATCH 0173/1181] Update Navigation.md --- Navigation.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Navigation.md b/Navigation.md index 31bde26f9..c826572e3 100644 --- a/Navigation.md +++ b/Navigation.md @@ -22,6 +22,9 @@ #### [1.如何定制业务逻辑?](/README.md#1%E5%A6%82%E4%BD%95%E5%AE%9A%E5%88%B6%E4%B8%9A%E5%8A%A1%E9%80%BB%E8%BE%91) #### [2.如何控制权限?](/README.md#2%E5%A6%82%E4%BD%95%E6%8E%A7%E5%88%B6%E6%9D%83%E9%99%90) #### [3.如何校验参数?](/README.md#3%E5%A6%82%E4%BD%95%E6%A0%A1%E9%AA%8C%E5%8F%82%E6%95%B0) +
+ +#### [注意事项](/README.md#%E6%B3%A8%E6%84%8F%E4%BA%8B%E9%A1%B9)

From ec37d585a1f076efb3933e6cb42ff8982fb2a5e2 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Mon, 28 Dec 2020 20:03:10 +0800 Subject: [PATCH 0174/1181] Update Navigation.md --- Navigation.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Navigation.md b/Navigation.md index c826572e3..e24c09080 100644 --- a/Navigation.md +++ b/Navigation.md @@ -22,9 +22,8 @@ #### [1.如何定制业务逻辑?](/README.md#1%E5%A6%82%E4%BD%95%E5%AE%9A%E5%88%B6%E4%B8%9A%E5%8A%A1%E9%80%BB%E8%BE%91) #### [2.如何控制权限?](/README.md#2%E5%A6%82%E4%BD%95%E6%8E%A7%E5%88%B6%E6%9D%83%E9%99%90) #### [3.如何校验参数?](/README.md#3%E5%A6%82%E4%BD%95%E6%A0%A1%E9%AA%8C%E5%8F%82%E6%95%B0) -
-#### [注意事项](/README.md#%E6%B3%A8%E6%84%8F%E4%BA%8B%E9%A1%B9) +### [注意事项](/README.md#%E6%B3%A8%E6%84%8F%E4%BA%8B%E9%A1%B9)

From 8b1291024039d39d026c7479fc7224569aa56a98 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Mon, 28 Dec 2020 20:03:27 +0800 Subject: [PATCH 0175/1181] Update Navigation.md --- Navigation.md | 1 + 1 file changed, 1 insertion(+) diff --git a/Navigation.md b/Navigation.md index e24c09080..5ad5cdc47 100644 --- a/Navigation.md +++ b/Navigation.md @@ -22,6 +22,7 @@ #### [1.如何定制业务逻辑?](/README.md#1%E5%A6%82%E4%BD%95%E5%AE%9A%E5%88%B6%E4%B8%9A%E5%8A%A1%E9%80%BB%E8%BE%91) #### [2.如何控制权限?](/README.md#2%E5%A6%82%E4%BD%95%E6%8E%A7%E5%88%B6%E6%9D%83%E9%99%90) #### [3.如何校验参数?](/README.md#3%E5%A6%82%E4%BD%95%E6%A0%A1%E9%AA%8C%E5%8F%82%E6%95%B0) +
### [注意事项](/README.md#%E6%B3%A8%E6%84%8F%E4%BA%8B%E9%A1%B9) From cf22f17531a0ab9d50cc6eb3770824ac4469954c Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Mon, 28 Dec 2020 20:04:40 +0800 Subject: [PATCH 0176/1181] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 4318724bc..014b91b92 100644 --- a/README.md +++ b/README.md @@ -179,7 +179,7 @@ https://github.com/Tencent/APIJSON/issues?q=is%3Aissue+is%3Aclosed ### 注意事项 APIJSON 表名、字段名、关键词及对应的值等基本都是大小写敏感、逗号敏感、分号敏感、空格敏感、换行敏感,
-大部分情况都不允许空格和换行,表名以大写字母开头,不要想当然,请严格按照 [设计规范](https://github.com/Tencent/APIJSON/blob/master/Document.md#3) 操作 +大部分情况都不允许空格和换行,表名以大写字母开头,不要想当然,请严格按照 [设计规范](https://github.com/Tencent/APIJSON/blob/master/Document.md#3) 来调用 API [#181](https://github.com/Tencent/APIJSON/issues/181)

From b2bb6acb90cafb7336fa782af530607ff4a74091 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Mon, 28 Dec 2020 20:10:57 +0800 Subject: [PATCH 0177/1181] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 014b91b92..5b397a66a 100644 --- a/README.md +++ b/README.md @@ -178,7 +178,7 @@ https://github.com/Tencent/APIJSON/issues?q=is%3Aissue+is%3Aclosed
### 注意事项 -APIJSON 表名、字段名、关键词及对应的值等基本都是大小写敏感、逗号敏感、分号敏感、空格敏感、换行敏感,
+请求参数 JSON 中表名、字段名、关键词及对应的值等基本都是大小写敏感、逗号敏感、分号敏感、空格敏感、换行敏感,
大部分情况都不允许空格和换行,表名以大写字母开头,不要想当然,请严格按照 [设计规范](https://github.com/Tencent/APIJSON/blob/master/Document.md#3) 来调用 API [#181](https://github.com/Tencent/APIJSON/issues/181)
From 9eec33ef8b99e91ecc0ccdf70ea309b001ec11c1 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Mon, 28 Dec 2020 20:11:36 +0800 Subject: [PATCH 0178/1181] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 5b397a66a..846d6a01b 100644 --- a/README.md +++ b/README.md @@ -178,7 +178,7 @@ https://github.com/Tencent/APIJSON/issues?q=is%3Aissue+is%3Aclosed
### 注意事项 -请求参数 JSON 中表名、字段名、关键词及对应的值等基本都是大小写敏感、逗号敏感、分号敏感、空格敏感、换行敏感,
+请求参数 JSON 中表名、字段名、关键词及对应的值都是大小写敏感、逗号敏感、分号敏感、空格敏感、换行敏感,
大部分情况都不允许空格和换行,表名以大写字母开头,不要想当然,请严格按照 [设计规范](https://github.com/Tencent/APIJSON/blob/master/Document.md#3) 来调用 API [#181](https://github.com/Tencent/APIJSON/issues/181)
From 9ed6a5f7914aa3f1fdfee80eb740b5b67b9d60d7 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Mon, 28 Dec 2020 20:27:07 +0800 Subject: [PATCH 0179/1181] Update README.md --- README.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/README.md b/README.md index 846d6a01b..d5df8d677 100644 --- a/README.md +++ b/README.md @@ -172,6 +172,15 @@ https://github.com/Tencent/APIJSON/issues/12 在 Request 表配置校验规则 structure,提供 MUST、TYPE、VERIFY 等通用方法,可通过 远程函数 来完全自定义
https://github.com/Tencent/APIJSON/wiki#%E5%AE%9E%E7%8E%B0%E5%8E%9F%E7%90%86 +#### 4.如何 OR 连接不同 key 对应的条件? +用对象关键词 @combine,例如 "@combine":"name~,tag~"
+https://github.com/Tencent/APIJSON/issues/107 + +#### 5.登录后 增删改 报错未登录? +如果是自己的网页、小程序、客户端这样报错,一般是因为没有存取 Cookie,需要在登录成功后保存 Cookie 并在调其它接口时带上;
+如果使用网页工具测试报错,则很可能是 Chrome 80+ 强制 same-site Cookie 的策略导致,可以改用 Postman 或修改 Chrome 设置
+https://github.com/Tencent/APIJSON/issues/166 +
其它问题见 Closed Issues
https://github.com/Tencent/APIJSON/issues?q=is%3Aissue+is%3Aclosed From e1caf304898818504d7b7fc5279b54dc23d2c646 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Mon, 28 Dec 2020 20:28:38 +0800 Subject: [PATCH 0180/1181] Update README.md --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index d5df8d677..bd022dae0 100644 --- a/README.md +++ b/README.md @@ -177,11 +177,11 @@ https://github.com/Tencent/APIJSON/wiki#%E5%AE%9E%E7%8E%B0%E5%8E%9F%E7%90%86 https://github.com/Tencent/APIJSON/issues/107 #### 5.登录后 增删改 报错未登录? -如果是自己的网页、小程序、客户端这样报错,一般是因为没有存取 Cookie,需要在登录成功后保存 Cookie 并在调其它接口时带上;
+如果是自己的网页、小程序、客户端这样报错,一般是因为没有存取 Cookie,需要在登录成功后保存 Cookie 并在调其它接口时带上
+https://gitee.com/Tencent/APIJSON/issues/I1JTYH
如果使用网页工具测试报错,则很可能是 Chrome 80+ 强制 same-site Cookie 的策略导致,可以改用 Postman 或修改 Chrome 设置
-https://github.com/Tencent/APIJSON/issues/166 +https://github.com/Tencent/APIJSON/issues/166
-
其它问题见 Closed Issues
https://github.com/Tencent/APIJSON/issues?q=is%3Aissue+is%3Aclosed
From a06f2a800bdd40ea7bf24a19c7c6f15fd9fa528a Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Mon, 28 Dec 2020 20:33:56 +0800 Subject: [PATCH 0181/1181] Update README.md --- README.md | 14 ++------------ 1 file changed, 2 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index bd022dae0..b7dab68e9 100644 --- a/README.md +++ b/README.md @@ -172,18 +172,8 @@ https://github.com/Tencent/APIJSON/issues/12 在 Request 表配置校验规则 structure,提供 MUST、TYPE、VERIFY 等通用方法,可通过 远程函数 来完全自定义
https://github.com/Tencent/APIJSON/wiki#%E5%AE%9E%E7%8E%B0%E5%8E%9F%E7%90%86 -#### 4.如何 OR 连接不同 key 对应的条件? -用对象关键词 @combine,例如 "@combine":"name~,tag~"
-https://github.com/Tencent/APIJSON/issues/107 - -#### 5.登录后 增删改 报错未登录? -如果是自己的网页、小程序、客户端这样报错,一般是因为没有存取 Cookie,需要在登录成功后保存 Cookie 并在调其它接口时带上
-https://gitee.com/Tencent/APIJSON/issues/I1JTYH
-如果使用网页工具测试报错,则很可能是 Chrome 80+ 强制 same-site Cookie 的策略导致,可以改用 Postman 或修改 Chrome 设置
-https://github.com/Tencent/APIJSON/issues/166
- -其它问题见 Closed Issues
-https://github.com/Tencent/APIJSON/issues?q=is%3Aissue+is%3Aclosed +更多常见问题及提问前必看
+https://github.com/Tencent/APIJSON/issues/36
### 注意事项 From b5bbad9a1db9f596440e50fe4d0f0007e5409ee1 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Mon, 28 Dec 2020 21:14:09 +0800 Subject: [PATCH 0182/1181] Update README.md --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index b7dab68e9..b08d7700d 100644 --- a/README.md +++ b/README.md @@ -133,6 +133,8 @@ APIJSON 是一种专为 API 而生的 JSON 网络传输协议 以及 基于这 ### APIJSON 分享演讲 https://github.com/TommyLemon/StaticResources/tree/master/APIJSON/Share/GiteeGVPMeetup2020
+https://my.oschina.net/u/4570368/blog/4818203
+https://my.oschina.net/gitosc/blog/4864607

From 297c251f36bfacac4fb896f002f2c6a90c4cddf0 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Mon, 28 Dec 2020 23:27:36 +0800 Subject: [PATCH 0183/1181] Update pom.xml --- APIJSONORM/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/APIJSONORM/pom.xml b/APIJSONORM/pom.xml index 09a9fcf4e..975c97d66 100755 --- a/APIJSONORM/pom.xml +++ b/APIJSONORM/pom.xml @@ -5,7 +5,7 @@ apijson.orm apijson-orm - 4.4.7 + 4.4.8 jar APIJSONORM From 62e5c0b7aeb3e6b2c8cae21218f14c8d72ba46cf Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Wed, 30 Dec 2020 11:09:01 +0800 Subject: [PATCH 0184/1181] Update Document.md --- Document.md | 1 + 1 file changed, 1 insertion(+) diff --git a/Document.md b/Document.md index 86869c255..7b299146f 100644 --- a/Document.md +++ b/Document.md @@ -20,6 +20,7 @@ 请求:

{
   "User":{
+    "id":38710
   }
 }
 
From 95fe9030f80c57f158a4c945f0eb277c8a4c3ceb Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Wed, 30 Dec 2020 11:09:49 +0800 Subject: [PATCH 0185/1181] Update Document.md --- Document.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Document.md b/Document.md index 7b299146f..ab37d9f31 100644 --- a/Document.md +++ b/Document.md @@ -25,7 +25,7 @@ } -[点击这里测试](http://apijson.cn:8080/get/{"User":{}}) +[点击这里测试](http://apijson.cn:8080/get/{"User":{"id":38710}}) 返回:
{

From 3a7371281aef2371508fb42e772248b4b72b4408 Mon Sep 17 00:00:00 2001
From: TommyLemon <1184482681@qq.com>
Date: Wed, 30 Dec 2020 21:51:56 +0800
Subject: [PATCH 0186/1181] Update README.md

---
 README.md | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/README.md b/README.md
index b08d7700d..3b0a59bcb 100644
--- a/README.md
+++ b/README.md
@@ -136,6 +136,8 @@ https://github.com/TommyLemon/StaticResources/tree/master/APIJSON/Share/GiteeGVP
 https://my.oschina.net/u/4570368/blog/4818203 
https://my.oschina.net/gitosc/blog/4864607
+演讲录播视频 +https://www.bilibili.com/video/BV1Tv411t74v?p=4
From 149c7109298cc2ba8b9d7fa93071706d45aa250e Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Wed, 30 Dec 2020 21:52:30 +0800 Subject: [PATCH 0187/1181] Update README.md --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 3b0a59bcb..95f7845a1 100644 --- a/README.md +++ b/README.md @@ -136,6 +136,7 @@ https://github.com/TommyLemon/StaticResources/tree/master/APIJSON/Share/GiteeGVP https://my.oschina.net/u/4570368/blog/4818203
https://my.oschina.net/gitosc/blog/4864607
+
演讲录播视频 https://www.bilibili.com/video/BV1Tv411t74v?p=4 From 6460c089dd358f464ccf01a6740ac0723eb5ed0e Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Sat, 2 Jan 2021 00:21:01 +0800 Subject: [PATCH 0188/1181] Update README.md --- README.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/README.md b/README.md index 95f7845a1..6245d313c 100644 --- a/README.md +++ b/README.md @@ -213,6 +213,7 @@ https://github.com/Tencent/APIJSON/issues/36 ### 使用登记 +如果您在使用 APIJSON,请让我们知道,您的使用对我们非常重要:https://github.com/Tencent/APIJSON/issues/187 (按登记顺序排列)
@@ -227,8 +228,6 @@ https://github.com/Tencent/APIJSON/issues/36
-[更多 APIJSON 使用者](https://github.com/Tencent/APIJSON/issues/73) - ### 贡献者们 主项目 APIJSON 的贡献者们和 生态周边项目 的作者们: From 92e9c99466caf4afb9494b2d6777613ba93db21b Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Sat, 2 Jan 2021 00:22:05 +0800 Subject: [PATCH 0189/1181] Update README.md --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 6245d313c..c47193d7b 100644 --- a/README.md +++ b/README.md @@ -213,7 +213,8 @@ https://github.com/Tencent/APIJSON/issues/36 ### 使用登记 -如果您在使用 APIJSON,请让我们知道,您的使用对我们非常重要:https://github.com/Tencent/APIJSON/issues/187 (按登记顺序排列) +如果您在使用 APIJSON,请让我们知道,您的使用对我们非常重要(按登记顺序排列):
+https://github.com/Tencent/APIJSON/issues/187
From e6e446d90b6a992c892f3aa3d47ceb6721fe6d33 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Sun, 3 Jan 2021 15:04:51 +0800 Subject: [PATCH 0190/1181] Update CONTRIBUTING.md --- CONTRIBUTING.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 6cc84c028..b5ac367e1 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -29,6 +29,13 @@ APIJSON 持续招募贡献者,即使是在 Issue 中回答问题,或者做 APIJSON 已开发近 4 年,在此感谢所有开发者对于 APIJSON 的喜欢和支持,希望你能够成为 APIJSON 的核心贡献者,
加入 APIJSON ,共同打造一个更棒的自动化 ORM 库!🍾🎉 +#### 为什么一定要贡献代码? +贡献代码可以避免你碰到以下麻烦:
+1.你的代码其他人看不到,不能帮你发现 Bug,更不可能帮你修复 Bug 甚至优化代码
+2.你需要自己维护你的代码,如果项目更新了,你需要下载新代码并合并你自己的更改
+3.作者和其它贡献者可能不兼容你更改的代码,导致和你的代码在功能甚至编译上出错
+##### 所以为了让你自己的更改始终能跟上项目版本,得到他人给予的可靠且持续的维护,强烈建议 [提交 Pull Rueqest]() 来贡献代码。 + ​ ## Issue 提交 From 4a53cdeead512f5ad6a0437cf541c4cde8c984b7 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Sun, 3 Jan 2021 15:05:09 +0800 Subject: [PATCH 0191/1181] Update CONTRIBUTING.md --- CONTRIBUTING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index b5ac367e1..f981096dc 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -34,7 +34,7 @@ APIJSON 已开发近 4 年,在此感谢所有开发者对于 APIJSON 的喜欢 1.你的代码其他人看不到,不能帮你发现 Bug,更不可能帮你修复 Bug 甚至优化代码
2.你需要自己维护你的代码,如果项目更新了,你需要下载新代码并合并你自己的更改
3.作者和其它贡献者可能不兼容你更改的代码,导致和你的代码在功能甚至编译上出错
-##### 所以为了让你自己的更改始终能跟上项目版本,得到他人给予的可靠且持续的维护,强烈建议 [提交 Pull Rueqest]() 来贡献代码。 +##### 所以为了让你自己的更改始终能跟上项目版本,得到他人给予的可靠且持续的维护,强烈建议 [提交 Pull Rueqest] 来贡献代码。 ​ From 8256d78765dd820851c9a207f53e5575509d5275 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Sun, 3 Jan 2021 15:05:29 +0800 Subject: [PATCH 0192/1181] Update CONTRIBUTING.md --- CONTRIBUTING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index f981096dc..c6c6e047e 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -34,7 +34,7 @@ APIJSON 已开发近 4 年,在此感谢所有开发者对于 APIJSON 的喜欢 1.你的代码其他人看不到,不能帮你发现 Bug,更不可能帮你修复 Bug 甚至优化代码
2.你需要自己维护你的代码,如果项目更新了,你需要下载新代码并合并你自己的更改
3.作者和其它贡献者可能不兼容你更改的代码,导致和你的代码在功能甚至编译上出错
-##### 所以为了让你自己的更改始终能跟上项目版本,得到他人给予的可靠且持续的维护,强烈建议 [提交 Pull Rueqest] 来贡献代码。 +##### 所以为了让你自己的更改始终能跟上项目版本,得到他人给予的可靠且持续的维护,强烈建议 [提交 Pull Rueqest](/CONTRIBUTING.md#pull-request) 来贡献代码。 ​ From 6b6eec3ade74e8c24355807731c0dff7a82e823f Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Sun, 3 Jan 2021 15:05:57 +0800 Subject: [PATCH 0193/1181] Update CONTRIBUTING.md --- CONTRIBUTING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index c6c6e047e..3807fc94e 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -19,7 +19,7 @@ - [CoolGeo2016](https://github.com/CoolGeo2016) - [1906522096](https://github.com/1906522096) -其中特别致谢:
+##### 其中特别致谢:
justinfengchen 提交的 6 个 Commits, 对 APIJSON 做出了 3,130 增加和 0 处删减(截止 2020/11/04 日);
ruoranw 提交的 18 个 Commits, 对 APIJSON 做出了 328 增加和 520 处删减(截止 2020/11/04 日);
Zerounary 提交的 6 个 Commits, 对 APIJSON 做出了 1,104 增加和 1 处删减(截止 2020/11/04 日)。
From b20a86894e10d7cd76e84ca7fe72ea74533b2f36 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Sun, 3 Jan 2021 15:06:17 +0800 Subject: [PATCH 0194/1181] Update CONTRIBUTING.md --- CONTRIBUTING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 3807fc94e..e8a57a972 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -34,7 +34,7 @@ APIJSON 已开发近 4 年,在此感谢所有开发者对于 APIJSON 的喜欢 1.你的代码其他人看不到,不能帮你发现 Bug,更不可能帮你修复 Bug 甚至优化代码
2.你需要自己维护你的代码,如果项目更新了,你需要下载新代码并合并你自己的更改
3.作者和其它贡献者可能不兼容你更改的代码,导致和你的代码在功能甚至编译上出错
-##### 所以为了让你自己的更改始终能跟上项目版本,得到他人给予的可靠且持续的维护,强烈建议 [提交 Pull Rueqest](/CONTRIBUTING.md#pull-request) 来贡献代码。 +##### 所以为了让你自己的更改始终能跟上项目版本,得到他人给予的可靠且持续的维护,强烈建议 [提交 Pull Request](/CONTRIBUTING.md#pull-request) 来贡献代码。 ​ From ec94a395eba3a32f9b46c1520179d5371c301072 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Sun, 3 Jan 2021 15:07:36 +0800 Subject: [PATCH 0195/1181] Update CONTRIBUTING.md --- CONTRIBUTING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index e8a57a972..d962fb402 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -32,7 +32,7 @@ APIJSON 已开发近 4 年,在此感谢所有开发者对于 APIJSON 的喜欢 #### 为什么一定要贡献代码? 贡献代码可以避免你碰到以下麻烦:
1.你的代码其他人看不到,不能帮你发现 Bug,更不可能帮你修复 Bug 甚至优化代码
-2.你需要自己维护你的代码,如果项目更新了,你需要下载新代码并合并你自己的更改
+2.你需要自己维护你的代码,如果项目更新了,你需要下载新代码再合并你自己的更改
3.作者和其它贡献者可能不兼容你更改的代码,导致和你的代码在功能甚至编译上出错
##### 所以为了让你自己的更改始终能跟上项目版本,得到他人给予的可靠且持续的维护,强烈建议 [提交 Pull Request](/CONTRIBUTING.md#pull-request) 来贡献代码。 From f63f72eed7d6a35d241c8cfa6e7dbdcf9170b0a3 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Sun, 3 Jan 2021 15:08:06 +0800 Subject: [PATCH 0196/1181] Update CONTRIBUTING.md --- CONTRIBUTING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index d962fb402..d40739431 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -32,7 +32,7 @@ APIJSON 已开发近 4 年,在此感谢所有开发者对于 APIJSON 的喜欢 #### 为什么一定要贡献代码? 贡献代码可以避免你碰到以下麻烦:
1.你的代码其他人看不到,不能帮你发现 Bug,更不可能帮你修复 Bug 甚至优化代码
-2.你需要自己维护你的代码,如果项目更新了,你需要下载新代码再合并你自己的更改
+2.你需要自己维护你的代码,每次项目更新时,你需要下载新代码再合并你自己的更改
3.作者和其它贡献者可能不兼容你更改的代码,导致和你的代码在功能甚至编译上出错
##### 所以为了让你自己的更改始终能跟上项目版本,得到他人给予的可靠且持续的维护,强烈建议 [提交 Pull Request](/CONTRIBUTING.md#pull-request) 来贡献代码。 From 10d64016821f7092976ad338d50e23740fb2261e Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Sun, 3 Jan 2021 15:08:57 +0800 Subject: [PATCH 0197/1181] Update CONTRIBUTING.md --- CONTRIBUTING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index d40739431..28a0cd8e8 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -32,7 +32,7 @@ APIJSON 已开发近 4 年,在此感谢所有开发者对于 APIJSON 的喜欢 #### 为什么一定要贡献代码? 贡献代码可以避免你碰到以下麻烦:
1.你的代码其他人看不到,不能帮你发现 Bug,更不可能帮你修复 Bug 甚至优化代码
-2.你需要自己维护你的代码,每次项目更新时,你需要下载新代码再合并你自己的更改
+2.你需要自己维护你的代码,每次升级版本时,你需要下载新代码再合并你自己的更改
3.作者和其它贡献者可能不兼容你更改的代码,导致和你的代码在功能甚至编译上出错
##### 所以为了让你自己的更改始终能跟上项目版本,得到他人给予的可靠且持续的维护,强烈建议 [提交 Pull Request](/CONTRIBUTING.md#pull-request) 来贡献代码。 From cae5a287c5de8bc224ea81c92f564aeff5a52488 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Sun, 3 Jan 2021 15:09:33 +0800 Subject: [PATCH 0198/1181] Update CONTRIBUTING.md --- CONTRIBUTING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 28a0cd8e8..640c732f1 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -34,7 +34,7 @@ APIJSON 已开发近 4 年,在此感谢所有开发者对于 APIJSON 的喜欢 1.你的代码其他人看不到,不能帮你发现 Bug,更不可能帮你修复 Bug 甚至优化代码
2.你需要自己维护你的代码,每次升级版本时,你需要下载新代码再合并你自己的更改
3.作者和其它贡献者可能不兼容你更改的代码,导致和你的代码在功能甚至编译上出错
-##### 所以为了让你自己的更改始终能跟上项目版本,得到他人给予的可靠且持续的维护,强烈建议 [提交 Pull Request](/CONTRIBUTING.md#pull-request) 来贡献代码。 +#### 所以为了让你自己的更改始终能跟上项目版本,得到他人给予的可靠且持续的维护,强烈建议 [提交 Pull Request](/CONTRIBUTING.md#pull-request) 来贡献代码。 ​ From c87605e9485af09bcc262802e00c4cd4a5f29e1d Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Sun, 3 Jan 2021 15:10:00 +0800 Subject: [PATCH 0199/1181] Update CONTRIBUTING.md --- CONTRIBUTING.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 640c732f1..266ee6858 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -19,7 +19,7 @@ - [CoolGeo2016](https://github.com/CoolGeo2016) - [1906522096](https://github.com/1906522096) -##### 其中特别致谢:
+#### 其中特别致谢:
justinfengchen 提交的 6 个 Commits, 对 APIJSON 做出了 3,130 增加和 0 处删减(截止 2020/11/04 日);
ruoranw 提交的 18 个 Commits, 对 APIJSON 做出了 328 增加和 520 处删减(截止 2020/11/04 日);
Zerounary 提交的 6 个 Commits, 对 APIJSON 做出了 1,104 增加和 1 处删减(截止 2020/11/04 日)。
@@ -29,7 +29,7 @@ APIJSON 持续招募贡献者,即使是在 Issue 中回答问题,或者做 APIJSON 已开发近 4 年,在此感谢所有开发者对于 APIJSON 的喜欢和支持,希望你能够成为 APIJSON 的核心贡献者,
加入 APIJSON ,共同打造一个更棒的自动化 ORM 库!🍾🎉 -#### 为什么一定要贡献代码? +### 为什么一定要贡献代码? 贡献代码可以避免你碰到以下麻烦:
1.你的代码其他人看不到,不能帮你发现 Bug,更不可能帮你修复 Bug 甚至优化代码
2.你需要自己维护你的代码,每次升级版本时,你需要下载新代码再合并你自己的更改
From bc69af1354281dd7a86bad2788ece573d08f2426 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Sun, 3 Jan 2021 15:11:44 +0800 Subject: [PATCH 0200/1181] Update CONTRIBUTING.md --- CONTRIBUTING.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 266ee6858..d379bca40 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -32,8 +32,8 @@ APIJSON 已开发近 4 年,在此感谢所有开发者对于 APIJSON 的喜欢 ### 为什么一定要贡献代码? 贡献代码可以避免你碰到以下麻烦:
1.你的代码其他人看不到,不能帮你发现 Bug,更不可能帮你修复 Bug 甚至优化代码
-2.你需要自己维护你的代码,每次升级版本时,你需要下载新代码再合并你自己的更改
-3.作者和其它贡献者可能不兼容你更改的代码,导致和你的代码在功能甚至编译上出错
+2.作者和其它贡献者可能不兼容你更改的代码,导致升级版本后在功能甚至编译上出错
+3.你需要自己维护你的代码,每次升级版本时,你需要下载新代码再合并你自己的更改
#### 所以为了让你自己的更改始终能跟上项目版本,得到他人给予的可靠且持续的维护,强烈建议 [提交 Pull Request](/CONTRIBUTING.md#pull-request) 来贡献代码。 ​ From 26705d0a12c8dac884f24492f8380c2aa77ff749 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Sun, 3 Jan 2021 15:12:50 +0800 Subject: [PATCH 0201/1181] Update CONTRIBUTING.md --- CONTRIBUTING.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index d379bca40..f1f2f2bc4 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -31,9 +31,9 @@ APIJSON 已开发近 4 年,在此感谢所有开发者对于 APIJSON 的喜欢 ### 为什么一定要贡献代码? 贡献代码可以避免你碰到以下麻烦:
-1.你的代码其他人看不到,不能帮你发现 Bug,更不可能帮你修复 Bug 甚至优化代码
-2.作者和其它贡献者可能不兼容你更改的代码,导致升级版本后在功能甚至编译上出错
-3.你需要自己维护你的代码,每次升级版本时,你需要下载新代码再合并你自己的更改
+1.你在 APIJSON 上更改的代码其他人看不到,不能帮你发现 Bug,更不可能帮你修复 Bug 甚至优化代码
+2.作者和其它贡献者可能不兼容你更改的代码,导致你的项目在升级 APIJSON 版本后在功能甚至编译上出错
+3.你需要自己维护你的代码,每次升级 APIJSON 版本时,你都需要下载 APIJSON 新代码再合并你自己的更改
#### 所以为了让你自己的更改始终能跟上项目版本,得到他人给予的可靠且持续的维护,强烈建议 [提交 Pull Request](/CONTRIBUTING.md#pull-request) 来贡献代码。 ​ From 84b3295c64ad986ff43a83bcdd0ade4170df2ab9 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Sun, 3 Jan 2021 15:20:05 +0800 Subject: [PATCH 0202/1181] Update CONTRIBUTING.md --- CONTRIBUTING.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index f1f2f2bc4..dce6401fd 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -95,3 +95,10 @@ $ git rebase / #### Commit 信息提交 Commit 信息请遵循 [Commit 消息约定](./CONTRIBUTING_COMMIT.md),以便可以自动生成 `CHANGELOG` 。具体格式请参考 Commit 文档规范。 + +#### 详细的图文步骤可参考 +GitHub - 对项目做出贡献
+https://www.jianshu.com/p/00cf29d2d66c +
+如何在 Github 上给别人的项目贡献代码
+https://git-scm.com/book/zh/v2/GitHub-%E5%AF%B9%E9%A1%B9%E7%9B%AE%E5%81%9A%E5%87%BA%E8%B4%A1%E7%8C%AE From f3e45e827064189d73fa9461fd6cd192d485a1f3 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Sun, 3 Jan 2021 15:20:47 +0800 Subject: [PATCH 0203/1181] Update CONTRIBUTING.md --- CONTRIBUTING.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index dce6401fd..d08dae672 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -96,9 +96,9 @@ $ git rebase / Commit 信息请遵循 [Commit 消息约定](./CONTRIBUTING_COMMIT.md),以便可以自动生成 `CHANGELOG` 。具体格式请参考 Commit 文档规范。 -#### 详细的图文步骤可参考 +#### 详细的图文步骤可参考以下任意一篇 GitHub - 对项目做出贡献
https://www.jianshu.com/p/00cf29d2d66c -
+

如何在 Github 上给别人的项目贡献代码
https://git-scm.com/book/zh/v2/GitHub-%E5%AF%B9%E9%A1%B9%E7%9B%AE%E5%81%9A%E5%87%BA%E8%B4%A1%E7%8C%AE From db9bd093dbf503ba83cde3ba5a3836fc88cd7d62 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Sun, 3 Jan 2021 15:21:16 +0800 Subject: [PATCH 0204/1181] Update CONTRIBUTING.md --- CONTRIBUTING.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index d08dae672..64d12cf6a 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -96,6 +96,8 @@ $ git rebase / Commit 信息请遵循 [Commit 消息约定](./CONTRIBUTING_COMMIT.md),以便可以自动生成 `CHANGELOG` 。具体格式请参考 Commit 文档规范。 +

+ #### 详细的图文步骤可参考以下任意一篇 GitHub - 对项目做出贡献
https://www.jianshu.com/p/00cf29d2d66c From 8aca85fe54cdab80573dd1590e7a9e44dd711b6c Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Sun, 3 Jan 2021 16:28:39 +0800 Subject: [PATCH 0205/1181] Update README.md --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index c47193d7b..cec2abf80 100644 --- a/README.md +++ b/README.md @@ -145,7 +145,7 @@ https://www.bilibili.com/video/BV1Tv411t74v?p=4 ### 为什么选择 APIJSON? 前后端 关于接口的 开发、文档、联调 等 10 大痛点解析
https://github.com/Tencent/APIJSON/wiki - +




* **解决十大痛点** (APIJSON 大幅提振开发效率、强力杜绝联调扯皮、巧妙规避文档缺陷、非常节省流量带宽 等) * **开发提速巨大** (CRUD 零代码热更新自动化,APIJSONBoot 对比 SSM、SSH 等保守估计可提速 20 倍以上) * **腾讯官方开源** (使用 GitHub、Gitee、工蜂 等平台的官方账号开源,微信公众号、腾讯云+社区 等官方公告) @@ -162,7 +162,7 @@ https://github.com/Tencent/APIJSON/wiki * **兼容各种项目** (对各类 Web 框架集成友好且提供 SpringBoot, JFinal 的 Demo,协议不限 HTTP,与其它库无冲突) * **工程轻量小巧** (仅依赖 fastjson,Jar 仅 280KB,Java 文件仅 59 个共 13719 行代码,例如 APIJSONORM 4.3.1) * **多年持续迭代** (自 2016 年开源至今已连续 4 年 2000+ Commits、70+ Releases,不断更新迭代中...) - +




### 常见问题 #### 1.如何定制业务逻辑? From e493c6cb6ba6fae4e55028daff21684ee993754a Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Sun, 3 Jan 2021 16:31:51 +0800 Subject: [PATCH 0206/1181] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index cec2abf80..acb906e89 100644 --- a/README.md +++ b/README.md @@ -156,7 +156,7 @@ https://github.com/Tencent/APIJSON/wiki * **周边生态丰富** (Android, iOS, Web 等各种 Demo、继承 JSON 的海量生态、零代码 接口测试 和 单元测试 工具等) * **文档视频齐全** (项目介绍、快速上手、安装部署 等后端、前端、客户端的 图文解说、视频教程、代码注释 等) * **功能丰富强大** (增删改查、分页排序、分组聚合、各种 JOIN、各种子查询、跨库跨表、性能分析 等零代码实现) -* **使用安全简单** (自动增删改查、自动生成文档、自动管理版本、自动控制权限、自动校验参数、自动防SQL注入等) +* **使用安全简单** (自动增删改查、自动生成文档、自动管理版本、自动控制权限、自动校验参数、自动防 SQL 注入等) * **灵活定制业务** (在后端编写 远程函数,可以拿到 session、version、当前 JSON 对象 等,然后自定义处理) * **高质可靠代码** (代码工整规范、商业分析软件源伞 Pinpoint 代码扫描报告平均每行代码 bug 率低至 0.15%) * **兼容各种项目** (对各类 Web 框架集成友好且提供 SpringBoot, JFinal 的 Demo,协议不限 HTTP,与其它库无冲突) From 0d078a6b7337f2af9032ff3d087a38a3f5602068 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Sun, 3 Jan 2021 16:37:31 +0800 Subject: [PATCH 0207/1181] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index acb906e89..477cce4a8 100644 --- a/README.md +++ b/README.md @@ -158,7 +158,7 @@ https://github.com/Tencent/APIJSON/wiki * **功能丰富强大** (增删改查、分页排序、分组聚合、各种 JOIN、各种子查询、跨库跨表、性能分析 等零代码实现) * **使用安全简单** (自动增删改查、自动生成文档、自动管理版本、自动控制权限、自动校验参数、自动防 SQL 注入等) * **灵活定制业务** (在后端编写 远程函数,可以拿到 session、version、当前 JSON 对象 等,然后自定义处理) -* **高质可靠代码** (代码工整规范、商业分析软件源伞 Pinpoint 代码扫描报告平均每行代码 bug 率低至 0.15%) +* **高质可靠代码** (代码严谨规范,商业分析软件源伞 Pinpoint 代码扫描报告平均每行代码 Bug 率低至 0.15%) * **兼容各种项目** (对各类 Web 框架集成友好且提供 SpringBoot, JFinal 的 Demo,协议不限 HTTP,与其它库无冲突) * **工程轻量小巧** (仅依赖 fastjson,Jar 仅 280KB,Java 文件仅 59 个共 13719 行代码,例如 APIJSONORM 4.3.1) * **多年持续迭代** (自 2016 年开源至今已连续 4 年 2000+ Commits、70+ Releases,不断更新迭代中...) From cca1e6183f9c7fc81bba4e1c1825883d8e734111 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Sun, 3 Jan 2021 16:43:35 +0800 Subject: [PATCH 0208/1181] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 477cce4a8..5c5a6e748 100644 --- a/README.md +++ b/README.md @@ -161,7 +161,7 @@ https://github.com/Tencent/APIJSON/wiki * **高质可靠代码** (代码严谨规范,商业分析软件源伞 Pinpoint 代码扫描报告平均每行代码 Bug 率低至 0.15%) * **兼容各种项目** (对各类 Web 框架集成友好且提供 SpringBoot, JFinal 的 Demo,协议不限 HTTP,与其它库无冲突) * **工程轻量小巧** (仅依赖 fastjson,Jar 仅 280KB,Java 文件仅 59 个共 13719 行代码,例如 APIJSONORM 4.3.1) -* **多年持续迭代** (自 2016 年开源至今已连续 4 年 2000+ Commits、70+ Releases,不断更新迭代中...) +* **多年持续迭代** (自 2016 年开源至今已连续维护 4 年,累计 2000+ Commits、70+ Releases,不断更新迭代中...)




### 常见问题 From 9ba4707e255ab0fed923ee1d9f5fecc2bbf36f17 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Sun, 3 Jan 2021 16:54:22 +0800 Subject: [PATCH 0209/1181] Update README.md --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 5c5a6e748..f5482763b 100644 --- a/README.md +++ b/README.md @@ -145,7 +145,7 @@ https://www.bilibili.com/video/BV1Tv411t74v?p=4 ### 为什么选择 APIJSON? 前后端 关于接口的 开发、文档、联调 等 10 大痛点解析
https://github.com/Tencent/APIJSON/wiki -




+ * **解决十大痛点** (APIJSON 大幅提振开发效率、强力杜绝联调扯皮、巧妙规避文档缺陷、非常节省流量带宽 等) * **开发提速巨大** (CRUD 零代码热更新自动化,APIJSONBoot 对比 SSM、SSH 等保守估计可提速 20 倍以上) * **腾讯官方开源** (使用 GitHub、Gitee、工蜂 等平台的官方账号开源,微信公众号、腾讯云+社区 等官方公告) @@ -162,7 +162,7 @@ https://github.com/Tencent/APIJSON/wiki * **兼容各种项目** (对各类 Web 框架集成友好且提供 SpringBoot, JFinal 的 Demo,协议不限 HTTP,与其它库无冲突) * **工程轻量小巧** (仅依赖 fastjson,Jar 仅 280KB,Java 文件仅 59 个共 13719 行代码,例如 APIJSONORM 4.3.1) * **多年持续迭代** (自 2016 年开源至今已连续维护 4 年,累计 2000+ Commits、70+ Releases,不断更新迭代中...) -




+ ### 常见问题 #### 1.如何定制业务逻辑? From e78e071db5c52ebf5c5d6181b1940c424670e674 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Sun, 3 Jan 2021 16:54:49 +0800 Subject: [PATCH 0210/1181] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index f5482763b..3ad88a1ea 100644 --- a/README.md +++ b/README.md @@ -156,7 +156,7 @@ https://github.com/Tencent/APIJSON/wiki * **周边生态丰富** (Android, iOS, Web 等各种 Demo、继承 JSON 的海量生态、零代码 接口测试 和 单元测试 工具等) * **文档视频齐全** (项目介绍、快速上手、安装部署 等后端、前端、客户端的 图文解说、视频教程、代码注释 等) * **功能丰富强大** (增删改查、分页排序、分组聚合、各种 JOIN、各种子查询、跨库跨表、性能分析 等零代码实现) -* **使用安全简单** (自动增删改查、自动生成文档、自动管理版本、自动控制权限、自动校验参数、自动防 SQL 注入等) +* **使用安全简单** (自动增删改查、自动生成文档、自动管理版本、自动控制权限、自动校验参数、自动防SQL注入等) * **灵活定制业务** (在后端编写 远程函数,可以拿到 session、version、当前 JSON 对象 等,然后自定义处理) * **高质可靠代码** (代码严谨规范,商业分析软件源伞 Pinpoint 代码扫描报告平均每行代码 Bug 率低至 0.15%) * **兼容各种项目** (对各类 Web 框架集成友好且提供 SpringBoot, JFinal 的 Demo,协议不限 HTTP,与其它库无冲突) From d7b22b55c13d0ebf8bb912564a5d76c33e112c23 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Fri, 8 Jan 2021 17:21:42 +0800 Subject: [PATCH 0211/1181] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 3ad88a1ea..95dc875f8 100644 --- a/README.md +++ b/README.md @@ -222,7 +222,7 @@ https://github.com/Tencent/APIJSON/issues/187 - + From 1be62d0a8f43cefbec29789ec81b7e81eafd7b99 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Fri, 8 Jan 2021 17:25:19 +0800 Subject: [PATCH 0212/1181] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 95dc875f8..837d3676b 100644 --- a/README.md +++ b/README.md @@ -225,8 +225,8 @@ https://github.com/Tencent/APIJSON/issues/187 - +
From b92ad2e0f11f40bfd7f6a13ae83f4d215931fe9a Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Fri, 8 Jan 2021 17:38:37 +0800 Subject: [PATCH 0213/1181] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 837d3676b..3a91b4ad8 100644 --- a/README.md +++ b/README.md @@ -149,7 +149,7 @@ https://github.com/Tencent/APIJSON/wiki * **解决十大痛点** (APIJSON 大幅提振开发效率、强力杜绝联调扯皮、巧妙规避文档缺陷、非常节省流量带宽 等) * **开发提速巨大** (CRUD 零代码热更新自动化,APIJSONBoot 对比 SSM、SSH 等保守估计可提速 20 倍以上) * **腾讯官方开源** (使用 GitHub、Gitee、工蜂 等平台的官方账号开源,微信公众号、腾讯云+社区 等官方公告) -* **社区影响力大** (GitHub 9.3K Star 在 350W Java 项目中排名前 200,远超 FLAG, BAT 等国内外绝大部分开源项目) +* **社区影响力大** (GitHub 9.6K Star 在 350W Java 项目中排名前 150,远超 FLAG, BAT 等国内外绝大部分开源项目) * **各项荣誉成就** (腾讯开源五个第一、腾讯首个 GVP 获奖项目、腾讯后端项目 Star 第一、GitHub Java 周榜第一 等) * **多样用户案例** (腾讯内部用户包含 互娱、音乐、云与智慧,外部用户包含 500 强上市公司、数千亿资本国企 等) * **适用场景广泛** (社交聊天、阅读资讯、影音视频、办公学习 等各种 App、网站、公众号、小程序 等非金融类项目) From b48c5e655c5d65322263863629d657519ce5ef84 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Sat, 9 Jan 2021 21:10:46 +0800 Subject: [PATCH 0214/1181] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 3a91b4ad8..9abefdad3 100644 --- a/README.md +++ b/README.md @@ -226,7 +226,7 @@ https://github.com/Tencent/APIJSON/issues/187 - +
From 90f408817f856efd96bdffd9ebe4cc0e711be396 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Sat, 9 Jan 2021 21:12:48 +0800 Subject: [PATCH 0215/1181] Update README.md --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 9abefdad3..cf2c46a56 100644 --- a/README.md +++ b/README.md @@ -225,8 +225,8 @@ https://github.com/Tencent/APIJSON/issues/187 - - + +
From 451fbdf39128a34e299d85d9b370c584e0a9046d Mon Sep 17 00:00:00 2001 From: "ganyu.gy" Date: Tue, 12 Jan 2021 16:59:02 +0800 Subject: [PATCH 0216/1181] Optimize some code --- .../main/java/apijson/orm/AbstractParser.java | 8 ++------ APIJSONORM/src/main/java/apijson/orm/Logic.java | 16 ++++++++++------ 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java b/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java index 3ab9225b3..3168cf856 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java @@ -709,12 +709,8 @@ public JSONObject getStructure(@NotNull String table, String method, String tag, if (result != null) { // 加快下次查询,查到值的话组合情况其实是有限的,不属于恶意请求 if (versionedMap == null) { - versionedMap = new TreeMap<>(new Comparator() { - - @Override - public int compare(Integer o1, Integer o2) { - return o2 == null ? -1 : o2.compareTo(o1); // 降序 - } + versionedMap = new TreeMap<>((o1, o2) -> { + return o2 == null ? -1 : o2.compareTo(o1); // 降序 }); } diff --git a/APIJSONORM/src/main/java/apijson/orm/Logic.java b/APIJSONORM/src/main/java/apijson/orm/Logic.java index 593800270..b795ae961 100755 --- a/APIJSONORM/src/main/java/apijson/orm/Logic.java +++ b/APIJSONORM/src/main/java/apijson/orm/Logic.java @@ -108,12 +108,16 @@ public static int getType(char logicChar) { public static int getType(String logicChar) { int type = -1; if (logicChar != null && logicChar.length() == 1) { - if ("|".equals(logicChar)) { - type = 0; - } else if ("&".equals(logicChar)) { - type = 1; - } else if ("!".equals(logicChar)) { - type = 2; + switch (logicChar) { + case "|": + type = 0; + break; + case "&": + type = 1; + break; + case "!": + type = 2; + break; } } return type; From 1530864c259e1a46429e3a52af5b768daa9df4c4 Mon Sep 17 00:00:00 2001 From: "ganyu.gy" Date: Tue, 12 Jan 2021 17:03:02 +0800 Subject: [PATCH 0217/1181] Upgrade fastjson --- APIJSONORM/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/APIJSONORM/pom.xml b/APIJSONORM/pom.xml index 975c97d66..80ec99bd8 100755 --- a/APIJSONORM/pom.xml +++ b/APIJSONORM/pom.xml @@ -22,7 +22,7 @@ com.alibaba fastjson - 1.2.74 + 1.2.75 From 6c13d3211ca084cbc0df1fa099e2aff978ab1f10 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Tue, 12 Jan 2021 20:45:51 +0800 Subject: [PATCH 0218/1181] Update README.md --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index cf2c46a56..e1f27e704 100644 --- a/README.md +++ b/README.md @@ -211,6 +211,8 @@ https://github.com/Tencent/APIJSON/issues/36 测试及自动生成代码工具
[APIJSONTest.apk](http://files.cnblogs.com/files/tommylemon/APIJSONTest.apk) +### 开源许可 +使用 [Apache License 2.0](/LICENSE),对 公司、团队、个人 等 商用、非商用 都自由免费且非常友好,请放心使用和登记 ### 使用登记 如果您在使用 APIJSON,请让我们知道,您的使用对我们非常重要(按登记顺序排列):
From 538d3f3bbc7643730492897db35de24c6459778d Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Tue, 12 Jan 2021 20:48:20 +0800 Subject: [PATCH 0219/1181] Update Navigation.md --- Navigation.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Navigation.md b/Navigation.md index 5ad5cdc47..cbb730679 100644 --- a/Navigation.md +++ b/Navigation.md @@ -35,6 +35,8 @@ ### [下载客户端 App](/README.md#%E4%B8%8B%E8%BD%BD%E5%AE%A2%E6%88%B7%E7%AB%AF-app) +### [开源许可](/README.md#%E5%BC%80%E6%BA%90%E8%AE%B8%E5%8F%AF) + ### [使用登记](/README.md#%E4%BD%BF%E7%94%A8%E7%99%BB%E8%AE%B0) ### [贡献者们](/README.md#%E8%B4%A1%E7%8C%AE%E8%80%85%E4%BB%AC) From f8c1636568a06e8d92a5764f1d0d4a466965ff64 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Tue, 12 Jan 2021 20:58:56 +0800 Subject: [PATCH 0220/1181] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index e1f27e704..d54ae9a4b 100644 --- a/README.md +++ b/README.md @@ -17,7 +17,7 @@ This source code is licensed under the Apache License Version 2.0

- +       From 828b6ce7ab29303bb36be870557d5ee05d6df54f Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Tue, 12 Jan 2021 21:09:52 +0800 Subject: [PATCH 0221/1181] Update README.md --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index d54ae9a4b..971f748fa 100644 --- a/README.md +++ b/README.md @@ -245,6 +245,7 @@ https://github.com/Tencent/APIJSON/issues/187 + From 71ab87c6a5519eed4d7b79b15d5a346f594611cb Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Tue, 12 Jan 2021 21:12:12 +0800 Subject: [PATCH 0222/1181] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E4=BA=86=E4=B8=80?= =?UTF-8?q?=E4=B8=AA=E8=B4=A1=E7=8C=AE=E8=80=85=EF=BC=8C=E9=A6=96=E9=A1=B5?= =?UTF-8?q?=E5=B1=95=E7=A4=BA=E8=B6=85=E5=87=BA=E4=B8=80=E8=A1=8C=EF=BC=8C?= =?UTF-8?q?=E8=B0=83=E5=B0=8F=E5=A4=B4=E5=83=8F=E6=9D=A5=E6=9B=B4=E5=A5=BD?= =?UTF-8?q?=E5=9C=B0=E9=80=82=E9=85=8D=E5=B1=8F=E5=B9=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 46 +++++++++++++++++++++++----------------------- 1 file changed, 23 insertions(+), 23 deletions(-) diff --git a/README.md b/README.md index 971f748fa..7ae81ccf8 100644 --- a/README.md +++ b/README.md @@ -236,35 +236,35 @@ https://github.com/Tencent/APIJSON/issues/187 主项目 APIJSON 的贡献者们和 生态周边项目 的作者们:

- - - - - - - - - - - - + height="60" width="60" > + + + + + + + + + + + +
- + height="60" width="60" > + + height="60" width="60" > - + height="60" width="60" > + - + height="60" width="60" > + - - + height="60" width="60" > + +

感谢大家的贡献。 From 05dc3ebf44ab0d4d49d90068389b4e3cea70d86c Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Tue, 12 Jan 2021 21:14:52 +0800 Subject: [PATCH 0223/1181] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E4=B8=80=E4=B8=AA?= =?UTF-8?q?=E8=B4=A1=E7=8C=AE=E8=80=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CONTRIBUTING.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 64d12cf6a..656bf254a 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -18,6 +18,7 @@ - [jinzhongjian](https://github.com/jinzhongjian) - [CoolGeo2016](https://github.com/CoolGeo2016) - [1906522096](https://github.com/1906522096) +- [github-ganyu](https://github.com/github-ganyu) #### 其中特别致谢:
justinfengchen 提交的 6 个 Commits, 对 APIJSON 做出了 3,130 增加和 0 处删减(截止 2020/11/04 日);
From c94406e768e3d5617fe101ab15eef8188f005279 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Fri, 15 Jan 2021 23:53:55 +0800 Subject: [PATCH 0224/1181] Update README-English.md --- README-English.md | 66 ++++++++++++++++++++++++----------------------- 1 file changed, 34 insertions(+), 32 deletions(-) diff --git a/README-English.md b/README-English.md index 67223f81a..21b650aba 100644 --- a/README-English.md +++ b/README-English.md @@ -303,16 +303,17 @@ If you have any questions or suggestions, you can [create an issue](https://gith ### Users of this project:
- - - - - - - - - - + + + + + + + + + + +
[More APIJSON Users](https://github.com/Tencent/APIJSON/issues/73) @@ -321,34 +322,35 @@ If you have any questions or suggestions, you can [create an issue](https://gith Here are the contributers of this project and authors of other projects for ecosystem of APIJSON:
- - - - - - - - - - - + height="60" width="60" > + + + + + + + + + + + +
- + height="60" width="60" > + + height="60" width="60" > - + height="60" width="60" > + - + height="60" width="60" > + - - + height="60" width="60" > + +

Thanks to all contributers of APIJSON! From 0bde019ba6857efef536b82f45e601c9b39d366f Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Sat, 16 Jan 2021 14:08:18 +0800 Subject: [PATCH 0225/1181] Update README.md --- README.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 7ae81ccf8..4c196c4e7 100644 --- a/README.md +++ b/README.md @@ -290,7 +290,11 @@ https://github.com/Tencent/APIJSON/blob/master/Roadmap.md 如果你解决了某些bug,或者新增了一些功能,欢迎 [贡献代码](https://github.com/Tencent/APIJSON/pulls),感激不尽~
https://github.com/Tencent/APIJSON/blob/master/CONTRIBUTING.md -QQ 技术群: 734652054(新)、607020115(旧) +QQ 技术群: 734652054(新)、607020115(旧) + +如果你为 APIJSON [提交了 PR 且被合并](https://github.com/Tencent/APIJSON/pull/94)、[提交了优质 Issue](https://github.com/Tencent/APIJSON/issues/189)、[发表了优质文章](https://blog.csdn.net/qq_41829492/article/details/88670940) 或 [登记了企业或项目](https://github.com/Tencent/APIJSON/issues/187),
+贡献者微信群,注意联系 LonelyExplorer,加好友描述中附上贡献链接,谢谢 + ### 相关推荐 [APIJSON, 让接口和文档见鬼去吧!](https://my.oschina.net/tommylemon/blog/805459) From 15522c7ea18a59b1c9daad813283030164a9c773 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Sat, 16 Jan 2021 14:10:39 +0800 Subject: [PATCH 0226/1181] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 4c196c4e7..525f0d137 100644 --- a/README.md +++ b/README.md @@ -292,7 +292,7 @@ https://github.com/Tencent/APIJSON/blob/master/CONTRIBUTING.md QQ 技术群: 734652054(新)、607020115(旧) -如果你为 APIJSON [提交了 PR 且被合并](https://github.com/Tencent/APIJSON/pull/94)、[提交了优质 Issue](https://github.com/Tencent/APIJSON/issues/189)、[发表了优质文章](https://blog.csdn.net/qq_41829492/article/details/88670940) 或 [登记了企业或项目](https://github.com/Tencent/APIJSON/issues/187),
+如果你为 APIJSON [提交了 PR 且被合并](https://github.com/Tencent/APIJSON/pull/94)、[提交了优质 Issue](https://github.com/Tencent/APIJSON/issues/189)、[发表了优质文章](https://blog.csdn.net/qq_41829492/article/details/88670940) 或 [登记了你的公司](https://github.com/Tencent/APIJSON/issues/187),
贡献者微信群,注意联系 LonelyExplorer,加好友描述中附上贡献链接,谢谢 From ad95f7d876b8f4c25b27d6d5292dcff7f4454210 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Sat, 16 Jan 2021 14:13:03 +0800 Subject: [PATCH 0227/1181] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 525f0d137..fbcd32851 100644 --- a/README.md +++ b/README.md @@ -292,7 +292,7 @@ https://github.com/Tencent/APIJSON/blob/master/CONTRIBUTING.md QQ 技术群: 734652054(新)、607020115(旧) -如果你为 APIJSON [提交了 PR 且被合并](https://github.com/Tencent/APIJSON/pull/94)、[提交了优质 Issue](https://github.com/Tencent/APIJSON/issues/189)、[发表了优质文章](https://blog.csdn.net/qq_41829492/article/details/88670940) 或 [登记了你的公司](https://github.com/Tencent/APIJSON/issues/187),
+如果你为 APIJSON [提交了 PR 且被合并](https://github.com/Tencent/APIJSON/pull/92)、[提交了优质 Issue](https://github.com/Tencent/APIJSON/issues/189)、[发表了优质文章](https://blog.csdn.net/qq_41829492/article/details/88670940) 或 [登记了你的公司](https://github.com/Tencent/APIJSON/issues/187),
贡献者微信群,注意联系 LonelyExplorer,加好友描述中附上贡献链接,谢谢 From 06822beeb47202e05b7274feef58e0e79355f469 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Sat, 16 Jan 2021 14:20:45 +0800 Subject: [PATCH 0228/1181] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index fbcd32851..cdd439fbd 100644 --- a/README.md +++ b/README.md @@ -292,7 +292,7 @@ https://github.com/Tencent/APIJSON/blob/master/CONTRIBUTING.md QQ 技术群: 734652054(新)、607020115(旧) -如果你为 APIJSON [提交了 PR 且被合并](https://github.com/Tencent/APIJSON/pull/92)、[提交了优质 Issue](https://github.com/Tencent/APIJSON/issues/189)、[发表了优质文章](https://blog.csdn.net/qq_41829492/article/details/88670940) 或 [登记了你的公司](https://github.com/Tencent/APIJSON/issues/187),
+如果你为 APIJSON [提交了 PR 且被合并](https://github.com/Tencent/APIJSON/pull/92)、[提交了优质 Issue](https://github.com/Tencent/APIJSON/issues/189)、[发表了优质文章](https://blog.csdn.net/qq_41829492/article/details/88670940) 或 [登记了你的公司](https://github.com/Tencent/APIJSON/issues/187),可以加
贡献者微信群,注意联系 LonelyExplorer,加好友描述中附上贡献链接,谢谢 From b64e7601fa34c768d534b9f012d3731941096aa2 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Sat, 16 Jan 2021 14:25:44 +0800 Subject: [PATCH 0229/1181] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index cdd439fbd..3b59d3e28 100644 --- a/README.md +++ b/README.md @@ -292,7 +292,7 @@ https://github.com/Tencent/APIJSON/blob/master/CONTRIBUTING.md QQ 技术群: 734652054(新)、607020115(旧) -如果你为 APIJSON [提交了 PR 且被合并](https://github.com/Tencent/APIJSON/pull/92)、[提交了优质 Issue](https://github.com/Tencent/APIJSON/issues/189)、[发表了优质文章](https://blog.csdn.net/qq_41829492/article/details/88670940) 或 [登记了你的公司](https://github.com/Tencent/APIJSON/issues/187),可以加
+如果你为 APIJSON [提交了 PR 且被合并](https://github.com/Tencent/APIJSON/pull/92)、[提交了优质 Issue](https://github.com/Tencent/APIJSON/issues/189)、[发表了优质文章](https://blog.csdn.net/qq_41829492/article/details/88670940)、[开发了可用的生态项目](https://github.com/zhangchunlin/uliweb-apijson), 或 [登记了你的公司](https://github.com/Tencent/APIJSON/issues/187),可以加
贡献者微信群,注意联系 LonelyExplorer,加好友描述中附上贡献链接,谢谢 From 57057244719c848b7af8e29801f6e99cadc537f4 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Sat, 16 Jan 2021 14:27:35 +0800 Subject: [PATCH 0230/1181] Update README.md --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 3b59d3e28..2a490bfa0 100644 --- a/README.md +++ b/README.md @@ -292,7 +292,8 @@ https://github.com/Tencent/APIJSON/blob/master/CONTRIBUTING.md QQ 技术群: 734652054(新)、607020115(旧) -如果你为 APIJSON [提交了 PR 且被合并](https://github.com/Tencent/APIJSON/pull/92)、[提交了优质 Issue](https://github.com/Tencent/APIJSON/issues/189)、[发表了优质文章](https://blog.csdn.net/qq_41829492/article/details/88670940)、[开发了可用的生态项目](https://github.com/zhangchunlin/uliweb-apijson), 或 [登记了你的公司](https://github.com/Tencent/APIJSON/issues/187),可以加
+如果你为 APIJSON 做出了以下任何一个贡献: +[提交了 PR 且被合并](https://github.com/Tencent/APIJSON/pull/92)、[提交了优质 Issue](https://github.com/Tencent/APIJSON/issues/189)、[发表了优质文章](https://blog.csdn.net/qq_41829492/article/details/88670940)、[开发了可用的生态项目](https://github.com/zhangchunlin/uliweb-apijson) 或 [登记了你的公司](https://github.com/Tencent/APIJSON/issues/187),可以加
贡献者微信群,注意联系 LonelyExplorer,加好友描述中附上贡献链接,谢谢 From 05917c19d4479a8dcea10980fdbc513708757729 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Sat, 16 Jan 2021 14:28:04 +0800 Subject: [PATCH 0231/1181] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 2a490bfa0..0041f2b18 100644 --- a/README.md +++ b/README.md @@ -292,7 +292,7 @@ https://github.com/Tencent/APIJSON/blob/master/CONTRIBUTING.md QQ 技术群: 734652054(新)、607020115(旧) -如果你为 APIJSON 做出了以下任何一个贡献: +如果你为 APIJSON 做出了以下任何一个贡献:
[提交了 PR 且被合并](https://github.com/Tencent/APIJSON/pull/92)、[提交了优质 Issue](https://github.com/Tencent/APIJSON/issues/189)、[发表了优质文章](https://blog.csdn.net/qq_41829492/article/details/88670940)、[开发了可用的生态项目](https://github.com/zhangchunlin/uliweb-apijson) 或 [登记了你的公司](https://github.com/Tencent/APIJSON/issues/187),可以加
贡献者微信群,注意联系 LonelyExplorer,加好友描述中附上贡献链接,谢谢 From 1f529277fa151e3ff3b73af4781f64e116ff24ac Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Sun, 17 Jan 2021 00:14:10 +0800 Subject: [PATCH 0232/1181] =?UTF-8?q?fastjson=20=E7=89=88=E6=9C=AC?= =?UTF-8?q?=E9=99=8D=E5=88=B0=201.2.74=EF=BC=9B=E9=A1=B9=E7=9B=AE=E7=89=88?= =?UTF-8?q?=E6=9C=AC=E5=8D=87=E5=88=B0=204.5.1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- APIJSONORM/pom.xml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/APIJSONORM/pom.xml b/APIJSONORM/pom.xml index 80ec99bd8..7d280e34d 100755 --- a/APIJSONORM/pom.xml +++ b/APIJSONORM/pom.xml @@ -5,13 +5,12 @@ apijson.orm apijson-orm - 4.4.8 + 4.5.1 jar APIJSONORM APIJSON ORM Library - UTF-8 UTF-8 @@ -19,10 +18,11 @@ + com.alibaba fastjson - 1.2.75 + 1.2.74 From 8fd88e0af62cb164ec3f07b4dcdf765f256b79bb Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Sun, 17 Jan 2021 01:54:03 +0800 Subject: [PATCH 0233/1181] =?UTF-8?q?=E8=A7=A3=E5=86=B3=E9=AB=98=E5=B9=B6?= =?UTF-8?q?=E5=8F=91=E6=83=85=E5=86=B5=E4=B8=8B=E5=8F=82=E6=95=B0=E6=A0=A1?= =?UTF-8?q?=E9=AA=8C=E8=A7=84=E5=88=99=E5=8F=AF=E8=83=BD=E5=9B=A0=E4=B8=BA?= =?UTF-8?q?=E6=89=A7=E8=A1=8C=E8=BF=87=E7=A8=8B=20remove=20=E4=BA=86?= =?UTF-8?q?=E9=83=A8=E5=88=86=E6=93=8D=E4=BD=9C=E5=85=B3=E9=94=AE=E8=AF=8D?= =?UTF-8?q?=E5=AF=BC=E8=87=B4=E6=A0=A1=E9=AA=8C=E5=87=BA=E9=94=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/apijson/orm/AbstractVerifier.java | 365 +++++++++--------- 1 file changed, 175 insertions(+), 190 deletions(-) diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractVerifier.java b/APIJSONORM/src/main/java/apijson/orm/AbstractVerifier.java index d7c796e33..2e14f0881 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractVerifier.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractVerifier.java @@ -82,6 +82,9 @@ public abstract class AbstractVerifier implements Verifier, IdCallback { private static final String TAG = "AbstractVerifier"; + // 共享 STRUCTURE_MAP 则不能 remove 等做任何变更,否则在并发情况下可能会出错,加锁效率又低,所以这里改为忽略对应的 key + public static final List OPERATION_KEY_LIST; + // > // > @NotNull @@ -97,6 +100,21 @@ public abstract class AbstractVerifier implements Verifier, IdCallback { @NotNull public static final Map COMPILE_MAP; static { + OPERATION_KEY_LIST = new ArrayList<>(); + OPERATION_KEY_LIST.add(TYPE.name()); + OPERATION_KEY_LIST.add(VERIFY.name()); + OPERATION_KEY_LIST.add(INSERT.name()); + OPERATION_KEY_LIST.add(UPDATE.name()); + OPERATION_KEY_LIST.add(REPLACE.name()); + OPERATION_KEY_LIST.add(EXIST.name()); + OPERATION_KEY_LIST.add(UNIQUE.name()); + OPERATION_KEY_LIST.add(REMOVE.name()); + OPERATION_KEY_LIST.add(MUST.name()); + OPERATION_KEY_LIST.add(REFUSE.name()); + OPERATION_KEY_LIST.add(NECESSARY.name()); + OPERATION_KEY_LIST.add(DISALLOW.name()); + + SYSTEM_ACCESS_MAP = new HashMap>(); SYSTEM_ACCESS_MAP.put(Access.class.getSimpleName(), getAccessMap(Access.class.getAnnotation(MethodAccess.class))); @@ -742,7 +760,7 @@ public static JSONObject parse(@NotNull final RequestMethod method, String name, return null; } - //获取配置<<<<<<<<<<<<<<<<<<<<<<<<<<<< + // 获取配置<<<<<<<<<<<<<<<<<<<<<<<<<<<< JSONObject type = target.getJSONObject(TYPE.name()); JSONObject verify = target.getJSONObject(VERIFY.name()); JSONObject insert = target.getJSONObject(INSERT.name()); @@ -757,238 +775,204 @@ public static JSONObject parse(@NotNull final RequestMethod method, String name, String necessary = StringUtil.getNoBlankString(target.getString(NECESSARY.name())); String disallow = StringUtil.getNoBlankString(target.getString(DISALLOW.name())); - target.remove(TYPE.name()); - target.remove(VERIFY.name()); - target.remove(INSERT.name()); - target.remove(UPDATE.name()); - target.remove(REPLACE.name()); - - target.remove(EXIST.name()); - target.remove(UNIQUE.name()); - target.remove(REMOVE.name()); - target.remove(MUST.name()); - target.remove(REFUSE.name()); - target.remove(NECESSARY.name()); - target.remove(DISALLOW.name()); - - - try { // 避免中间抛异常导致 target 未 put 进 remove 掉的键值对 - //移除字段<<<<<<<<<<<<<<<<<<< - String[] removes = StringUtil.split(remove); - if (removes != null && removes.length > 0) { - for (String r : removes) { - real.remove(r); - } + // 移除字段<<<<<<<<<<<<<<<<<<< + String[] removes = StringUtil.split(remove); + if (removes != null && removes.length > 0) { + for (String r : removes) { + real.remove(r); } - //移除字段>>>>>>>>>>>>>>>>>>> - - //判断必要字段是否都有<<<<<<<<<<<<<<<<<<< - String[] musts = StringUtil.split(must); - List mustList = musts == null ? new ArrayList() : Arrays.asList(musts); - for (String s : mustList) { - if (real.get(s) == null) {//可能传null进来,这里还会通过 real.containsKey(s) == false) { - throw new IllegalArgumentException(method + "请求," + name - + " 里面不能缺少 " + s + " 等[" + must + "]内的任何字段!"); - } + } + // 移除字段>>>>>>>>>>>>>>>>>>> + + // 判断必要字段是否都有<<<<<<<<<<<<<<<<<<< + String[] musts = StringUtil.split(must); + List mustList = musts == null ? new ArrayList() : Arrays.asList(musts); + for (String s : mustList) { + if (real.get(s) == null) { // 可能传null进来,这里还会通过 real.containsKey(s) == false) { + throw new IllegalArgumentException(method + "请求," + name + + " 里面不能缺少 " + s + " 等[" + must + "]内的任何字段!"); } + } - String[] necessarys = StringUtil.split(necessary); - List necessaryList = necessarys == null ? new ArrayList() : Arrays.asList(necessarys); - for (String s : necessaryList) { - if (real.get(s) == null) {//可能传null进来,这里还会通过 real.containsKey(s) == false) { - throw new IllegalArgumentException(method + "请求," + name - + " 里面不能缺少 " + s + " 等[" + necessary + "]内的任何字段!"); - } + String[] necessarys = StringUtil.split(necessary); + List necessaryList = necessarys == null ? new ArrayList() : Arrays.asList(necessarys); + for (String s : necessaryList) { + if (real.get(s) == null) {//可能传null进来,这里还会通过 real.containsKey(s) == false) { + throw new IllegalArgumentException(method + "请求," + name + + " 里面不能缺少 " + s + " 等[" + necessary + "]内的任何字段!"); } - //判断必要字段是否都有>>>>>>>>>>>>>>>>>>> - + } + //判断必要字段是否都有>>>>>>>>>>>>>>>>>>> - Set objKeySet = new HashSet(); //不能用tableKeySet,仅判断 Table:{} 会导致 key:{ Table:{} } 绕过判断 - //解析内容<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< + Set objKeySet = new HashSet(); //不能用tableKeySet,仅判断 Table:{} 会导致 key:{ Table:{} } 绕过判断 - Set> set = new LinkedHashSet<>(target.entrySet()); - if (set.isEmpty() == false) { + //解析内容<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< - String key; - Object tvalue; - Object rvalue; - for (Entry entry : set) { - key = entry == null ? null : entry.getKey(); - if (key == null) { - continue; - } - tvalue = entry.getValue(); - rvalue = real.get(key); - if (callback.onParse(key, tvalue, rvalue) == false) { - continue; - } + Set> set = new LinkedHashSet<>(target.entrySet()); + if (set.isEmpty() == false) { - if (tvalue instanceof JSONObject) { //JSONObject,往下一级提取 - if (rvalue != null && rvalue instanceof JSONObject == false) { - throw new UnsupportedDataTypeException(key + ":value 的value不合法!类型必须是 OBJECT ,结构为 {} !"); - } - tvalue = callback.onParseJSONObject(key, (JSONObject) tvalue, (JSONObject) rvalue); + String key; + Object tvalue; + Object rvalue; + for (Entry entry : set) { + key = entry == null ? null : entry.getKey(); + if (key == null || OPERATION_KEY_LIST.contains(key)) { + continue; + } + tvalue = entry.getValue(); + rvalue = real.get(key); + if (callback.onParse(key, tvalue, rvalue) == false) { + continue; + } - objKeySet.add(key); - } else if (tvalue instanceof JSONArray) { //JSONArray - if (rvalue != null && rvalue instanceof JSONArray == false) { - throw new UnsupportedDataTypeException(key + ":value 的value不合法!类型必须是 ARRAY ,结构为 [] !"); - } - tvalue = callback.onParseJSONArray(key, (JSONArray) tvalue, (JSONArray) rvalue); + if (tvalue instanceof JSONObject) { //JSONObject,往下一级提取 + if (rvalue != null && rvalue instanceof JSONObject == false) { + throw new UnsupportedDataTypeException(key + ":value 的value不合法!类型必须是 OBJECT ,结构为 {} !"); + } + tvalue = callback.onParseJSONObject(key, (JSONObject) tvalue, (JSONObject) rvalue); - if ((method == RequestMethod.POST || method == RequestMethod.PUT) && JSONRequest.isArrayKey(key)) { - objKeySet.add(key); - } - } else {//其它Object - tvalue = callback.onParseObject(key, tvalue, rvalue); + objKeySet.add(key); + } else if (tvalue instanceof JSONArray) { //JSONArray + if (rvalue != null && rvalue instanceof JSONArray == false) { + throw new UnsupportedDataTypeException(key + ":value 的value不合法!类型必须是 ARRAY ,结构为 [] !"); } + tvalue = callback.onParseJSONArray(key, (JSONArray) tvalue, (JSONArray) rvalue); - if (tvalue != null) {//可以在target中加上一些不需要客户端传的键值对 - real.put(key, tvalue); + if ((method == RequestMethod.POST || method == RequestMethod.PUT) && JSONRequest.isArrayKey(key)) { + objKeySet.add(key); } + } else {//其它Object + tvalue = callback.onParseObject(key, tvalue, rvalue); } + if (tvalue != null) {//可以在target中加上一些不需要客户端传的键值对 + real.put(key, tvalue); + } } - //解析内容>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> + } + //解析内容>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> - Set rkset = real.keySet(); //解析内容并没有改变rkset - //解析不允许的字段<<<<<<<<<<<<<<<<<<< - List refuseList = new ArrayList(); - if ("!".equals(refuse)) {//所有非 must,改成 !must 更好 - for (String key : rkset) {//对@key放行,@role,@column,自定义@position等 - if (key != null && key.startsWith("@") == false - && mustList.contains(key) == false && objKeySet.contains(key) == false) { - refuseList.add(key); - } - } - } else { - String[] refuses = StringUtil.split(refuse); - if (refuses != null && refuses.length > 0) { - refuseList.addAll(Arrays.asList(refuses)); + Set rkset = real.keySet(); //解析内容并没有改变rkset + + //解析不允许的字段<<<<<<<<<<<<<<<<<<< + List refuseList = new ArrayList(); + if ("!".equals(refuse)) {//所有非 must,改成 !must 更好 + for (String key : rkset) {//对@key放行,@role,@column,自定义@position等 + if (key != null && key.startsWith("@") == false + && mustList.contains(key) == false && objKeySet.contains(key) == false) { + refuseList.add(key); } } + } else { + String[] refuses = StringUtil.split(refuse); + if (refuses != null && refuses.length > 0) { + refuseList.addAll(Arrays.asList(refuses)); + } + } - List disallowList = new ArrayList(); - if ("!".equals(disallow)) {//所有非necessary,改成 !necessary 更好 - for (String key : rkset) {//对@key放行,@role,@column,自定义@position等 - if (key != null && key.startsWith("@") == false - && necessaryList.contains(key) == false && objKeySet.contains(key) == false) { - disallowList.add(key); - } - } - } else { - String[] disallows = StringUtil.split(disallow); - if (disallows != null && disallows.length > 0) { - disallowList.addAll(Arrays.asList(disallows)); + List disallowList = new ArrayList(); + if ("!".equals(disallow)) {//所有非necessary,改成 !necessary 更好 + for (String key : rkset) {//对@key放行,@role,@column,自定义@position等 + if (key != null && key.startsWith("@") == false + && necessaryList.contains(key) == false && objKeySet.contains(key) == false) { + disallowList.add(key); } } - //解析不允许的字段>>>>>>>>>>>>>>>>>>> + } else { + String[] disallows = StringUtil.split(disallow); + if (disallows != null && disallows.length > 0) { + disallowList.addAll(Arrays.asList(disallows)); + } + } + //解析不允许的字段>>>>>>>>>>>>>>>>>>> - //判断不允许传的key<<<<<<<<<<<<<<<<<<<<<<<<< - for (String rk : rkset) { - if (refuseList.contains(rk)) { //不允许的字段 - throw new IllegalArgumentException(method + "请求," + name - + " 里面不允许传 " + rk + " 等" + StringUtil.getString(refuseList) + "内的任何字段!"); - } - if (disallowList.contains(rk)) { //不允许的字段 - throw new IllegalArgumentException(method + "请求," + name - + " 里面不允许传 " + rk + " 等" + StringUtil.getString(disallowList) + "内的任何字段!"); - } + //判断不允许传的key<<<<<<<<<<<<<<<<<<<<<<<<< + for (String rk : rkset) { + if (refuseList.contains(rk)) { //不允许的字段 + throw new IllegalArgumentException(method + "请求," + name + + " 里面不允许传 " + rk + " 等" + StringUtil.getString(refuseList) + "内的任何字段!"); + } + if (disallowList.contains(rk)) { //不允许的字段 + throw new IllegalArgumentException(method + "请求," + name + + " 里面不允许传 " + rk + " 等" + StringUtil.getString(disallowList) + "内的任何字段!"); + } - if (rk == null) { //无效的key - real.remove(rk); - continue; - } + if (rk == null) { //无效的key + real.remove(rk); + continue; + } - Object rv = real.get(rk); + Object rv = real.get(rk); - //不允许传远程函数,只能后端配置 - if (rk.endsWith("()") && rv instanceof String) { - throw new UnsupportedOperationException(method + " 请求," + rk + " 不合法!非开放请求不允许传远程函数 key():\"fun()\" !"); - } + //不允许传远程函数,只能后端配置 + if (rk.endsWith("()") && rv instanceof String) { + throw new UnsupportedOperationException(method + " 请求," + rk + " 不合法!非开放请求不允许传远程函数 key():\"fun()\" !"); + } - //不在target内的 key:{} - if (rk.startsWith("@") == false && objKeySet.contains(rk) == false) { - if (rv instanceof JSONObject) { - throw new UnsupportedOperationException(method + " 请求," +name + " 里面不允许传 " + rk + ":{} !"); - } - if ((method == RequestMethod.POST || method == RequestMethod.PUT) && rv instanceof JSONArray && JSONRequest.isArrayKey(rk)) { - throw new UnsupportedOperationException(method + " 请求," + name + " 里面不允许 " + rk + ":[] 等未定义的 Table[]:[{}] 批量操作键值对!"); - } + //不在target内的 key:{} + if (rk.startsWith("@") == false && objKeySet.contains(rk) == false) { + if (rv instanceof JSONObject) { + throw new UnsupportedOperationException(method + " 请求," +name + " 里面不允许传 " + rk + ":{} !"); + } + if ((method == RequestMethod.POST || method == RequestMethod.PUT) && rv instanceof JSONArray && JSONRequest.isArrayKey(rk)) { + throw new UnsupportedOperationException(method + " 请求," + name + " 里面不允许 " + rk + ":[] 等未定义的 Table[]:[{}] 批量操作键值对!"); } } - //判断不允许传的key>>>>>>>>>>>>>>>>>>>>>>>>> + } + //判断不允许传的key>>>>>>>>>>>>>>>>>>>>>>>>> - //校验与修改Request<<<<<<<<<<<<<<<<< - //在tableKeySet校验后操作,避免 导致put/add进去的Table 被当成原Request的内容 - real = operate(TYPE, type, real, creator); - real = operate(VERIFY, verify, real, creator); - real = operate(INSERT, insert, real, creator); - real = operate(UPDATE, update, real, creator); - real = operate(REPLACE, replace, real, creator); - //校验与修改Request>>>>>>>>>>>>>>>>> + //校验与修改Request<<<<<<<<<<<<<<<<< + //在tableKeySet校验后操作,避免 导致put/add进去的Table 被当成原Request的内容 + real = operate(TYPE, type, real, creator); + real = operate(VERIFY, verify, real, creator); + real = operate(INSERT, insert, real, creator); + real = operate(UPDATE, update, real, creator); + real = operate(REPLACE, replace, real, creator); + //校验与修改Request>>>>>>>>>>>>>>>>> - String db = real.getString(apijson.JSONObject.KEY_DATABASE); - String sh = real.getString(apijson.JSONObject.KEY_SCHEMA); - if (StringUtil.isEmpty(db, false)) { - db = database; - } - if (StringUtil.isEmpty(sh, false)) { - sh = schema; - } - String idKey = idCallback == null ? null : idCallback.getIdKey(db, sh, name); - String finalIdKey = StringUtil.isEmpty(idKey, false) ? apijson.JSONObject.KEY_ID : idKey; - - //TODO放在operate前?考虑性能、operate修改后再验证的值是否和原来一样 - //校验存在<<<<<<<<<<<<<<<<<<< TODO 格式改为 id;version,tag 兼容多个字段联合主键 - String[] exists = StringUtil.split(exist); - if (exists != null && exists.length > 0) { - long exceptId = real.getLongValue(finalIdKey); - for (String e : exists) { - verifyExist(name, e, real.get(e), exceptId, creator); - } - } - //校验存在>>>>>>>>>>>>>>>>>>> - - //TODO放在operate前?考虑性能、operate修改后再验证的值是否和原来一样 - //校验重复<<<<<<<<<<<<<<<<<<< TODO 格式改为 id;version,tag 兼容多个字段联合主键 - String[] uniques = StringUtil.split(unique); - if (uniques != null && uniques.length > 0) { - long exceptId = real.getLongValue(finalIdKey); - for (String u : uniques) { - verifyRepeat(name, u, real.get(u), exceptId, finalIdKey, creator); - } - } - //校验重复>>>>>>>>>>>>>>>>>>> + String db = real.getString(apijson.JSONObject.KEY_DATABASE); + String sh = real.getString(apijson.JSONObject.KEY_SCHEMA); + if (StringUtil.isEmpty(db, false)) { + db = database; + } + if (StringUtil.isEmpty(sh, false)) { + sh = schema; + } + String idKey = idCallback == null ? null : idCallback.getIdKey(db, sh, name); + String finalIdKey = StringUtil.isEmpty(idKey, false) ? apijson.JSONObject.KEY_ID : idKey; + //TODO放在operate前?考虑性能、operate修改后再验证的值是否和原来一样 + //校验存在<<<<<<<<<<<<<<<<<<< TODO 格式改为 id;version,tag 兼容多个字段联合主键 + String[] exists = StringUtil.split(exist); + if (exists != null && exists.length > 0) { + long exceptId = real.getLongValue(finalIdKey); + for (String e : exists) { + verifyExist(name, e, real.get(e), exceptId, creator); + } } - finally { - //还原 <<<<<<<<<< - target.put(TYPE.name(), type); - target.put(VERIFY.name(), verify); - target.put(INSERT.name(), insert); - target.put(UPDATE.name(), update); - target.put(REPLACE.name(), replace); - - target.put(EXIST.name(), exist); - target.put(UNIQUE.name(), unique); - target.put(REMOVE.name(), remove); - target.put(MUST.name(), must); - target.put(REFUSE.name(), refuse); - target.put(NECESSARY.name(), necessary); - target.put(DISALLOW.name(), disallow); - //还原 >>>>>>>>>> + //校验存在>>>>>>>>>>>>>>>>>>> + + //TODO放在operate前?考虑性能、operate修改后再验证的值是否和原来一样 + //校验重复<<<<<<<<<<<<<<<<<<< TODO 格式改为 id;version,tag 兼容多个字段联合主键 + String[] uniques = StringUtil.split(unique); + if (uniques != null && uniques.length > 0) { + long exceptId = real.getLongValue(finalIdKey); + for (String u : uniques) { + verifyRepeat(name, u, real.get(u), exceptId, finalIdKey, creator); + } } + //校验重复>>>>>>>>>>>>>>>>>>> + Log.i(TAG, "parse return real = " + JSON.toJSONString(real)); return real; @@ -1019,9 +1003,10 @@ private static JSONObject operate(Operation opt, JSONObject targetChild, JSONObj for (Entry e : set) { tk = e == null ? null : e.getKey(); - if (tk == null) { + if (tk == null || OPERATION_KEY_LIST.contains(tk)) { continue; } + tv = e.getValue(); if (opt == TYPE) { From 7c3171934799022b69bdc9f494b1bf3d142e8665 Mon Sep 17 00:00:00 2001 From: Xiaoguang Sun Date: Wed, 20 Jan 2021 12:20:41 +0800 Subject: [PATCH 0234/1181] Add javax.activation dependencies to pom.xml Add javax.activation dependencies to pom.xml so it could be built with newer version of JDK starting from 11 which removed the deprecated Java EE and CORBA Modules according to JEP 320 Signed-off-by: Xiaoguang Sun --- APIJSONORM/pom.xml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/APIJSONORM/pom.xml b/APIJSONORM/pom.xml index 7d280e34d..6802c6753 100755 --- a/APIJSONORM/pom.xml +++ b/APIJSONORM/pom.xml @@ -24,6 +24,11 @@ fastjson 1.2.74 + + javax.activation + activation + 1.1.1 + From 2d7222e8aeb16f6ff1a36afc4b7aecfa3d0e914b Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Wed, 20 Jan 2021 14:22:42 +0800 Subject: [PATCH 0235/1181] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E4=B8=80=E4=B8=AA?= =?UTF-8?q?=E6=9D=A5=E8=87=AA=E7=9F=A5=E4=B9=8E=E7=9A=84=E8=B4=A1=E7=8C=AE?= =?UTF-8?q?=E8=80=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 0041f2b18..73b68f069 100644 --- a/README.md +++ b/README.md @@ -245,6 +245,7 @@ https://github.com/Tencent/APIJSON/issues/187 + From d9dba9f140fe2420b7a4ed4e6f1985bf2f84d13c Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Wed, 20 Jan 2021 14:25:45 +0800 Subject: [PATCH 0236/1181] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E4=B8=80=E4=B8=AA?= =?UTF-8?q?=E6=9D=A5=E8=87=AA=E7=9F=A5=E4=B9=8E=E7=9A=84=E8=B4=A1=E7=8C=AE?= =?UTF-8?q?=E8=80=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CONTRIBUTING.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 656bf254a..af41fa23b 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -19,6 +19,7 @@ - [CoolGeo2016](https://github.com/CoolGeo2016) - [1906522096](https://github.com/1906522096) - [github-ganyu](https://github.com/github-ganyu) +- [sunxiaoguang](https://github.com/sunxiaoguang) #### 其中特别致谢:
justinfengchen 提交的 6 个 Commits, 对 APIJSON 做出了 3,130 增加和 0 处删减(截止 2020/11/04 日);
From c479e3bfc27dddda65b699d8927568dbc76c06c2 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Thu, 21 Jan 2021 15:47:53 +0800 Subject: [PATCH 0237/1181] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 73b68f069..9877451d9 100644 --- a/README.md +++ b/README.md @@ -32,7 +32,7 @@ This source code is licensed under the Apache License Version 2.0
English  通用文档 视频教程 - 在线体验 + 在线体验

From d48cbb369ca9e6c48425cbf5e301ac74a5058ec7 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Thu, 21 Jan 2021 16:31:44 +0800 Subject: [PATCH 0238/1181] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 9877451d9..6d9909813 100644 --- a/README.md +++ b/README.md @@ -276,7 +276,7 @@ https://github.com/Tencent/APIJSON/issues/187 https://github.com/Tencent/APIJSON/blob/master/Roadmap.md 理论上所有支持 SQL 与 JDBC/ODBC 的软件,都可以用本项目对接 CRUD,待测试:
-[DB2](https://www.ibm.com/support/knowledgecenter/SSEPGG_11.1.0/com.ibm.db2.luw.sql.ref.doc/doc/r0059224.html), [Elasticsearch](https://www.elastic.co/cn/what-is/elasticsearch-sql), [ClickHouse](https://clickhouse.tech/docs/zh/sql-reference/syntax/), [OceanBase](https://www.oceanbase.com/docs/oceanbase/V2.2.50/ss-sr-select_daur3l), [Presto](https://prestodb.io/docs/current/admin/function-namespace-managers.html), [Spark](http://spark.apache.org/sql/), [Hive](https://cwiki.apache.org/confluence/display/Hive/LanguageManual+Select)(延伸支持 Hadoop, Spark), [Phoenix](http://phoenix.apache.org/language/index.html#select)(延伸支持 HBase) +[DB2](https://www.ibm.com/support/knowledgecenter/SSEPGG_11.1.0/com.ibm.db2.luw.sql.ref.doc/doc/r0059224.html), [Elasticsearch](https://www.elastic.co/cn/what-is/elasticsearch-sql), [ClickHouse](https://clickhouse.tech/docs/zh/sql-reference/syntax/), [OceanBase](https://www.oceanbase.com/docs/oceanbase/V2.2.50/ss-sr-select_daur3l), [Presto](https://prestodb.io/docs/current/admin/function-namespace-managers.html), [Spark](http://spark.apache.org/sql/), [Hive](https://cwiki.apache.org/confluence/display/Hive/LanguageManual+Select)(延伸支持 Hadoop, Spark), [Phoenix](http://phoenix.apache.org/language/index.html#select)(延伸支持 HBase), [Presto/Trino](https://prestodb.io/docs/current/sql/select.html)(延伸支持 Redis, Hive, Kafka, Elasticsearch, Thrift, Cassandra, MySQL, PostgreSQL, Oracle...) ### 我要赞赏 如果你喜欢 APIJSON,感觉 APIJSON 帮助到了你,可以点右上角 ⭐Star 支持一下,谢谢 ^_^
From bd481ba3d534b286e95413cc57c82c5a5c2b172e Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Thu, 21 Jan 2021 16:35:13 +0800 Subject: [PATCH 0239/1181] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 6d9909813..9c74aad60 100644 --- a/README.md +++ b/README.md @@ -276,7 +276,7 @@ https://github.com/Tencent/APIJSON/issues/187 https://github.com/Tencent/APIJSON/blob/master/Roadmap.md 理论上所有支持 SQL 与 JDBC/ODBC 的软件,都可以用本项目对接 CRUD,待测试:
-[DB2](https://www.ibm.com/support/knowledgecenter/SSEPGG_11.1.0/com.ibm.db2.luw.sql.ref.doc/doc/r0059224.html), [Elasticsearch](https://www.elastic.co/cn/what-is/elasticsearch-sql), [ClickHouse](https://clickhouse.tech/docs/zh/sql-reference/syntax/), [OceanBase](https://www.oceanbase.com/docs/oceanbase/V2.2.50/ss-sr-select_daur3l), [Presto](https://prestodb.io/docs/current/admin/function-namespace-managers.html), [Spark](http://spark.apache.org/sql/), [Hive](https://cwiki.apache.org/confluence/display/Hive/LanguageManual+Select)(延伸支持 Hadoop, Spark), [Phoenix](http://phoenix.apache.org/language/index.html#select)(延伸支持 HBase), [Presto/Trino](https://prestodb.io/docs/current/sql/select.html)(延伸支持 Redis, Hive, Kafka, Elasticsearch, Thrift, Cassandra, MySQL, PostgreSQL, Oracle...) +[DB2](https://www.ibm.com/support/knowledgecenter/SSEPGG_11.1.0/com.ibm.db2.luw.sql.ref.doc/doc/r0059224.html), [Elasticsearch](https://www.elastic.co/cn/what-is/elasticsearch-sql), [ClickHouse](https://clickhouse.tech/docs/zh/sql-reference/syntax/), [OceanBase](https://www.oceanbase.com/docs/oceanbase/V2.2.50/ss-sr-select_daur3l), [Presto](https://prestodb.io/docs/current/admin/function-namespace-managers.html), [Spark](http://spark.apache.org/sql/), [Hive](https://cwiki.apache.org/confluence/display/Hive/LanguageManual+Select)(延伸支持 Hadoop, Spark), [Phoenix](http://phoenix.apache.org/language/index.html#select)(延伸支持 HBase), [Presto/Trino](https://prestodb.io/docs/current/sql/select.html)(延伸支持 Redis, Hive, Kafka, Elasticsearch, Thrift, Cassandra, MySQL, PostgreSQL, Oracle, MongoDB...) ### 我要赞赏 如果你喜欢 APIJSON,感觉 APIJSON 帮助到了你,可以点右上角 ⭐Star 支持一下,谢谢 ^_^
From 9d0345929cbfc6281034c112fde409da2de2c85f Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Fri, 22 Jan 2021 17:32:21 +0800 Subject: [PATCH 0240/1181] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E7=94=A8=E6=88=B7?= =?UTF-8?q?=E5=8F=91=E7=9A=84=E5=8D=9A=E5=AE=A2=20apijson=E7=AE=80?= =?UTF-8?q?=E5=8D=95=E4=BD=BF=E7=94=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 9c74aad60..ed49bc291 100644 --- a/README.md +++ b/README.md @@ -311,6 +311,8 @@ https://github.com/Tencent/APIJSON/blob/master/CONTRIBUTING.md [APIJSON对接分布式HTAP数据库TiDB](https://asktug.com/t/htap-tidb/395) +[apijson简单使用](https://www.cnblogs.com/greyzeng/p/14311995.html) + [APIJSON简单部署和使用](https://blog.csdn.net/m450744192/article/details/108462611) [学习自动化接口APIJSON](https://www.jianshu.com/p/981a2a630c7b) From 41b625b8b53ec01ae9e84b57aec8e67efae5c9a5 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Sat, 23 Jan 2021 14:54:13 +0800 Subject: [PATCH 0241/1181] Update README.md --- README.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/README.md b/README.md index ed49bc291..32f2b7253 100644 --- a/README.md +++ b/README.md @@ -268,6 +268,11 @@ https://github.com/Tencent/APIJSON/issues/187


+还有为 APIJSON 扫描代码贡献 Bug 的 奇安信代码卫士 和 源伞科技 +
+ + +
感谢大家的贡献。 From 11e8060d5915463e90f27cb01f9755605d036af2 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Sat, 23 Jan 2021 14:56:48 +0800 Subject: [PATCH 0242/1181] Update README.md --- README.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 32f2b7253..64f06eb30 100644 --- a/README.md +++ b/README.md @@ -268,11 +268,12 @@ https://github.com/Tencent/APIJSON/issues/187

-还有为 APIJSON 扫描代码贡献 Bug 的 奇安信代码卫士 和 源伞科技 +还有为 APIJSON 扫描代码贡献 Bug 的 [奇安信代码卫士](https://github.com/QiAnXinCodeSafe) 和 [源伞科技](https://www.sourcebrella.com)
- +
+
感谢大家的贡献。 From e9b9e95b61d1c94d2420f59012396ac6c8f7499d Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Sat, 23 Jan 2021 14:58:17 +0800 Subject: [PATCH 0243/1181] Update README.md --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 64f06eb30..804405fe4 100644 --- a/README.md +++ b/README.md @@ -270,8 +270,8 @@ https://github.com/Tencent/APIJSON/issues/187
还有为 APIJSON 扫描代码贡献 Bug 的 [奇安信代码卫士](https://github.com/QiAnXinCodeSafe) 和 [源伞科技](https://www.sourcebrella.com)
- - + +

感谢大家的贡献。 From 1750932d7797b4a2416fa17ac5f556a73233293a Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Sat, 23 Jan 2021 15:00:27 +0800 Subject: [PATCH 0244/1181] Update README.md --- README.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 804405fe4..235e64571 100644 --- a/README.md +++ b/README.md @@ -268,10 +268,12 @@ https://github.com/Tencent/APIJSON/issues/187

-还有为 APIJSON 扫描代码贡献 Bug 的 [奇安信代码卫士](https://github.com/QiAnXinCodeSafe) 和 [源伞科技](https://www.sourcebrella.com) + +还有为 APIJSON 扫描代码贡献 Bug 的 [奇安信代码卫士](https://github.com/QiAnXinCodeSafe) 和 [源伞科技](https://www.sourcebrella.com) +
- +

感谢大家的贡献。 From 5c3a98634afeda2fe6f6f4c7501b95b0b8e05386 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Sat, 23 Jan 2021 15:03:23 +0800 Subject: [PATCH 0245/1181] Update README.md --- README.md | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/README.md b/README.md index 235e64571..adbeba662 100644 --- a/README.md +++ b/README.md @@ -236,20 +236,20 @@ https://github.com/Tencent/APIJSON/issues/187 主项目 APIJSON 的贡献者们和 生态周边项目 的作者们:
- - - - - - - - - - - - - + height="54" width="54" > + + + + + + + + + + + + +
From 8a19f3738a5d297f021388e1d99d5d776cf77bb9 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Sat, 23 Jan 2021 15:05:16 +0800 Subject: [PATCH 0246/1181] Update README.md --- README.md | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index adbeba662..cd585559b 100644 --- a/README.md +++ b/README.md @@ -234,6 +234,7 @@ https://github.com/Tencent/APIJSON/issues/187 ### 贡献者们 主项目 APIJSON 的贡献者们和 生态周边项目 的作者们: +
@@ -252,20 +253,20 @@ https://github.com/Tencent/APIJSON/issues/187
- + height="54" width="54" > + + height="54" width="54" > - + height="54" width="54" > + - + height="54" width="54" > + - - + height="54" width="54" > + +

@@ -276,6 +277,7 @@ https://github.com/Tencent/APIJSON/issues/187

+ 感谢大家的贡献。 From 3b5d3a5f12e437f4aa9d20fed28599dcc0a538ea Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Sat, 23 Jan 2021 15:07:05 +0800 Subject: [PATCH 0247/1181] Update README.md --- README.md | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index cd585559b..bf6a01b20 100644 --- a/README.md +++ b/README.md @@ -218,17 +218,17 @@ https://github.com/Tencent/APIJSON/issues/36 如果您在使用 APIJSON,请让我们知道,您的使用对我们非常重要(按登记顺序排列):
https://github.com/Tencent/APIJSON/issues/187
- - - - - - - - - - - + + + + + + + + + + +
From d2c47d482f6112564226d979b7a72e9f66592d34 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Sat, 23 Jan 2021 15:08:45 +0800 Subject: [PATCH 0248/1181] Update README.md --- README.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/README.md b/README.md index bf6a01b20..1216588cd 100644 --- a/README.md +++ b/README.md @@ -234,7 +234,6 @@ https://github.com/Tencent/APIJSON/issues/187 ### 贡献者们 主项目 APIJSON 的贡献者们和 生态周边项目 的作者们: -
@@ -271,7 +270,6 @@ https://github.com/Tencent/APIJSON/issues/187
还有为 APIJSON 扫描代码贡献 Bug 的 [奇安信代码卫士](https://github.com/QiAnXinCodeSafe) 和 [源伞科技](https://www.sourcebrella.com) -
From 37287d4eebbebabefb4f511ea3dce701f042db9a Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Sat, 23 Jan 2021 15:11:14 +0800 Subject: [PATCH 0249/1181] Update README.md --- README.md | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index 1216588cd..5737163be 100644 --- a/README.md +++ b/README.md @@ -218,17 +218,17 @@ https://github.com/Tencent/APIJSON/issues/36 如果您在使用 APIJSON,请让我们知道,您的使用对我们非常重要(按登记顺序排列):
https://github.com/Tencent/APIJSON/issues/187
- - - - - - - - - - - + + + + + + + + + + +
From 9e215311c41b201739f749fef64d64c24c115591 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Sat, 23 Jan 2021 15:15:07 +0800 Subject: [PATCH 0250/1181] Update README.md --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 5737163be..b161d29b6 100644 --- a/README.md +++ b/README.md @@ -223,10 +223,10 @@ https://github.com/Tencent/APIJSON/issues/187 - - + +
From ffb19c86ea07b17708d8658f52c88f06452d1a62 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Sat, 23 Jan 2021 15:17:02 +0800 Subject: [PATCH 0251/1181] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index b161d29b6..d10a74d25 100644 --- a/README.md +++ b/README.md @@ -225,8 +225,8 @@ https://github.com/Tencent/APIJSON/issues/187 - +
From d2cf5bc3d25f426ebe22c94fac6210d79132d51e Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Sat, 23 Jan 2021 15:23:35 +0800 Subject: [PATCH 0252/1181] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index d10a74d25..31f3d4940 100644 --- a/README.md +++ b/README.md @@ -272,7 +272,7 @@ https://github.com/Tencent/APIJSON/issues/187 还有为 APIJSON 扫描代码贡献 Bug 的 [奇安信代码卫士](https://github.com/QiAnXinCodeSafe) 和 [源伞科技](https://www.sourcebrella.com)
- +

From 9b82b5ed4768bd9bb30029ec44c5803968c458eb Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Sat, 23 Jan 2021 23:43:23 +0800 Subject: [PATCH 0253/1181] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 31f3d4940..7d6f552bc 100644 --- a/README.md +++ b/README.md @@ -269,7 +269,7 @@ https://github.com/Tencent/APIJSON/issues/187

-还有为 APIJSON 扫描代码贡献 Bug 的 [奇安信代码卫士](https://github.com/QiAnXinCodeSafe) 和 [源伞科技](https://www.sourcebrella.com) +还有为 APIJSON 扫描代码贡献 Issue 的 [奇安信代码卫士](https://github.com/QiAnXinCodeSafe) 和 [源伞科技](https://www.sourcebrella.com)
From 1bf71489e3039a0d376264c9328d705e91147a6a Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Sat, 23 Jan 2021 23:45:16 +0800 Subject: [PATCH 0254/1181] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 7d6f552bc..1d66d754f 100644 --- a/README.md +++ b/README.md @@ -149,7 +149,7 @@ https://github.com/Tencent/APIJSON/wiki * **解决十大痛点** (APIJSON 大幅提振开发效率、强力杜绝联调扯皮、巧妙规避文档缺陷、非常节省流量带宽 等) * **开发提速巨大** (CRUD 零代码热更新自动化,APIJSONBoot 对比 SSM、SSH 等保守估计可提速 20 倍以上) * **腾讯官方开源** (使用 GitHub、Gitee、工蜂 等平台的官方账号开源,微信公众号、腾讯云+社区 等官方公告) -* **社区影响力大** (GitHub 9.6K Star 在 350W Java 项目中排名前 150,远超 FLAG, BAT 等国内外绝大部分开源项目) +* **社区影响力大** (GitHub 9.8K Star 在 350W Java 项目中排名前 150,远超 FLAG, BAT 等国内外绝大部分开源项目) * **各项荣誉成就** (腾讯开源五个第一、腾讯首个 GVP 获奖项目、腾讯后端项目 Star 第一、GitHub Java 周榜第一 等) * **多样用户案例** (腾讯内部用户包含 互娱、音乐、云与智慧,外部用户包含 500 强上市公司、数千亿资本国企 等) * **适用场景广泛** (社交聊天、阅读资讯、影音视频、办公学习 等各种 App、网站、公众号、小程序 等非金融类项目) From f05df3a1887999769ac6db18cfa8f314a5585165 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Sun, 24 Jan 2021 01:29:22 +0800 Subject: [PATCH 0255/1181] =?UTF-8?q?=E8=A7=A3=E5=86=B3=E5=BC=82=E5=B8=B8?= =?UTF-8?q?=E6=83=85=E5=86=B5=E4=B8=8B=E6=9C=AA=E5=8F=8A=E6=97=B6=E9=87=8A?= =?UTF-8?q?=E6=94=BE=E8=B5=84=E6=BA=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/apijson/orm/AbstractSQLExecutor.java | 254 ++++++++++-------- 1 file changed, 143 insertions(+), 111 deletions(-) diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java index ccfe38f8c..92508640d 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java @@ -176,127 +176,142 @@ public JSONObject execute(@NotNull SQLConfig config, boolean unknowType) throws + "\n>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>\n"); ResultSet rs = null; + List resultList = null; + Map childMap = null; - if (unknowType) { - Statement statement = getStatement(config); - rs = execute(statement, sql); + try { + if (unknowType) { + Statement statement = getStatement(config); + rs = execute(statement, sql); + + result = new JSONObject(true); + int updateCount = statement.getUpdateCount(); + result.put(JSONResponse.KEY_COUNT, updateCount); + result.put("update", updateCount >= 0); + //导致后面 rs.getMetaData() 报错 Operation not allowed after ResultSet closed result.put("moreResults", statement.getMoreResults()); + } + else { + switch (config.getMethod()) { + case HEAD: + case HEADS: + rs = executeQuery(config); - result = new JSONObject(true); - int updateCount = statement.getUpdateCount(); - result.put(JSONResponse.KEY_COUNT, updateCount); - result.put("update", updateCount >= 0); - //导致后面 rs.getMetaData() 报错 Operation not allowed after ResultSet closed result.put("moreResults", statement.getMoreResults()); - } - else { - switch (config.getMethod()) { - case HEAD: - case HEADS: - rs = executeQuery(config); + executedSQLCount ++; - executedSQLCount ++; + result = rs.next() ? AbstractParser.newSuccessResult() + : AbstractParser.newErrorResult(new SQLException("数据库错误, rs.next() 失败!")); + result.put(JSONResponse.KEY_COUNT, rs.getLong(1)); + return result; - result = rs.next() ? AbstractParser.newSuccessResult() - : AbstractParser.newErrorResult(new SQLException("数据库错误, rs.next() 失败!")); - result.put(JSONResponse.KEY_COUNT, rs.getLong(1)); + case POST: + case PUT: + case DELETE: + executedSQLCount ++; - rs.close(); - return result; + int updateCount = executeUpdate(config); - case POST: - case PUT: - case DELETE: - executedSQLCount ++; + result = AbstractParser.newResult(updateCount > 0 ? JSONResponse.CODE_SUCCESS : JSONResponse.CODE_NOT_FOUND + , updateCount > 0 ? JSONResponse.MSG_SUCCEED : "没权限访问或对象不存在!"); - int updateCount = executeUpdate(config); + //id,id{}至少一个会有,一定会返回,不用抛异常来阻止关联写操作时前面错误导致后面无条件执行! + result.put(JSONResponse.KEY_COUNT, updateCount);//返回修改的记录数 + if (config.getId() != null) { + result.put(config.getIdKey(), config.getId()); + } else { + result.put(config.getIdKey() + "[]", config.getWhere(config.getIdKey() + "{}", true)); + } + return result; - result = AbstractParser.newResult(updateCount > 0 ? JSONResponse.CODE_SUCCESS : JSONResponse.CODE_NOT_FOUND - , updateCount > 0 ? JSONResponse.MSG_SUCCEED : "没权限访问或对象不存在!"); + case GET: + case GETS: + result = getCacheItem(sql, position, config.getCache()); + Log.i(TAG, ">>> select result = getCache('" + sql + "', " + position + ") = " + result); + if (result != null) { + cachedSQLCount ++; - //id,id{}至少一个会有,一定会返回,不用抛异常来阻止关联写操作时前面错误导致后面无条件执行! - result.put(JSONResponse.KEY_COUNT, updateCount);//返回修改的记录数 - if (config.getId() != null) { - result.put(config.getIdKey(), config.getId()); - } else { - result.put(config.getIdKey() + "[]", config.getWhere(config.getIdKey() + "{}", true)); - } - return result; + Log.d(TAG, "\n\n select result != null >> return result;" + "\n>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>\n\n"); + return result; + } - case GET: - case GETS: - result = getCacheItem(sql, position, config.getCache()); - Log.i(TAG, ">>> select result = getCache('" + sql + "', " + position + ") = " + result); - if (result != null) { - cachedSQLCount ++; + rs = executeQuery(config); //FIXME SQL Server 是一次返回两个结果集,包括查询结果和执行计划,需要 moreResults - Log.d(TAG, "\n\n select result != null >> return result;" + "\n>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>\n\n"); - return result; - } - - rs = executeQuery(config); //FIXME SQL Server 是一次返回两个结果集,包括查询结果和执行计划,需要 moreResults + if (config.isExplain() == false) { //只有 SELECT 才能 EXPLAIN + executedSQLCount ++; + } + break; - if (config.isExplain() == false) { //只有 SELECT 才能 EXPLAIN - executedSQLCount ++; + default://OPTIONS, TRACE等 + Log.e(TAG, "select sql = " + sql + " ; method = " + config.getMethod() + " >> return null;"); + return null; } - break; - - default://OPTIONS, TRACE等 - Log.e(TAG, "select sql = " + sql + " ; method = " + config.getMethod() + " >> return null;"); - return null; } - } - // final boolean cache = config.getCount() != 1; - List resultList = new ArrayList<>(); - // Log.d(TAG, "select cache = " + cache + "; resultList" + (resultList == null ? "=" : "!=") + "null"); + // final boolean cache = config.getCount() != 1; + resultList = new ArrayList<>(); + // Log.d(TAG, "select cache = " + cache + "; resultList" + (resultList == null ? "=" : "!=") + "null"); - int index = -1; + int index = -1; - ResultSetMetaData rsmd = rs.getMetaData(); - final int length = rsmd.getColumnCount(); + ResultSetMetaData rsmd = rs.getMetaData(); + final int length = rsmd.getColumnCount(); - // + childMap = new HashMap<>(); //要存到cacheMap + // WHERE id = ? AND ... 或 WHERE ... AND id = ? 强制排序 remove 再 put,还是重新 getSQL吧 - boolean hasJoin = config.hasJoin(); - int viceColumnStart = length + 1; //第一个副表字段的index - while (rs.next()) { - index ++; - Log.d(TAG, "\n\n<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<\n select while (rs.next()){ index = " + index + "\n\n"); + boolean hasJoin = config.hasJoin(); + int viceColumnStart = length + 1; //第一个副表字段的index + while (rs.next()) { + index ++; + Log.d(TAG, "\n\n<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<\n select while (rs.next()){ index = " + index + "\n\n"); - JSONObject item = new JSONObject(true); + JSONObject item = new JSONObject(true); - for (int i = 1; i <= length; i++) { + for (int i = 1; i <= length; i++) { - // if (hasJoin && viceColumnStart > length && config.getSQLTable().equalsIgnoreCase(rsmd.getTableName(i)) == false) { - // viceColumnStart = i; - // } + // if (hasJoin && viceColumnStart > length && config.getSQLTable().equalsIgnoreCase(rsmd.getTableName(i)) == false) { + // viceColumnStart = i; + // } - // bugfix-修复非常规数据库字段,获取表名失败导致输出异常 - if (config.isExplain() == false && hasJoin && viceColumnStart > length) { - List column = config.getColumn(); + // bugfix-修复非常规数据库字段,获取表名失败导致输出异常 + if (config.isExplain() == false && hasJoin && viceColumnStart > length) { + List column = config.getColumn(); - if (column != null && column.isEmpty() == false) { - viceColumnStart = column.size() + 1; - } - else if (config.getSQLTable().equalsIgnoreCase(rsmd.getTableName(i)) == false) { - viceColumnStart = i; + if (column != null && column.isEmpty() == false) { + viceColumnStart = column.size() + 1; + } + else if (config.getSQLTable().equalsIgnoreCase(rsmd.getTableName(i)) == false) { + viceColumnStart = i; + } } + + item = onPutColumn(config, rs, rsmd, index, item, i, config.isExplain() == false && hasJoin && i >= viceColumnStart ? childMap : null); } - item = onPutColumn(config, rs, rsmd, index, item, i, config.isExplain() == false && hasJoin && i >= viceColumnStart ? childMap : null); - } + resultList = onPutTable(config, rs, rsmd, resultList, index, item); - resultList = onPutTable(config, rs, rsmd, resultList, index, item); + Log.d(TAG, "\n select while (rs.next()) { resultList.put( " + index + ", result); " + + "\n >>>>>>>>>>>>>>>>>>>>>>>>>>> \n\n"); + } - Log.d(TAG, "\n select while (rs.next()) { resultList.put( " + index + ", result); " - + "\n >>>>>>>>>>>>>>>>>>>>>>>>>>> \n\n"); + } + finally { + if (rs != null) { + try { + rs.close(); + } + catch (Exception e) { + e.printStackTrace(); + } + } } - rs.close(); + if (resultList == null) { + return null; + } if (unknowType || config.isExplain()) { if (config.isExplain()) { @@ -397,41 +412,51 @@ protected void executeAppJoin(SQLConfig config, List resultList, Map + "\n>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>\n"); //执行副表的批量查询 并 缓存到 childMap - ResultSet rs = executeQuery(jc); + ResultSet rs = null; + try { + rs = executeQuery(jc); - int index = -1; + int index = -1; - ResultSetMetaData rsmd = rs.getMetaData(); - final int length = rsmd.getColumnCount(); + ResultSetMetaData rsmd = rs.getMetaData(); + final int length = rsmd.getColumnCount(); - JSONObject result; - String cacheSql; - while (rs.next()) { //FIXME 同时有 @ APP JOIN 和 < 等 SQL JOIN 时,next = false 总是无法进入循环,导致缓存失效,可能是连接池或线程问题 - index ++; - Log.d(TAG, "\n\n<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<\n executeAppJoin while (rs.next()){ index = " + index + "\n\n"); + JSONObject result; + String cacheSql; + while (rs.next()) { //FIXME 同时有 @ APP JOIN 和 < 等 SQL JOIN 时,next = false 总是无法进入循环,导致缓存失效,可能是连接池或线程问题 + index ++; + Log.d(TAG, "\n\n<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<\n executeAppJoin while (rs.next()){ index = " + index + "\n\n"); - result = new JSONObject(true); + result = new JSONObject(true); - for (int i = 1; i <= length; i++) { + for (int i = 1; i <= length; i++) { - result = onPutColumn(jc, rs, rsmd, index, result, i, null); - } + result = onPutColumn(jc, rs, rsmd, index, result, i, null); + } - //每个 result 都要用新的 SQL 来存 childResultMap = onPutTable(config, rs, rsmd, childResultMap, index, result); + //每个 result 都要用新的 SQL 来存 childResultMap = onPutTable(config, rs, rsmd, childResultMap, index, result); - Log.d(TAG, "\n executeAppJoin while (rs.next()) { resultList.put( " + index + ", result); " - + "\n >>>>>>>>>>>>>>>>>>>>>>>>>>> \n\n"); + Log.d(TAG, "\n executeAppJoin while (rs.next()) { resultList.put( " + index + ", result); " + + "\n >>>>>>>>>>>>>>>>>>>>>>>>>>> \n\n"); - //缓存到 childMap - cc.putWhere(j.getKey(), result.get(j.getKey()), false); - cacheSql = cc.getSQL(false); - childMap.put(cacheSql, result); + //缓存到 childMap + cc.putWhere(j.getKey(), result.get(j.getKey()), false); + cacheSql = cc.getSQL(false); + childMap.put(cacheSql, result); - Log.d(TAG, ">>> executeAppJoin childMap.put('" + cacheSql + "', result); childMap.size() = " + childMap.size()); + Log.d(TAG, ">>> executeAppJoin childMap.put('" + cacheSql + "', result); childMap.size() = " + childMap.size()); + } + } + finally { + if (rs != null) { + try { + rs.close(); + } + catch (Exception e) { + e.printStackTrace(); + } + } } - - rs.close(); - long endTime = System.currentTimeMillis(); Log.d(TAG, "\n\n executeAppJoin endTime = " + endTime + "; duration = " + (endTime - startTime) @@ -576,6 +601,13 @@ else if (value instanceof Clob) { //SQL Server TEXT 类型 居然走这个 s = br.readLine(); } value = sb.toString(); + + try { + br.close(); + } + catch (Exception e) { + e.printStackTrace(); + } } if (castToJson == false) { From bbcf05e01c724894a5ba0dcddd49a14bbfeb5346 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Sun, 24 Jan 2021 01:55:59 +0800 Subject: [PATCH 0256/1181] =?UTF-8?q?=E8=A7=A3=E5=86=B3=E6=9F=90=E4=BA=9B?= =?UTF-8?q?=E6=83=85=E5=86=B5=E4=B8=8B=E8=A7=A3=E6=9E=90=20=E5=BC=95?= =?UTF-8?q?=E7=94=A8=E8=B5=8B=E5=80=BC=20=E5=87=BA=E9=94=99=20throw=20Null?= =?UTF-8?q?PointerException=20=E6=88=96=20IndexOutOfBoundsException?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../main/java/apijson/orm/AbstractParser.java | 152 +++++++++--------- .../src/main/java/apijson/orm/SQLConfig.java | 2 +- 2 files changed, 78 insertions(+), 76 deletions(-) diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java b/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java index 3168cf856..85bbb4c1e 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java @@ -462,7 +462,7 @@ public static JSONObject parseRequest(String request) throws Exception { @Override public JSONObject parseCorrectRequest(RequestMethod method, String tag, int version, String name, @NotNull JSONObject request , int maxUpdateCount, SQLCreator creator) throws Exception { - + if (RequestMethod.isPublicMethod(method)) { return request;//需要指定JSON结构的get请求可以改为post请求。一般只有对安全性要求高的才会指定,而这种情况用明文的GET方式几乎肯定不安全 } @@ -486,10 +486,10 @@ public JSONObject parseCorrectRequest(RequestMethod method, String tag, int vers JSONObject target = object; if (object.containsKey(tag) == false) { //tag 是 Table 名或 Table[] - + boolean isArrayKey = tag.endsWith(":[]"); // JSONRequest.isArrayKey(tag); String key = isArrayKey ? tag.substring(0, tag.length() - 3) : tag; - + if (apijson.JSONObject.isTableKey(key)) { if (isArrayKey) { //自动为 tag = Comment:[] 的 { ... } 新增键值对 "Comment[]":[] 为 { "Comment[]":[], ... } target.put(key + "[]", new JSONArray()); @@ -500,15 +500,15 @@ public JSONObject parseCorrectRequest(RequestMethod method, String tag, int vers } } } - + //获取指定的JSON结构 >>>>>>>>>>>>>> - + //JSONObject clone 浅拷贝没用,Structure.parse 会导致 structure 里面被清空,第二次从缓存里取到的就是 {} return getVerifier().verifyRequest(method, name, target, request, maxUpdateCount, getGlobleDatabase(), getGlobleSchema(), creator); } - - + + /**新建带状态内容的JSONObject * @param code * @param msg @@ -533,7 +533,7 @@ public static JSONObject extendResult(JSONObject object, int code, String msg) { if (object.containsKey(JSONResponse.KEY_CODE) == false) { object.put(JSONResponse.KEY_CODE, code); } - + String m = StringUtil.getString(object.getString(JSONResponse.KEY_MSG)); if (m.isEmpty() == false) { msg = m + " ;\n " + StringUtil.getString(msg); @@ -679,14 +679,14 @@ public JSONObject getStructure(@NotNull String table, String method, String tag, // TODO 目前只使用 Request 而不使用 Response,所以这里写死用 REQUEST_MAP,以后可能 Response 表也会与 Request 表合并,用字段来区分 String cacheKey = AbstractVerifier.getCacheKeyForRequest(method, tag); SortedMap versionedMap = AbstractVerifier.REQUEST_MAP.get(cacheKey); - + JSONObject result = versionedMap == null ? null : versionedMap.get(Integer.valueOf(version)); if (result == null) { // version <= 0 时使用最新,version > 0 时使用 > version 的最接近版本(最小版本) Set> set = versionedMap == null ? null : versionedMap.entrySet(); - + if (set != null && set.isEmpty() == false) { Entry maxEntry = null; - + for (Entry entry : set) { if (entry == null || entry.getKey() == null || entry.getValue() == null) { continue; @@ -700,30 +700,30 @@ public JSONObject getStructure(@NotNull String table, String method, String tag, if (entry.getKey() < version) { break; } - + maxEntry = entry; } - + result = maxEntry == null ? null : maxEntry.getValue(); } - + if (result != null) { // 加快下次查询,查到值的话组合情况其实是有限的,不属于恶意请求 if (versionedMap == null) { versionedMap = new TreeMap<>((o1, o2) -> { return o2 == null ? -1 : o2.compareTo(o1); // 降序 }); } - + versionedMap.put(Integer.valueOf(version), result); AbstractVerifier.REQUEST_MAP.put(cacheKey, versionedMap); } } - + if (result == null) { if (AbstractVerifier.REQUEST_MAP.isEmpty() == false) { return null; // 已使用 REQUEST_MAP 缓存全部,但没查到 } - + //获取指定的JSON结构 <<<<<<<<<<<<<< SQLConfig config = createSQLConfig().setMethod(GET).setTable(table); config.setPrepared(false); @@ -732,7 +732,7 @@ public JSONObject getStructure(@NotNull String table, String method, String tag, Map where = new HashMap(); where.put("method", method); where.put(JSONRequest.KEY_TAG, tag); - + if (version > 0) { where.put(JSONRequest.KEY_VERSION + "{}", ">=" + version); } @@ -742,12 +742,12 @@ public JSONObject getStructure(@NotNull String table, String method, String tag, //too many connections error: 不try-catch,可以让客户端看到是服务器内部异常 result = getSQLExecutor().execute(config, false); - + // version, method, tag 组合情况太多了,JDK 里又没有 LRUCache,所以要么启动时一次性缓存全部后面只用缓存,要么每次都查数据库 // versionedMap.put(Integer.valueOf(version), result); // AbstractVerifier.REQUEST_MAP.put(cacheKey, versionedMap); } - + return getJSONObject(result, "structure"); //解决返回值套了一层 "structure":{} } @@ -807,10 +807,10 @@ public JSONObject onObjectParse(final JSONObject request int index = parentPath.lastIndexOf("]/"); if (index >= 0) { int total = rp.getIntValue(JSONResponse.KEY_COUNT); - + String pathPrefix = parentPath.substring(0, index) + "]/"; putQueryResult(pathPrefix + JSONResponse.KEY_TOTAL, total); - + //详细的分页信息,主要为 PC 端提供 int count = arrayConfig.getCount(); int page = arrayConfig.getPage(); @@ -818,7 +818,7 @@ public JSONObject onObjectParse(final JSONObject request if (max < 0) { max = 0; } - + JSONObject pagination = new JSONObject(true); pagination.put(JSONResponse.KEY_TOTAL, total); pagination.put(JSONRequest.KEY_COUNT, count); @@ -929,51 +929,52 @@ public JSONArray onArrayParse(JSONObject request, String parentPath, String name request.remove(JSONRequest.KEY_JOIN); Log.d(TAG, "onArrayParse query = " + query + "; count = " + count + "; page = " + page + "; join = " + join); - if (request.isEmpty()) {//如果条件成立,说明所有的 parentPath/name:request 中request都无效!!! + if (request.isEmpty()) { // 如果条件成立,说明所有的 parentPath/name:request 中request都无效!!! 后续都不执行,没必要还原数组关键词浪费性能 Log.e(TAG, "onArrayParse request.isEmpty() >> return null;"); return null; } + JSONArray response = null; + try { + int size = count2 == 0 ? max : count2;//count为每页数量,size为第page页实际数量,max(size) = count + Log.d(TAG, "onArrayParse size = " + size + "; page = " + page); - int size = count2 == 0 ? max : count2;//count为每页数量,size为第page页实际数量,max(size) = count - Log.d(TAG, "onArrayParse size = " + size + "; page = " + page); - - - //key[]:{Table:{}}中key equals Table时 提取Table - int index = isSubquery || name == null ? -1 : name.lastIndexOf("[]"); - String childPath = index <= 0 ? null : Pair.parseEntry(name.substring(0, index), true).getKey(); // Table-key1-key2... - //判断第一个key,即Table是否存在,如果存在就提取 - String[] childKeys = StringUtil.split(childPath, "-", false); - if (childKeys == null || childKeys.length <= 0 || request.containsKey(childKeys[0]) == false) { - childKeys = null; - } + //key[]:{Table:{}}中key equals Table时 提取Table + int index = isSubquery || name == null ? -1 : name.lastIndexOf("[]"); + String childPath = index <= 0 ? null : Pair.parseEntry(name.substring(0, index), true).getKey(); // Table-key1-key2... + //判断第一个key,即Table是否存在,如果存在就提取 + String[] childKeys = StringUtil.split(childPath, "-", false); + if (childKeys == null || childKeys.length <= 0 || request.containsKey(childKeys[0]) == false) { + childKeys = null; + } - //Table<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< - JSONArray response = new JSONArray(); - SQLConfig config = createSQLConfig() - .setMethod(requestMethod) - .setCount(size) - .setPage(page) - .setQuery(query2) - .setJoinList(onJoinParse(join, request)); - JSONObject parent; - //生成size个 - for (int i = 0; i < (isSubquery ? 1 : size); i++) { - parent = onObjectParse(request, isSubquery ? parentPath : path, isSubquery ? name : "" + i, config.setType(SQLConfig.TYPE_ITEM).setPosition(i), isSubquery); - if (parent == null || parent.isEmpty()) { - break; + //Table<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< + response = new JSONArray(); + SQLConfig config = createSQLConfig() + .setMethod(requestMethod) + .setCount(size) + .setPage(page) + .setQuery(query2) + .setJoinList(onJoinParse(join, request)); + + JSONObject parent; + //生成size个 + for (int i = 0; i < (isSubquery ? 1 : size); i++) { + parent = onObjectParse(request, isSubquery ? parentPath : path, isSubquery ? name : "" + i, config.setType(SQLConfig.TYPE_ITEM).setPosition(i), isSubquery); + if (parent == null || parent.isEmpty()) { + break; + } + //key[]:{Table:{}}中key equals Table时 提取Table + response.add(getValue(parent, childKeys)); //null有意义 } - //key[]:{Table:{}}中key equals Table时 提取Table - response.add(getValue(parent, childKeys)); //null有意义 - } - //Table>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> + //Table>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> - /* - * 支持引用取值后的数组 + /* + * 支持引用取值后的数组 { "User-id[]": { "User": { @@ -986,18 +987,19 @@ public JSONArray onArrayParse(JSONObject request, String parentPath, String name } } } - */ - Object fo = childKeys == null || response.isEmpty() ? null : response.get(0); - if (fo instanceof Boolean || fo instanceof Number || fo instanceof String) { //[{}] 和 [[]] 都没意义 - putQueryResult(path, response); - } - + */ + Object fo = childKeys == null || response.isEmpty() ? null : response.get(0); + if (fo instanceof Boolean || fo instanceof Number || fo instanceof String) { //[{}] 和 [[]] 都没意义 + putQueryResult(path, response); + } + } finally { - //后面还可能用到,要还原 - request.put(JSONRequest.KEY_QUERY, query); - request.put(JSONRequest.KEY_COUNT, count); - request.put(JSONRequest.KEY_PAGE, page); - request.put(JSONRequest.KEY_JOIN, join); + //后面还可能用到,要还原 + request.put(JSONRequest.KEY_QUERY, query); + request.put(JSONRequest.KEY_COUNT, count); + request.put(JSONRequest.KEY_PAGE, page); + request.put(JSONRequest.KEY_JOIN, join); + } if (Log.DEBUG) { Log.i(TAG, "onArrayParse return response = \n" + JSON.toJSONString(response) + "\n>>>>>>>>>>>>>>>\n\n\n"); @@ -1142,8 +1144,9 @@ else if (join != null){ private static final List JOIN_COPY_KEY_LIST; - static {//TODO 不全 + static { // TODO 不全 JOIN_COPY_KEY_LIST = new ArrayList(); + JOIN_COPY_KEY_LIST.add(JSONRequest.KEY_ROLE); JOIN_COPY_KEY_LIST.add(JSONRequest.KEY_DATABASE); JOIN_COPY_KEY_LIST.add(JSONRequest.KEY_SCHEMA); JOIN_COPY_KEY_LIST.add(JSONRequest.KEY_COLUMN); @@ -1151,14 +1154,13 @@ else if (join != null){ JOIN_COPY_KEY_LIST.add(JSONRequest.KEY_GROUP); JOIN_COPY_KEY_LIST.add(JSONRequest.KEY_HAVING); JOIN_COPY_KEY_LIST.add(JSONRequest.KEY_ORDER); + JOIN_COPY_KEY_LIST.add(JSONRequest.KEY_RAW); } - /** - * 取指定json对象的id集合 + /**取指定 JSON 对象的 id 集合 * @param table * @param key * @param obj - * @param targetKey * @return null ? 全部 : 有限的数组 */ private JSONObject getJoinObject(String table, JSONObject obj, String key) { @@ -1171,8 +1173,8 @@ private JSONObject getJoinObject(String table, JSONObject obj, String key) { return null; } - //取出所有join条件 - JSONObject requestObj = new JSONObject(true);//(JSONObject) obj.clone();// + // 取出所有 join 条件 + JSONObject requestObj = new JSONObject(true); // (JSONObject) obj.clone(); Set set = new LinkedHashSet<>(obj.keySet()); for (String k : set) { if (StringUtil.isEmpty(k, true)) { @@ -1190,7 +1192,7 @@ private JSONObject getJoinObject(String table, JSONObject obj, String key) { continue; } throw new UnsupportedOperationException(table + "." + k + " 不合法!" + JSONRequest.KEY_JOIN - + " 关联的Table中只能有1个 key@:value !"); + + " 关联的Table中只能有1个 key@:value !"); // TODO 支持 join on } if (k.contains("()") == false) { //不需要远程函数 @@ -1393,7 +1395,7 @@ public Object getValueByPath(String valuePath) { if (parent != null) { Log.i(TAG, "getValueByPath >> get from queryResultMap >> return parent.get(keys[keys.length - 1]);"); - target = parent.get(keys[keys.length - 1]); //值为null应该报错NotExistExeption,一般都是id关联,不可为null,否则可能绕过安全机制 + target = keys == null || keys.length <= 0 ? parent : parent.get(keys[keys.length - 1]); //值为null应该报错NotExistExeption,一般都是id关联,不可为null,否则可能绕过安全机制 if (target != null) { Log.i(TAG, "getValueByPath >> getValue >> return target = " + target); return target; diff --git a/APIJSONORM/src/main/java/apijson/orm/SQLConfig.java b/APIJSONORM/src/main/java/apijson/orm/SQLConfig.java index ed466e2cf..7e73bdd18 100755 --- a/APIJSONORM/src/main/java/apijson/orm/SQLConfig.java +++ b/APIJSONORM/src/main/java/apijson/orm/SQLConfig.java @@ -112,7 +112,7 @@ public interface SQLConfig { SQLConfig setId(Object id); RequestRole getRole(); - SQLConfig setRole(RequestRole role); + SQLConfig setRole(RequestRole role); // TODO 提供 String 类型的,方便扩展 public boolean isDistinct(); public SQLConfig setDistinct(boolean distinct); From b7d1fef19286e30692ff628f78875e130d1583ca Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Sun, 24 Jan 2021 02:55:03 +0800 Subject: [PATCH 0257/1181] =?UTF-8?q?=E4=BC=98=E5=8C=96=E5=85=A8=E5=B1=80?= =?UTF-8?q?=E9=BB=98=E8=AE=A4=E5=8F=82=E6=95=B0=E7=9A=84=E8=A7=A3=E6=9E=90?= =?UTF-8?q?=E6=80=A7=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../main/java/apijson/orm/AbstractObjectParser.java | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractObjectParser.java b/APIJSONORM/src/main/java/apijson/orm/AbstractObjectParser.java index f33534d19..8fcd78d80 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractObjectParser.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractObjectParser.java @@ -177,7 +177,7 @@ public AbstractObjectParser parse() throws Exception { functionMap = null;//must init childMap = null;//must init - Set> set = new LinkedHashSet>(request.entrySet()); + Set> set = request.isEmpty() ? null : new LinkedHashSet>(request.entrySet()); if (set != null && set.isEmpty() == false) {//判断换取少几个变量的初始化是否值得? if (isTable) {//非Table下必须保证原有顺序!否则 count,page 会丢, total@:"/[]/total" 会在[]:{}前执行! customMap = new LinkedHashMap(); @@ -261,17 +261,17 @@ else if (method == PUT && value instanceof JSONArray } if (isTable) { - if (sqlRequest.get(JSONRequest.KEY_DATABASE) == null && parser.getGlobleDatabase() != null) { + if (parser.getGlobleDatabase() != null && sqlRequest.get(JSONRequest.KEY_DATABASE) == null) { sqlRequest.put(JSONRequest.KEY_DATABASE, parser.getGlobleDatabase()); } - if (sqlRequest.get(JSONRequest.KEY_SCHEMA) == null && parser.getGlobleSchema() != null) { + if (parser.getGlobleSchema() != null && sqlRequest.get(JSONRequest.KEY_SCHEMA) == null) { sqlRequest.put(JSONRequest.KEY_SCHEMA, parser.getGlobleSchema()); } if (isSubquery == false) { //解决 SQL 语法报错,子查询不能 EXPLAIN - if (sqlRequest.get(JSONRequest.KEY_EXPLAIN) == null && parser.getGlobleExplain() != null) { + if (parser.getGlobleExplain() != null && sqlRequest.get(JSONRequest.KEY_EXPLAIN) == null) { sqlRequest.put(JSONRequest.KEY_EXPLAIN, parser.getGlobleExplain()); } - if (sqlRequest.get(JSONRequest.KEY_CACHE) == null && parser.getGlobleCache() != null) { + if (parser.getGlobleCache() != null && sqlRequest.get(JSONRequest.KEY_CACHE) == null) { sqlRequest.put(JSONRequest.KEY_CACHE, parser.getGlobleCache()); } } From 893d3b3fd9cb617b2bcfa17b503b097dede1dfdc Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Sun, 24 Jan 2021 22:43:13 +0800 Subject: [PATCH 0258/1181] =?UTF-8?q?=E7=9B=B8=E5=85=B3=E6=8E=A8=E8=8D=90?= =?UTF-8?q?=20=E6=96=B0=E5=A2=9E=20APIJSON=20=E6=8E=A5=E5=8F=A3=E8=B0=83?= =?UTF-8?q?=E8=AF=95=E5=AE=9E=E8=B7=B5=EF=BC=8C=E6=84=9F=E8=B0=A2=E8=85=BE?= =?UTF-8?q?=E8=AE=AF=E5=90=8C=E4=BA=8B=E7=9A=84=E8=B4=A1=E7=8C=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 1d66d754f..4981da0d3 100644 --- a/README.md +++ b/README.md @@ -325,6 +325,8 @@ https://github.com/Tencent/APIJSON/blob/master/CONTRIBUTING.md [学习自动化接口APIJSON](https://www.jianshu.com/p/981a2a630c7b) +[APIJSON 接口调试实践](https://github.com/Tencent/APIJSON/issues/189) + [APIJSON使用例子总结](https://blog.csdn.net/weixin_41077841/article/details/110518007) [APIJSON 自动化接口和文档的快速开发神器 (一)](https://blog.csdn.net/qq_41829492/article/details/88670940) From bb4896d208e813a3f5eec21f60e1c083621b1f92 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Sat, 30 Jan 2021 19:56:35 +0800 Subject: [PATCH 0259/1181] =?UTF-8?q?=E9=80=9A=E8=BF=87=E5=87=8F=E5=B0=91?= =?UTF-8?q?=E4=B8=8D=E5=BF=85=E8=A6=81=E7=9A=84=20newSQLConfig=20=E5=8F=8A?= =?UTF-8?q?=20getSQL=20=E7=AD=89=E6=AD=A5=E9=AA=A4=E6=9D=A5=E5=A4=A7?= =?UTF-8?q?=E5=B9=85=E6=8F=90=E5=8D=87=E6=95=B0=E7=BB=84=E5=86=85=E4=B8=BB?= =?UTF-8?q?=E8=A1=A8=E7=9A=84=E6=9F=A5=E8=AF=A2=E6=80=A7=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../apijson/orm/AbstractObjectParser.java | 248 +++++++++++------- .../main/java/apijson/orm/AbstractParser.java | 22 +- .../java/apijson/orm/AbstractSQLExecutor.java | 32 ++- .../main/java/apijson/orm/SQLExecutor.java | 2 + 4 files changed, 189 insertions(+), 115 deletions(-) diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractObjectParser.java b/APIJSONORM/src/main/java/apijson/orm/AbstractObjectParser.java index 8fcd78d80..2ac9447e6 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractObjectParser.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractObjectParser.java @@ -60,6 +60,8 @@ public AbstractObjectParser setParser(AbstractParser parser) { protected final int type; protected final List joinList; protected final boolean isTable; + protected final boolean isArrayMainTable; + protected final boolean isReuse; protected final String path; protected final String name; protected final String table; @@ -96,6 +98,8 @@ public AbstractObjectParser(@NotNull JSONObject request, String parentPath, Stri this.table = entry.getKey(); this.alias = entry.getValue(); this.isTable = apijson.JSONObject.isTableKey(table); + this.isArrayMainTable = isSubquery == false && this.isTable && this.type == SQLConfig.TYPE_ITEM_CHILD_0 && RequestMethod.isGetMethod(method, true); + this.isReuse = isArrayMainTable && arrayConfig != null && arrayConfig.getPosition() > 0; this.objectCount = 0; this.arrayCount = 0; @@ -118,6 +122,15 @@ public AbstractObjectParser(@NotNull JSONObject request, String parentPath, Stri Log.d(TAG, "AbstractObjectParser isEmpty = " + isEmpty + "; tri = " + tri + "; drop = " + drop); } + protected int position; + public int getPosition() { + return position; + } + public AbstractObjectParser setPosition(int position) { + this.position = position; + return this; + } + private boolean invalidate = false; public void invalidate() { @@ -260,7 +273,7 @@ else if (method == PUT && value instanceof JSONArray // 非Table内的函数会被滞后在onChildParse后调用! onFunctionResponse("-"); } - if (isTable) { + if (isReuse == false && isTable) { if (parser.getGlobleDatabase() != null && sqlRequest.get(JSONRequest.KEY_DATABASE) == null) { sqlRequest.put(JSONRequest.KEY_DATABASE, parser.getGlobleDatabase()); } @@ -297,96 +310,96 @@ else if (method == PUT && value instanceof JSONArray */ @Override public boolean onParse(@NotNull String key, @NotNull Object value) throws Exception { - if (key.endsWith("@")) {//StringUtil.isPath((String) value)) { - - if (value instanceof JSONObject) { // SQL 子查询对象,JSONObject -> SQLConfig.getSQL - String replaceKey = key.substring(0, key.length() - 1);//key{}@ getRealKey - - JSONObject subquery = (JSONObject) value; - String range = subquery.getString(JSONRequest.KEY_SUBQUERY_RANGE); - if (range != null && JSONRequest.SUBQUERY_RANGE_ALL.equals(range) == false && JSONRequest.SUBQUERY_RANGE_ANY.equals(range) == false) { - throw new IllegalArgumentException("子查询 " + path + "/" + key + ":{ range:value } 中 value 只能为 [" + JSONRequest.SUBQUERY_RANGE_ALL + ", " + JSONRequest.SUBQUERY_RANGE_ANY + "] 中的一个!"); - } - + if (key.endsWith("@")) { // StringUtil.isPath((String) value)) { + String replaceKey = key.substring(0, key.length() - 1); + + // [] 内主表 position > 0 时,用来生成 SQLConfig 的键值对全都忽略,不解析 +// if (isReuse == false || replaceKey.endsWith("()") || (replaceKey.startsWith("@") && JSONRequest.TABLE_KEY_LIST.contains(replaceKey) == false)) { + if (value instanceof JSONObject) { // key{}@ getRealKey, SQL 子查询对象,JSONObject -> SQLConfig.getSQL + + JSONObject subquery = (JSONObject) value; + String range = subquery.getString(JSONRequest.KEY_SUBQUERY_RANGE); + if (range != null && JSONRequest.SUBQUERY_RANGE_ALL.equals(range) == false && JSONRequest.SUBQUERY_RANGE_ANY.equals(range) == false) { + throw new IllegalArgumentException("子查询 " + path + "/" + key + ":{ range:value } 中 value 只能为 [" + JSONRequest.SUBQUERY_RANGE_ALL + ", " + JSONRequest.SUBQUERY_RANGE_ANY + "] 中的一个!"); + } - JSONArray arr = parser.onArrayParse(subquery, path, key, true); - JSONObject obj = arr == null || arr.isEmpty() ? null : arr.getJSONObject(0); - if (obj == null) { - throw new Exception("服务器内部错误,解析子查询 " + path + "/" + key + ":{ } 为 Subquery 对象失败!"); - } + JSONArray arr = parser.onArrayParse(subquery, path, key, true); - String from = subquery.getString(JSONRequest.KEY_SUBQUERY_FROM); - JSONObject arrObj = from == null ? null : obj.getJSONObject(from); - if (arrObj == null) { - throw new IllegalArgumentException("子查询 " + path + "/" + key + ":{ from:value } 中 value 对应的主表对象 " + from + ":{} 不存在!"); - } - // - SQLConfig cfg = (SQLConfig) arrObj.get(AbstractParser.KEY_CONFIG); - if (cfg == null) { - throw new NotExistException(TAG + ".onParse cfg == null"); - } - - Subquery s = new Subquery(); - s.setPath(path); - s.setOriginKey(key); - s.setOriginValue(subquery); - - s.setFrom(from); - s.setRange(range); - s.setKey(replaceKey); - s.setConfig(cfg); + JSONObject obj = arr == null || arr.isEmpty() ? null : arr.getJSONObject(0); + if (obj == null) { + throw new Exception("服务器内部错误,解析子查询 " + path + "/" + key + ":{ } 为 Subquery 对象失败!"); + } - key = replaceKey; - value = s; //(range == null || range.isEmpty() ? "" : "range") + "(" + cfg.getSQL(false) + ") "; + String from = subquery.getString(JSONRequest.KEY_SUBQUERY_FROM); + JSONObject arrObj = from == null ? null : obj.getJSONObject(from); + if (arrObj == null) { + throw new IllegalArgumentException("子查询 " + path + "/" + key + ":{ from:value } 中 value 对应的主表对象 " + from + ":{} 不存在!"); + } + // + SQLConfig cfg = (SQLConfig) arrObj.get(AbstractParser.KEY_CONFIG); + if (cfg == null) { + throw new NotExistException(TAG + ".onParse cfg == null"); + } - parser.putQueryResult(AbstractParser.getAbsPath(path, key), s); //字符串引用保证不了安全性 parser.getSQL(cfg)); - } - else if (value instanceof String) { // 引用赋值路径 + Subquery s = new Subquery(); + s.setPath(path); + s.setOriginKey(key); + s.setOriginValue(subquery); - // System.out.println("getObject key.endsWith(@) >> parseRelation = " + parseRelation); - String replaceKey = key.substring(0, key.length() - 1);//key{}@ getRealKey - String targetPath = AbstractParser.getValuePath(type == TYPE_ITEM - ? path : parentPath, new String((String) value)); + s.setFrom(from); + s.setRange(range); + s.setKey(replaceKey); + s.setConfig(cfg); - //先尝试获取,尽量保留缺省依赖路径,这样就不需要担心路径改变 - Object target = onReferenceParse(targetPath); - Log.i(TAG, "onParse targetPath = " + targetPath + "; target = " + target); + key = replaceKey; + value = s; //(range == null || range.isEmpty() ? "" : "range") + "(" + cfg.getSQL(false) + ") "; - if (target == null) {//String#equals(null)会出错 - Log.d(TAG, "onParse target == null >> return true;"); - return true; + parser.putQueryResult(AbstractParser.getAbsPath(path, key), s); //字符串引用保证不了安全性 parser.getSQL(cfg)); } - if (target instanceof Map) { //target可能是从requestObject里取出的 {} - if (isTable || targetPath.endsWith("[]/" + JSONResponse.KEY_INFO) == false) { - Log.d(TAG, "onParse target instanceof Map >> return false;"); - return false; //FIXME 这个判断现在来看是否还有必要?为啥不允许为 JSONObject ?以前可能因为防止二次遍历再解析,现在只有一次遍历 + else if (value instanceof String) { // //key{}@ getRealKey, 引用赋值路径 + // System.out.println("getObject key.endsWith(@) >> parseRelation = " + parseRelation); + String targetPath = AbstractParser.getValuePath(type == TYPE_ITEM + ? path : parentPath, new String((String) value)); + + //先尝试获取,尽量保留缺省依赖路径,这样就不需要担心路径改变 + Object target = onReferenceParse(targetPath); + Log.i(TAG, "onParse targetPath = " + targetPath + "; target = " + target); + + if (target == null) {//String#equals(null)会出错 + Log.d(TAG, "onParse target == null >> return true;"); + return true; } - } - if (targetPath.equals(target)) {//必须valuePath和保证getValueByPath传进去的一致! - Log.d(TAG, "onParse targetPath.equals(target) >>"); - - //非查询关键词 @key 不影响查询,直接跳过 - if (isTable && (key.startsWith("@") == false || JSONRequest.TABLE_KEY_LIST.contains(key))) { - Log.e(TAG, "onParse isTable && (key.startsWith(@) == false" - + " || JSONRequest.TABLE_KEY_LIST.contains(key)) >> return null;"); - return false;//获取不到就不用再做无效的query了。不考虑 Table:{Table:{}}嵌套 - } else { - Log.d(TAG, "onParse isTable(table) == false >> return true;"); - return true;//舍去,对Table无影响 + if (target instanceof Map) { //target可能是从requestObject里取出的 {} + if (isTable || targetPath.endsWith("[]/" + JSONResponse.KEY_INFO) == false) { + Log.d(TAG, "onParse target instanceof Map >> return false;"); + return false; //FIXME 这个判断现在来看是否还有必要?为啥不允许为 JSONObject ?以前可能因为防止二次遍历再解析,现在只有一次遍历 + } } - } - - //直接替换原来的key@:path为key:target - Log.i(TAG, "onParse >> key = replaceKey; value = target;"); - key = replaceKey; - value = target; - Log.d(TAG, "onParse key = " + key + "; value = " + value); - } - else { - throw new IllegalArgumentException(path + "/" + key + ":value 中 value 必须为 依赖路径String 或 SQL子查询JSONObject !"); - } + if (targetPath.equals(target)) {//必须valuePath和保证getValueByPath传进去的一致! + Log.d(TAG, "onParse targetPath.equals(target) >>"); + + //非查询关键词 @key 不影响查询,直接跳过 + if (isTable && (key.startsWith("@") == false || JSONRequest.TABLE_KEY_LIST.contains(key))) { + Log.e(TAG, "onParse isTable && (key.startsWith(@) == false" + + " || JSONRequest.TABLE_KEY_LIST.contains(key)) >> return null;"); + return false;//获取不到就不用再做无效的query了。不考虑 Table:{Table:{}}嵌套 + } else { + Log.d(TAG, "onParse isTable(table) == false >> return true;"); + return true;//舍去,对Table无影响 + } + } + //直接替换原来的key@:path为key:target + Log.i(TAG, "onParse >> key = replaceKey; value = target;"); + key = replaceKey; + value = target; + Log.d(TAG, "onParse key = " + key + "; value = " + value); + } + else { + throw new IllegalArgumentException(path + "/" + key + ":value 中 value 必须为 依赖路径String 或 SQL子查询JSONObject !"); + } +// } } if (key.endsWith("()")) { @@ -426,7 +439,9 @@ else if (isTable && key.startsWith("@") && JSONRequest.TABLE_KEY_LIST.contains(k customMap.put(key, value); } else { + // 导致副表从 1 开始都不查了 if (isReuse == false) { sqlRequest.put(key, value); + // } } return true; @@ -640,8 +655,8 @@ public AbstractObjectParser setSQLConfig() throws Exception { @Override public AbstractObjectParser setSQLConfig(int count, int page, int position) throws Exception { - if (isTable == false) { - return this; + if (isTable == false || isReuse) { + return setPosition(position); } if (sqlConfig == null) { @@ -676,28 +691,30 @@ public AbstractObjectParser executeSQL() throws Exception { //执行SQL操作数据库 if (isTable == false) {//提高性能 sqlReponse = new JSONObject(sqlRequest); - } else { - try { - sqlReponse = onSQLExecute(); - } - catch (NotExistException e) { - // Log.e(TAG, "getObject try { response = getSQLObject(config2); } catch (Exception e) {"); - // if (e instanceof NotExistException) {//非严重异常,有时候只是数据不存在 - // // e.printStackTrace(); - sqlReponse = null;//内部吃掉异常,put到最外层 - // requestObject.put(JSONResponse.KEY_MSG - // , StringUtil.getString(requestObject.get(JSONResponse.KEY_MSG) - // + "; query " + path + " cath NotExistException:" - // + newErrorResult(e).getString(JSONResponse.KEY_MSG))); - // } else { - // throw e; - // } + } + else { + try { + sqlReponse = onSQLExecute(); + + } + catch (NotExistException e) { + // Log.e(TAG, "getObject try { response = getSQLObject(config2); } catch (Exception e) {"); + // if (e instanceof NotExistException) {//非严重异常,有时候只是数据不存在 + // // e.printStackTrace(); + sqlReponse = null;//内部吃掉异常,put到最外层 + // requestObject.put(JSONResponse.KEY_MSG + // , StringUtil.getString(requestObject.get(JSONResponse.KEY_MSG) + // + "; query " + path + " cath NotExistException:" + // + newErrorResult(e).getString(JSONResponse.KEY_MSG))); + // } else { + // throw e; + // } + } } if (drop) {//丢弃Table,只为了向下提供条件 sqlReponse = null; } - } return this; } @@ -799,10 +816,39 @@ public Object onReferenceParse(@NotNull String path) { @Override public JSONObject onSQLExecute() throws Exception { - JSONObject result = parser.executeSQL(sqlConfig, isSubquery); - if (isSubquery == false && result != null) { - parser.putQueryResult(path, result);//解决获取关联数据时requestObject里不存在需要的关联数据 + int position = getPosition(); + + JSONObject result; + if (isArrayMainTable && position > 0) { // 数组主表使用专门的缓存数据 + result = parser.getArrayMainCacheItem(parentPath.substring(0, parentPath.lastIndexOf("[]") + 2), position); } + else { + result = parser.executeSQL(sqlConfig, isSubquery); + + if (isArrayMainTable && position == 0 && result != null) { // 提取并缓存数组主表的列表数据 + @SuppressWarnings("unchecked") + List list = (List) result.remove(SQLExecutor.KEY_RAW_LIST); + if (list != null) { + String arrayPath = parentPath.substring(0, parentPath.lastIndexOf("[]") + 2); + + for (int i = 1; i < list.size(); i++) { // 从 1 开始,0 已经处理过 + JSONObject obj = parser.parseCorrectResponse(table, list.get(i)); + list.set(i, obj); + + if (obj != null) { + parser.putQueryResult(arrayPath + "/" + i + "/" + table, obj); //解决获取关联数据时requestObject里不存在需要的关联数据 + } + } + + parser.putArrayMainCache(arrayPath, list); + } + } + + if (isSubquery == false && result != null) { + parser.putQueryResult(path, result);//解决获取关联数据时requestObject里不存在需要的关联数据 + } + } + return result; } diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java b/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java index 85bbb4c1e..1f0880128 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java @@ -14,7 +14,6 @@ import java.sql.Savepoint; import java.util.ArrayList; import java.util.Arrays; -import java.util.Comparator; import java.util.HashMap; import java.util.LinkedHashSet; import java.util.List; @@ -792,8 +791,8 @@ public JSONObject onObjectParse(final JSONObject request JSONObject response = null; - if (op != null) {//TODO SQL查询结果为空时,functionMap和customMap还有没有意义? - if (arrayConfig == null) {//Common + if (op != null) {//SQL查询结果为空时,functionMap和customMap没有意义 + if (arrayConfig == null) { //Common response = op.setSQLConfig().executeSQL().response(); } else {//Array Item Child @@ -1431,6 +1430,20 @@ public static JSONObject getJSONObject(JSONObject object, String key) { public static final String KEY_CONFIG = "config"; + + protected Map> arrayMainCacheMap = new HashMap<>(); + public void putArrayMainCache(String arrayPath, List mainTableDataList) { + arrayMainCacheMap.put(arrayPath, mainTableDataList); + } + public List getArrayMainCache(String arrayPath) { + return arrayMainCacheMap.get(arrayPath); + } + public JSONObject getArrayMainCacheItem(String arrayPath, int position) { + List list = getArrayMainCache(arrayPath); + return list == null || position >= list.size() ? null : list.get(position); + } + + /**执行 SQL 并返回 JSONObject * @param config @@ -1451,8 +1464,9 @@ public JSONObject executeSQL(SQLConfig config, boolean isSubquery) throws Except } try { - boolean explain = config.isExplain(); JSONObject result; + + boolean explain = config.isExplain(); if (explain) { //如果先执行 explain,则 execute 会死循环,所以只能先执行非 explain config.setExplain(false); //对下面 config.getSQL(false); 生效 JSONObject res = getSQLExecutor().execute(config, false); diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java index 92508640d..47c2d46e5 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java @@ -155,7 +155,7 @@ public JSONObject execute(@NotNull SQLConfig config, boolean unknowType) throws config.setPrepared(prepared); if (StringUtil.isEmpty(sql, true)) { - Log.e(TAG, "select StringUtil.isEmpty(sql, true) >> return null;"); + Log.e(TAG, "execute StringUtil.isEmpty(sql, true) >> return null;"); return null; } @@ -169,7 +169,7 @@ public JSONObject execute(@NotNull SQLConfig config, boolean unknowType) throws long startTime = System.currentTimeMillis(); Log.d(TAG, "\n<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<" + "\n已生成 " + generatedSQLCount + " 条 SQL" - + "\nselect startTime = " + startTime + + "\nexecute startTime = " + startTime + "\ndatabase = " + StringUtil.getString(config.getDatabase()) + "; schema = " + StringUtil.getString(config.getSchema()) + "; sql = \n" + sql @@ -225,11 +225,11 @@ public JSONObject execute(@NotNull SQLConfig config, boolean unknowType) throws case GET: case GETS: result = getCacheItem(sql, position, config.getCache()); - Log.i(TAG, ">>> select result = getCache('" + sql + "', " + position + ") = " + result); + Log.i(TAG, ">>> execute result = getCache('" + sql + "', " + position + ") = " + result); if (result != null) { cachedSQLCount ++; - Log.d(TAG, "\n\n select result != null >> return result;" + "\n>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>\n\n"); + Log.d(TAG, "\n\n execute result != null >> return result;" + "\n>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>\n\n"); return result; } @@ -241,7 +241,7 @@ public JSONObject execute(@NotNull SQLConfig config, boolean unknowType) throws break; default://OPTIONS, TRACE等 - Log.e(TAG, "select sql = " + sql + " ; method = " + config.getMethod() + " >> return null;"); + Log.e(TAG, "execute sql = " + sql + " ; method = " + config.getMethod() + " >> return null;"); return null; } } @@ -266,7 +266,7 @@ public JSONObject execute(@NotNull SQLConfig config, boolean unknowType) throws int viceColumnStart = length + 1; //第一个副表字段的index while (rs.next()) { index ++; - Log.d(TAG, "\n\n<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<\n select while (rs.next()){ index = " + index + "\n\n"); + Log.d(TAG, "\n\n<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<\n execute while (rs.next()){ index = " + index + "\n\n"); JSONObject item = new JSONObject(true); @@ -293,7 +293,7 @@ else if (config.getSQLTable().equalsIgnoreCase(rsmd.getTableName(i)) == false) { resultList = onPutTable(config, rs, rsmd, resultList, index, item); - Log.d(TAG, "\n select while (rs.next()) { resultList.put( " + index + ", result); " + Log.d(TAG, "\n execute while (rs.next()) { resultList.put( " + index + ", result); " + "\n >>>>>>>>>>>>>>>>>>>>>>>>>>> \n\n"); } @@ -345,12 +345,24 @@ else if (config.getSQLTable().equalsIgnoreCase(rsmd.getTableName(i)) == false) { } putCache(sql, resultList, config.getCache()); - Log.i(TAG, ">>> select putCache('" + sql + "', resultList); resultList.size() = " + resultList.size()); + Log.i(TAG, ">>> execute putCache('" + sql + "', resultList); resultList.size() = " + resultList.size()); + // 数组主表对象额外一次返回全部,方便 Parser 缓存来提高性能 + + result = position >= resultList.size() ? new JSONObject() : resultList.get(position); + if (position == 0 && resultList.size() > 1 && result != null && result.isEmpty() == false) { + // 不是 main 不会直接执行,count=1 返回的不会超过 1 && config.isMain() && config.getCount() != 1 + Log.i(TAG, ">>> execute position == 0 && resultList.size() > 1 && result != null && result.isEmpty() == false" + + " >> result = new JSONObject(result); result.put(KEY_RAW_LIST, resultList);"); + + result = new JSONObject(result); + result.put(KEY_RAW_LIST, resultList); + } + long endTime = System.currentTimeMillis(); - Log.d(TAG, "\n\n select endTime = " + endTime + "; duration = " + (endTime - startTime) + Log.d(TAG, "\n\n execute endTime = " + endTime + "; duration = " + (endTime - startTime) + "\n return resultList.get(" + position + ");" + "\n>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>\n\n"); - return position >= resultList.size() ? new JSONObject() : resultList.get(position); + return result; } diff --git a/APIJSONORM/src/main/java/apijson/orm/SQLExecutor.java b/APIJSONORM/src/main/java/apijson/orm/SQLExecutor.java index b2b0425d5..9fe5917a7 100755 --- a/APIJSONORM/src/main/java/apijson/orm/SQLExecutor.java +++ b/APIJSONORM/src/main/java/apijson/orm/SQLExecutor.java @@ -22,6 +22,8 @@ */ public interface SQLExecutor { + String KEY_RAW_LIST = "@RAW@LIST"; // 避免和字段命名冲突,不用 $RAW@LIST$ 是因为 $ 会在 fastjson 内部转义,浪费性能 + /**保存缓存 * @param sql * @param map From 2d193df9992aee4ed1fc020b34fbc5c87a330e4a Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Sun, 31 Jan 2021 01:24:53 +0800 Subject: [PATCH 0260/1181] Update Document.md --- Document.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Document.md b/Document.md index ab37d9f31..09d127be1 100644 --- a/Document.md +++ b/Document.md @@ -371,6 +371,6 @@ DELETE:
删除数据 | base_url/delete/ | {
   TableName:{< 比较运算 | >, <, >=, <= 比较运算符,用于
① 提供 "id{}":"<=90000" 这种条件范围的简化写法

② 实现子查询相关比较运算

不支持 "key=":Object 和 "key!=":Object 这两种写法,直接用更简单的 "key":Object 和 "key!":Object 替代。 | ① ["id<=":90000](http://apijson.cn:8080/get/{"[]":{"User":{"id<=":90000}}}),对应SQL是`id<=90000`,查询符合id<=90000的一个User数组

② ["id>@":{
   "from":"Comment",
   "Comment":{
      "@column":"min(userId)"
   }
}](http://apijson.cn:8080/get/{"User":{"id>@":{"from":"Comment","Comment":{"@column":"min(userId)"}}}})
WHERE id>(SELECT min(userId) FROM Comment) 逻辑运算 | &, \|, ! 逻辑运算符,对应数据库 SQL 中的 AND, OR, NOT。
横或纵与:同一键值对的值内条件默认 \| 或连接,可以在 key 后加逻辑运算符来具体指定;不同键值对的条件默认 & 与连接,可以用下面说明的对象关键词 @combine 来具体指定。

① & 可用于"key&{}":"条件"等

② \| 可用于"key\|{}":"条件", "key\|{}":[]等,一般可省略

③ ! 可单独使用,如"key!":Object,也可像&,\|一样配合其他功能符使用
"key!":null 无效,null 值会导致整个键值对被忽略解析,可以用 "key{}":"!=null" 替代,
"key":null 同理,用 "key{}":"=null" 替代。 | ① ["id&{}":">80000,<=90000"](http://apijson.cn:8080/head/{"User":{"id&{}":">80000,<=90000"}}),对应SQL是`id>80000 AND id<=90000`,即id满足id>80000 & id<=90000

② ["id\|{}":">90000,<=80000"](http://apijson.cn:8080/head/{"User":{"id\|{}":">90000,<=80000"}}),同"id{}":">90000,<=80000",对应SQL是`id>80000 OR id<=90000`,即id满足id>90000 \| id<=80000

③ ["id!{}":[82001,38710]](http://apijson.cn:8080/head/{"User":{"id!{}":[82001,38710]}}),对应SQL是`id NOT IN(82001,38710)`,即id满足 ! (id=82001 \| id=38710),可过滤黑名单的消息 数组关键词,可自定义 | "key":Object,key为 "[]":{} 中{}内的关键词,Object的类型由key指定

① "count":Integer,查询数量,0 表示最大值,默认最大值为100

② "page":Integer,查询页码,从0开始,默认最大值为100,一般和count一起用

③ "query":Integer,查询内容
0-对象,1-总数和分页详情,2-以上全部
总数关键词为 total,分页详情关键词为 info,
它们都和 query 同级,通过引用赋值得到,例如
"total@":"/[]/total", "info@":"/[]/info"
这里query及total仅为GET类型的请求提供方便,
一般可直接用HEAD类型的请求获取总数

④ "join":"&/Table0/key0@,\多表连接方式:
"\<" - LEFT JOIN
">" - RIGHT JOIN
"&" - INNER JOIN
"\|" - FULL JOIN
"!" - OUTTER JOIN
"@" - APP JOIN
其中 @ APP JOIN 为应用层连表,会从已查出的主表里取得所有副表 key@ 关联的主表内的 refKey 作为一个数组 refKeys: [value0, value1...],然后把原来副表 count 次查询 key=$refKey 的 SQL 用 key IN($refKeys) 的方式合并为一条 SQL 来优化性能;
其它 JOIN 都是 SQL JOIN,具体功能和 MySQL,PostgreSQL 等数据库的 JOIN 一一对应
`"join":"`"MainTable":{},`
`"ViceTable":{"key@":"/MainTable/refKey"}`
会对应生成
`MainTable LEFT JOIN ViceTable`
`ON ViceTable.key=MainTable.refKey`

⑤ "otherKey":Object,自定义关键词,名称和以上系统关键词不一样,且原样返回上传的值 | ① 查询User数组,最多5个:
["count":5](http://apijson.cn:8080/get/{"[]":{"count":5,"User":{}}})
对应SQL是`LIMIT 5`

② 查询第3页的User数组,每页5个:
["count":5,
"page":3](http://apijson.cn:8080/get/{"[]":{"count":5,"page":3,"User":{}}})
对应SQL是`LIMIT 5 OFFSET 15`

③ 查询User数组和对应的User总数:
["[]":{
   "query":2,
   "User":{}
},
"total@":"/[]/total",
"info@":"/[]/info"](http://apijson.cn:8080/get/{"[]":{"query":2,"count":5,"User":{}},"total@":"%252F[]%252Ftotal","info@":"%252F[]%252Finfo"})
返回的数据中,总数及分页详情结构为:
"total":139, //总数
"info":{ //分页详情
   "total":139, //总数
   "count":5, //每页数量
   "page":0, //当前页码
   "max":27, //最大页码
   "more":true, //是否还有更多
   "first":true, //是否为首页
   "last":false //是否为尾页
}

④ Moment INNER JOIN User LEFT JOIN Comment:
["[]":{
   "join":"&/User/id@,\    "Moment":{},
   "User":{
     "name~":"t",
     "id@":"/Moment/userId"
   },
   "Comment":{
     "momentId@":"/Moment/id"
   }
}](http://apijson.cn:8080/get/{"[]":{"count":5,"join":"&%252FUser%252Fid@,\<%252FComment%252FmomentId@","Moment":{"@column":"id,userId,content"},"User":{"name~":"t","id@":"%252FMoment%252FuserId","@column":"id,name,head"},"Comment":{"momentId@":"%252FMoment%252Fid","@column":"id,momentId,content"}}})

⑤ 每一层都加当前用户名:
["User":{},
"[]":{
   "name@":"User/name", //自定义关键词
   "Moment":{}
}](http://apijson.cn:8080/get/{"User":{},"[]":{"name@":"User%252Fname","Moment":{}}}) - 对象关键词,可自定义 | "@key":Object,@key为 Table:{} 中{}内的关键词,Object的类型由@key指定

① "@combine":"&key0,&key1,\|key2,key3,
!key4,!key5,&key6,key7...",条件组合方式,\| 可省略。会自动把同类的合并,外层按照 & \| ! 顺序,内层的按传参顺序组合成
(key0 & key1 & key6 & 其它key) & (key2 \| key3 \| key7) & !(key4 \| key5)
这种连接方式,其中 "其它key" 是指与 @combine 在同一对象,且未被它声明的条件 key,默认都是 & 连接

② "@column":"column;function(arg)...",返回字段

③ "@order":"column0+,column1-...",排序方式

④ "@group":"column0,column1...",分组方式。如果@column里声明了Table的id,则id也必须在@group中声明;其它情况下必须满足至少一个条件:
1.分组的key在@column里声明
2.Table主键在@group中声明

⑤ "@having":"function0(...)?value0;function1(...)?value1;function2(...)?value2...",SQL函数条件,一般和@group一起用,函数一般在@column里声明

⑥ "@schema":"sys",集合空间(模式),非默认的值可通过它来指定,可以在最外层作为全局默认配置

⑦ "@database":"POSTGRESQL",跨数据库,非默认的值可通过它来指定,可以在最外层作为全局默认配置

⑧ "@json":"key0,key1...",转为 JSON 格式返回,符合 JSONObject 则转为 {...},符合 JSONArray 则转为 \[...]

⑨ "@role":"OWNER",来访角色,包括
UNKNOWN,LOGIN,CONTACT,CIRCLE,OWNER,ADMIN,
可以在最外层作为全局默认配置,
可自定义其它角色并重写 Verifier.verify 等相关方法来自定义校验

⑩ "@explain":true,性能分析,可以在最外层作为全局默认配置

⑪ "@raw":"key0,key1...",其中 key0, key1 都对应有键值对
"key0":"SQL片段或SQL片段的别名",
"key1":"SQL片段或SQL片段的别名"
自定义原始SQL片段,可扩展嵌套SQL函数等复杂语句,必须是后端已配置的,只有其它功能符都做不到才考虑,谨慎使用,注意防SQL注入

⑫ "@otherKey":Object,自定义关键词,名称和以上系统关键词不一样,且原样返回上传的值 | ① 搜索name或tag任何一个字段包含字符a的User列表:
["name~":"a",
"tag~":"a",
"@combine":"name~,tag~"](http://apijson.cn:8080/get/{"User[]":{"count":10,"User":{"@column":"id,name,tag","name~":"a","tag~":"a","@combine":"name~,tag~"}}})
对应SQL是`name REGEXP 'a' OR tag REGEXP 'a'`

② 只查询id,sex,name这几列并且请求结果也按照这个顺序:
["@column":"id,sex,name"](http://apijson.cn:8080/get/{"User":{"@column":"id,sex,name","id":38710}})
对应SQL是`SELECT id,sex,name`

③ 查询按 name降序、id默认顺序 排序的User数组:
["@order":"name-,id"](http://apijson.cn:8080/get/{"[]":{"count":10,"User":{"@column":"name,id","@order":"name-,id"}}})
对应SQL是`ORDER BY name DESC,id`

④ 查询按userId分组的Moment数组:
["@group":"userId,id"](http://apijson.cn:8080/get/{"[]":{"count":10,"Moment":%7B"@column":"userId,id","@group":"userId,id"}}})
对应SQL是`GROUP BY userId,id`

⑤ 查询 按userId分组、id最大值>=100 的Moment数组:
["@column":"userId;max(id)",
"@group":"userId",
"@having":"max(id)>=100"](http://apijson.cn:8080/get/{"[]":{"count":10,"Moment":{"@column":"userId%253Bmax(id)","@group":"userId","@having":"max(id)>=100"}}})
对应SQL是`SELECT userId,max(id) ... GROUP BY userId HAVING max(id)>=100`
还可以指定函数返回名:
["@column":"userId;max(id):maxId",
"@group":"userId",
"@having":"maxId>=100"](http://apijson.cn:8080/get/{"[]":{"count":10,"Moment":{"@column":"userId%253Bmax(id):maxId","@group":"userId","@having":"maxId>=100"}}})
对应SQL是`SELECT userId,max(id) AS maxId ... GROUP BY userId HAVING maxId>=100`

⑥ 查询 sys 内的 User 表:
["@schema":"sys"](http://apijson.cn:8080/get/{"User":{"@schema":"sys"}})
对应SQL是`FROM sys.User`

⑦ 查询 PostgreSQL 数据库的 User 表:
["@database":"POSTGRESQL"](http://apijson.cn:8080/get/{"User":{"@database":"POSTGRESQL","@explain":true}})

⑧ 将 VARCHAR 字符串字段 get 转为 JSONArray 返回:
["@json":"get"](http://apijson.cn:8080/get/{"Access":{"@json":"get"}})

⑨ 查询当前用户的动态:
["@role":"OWNER"](http://apijson.cn:8080/get/{"[]":{"Moment":{"@role":"OWNER"}}})

⑩ 开启性能分析:
["@explain":true](http://apijson.cn:8080/get/{"[]":{"Moment":{"@explain":true}}})
对应SQL是`EXPLAIN`

⑪ 统计最近一周偶数userId的数量
["@column":"sum(if(userId%2=0,1,0))",
"@having":"to_days(now())-to_days(\`date\`)<=7",
"@raw":"@column,@having"](http://apijson.cn:8080/get/{"Comment":{"@column":"sum(if(userId%252=0,1,0))","@having":"to_days(now())-to_days(\`date\`)<=7","@raw":"@column,@having"}})
对应SQL是``SELECT sum(if(userId%2=0,1,0)) ... HAVING to_days(now())-to_days(`date`)<=7``

⑫ 从pictureList获取第0张图片:
["@position":0, //自定义关键词
"firstPicture()":"getFromArray(pictureList,@position)"](http://apijson.cn:8080/get/{"User":{"id":38710,"@position":0,"firstPicture()":"getFromArray(pictureList,@position)"}}) + 对象关键词,可自定义 | "@key":Object,@key为 Table:{} 中{}内的关键词,Object的类型由@key指定

① "@combine":"&key0,&key1,\|key2,key3,
!key4,!key5,&key6,key7...",条件组合方式,\| 可省略。会自动把同类的合并,外层按照 & \| ! 顺序,内层的按传参顺序组合成
(key0 & key1 & key6 & 其它key) & (key2 \| key3 \| key7) & !(key4 \| key5)
这种连接方式,其中 "其它key" 是指与 @combine 在同一对象,且未被它声明的条件 key,默认都是 & 连接

② "@column":"column;function(arg)...",返回字段

③ "@order":"column0+,column1-...",排序方式

④ "@group":"column0,column1...",分组方式。如果@column里声明了Table的id,则id也必须在@group中声明;其它情况下必须满足至少一个条件:
1.分组的key在@column里声明
2.Table主键在@group中声明

⑤ "@having":"function0(...)?value0;function1(...)?value1;function2(...)?value2...",SQL函数条件,一般和@group一起用,函数一般在@column里声明

⑥ "@schema":"sys",集合空间(模式),非默认的值可通过它来指定,可以在最外层作为全局默认配置

⑦ "@database":"POSTGRESQL",跨数据库,非默认的值可通过它来指定,可以在最外层作为全局默认配置

⑧ "@json":"key0,key1...",转为 JSON 格式返回,符合 JSONObject 则转为 {...},符合 JSONArray 则转为 \[...]

⑨ "@role":"OWNER",来访角色,包括
UNKNOWN,LOGIN,CONTACT,CIRCLE,OWNER,ADMIN,
可以在最外层作为全局默认配置,
可自定义其它角色并重写 Verifier.verify 等相关方法来自定义校验

⑩ "@explain":true,性能分析,可以在最外层作为全局默认配置

⑪ "@raw":"key0,key1...",其中 key0, key1 都对应有键值对
"key0":"SQL片段或SQL片段的别名",
"key1":"SQL片段或SQL片段的别名"
自定义原始SQL片段,可扩展嵌套SQL函数等复杂语句,必须是后端已配置的,只有其它功能符都做不到才考虑,谨慎使用,注意防SQL注入

⑫ "@otherKey":Object,自定义关键词,名称和以上系统关键词不一样,且原样返回上传的值 | ① 搜索name或tag任何一个字段包含字符a的User列表:
["name~":"a",
"tag~":"a",
"@combine":"name~,tag~"](http://apijson.cn:8080/get/{"User[]":{"count":10,"User":{"@column":"id,name,tag","name~":"a","tag~":"a","@combine":"name~,tag~"}}})
对应SQL是`name REGEXP 'a' OR tag REGEXP 'a'`

② 只查询id,sex,name这几列并且请求结果也按照这个顺序:
["@column":"id,sex,name"](http://apijson.cn:8080/get/{"User":{"@column":"id,sex,name","id":38710}})
对应SQL是`SELECT id,sex,name`

③ 查询按 name降序、id默认顺序 排序的User数组:
["@order":"name-,id"](http://apijson.cn:8080/get/{"[]":{"count":10,"User":{"@column":"name,id","@order":"name-,id"}}})
对应SQL是`ORDER BY name DESC,id`

④ 查询按userId分组的Moment数组:
["@group":"userId,id"](http://apijson.cn:8080/get/{"[]":{"count":10,"Moment":%7B"@column":"userId,id","@group":"userId,id"}}})
对应SQL是`GROUP BY userId,id`

⑤ 查询 按userId分组、id最大值>=100 的Moment数组:
["@column":"userId;max(id)",
"@group":"userId",
"@having":"max(id)>=100"](http://apijson.cn:8080/get/{"[]":{"count":10,"Moment":{"@column":"userId%253Bmax(id)","@group":"userId","@having":"max(id)>=100"}}})
对应SQL是`SELECT userId,max(id) ... GROUP BY userId HAVING max(id)>=100`
还可以指定函数返回名:
["@column":"userId;max(id):maxId",
"@group":"userId",
"@having":"maxId>=100"](http://apijson.cn:8080/get/{"[]":{"count":10,"Moment":{"@column":"userId%253Bmax(id):maxId","@group":"userId","@having":"maxId>=100"}}})
对应SQL是`SELECT userId,max(id) AS maxId ... GROUP BY userId HAVING maxId>=100`

⑥ 查询 sys 内的 User 表:
["@schema":"sys"](http://apijson.cn:8080/get/{"User":{"@schema":"sys"}})
对应SQL是`FROM sys.User`

⑦ 查询 PostgreSQL 数据库的 User 表:
["@database":"POSTGRESQL"](http://apijson.cn:8080/get/{"User":{"@database":"POSTGRESQL","@explain":true}})

⑧ 将 VARCHAR 字符串字段 get 转为 JSONArray 返回:
["@json":"get"](http://apijson.cn:8080/get/{"Access":{"@json":"get"}})

⑨ 查询当前用户的动态:
["@role":"OWNER"](http://apijson.cn:8080/get/{"[]":{"Moment":{"@role":"OWNER"}}})

⑩ 开启性能分析:
["@explain":true](http://apijson.cn:8080/get/{"[]":{"Moment":{"@explain":true}}})
对应SQL是`EXPLAIN`

⑪ 统计最近一周偶数userId的数量
["@column":"date;sum(if(userId%2=0,1,0))",
"@having":"to_days(now())-to_days(\`date\`)<=7",
"@raw":"@column,@having"](http://apijson.cn:8080/get/{"Comment":{"@column":"date%3bsum(if(userId%252=0,1,0))","@having":"to_days(now())-to_days(\`date\`)<=7","@raw":"@column,@having"}})
对应SQL是``SELECT sum(if(userId%2=0,1,0)) ... HAVING to_days(now())-to_days(`date`)<=7``

⑫ 从pictureList获取第0张图片:
["@position":0, //自定义关键词
"firstPicture()":"getFromArray(pictureList,@position)"](http://apijson.cn:8080/get/{"User":{"id":38710,"@position":0,"firstPicture()":"getFromArray(pictureList,@position)"}})
From e73a1a9385a31955300bddff23c54d17a89b44ee Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Sun, 31 Jan 2021 01:26:19 +0800 Subject: [PATCH 0261/1181] Update Document.md --- Document.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Document.md b/Document.md index 09d127be1..727187cfe 100644 --- a/Document.md +++ b/Document.md @@ -371,6 +371,6 @@ DELETE:
删除数据 | base_url/delete/ | {
   TableName:{< 比较运算 | >, <, >=, <= 比较运算符,用于
① 提供 "id{}":"<=90000" 这种条件范围的简化写法

② 实现子查询相关比较运算

不支持 "key=":Object 和 "key!=":Object 这两种写法,直接用更简单的 "key":Object 和 "key!":Object 替代。 | ① ["id<=":90000](http://apijson.cn:8080/get/{"[]":{"User":{"id<=":90000}}}),对应SQL是`id<=90000`,查询符合id<=90000的一个User数组

② ["id>@":{
   "from":"Comment",
   "Comment":{
      "@column":"min(userId)"
   }
}](http://apijson.cn:8080/get/{"User":{"id>@":{"from":"Comment","Comment":{"@column":"min(userId)"}}}})
WHERE id>(SELECT min(userId) FROM Comment) 逻辑运算 | &, \|, ! 逻辑运算符,对应数据库 SQL 中的 AND, OR, NOT。
横或纵与:同一键值对的值内条件默认 \| 或连接,可以在 key 后加逻辑运算符来具体指定;不同键值对的条件默认 & 与连接,可以用下面说明的对象关键词 @combine 来具体指定。

① & 可用于"key&{}":"条件"等

② \| 可用于"key\|{}":"条件", "key\|{}":[]等,一般可省略

③ ! 可单独使用,如"key!":Object,也可像&,\|一样配合其他功能符使用
"key!":null 无效,null 值会导致整个键值对被忽略解析,可以用 "key{}":"!=null" 替代,
"key":null 同理,用 "key{}":"=null" 替代。 | ① ["id&{}":">80000,<=90000"](http://apijson.cn:8080/head/{"User":{"id&{}":">80000,<=90000"}}),对应SQL是`id>80000 AND id<=90000`,即id满足id>80000 & id<=90000

② ["id\|{}":">90000,<=80000"](http://apijson.cn:8080/head/{"User":{"id\|{}":">90000,<=80000"}}),同"id{}":">90000,<=80000",对应SQL是`id>80000 OR id<=90000`,即id满足id>90000 \| id<=80000

③ ["id!{}":[82001,38710]](http://apijson.cn:8080/head/{"User":{"id!{}":[82001,38710]}}),对应SQL是`id NOT IN(82001,38710)`,即id满足 ! (id=82001 \| id=38710),可过滤黑名单的消息 数组关键词,可自定义 | "key":Object,key为 "[]":{} 中{}内的关键词,Object的类型由key指定

① "count":Integer,查询数量,0 表示最大值,默认最大值为100

② "page":Integer,查询页码,从0开始,默认最大值为100,一般和count一起用

③ "query":Integer,查询内容
0-对象,1-总数和分页详情,2-以上全部
总数关键词为 total,分页详情关键词为 info,
它们都和 query 同级,通过引用赋值得到,例如
"total@":"/[]/total", "info@":"/[]/info"
这里query及total仅为GET类型的请求提供方便,
一般可直接用HEAD类型的请求获取总数

④ "join":"&/Table0/key0@,\多表连接方式:
"\<" - LEFT JOIN
">" - RIGHT JOIN
"&" - INNER JOIN
"\|" - FULL JOIN
"!" - OUTTER JOIN
"@" - APP JOIN
其中 @ APP JOIN 为应用层连表,会从已查出的主表里取得所有副表 key@ 关联的主表内的 refKey 作为一个数组 refKeys: [value0, value1...],然后把原来副表 count 次查询 key=$refKey 的 SQL 用 key IN($refKeys) 的方式合并为一条 SQL 来优化性能;
其它 JOIN 都是 SQL JOIN,具体功能和 MySQL,PostgreSQL 等数据库的 JOIN 一一对应
`"join":"`"MainTable":{},`
`"ViceTable":{"key@":"/MainTable/refKey"}`
会对应生成
`MainTable LEFT JOIN ViceTable`
`ON ViceTable.key=MainTable.refKey`

⑤ "otherKey":Object,自定义关键词,名称和以上系统关键词不一样,且原样返回上传的值 | ① 查询User数组,最多5个:
["count":5](http://apijson.cn:8080/get/{"[]":{"count":5,"User":{}}})
对应SQL是`LIMIT 5`

② 查询第3页的User数组,每页5个:
["count":5,
"page":3](http://apijson.cn:8080/get/{"[]":{"count":5,"page":3,"User":{}}})
对应SQL是`LIMIT 5 OFFSET 15`

③ 查询User数组和对应的User总数:
["[]":{
   "query":2,
   "User":{}
},
"total@":"/[]/total",
"info@":"/[]/info"](http://apijson.cn:8080/get/{"[]":{"query":2,"count":5,"User":{}},"total@":"%252F[]%252Ftotal","info@":"%252F[]%252Finfo"})
返回的数据中,总数及分页详情结构为:
"total":139, //总数
"info":{ //分页详情
   "total":139, //总数
   "count":5, //每页数量
   "page":0, //当前页码
   "max":27, //最大页码
   "more":true, //是否还有更多
   "first":true, //是否为首页
   "last":false //是否为尾页
}

④ Moment INNER JOIN User LEFT JOIN Comment:
["[]":{
   "join":"&/User/id@,\    "Moment":{},
   "User":{
     "name~":"t",
     "id@":"/Moment/userId"
   },
   "Comment":{
     "momentId@":"/Moment/id"
   }
}](http://apijson.cn:8080/get/{"[]":{"count":5,"join":"&%252FUser%252Fid@,\<%252FComment%252FmomentId@","Moment":{"@column":"id,userId,content"},"User":{"name~":"t","id@":"%252FMoment%252FuserId","@column":"id,name,head"},"Comment":{"momentId@":"%252FMoment%252Fid","@column":"id,momentId,content"}}})

⑤ 每一层都加当前用户名:
["User":{},
"[]":{
   "name@":"User/name", //自定义关键词
   "Moment":{}
}](http://apijson.cn:8080/get/{"User":{},"[]":{"name@":"User%252Fname","Moment":{}}}) - 对象关键词,可自定义 | "@key":Object,@key为 Table:{} 中{}内的关键词,Object的类型由@key指定

① "@combine":"&key0,&key1,\|key2,key3,
!key4,!key5,&key6,key7...",条件组合方式,\| 可省略。会自动把同类的合并,外层按照 & \| ! 顺序,内层的按传参顺序组合成
(key0 & key1 & key6 & 其它key) & (key2 \| key3 \| key7) & !(key4 \| key5)
这种连接方式,其中 "其它key" 是指与 @combine 在同一对象,且未被它声明的条件 key,默认都是 & 连接

② "@column":"column;function(arg)...",返回字段

③ "@order":"column0+,column1-...",排序方式

④ "@group":"column0,column1...",分组方式。如果@column里声明了Table的id,则id也必须在@group中声明;其它情况下必须满足至少一个条件:
1.分组的key在@column里声明
2.Table主键在@group中声明

⑤ "@having":"function0(...)?value0;function1(...)?value1;function2(...)?value2...",SQL函数条件,一般和@group一起用,函数一般在@column里声明

⑥ "@schema":"sys",集合空间(模式),非默认的值可通过它来指定,可以在最外层作为全局默认配置

⑦ "@database":"POSTGRESQL",跨数据库,非默认的值可通过它来指定,可以在最外层作为全局默认配置

⑧ "@json":"key0,key1...",转为 JSON 格式返回,符合 JSONObject 则转为 {...},符合 JSONArray 则转为 \[...]

⑨ "@role":"OWNER",来访角色,包括
UNKNOWN,LOGIN,CONTACT,CIRCLE,OWNER,ADMIN,
可以在最外层作为全局默认配置,
可自定义其它角色并重写 Verifier.verify 等相关方法来自定义校验

⑩ "@explain":true,性能分析,可以在最外层作为全局默认配置

⑪ "@raw":"key0,key1...",其中 key0, key1 都对应有键值对
"key0":"SQL片段或SQL片段的别名",
"key1":"SQL片段或SQL片段的别名"
自定义原始SQL片段,可扩展嵌套SQL函数等复杂语句,必须是后端已配置的,只有其它功能符都做不到才考虑,谨慎使用,注意防SQL注入

⑫ "@otherKey":Object,自定义关键词,名称和以上系统关键词不一样,且原样返回上传的值 | ① 搜索name或tag任何一个字段包含字符a的User列表:
["name~":"a",
"tag~":"a",
"@combine":"name~,tag~"](http://apijson.cn:8080/get/{"User[]":{"count":10,"User":{"@column":"id,name,tag","name~":"a","tag~":"a","@combine":"name~,tag~"}}})
对应SQL是`name REGEXP 'a' OR tag REGEXP 'a'`

② 只查询id,sex,name这几列并且请求结果也按照这个顺序:
["@column":"id,sex,name"](http://apijson.cn:8080/get/{"User":{"@column":"id,sex,name","id":38710}})
对应SQL是`SELECT id,sex,name`

③ 查询按 name降序、id默认顺序 排序的User数组:
["@order":"name-,id"](http://apijson.cn:8080/get/{"[]":{"count":10,"User":{"@column":"name,id","@order":"name-,id"}}})
对应SQL是`ORDER BY name DESC,id`

④ 查询按userId分组的Moment数组:
["@group":"userId,id"](http://apijson.cn:8080/get/{"[]":{"count":10,"Moment":%7B"@column":"userId,id","@group":"userId,id"}}})
对应SQL是`GROUP BY userId,id`

⑤ 查询 按userId分组、id最大值>=100 的Moment数组:
["@column":"userId;max(id)",
"@group":"userId",
"@having":"max(id)>=100"](http://apijson.cn:8080/get/{"[]":{"count":10,"Moment":{"@column":"userId%253Bmax(id)","@group":"userId","@having":"max(id)>=100"}}})
对应SQL是`SELECT userId,max(id) ... GROUP BY userId HAVING max(id)>=100`
还可以指定函数返回名:
["@column":"userId;max(id):maxId",
"@group":"userId",
"@having":"maxId>=100"](http://apijson.cn:8080/get/{"[]":{"count":10,"Moment":{"@column":"userId%253Bmax(id):maxId","@group":"userId","@having":"maxId>=100"}}})
对应SQL是`SELECT userId,max(id) AS maxId ... GROUP BY userId HAVING maxId>=100`

⑥ 查询 sys 内的 User 表:
["@schema":"sys"](http://apijson.cn:8080/get/{"User":{"@schema":"sys"}})
对应SQL是`FROM sys.User`

⑦ 查询 PostgreSQL 数据库的 User 表:
["@database":"POSTGRESQL"](http://apijson.cn:8080/get/{"User":{"@database":"POSTGRESQL","@explain":true}})

⑧ 将 VARCHAR 字符串字段 get 转为 JSONArray 返回:
["@json":"get"](http://apijson.cn:8080/get/{"Access":{"@json":"get"}})

⑨ 查询当前用户的动态:
["@role":"OWNER"](http://apijson.cn:8080/get/{"[]":{"Moment":{"@role":"OWNER"}}})

⑩ 开启性能分析:
["@explain":true](http://apijson.cn:8080/get/{"[]":{"Moment":{"@explain":true}}})
对应SQL是`EXPLAIN`

⑪ 统计最近一周偶数userId的数量
["@column":"date;sum(if(userId%2=0,1,0))",
"@having":"to_days(now())-to_days(\`date\`)<=7",
"@raw":"@column,@having"](http://apijson.cn:8080/get/{"Comment":{"@column":"date%3bsum(if(userId%252=0,1,0))","@having":"to_days(now())-to_days(\`date\`)<=7","@raw":"@column,@having"}})
对应SQL是``SELECT sum(if(userId%2=0,1,0)) ... HAVING to_days(now())-to_days(`date`)<=7``

⑫ 从pictureList获取第0张图片:
["@position":0, //自定义关键词
"firstPicture()":"getFromArray(pictureList,@position)"](http://apijson.cn:8080/get/{"User":{"id":38710,"@position":0,"firstPicture()":"getFromArray(pictureList,@position)"}}) + 对象关键词,可自定义 | "@key":Object,@key为 Table:{} 中{}内的关键词,Object的类型由@key指定

① "@combine":"&key0,&key1,\|key2,key3,
!key4,!key5,&key6,key7...",条件组合方式,\| 可省略。会自动把同类的合并,外层按照 & \| ! 顺序,内层的按传参顺序组合成
(key0 & key1 & key6 & 其它key) & (key2 \| key3 \| key7) & !(key4 \| key5)
这种连接方式,其中 "其它key" 是指与 @combine 在同一对象,且未被它声明的条件 key,默认都是 & 连接

② "@column":"column;function(arg)...",返回字段

③ "@order":"column0+,column1-...",排序方式

④ "@group":"column0,column1...",分组方式。如果@column里声明了Table的id,则id也必须在@group中声明;其它情况下必须满足至少一个条件:
1.分组的key在@column里声明
2.Table主键在@group中声明

⑤ "@having":"function0(...)?value0;function1(...)?value1;function2(...)?value2...",SQL函数条件,一般和@group一起用,函数一般在@column里声明

⑥ "@schema":"sys",集合空间(模式),非默认的值可通过它来指定,可以在最外层作为全局默认配置

⑦ "@database":"POSTGRESQL",跨数据库,非默认的值可通过它来指定,可以在最外层作为全局默认配置

⑧ "@json":"key0,key1...",转为 JSON 格式返回,符合 JSONObject 则转为 {...},符合 JSONArray 则转为 \[...]

⑨ "@role":"OWNER",来访角色,包括
UNKNOWN,LOGIN,CONTACT,CIRCLE,OWNER,ADMIN,
可以在最外层作为全局默认配置,
可自定义其它角色并重写 Verifier.verify 等相关方法来自定义校验

⑩ "@explain":true,性能分析,可以在最外层作为全局默认配置

⑪ "@raw":"key0,key1...",其中 key0, key1 都对应有键值对
"key0":"SQL片段或SQL片段的别名",
"key1":"SQL片段或SQL片段的别名"
自定义原始SQL片段,可扩展嵌套SQL函数等复杂语句,必须是后端已配置的,只有其它功能符都做不到才考虑,谨慎使用,注意防SQL注入

⑫ "@otherKey":Object,自定义关键词,名称和以上系统关键词不一样,且原样返回上传的值 | ① 搜索name或tag任何一个字段包含字符a的User列表:
["name~":"a",
"tag~":"a",
"@combine":"name~,tag~"](http://apijson.cn:8080/get/{"User[]":{"count":10,"User":{"@column":"id,name,tag","name~":"a","tag~":"a","@combine":"name~,tag~"}}})
对应SQL是`name REGEXP 'a' OR tag REGEXP 'a'`

② 只查询id,sex,name这几列并且请求结果也按照这个顺序:
["@column":"id,sex,name"](http://apijson.cn:8080/get/{"User":{"@column":"id,sex,name","id":38710}})
对应SQL是`SELECT id,sex,name`

③ 查询按 name降序、id默认顺序 排序的User数组:
["@order":"name-,id"](http://apijson.cn:8080/get/{"[]":{"count":10,"User":{"@column":"name,id","@order":"name-,id"}}})
对应SQL是`ORDER BY name DESC,id`

④ 查询按userId分组的Moment数组:
["@group":"userId,id"](http://apijson.cn:8080/get/{"[]":{"count":10,"Moment":%7B"@column":"userId,id","@group":"userId,id"}}})
对应SQL是`GROUP BY userId,id`

⑤ 查询 按userId分组、id最大值>=100 的Moment数组:
["@column":"userId;max(id)",
"@group":"userId",
"@having":"max(id)>=100"](http://apijson.cn:8080/get/{"[]":{"count":10,"Moment":{"@column":"userId%253Bmax(id)","@group":"userId","@having":"max(id)>=100"}}})
对应SQL是`SELECT userId,max(id) ... GROUP BY userId HAVING max(id)>=100`
还可以指定函数返回名:
["@column":"userId;max(id):maxId",
"@group":"userId",
"@having":"maxId>=100"](http://apijson.cn:8080/get/{"[]":{"count":10,"Moment":{"@column":"userId%253Bmax(id):maxId","@group":"userId","@having":"maxId>=100"}}})
对应SQL是`SELECT userId,max(id) AS maxId ... GROUP BY userId HAVING maxId>=100`

⑥ 查询 sys 内的 User 表:
["@schema":"sys"](http://apijson.cn:8080/get/{"User":{"@schema":"sys"}})
对应SQL是`FROM sys.User`

⑦ 查询 PostgreSQL 数据库的 User 表:
["@database":"POSTGRESQL"](http://apijson.cn:8080/get/{"User":{"@database":"POSTGRESQL","@explain":true}})

⑧ 将 VARCHAR 字符串字段 get 转为 JSONArray 返回:
["@json":"get"](http://apijson.cn:8080/get/{"Access":{"@json":"get"}})

⑨ 查询当前用户的动态:
["@role":"OWNER"](http://apijson.cn:8080/get/{"[]":{"Moment":{"@role":"OWNER"}}})

⑩ 开启性能分析:
["@explain":true](http://apijson.cn:8080/get/{"[]":{"Moment":{"@explain":true}}})
对应SQL是`EXPLAIN`

⑪ 统计最近一周偶数userId的数量
["@column":"date;sum(if(userId%2=0,1,0))",
"@group":"date",
"@having":"to_days(now())-to_days(\`date\`)<=7",
"@raw":"@column,@having"](http://apijson.cn:8080/get/{"[]":{"Comment":{"@column":"date%3bsum(if(userId%252=0,1,0))","@group":"date","@having":"to_days(now())-to_days(\`date\`)<=7","@raw":"@column,@having"}}})
对应SQL是``SELECT sum(if(userId%2=0,1,0)) ... HAVING to_days(now())-to_days(`date`)<=7``

⑫ 从pictureList获取第0张图片:
["@position":0, //自定义关键词
"firstPicture()":"getFromArray(pictureList,@position)"](http://apijson.cn:8080/get/{"User":{"id":38710,"@position":0,"firstPicture()":"getFromArray(pictureList,@position)"}})
From 3ce23f7fcea50be963a4e008c3b8aa14911b4ea2 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Sun, 31 Jan 2021 01:26:58 +0800 Subject: [PATCH 0262/1181] Update Document.md --- Document.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Document.md b/Document.md index 727187cfe..8479881eb 100644 --- a/Document.md +++ b/Document.md @@ -371,6 +371,6 @@ DELETE:
删除数据 | base_url/delete/ | {
   TableName:{< 比较运算 | >, <, >=, <= 比较运算符,用于
① 提供 "id{}":"<=90000" 这种条件范围的简化写法

② 实现子查询相关比较运算

不支持 "key=":Object 和 "key!=":Object 这两种写法,直接用更简单的 "key":Object 和 "key!":Object 替代。 | ① ["id<=":90000](http://apijson.cn:8080/get/{"[]":{"User":{"id<=":90000}}}),对应SQL是`id<=90000`,查询符合id<=90000的一个User数组

② ["id>@":{
   "from":"Comment",
   "Comment":{
      "@column":"min(userId)"
   }
}](http://apijson.cn:8080/get/{"User":{"id>@":{"from":"Comment","Comment":{"@column":"min(userId)"}}}})
WHERE id>(SELECT min(userId) FROM Comment) 逻辑运算 | &, \|, ! 逻辑运算符,对应数据库 SQL 中的 AND, OR, NOT。
横或纵与:同一键值对的值内条件默认 \| 或连接,可以在 key 后加逻辑运算符来具体指定;不同键值对的条件默认 & 与连接,可以用下面说明的对象关键词 @combine 来具体指定。

① & 可用于"key&{}":"条件"等

② \| 可用于"key\|{}":"条件", "key\|{}":[]等,一般可省略

③ ! 可单独使用,如"key!":Object,也可像&,\|一样配合其他功能符使用
"key!":null 无效,null 值会导致整个键值对被忽略解析,可以用 "key{}":"!=null" 替代,
"key":null 同理,用 "key{}":"=null" 替代。 | ① ["id&{}":">80000,<=90000"](http://apijson.cn:8080/head/{"User":{"id&{}":">80000,<=90000"}}),对应SQL是`id>80000 AND id<=90000`,即id满足id>80000 & id<=90000

② ["id\|{}":">90000,<=80000"](http://apijson.cn:8080/head/{"User":{"id\|{}":">90000,<=80000"}}),同"id{}":">90000,<=80000",对应SQL是`id>80000 OR id<=90000`,即id满足id>90000 \| id<=80000

③ ["id!{}":[82001,38710]](http://apijson.cn:8080/head/{"User":{"id!{}":[82001,38710]}}),对应SQL是`id NOT IN(82001,38710)`,即id满足 ! (id=82001 \| id=38710),可过滤黑名单的消息 数组关键词,可自定义 | "key":Object,key为 "[]":{} 中{}内的关键词,Object的类型由key指定

① "count":Integer,查询数量,0 表示最大值,默认最大值为100

② "page":Integer,查询页码,从0开始,默认最大值为100,一般和count一起用

③ "query":Integer,查询内容
0-对象,1-总数和分页详情,2-以上全部
总数关键词为 total,分页详情关键词为 info,
它们都和 query 同级,通过引用赋值得到,例如
"total@":"/[]/total", "info@":"/[]/info"
这里query及total仅为GET类型的请求提供方便,
一般可直接用HEAD类型的请求获取总数

④ "join":"&/Table0/key0@,\多表连接方式:
"\<" - LEFT JOIN
">" - RIGHT JOIN
"&" - INNER JOIN
"\|" - FULL JOIN
"!" - OUTTER JOIN
"@" - APP JOIN
其中 @ APP JOIN 为应用层连表,会从已查出的主表里取得所有副表 key@ 关联的主表内的 refKey 作为一个数组 refKeys: [value0, value1...],然后把原来副表 count 次查询 key=$refKey 的 SQL 用 key IN($refKeys) 的方式合并为一条 SQL 来优化性能;
其它 JOIN 都是 SQL JOIN,具体功能和 MySQL,PostgreSQL 等数据库的 JOIN 一一对应
`"join":"`"MainTable":{},`
`"ViceTable":{"key@":"/MainTable/refKey"}`
会对应生成
`MainTable LEFT JOIN ViceTable`
`ON ViceTable.key=MainTable.refKey`

⑤ "otherKey":Object,自定义关键词,名称和以上系统关键词不一样,且原样返回上传的值 | ① 查询User数组,最多5个:
["count":5](http://apijson.cn:8080/get/{"[]":{"count":5,"User":{}}})
对应SQL是`LIMIT 5`

② 查询第3页的User数组,每页5个:
["count":5,
"page":3](http://apijson.cn:8080/get/{"[]":{"count":5,"page":3,"User":{}}})
对应SQL是`LIMIT 5 OFFSET 15`

③ 查询User数组和对应的User总数:
["[]":{
   "query":2,
   "User":{}
},
"total@":"/[]/total",
"info@":"/[]/info"](http://apijson.cn:8080/get/{"[]":{"query":2,"count":5,"User":{}},"total@":"%252F[]%252Ftotal","info@":"%252F[]%252Finfo"})
返回的数据中,总数及分页详情结构为:
"total":139, //总数
"info":{ //分页详情
   "total":139, //总数
   "count":5, //每页数量
   "page":0, //当前页码
   "max":27, //最大页码
   "more":true, //是否还有更多
   "first":true, //是否为首页
   "last":false //是否为尾页
}

④ Moment INNER JOIN User LEFT JOIN Comment:
["[]":{
   "join":"&/User/id@,\    "Moment":{},
   "User":{
     "name~":"t",
     "id@":"/Moment/userId"
   },
   "Comment":{
     "momentId@":"/Moment/id"
   }
}](http://apijson.cn:8080/get/{"[]":{"count":5,"join":"&%252FUser%252Fid@,\<%252FComment%252FmomentId@","Moment":{"@column":"id,userId,content"},"User":{"name~":"t","id@":"%252FMoment%252FuserId","@column":"id,name,head"},"Comment":{"momentId@":"%252FMoment%252Fid","@column":"id,momentId,content"}}})

⑤ 每一层都加当前用户名:
["User":{},
"[]":{
   "name@":"User/name", //自定义关键词
   "Moment":{}
}](http://apijson.cn:8080/get/{"User":{},"[]":{"name@":"User%252Fname","Moment":{}}}) - 对象关键词,可自定义 | "@key":Object,@key为 Table:{} 中{}内的关键词,Object的类型由@key指定

① "@combine":"&key0,&key1,\|key2,key3,
!key4,!key5,&key6,key7...",条件组合方式,\| 可省略。会自动把同类的合并,外层按照 & \| ! 顺序,内层的按传参顺序组合成
(key0 & key1 & key6 & 其它key) & (key2 \| key3 \| key7) & !(key4 \| key5)
这种连接方式,其中 "其它key" 是指与 @combine 在同一对象,且未被它声明的条件 key,默认都是 & 连接

② "@column":"column;function(arg)...",返回字段

③ "@order":"column0+,column1-...",排序方式

④ "@group":"column0,column1...",分组方式。如果@column里声明了Table的id,则id也必须在@group中声明;其它情况下必须满足至少一个条件:
1.分组的key在@column里声明
2.Table主键在@group中声明

⑤ "@having":"function0(...)?value0;function1(...)?value1;function2(...)?value2...",SQL函数条件,一般和@group一起用,函数一般在@column里声明

⑥ "@schema":"sys",集合空间(模式),非默认的值可通过它来指定,可以在最外层作为全局默认配置

⑦ "@database":"POSTGRESQL",跨数据库,非默认的值可通过它来指定,可以在最外层作为全局默认配置

⑧ "@json":"key0,key1...",转为 JSON 格式返回,符合 JSONObject 则转为 {...},符合 JSONArray 则转为 \[...]

⑨ "@role":"OWNER",来访角色,包括
UNKNOWN,LOGIN,CONTACT,CIRCLE,OWNER,ADMIN,
可以在最外层作为全局默认配置,
可自定义其它角色并重写 Verifier.verify 等相关方法来自定义校验

⑩ "@explain":true,性能分析,可以在最外层作为全局默认配置

⑪ "@raw":"key0,key1...",其中 key0, key1 都对应有键值对
"key0":"SQL片段或SQL片段的别名",
"key1":"SQL片段或SQL片段的别名"
自定义原始SQL片段,可扩展嵌套SQL函数等复杂语句,必须是后端已配置的,只有其它功能符都做不到才考虑,谨慎使用,注意防SQL注入

⑫ "@otherKey":Object,自定义关键词,名称和以上系统关键词不一样,且原样返回上传的值 | ① 搜索name或tag任何一个字段包含字符a的User列表:
["name~":"a",
"tag~":"a",
"@combine":"name~,tag~"](http://apijson.cn:8080/get/{"User[]":{"count":10,"User":{"@column":"id,name,tag","name~":"a","tag~":"a","@combine":"name~,tag~"}}})
对应SQL是`name REGEXP 'a' OR tag REGEXP 'a'`

② 只查询id,sex,name这几列并且请求结果也按照这个顺序:
["@column":"id,sex,name"](http://apijson.cn:8080/get/{"User":{"@column":"id,sex,name","id":38710}})
对应SQL是`SELECT id,sex,name`

③ 查询按 name降序、id默认顺序 排序的User数组:
["@order":"name-,id"](http://apijson.cn:8080/get/{"[]":{"count":10,"User":{"@column":"name,id","@order":"name-,id"}}})
对应SQL是`ORDER BY name DESC,id`

④ 查询按userId分组的Moment数组:
["@group":"userId,id"](http://apijson.cn:8080/get/{"[]":{"count":10,"Moment":%7B"@column":"userId,id","@group":"userId,id"}}})
对应SQL是`GROUP BY userId,id`

⑤ 查询 按userId分组、id最大值>=100 的Moment数组:
["@column":"userId;max(id)",
"@group":"userId",
"@having":"max(id)>=100"](http://apijson.cn:8080/get/{"[]":{"count":10,"Moment":{"@column":"userId%253Bmax(id)","@group":"userId","@having":"max(id)>=100"}}})
对应SQL是`SELECT userId,max(id) ... GROUP BY userId HAVING max(id)>=100`
还可以指定函数返回名:
["@column":"userId;max(id):maxId",
"@group":"userId",
"@having":"maxId>=100"](http://apijson.cn:8080/get/{"[]":{"count":10,"Moment":{"@column":"userId%253Bmax(id):maxId","@group":"userId","@having":"maxId>=100"}}})
对应SQL是`SELECT userId,max(id) AS maxId ... GROUP BY userId HAVING maxId>=100`

⑥ 查询 sys 内的 User 表:
["@schema":"sys"](http://apijson.cn:8080/get/{"User":{"@schema":"sys"}})
对应SQL是`FROM sys.User`

⑦ 查询 PostgreSQL 数据库的 User 表:
["@database":"POSTGRESQL"](http://apijson.cn:8080/get/{"User":{"@database":"POSTGRESQL","@explain":true}})

⑧ 将 VARCHAR 字符串字段 get 转为 JSONArray 返回:
["@json":"get"](http://apijson.cn:8080/get/{"Access":{"@json":"get"}})

⑨ 查询当前用户的动态:
["@role":"OWNER"](http://apijson.cn:8080/get/{"[]":{"Moment":{"@role":"OWNER"}}})

⑩ 开启性能分析:
["@explain":true](http://apijson.cn:8080/get/{"[]":{"Moment":{"@explain":true}}})
对应SQL是`EXPLAIN`

⑪ 统计最近一周偶数userId的数量
["@column":"date;sum(if(userId%2=0,1,0))",
"@group":"date",
"@having":"to_days(now())-to_days(\`date\`)<=7",
"@raw":"@column,@having"](http://apijson.cn:8080/get/{"[]":{"Comment":{"@column":"date%3bsum(if(userId%252=0,1,0))","@group":"date","@having":"to_days(now())-to_days(\`date\`)<=7","@raw":"@column,@having"}}})
对应SQL是``SELECT sum(if(userId%2=0,1,0)) ... HAVING to_days(now())-to_days(`date`)<=7``

⑫ 从pictureList获取第0张图片:
["@position":0, //自定义关键词
"firstPicture()":"getFromArray(pictureList,@position)"](http://apijson.cn:8080/get/{"User":{"id":38710,"@position":0,"firstPicture()":"getFromArray(pictureList,@position)"}}) + 对象关键词,可自定义 | "@key":Object,@key为 Table:{} 中{}内的关键词,Object的类型由@key指定

① "@combine":"&key0,&key1,\|key2,key3,
!key4,!key5,&key6,key7...",条件组合方式,\| 可省略。会自动把同类的合并,外层按照 & \| ! 顺序,内层的按传参顺序组合成
(key0 & key1 & key6 & 其它key) & (key2 \| key3 \| key7) & !(key4 \| key5)
这种连接方式,其中 "其它key" 是指与 @combine 在同一对象,且未被它声明的条件 key,默认都是 & 连接

② "@column":"column;function(arg)...",返回字段

③ "@order":"column0+,column1-...",排序方式

④ "@group":"column0,column1...",分组方式。如果@column里声明了Table的id,则id也必须在@group中声明;其它情况下必须满足至少一个条件:
1.分组的key在@column里声明
2.Table主键在@group中声明

⑤ "@having":"function0(...)?value0;function1(...)?value1;function2(...)?value2...",SQL函数条件,一般和@group一起用,函数一般在@column里声明

⑥ "@schema":"sys",集合空间(模式),非默认的值可通过它来指定,可以在最外层作为全局默认配置

⑦ "@database":"POSTGRESQL",跨数据库,非默认的值可通过它来指定,可以在最外层作为全局默认配置

⑧ "@json":"key0,key1...",转为 JSON 格式返回,符合 JSONObject 则转为 {...},符合 JSONArray 则转为 \[...]

⑨ "@role":"OWNER",来访角色,包括
UNKNOWN,LOGIN,CONTACT,CIRCLE,OWNER,ADMIN,
可以在最外层作为全局默认配置,
可自定义其它角色并重写 Verifier.verify 等相关方法来自定义校验

⑩ "@explain":true,性能分析,可以在最外层作为全局默认配置

⑪ "@raw":"key0,key1...",其中 key0, key1 都对应有键值对
"key0":"SQL片段或SQL片段的别名",
"key1":"SQL片段或SQL片段的别名"
自定义原始SQL片段,可扩展嵌套SQL函数等复杂语句,必须是后端已配置的,只有其它功能符都做不到才考虑,谨慎使用,注意防SQL注入

⑫ "@otherKey":Object,自定义关键词,名称和以上系统关键词不一样,且原样返回上传的值 | ① 搜索name或tag任何一个字段包含字符a的User列表:
["name~":"a",
"tag~":"a",
"@combine":"name~,tag~"](http://apijson.cn:8080/get/{"User[]":{"count":10,"User":{"@column":"id,name,tag","name~":"a","tag~":"a","@combine":"name~,tag~"}}})
对应SQL是`name REGEXP 'a' OR tag REGEXP 'a'`

② 只查询id,sex,name这几列并且请求结果也按照这个顺序:
["@column":"id,sex,name"](http://apijson.cn:8080/get/{"User":{"@column":"id,sex,name","id":38710}})
对应SQL是`SELECT id,sex,name`

③ 查询按 name降序、id默认顺序 排序的User数组:
["@order":"name-,id"](http://apijson.cn:8080/get/{"[]":{"count":10,"User":{"@column":"name,id","@order":"name-,id"}}})
对应SQL是`ORDER BY name DESC,id`

④ 查询按userId分组的Moment数组:
["@group":"userId,id"](http://apijson.cn:8080/get/{"[]":{"count":10,"Moment":%7B"@column":"userId,id","@group":"userId,id"}}})
对应SQL是`GROUP BY userId,id`

⑤ 查询 按userId分组、id最大值>=100 的Moment数组:
["@column":"userId;max(id)",
"@group":"userId",
"@having":"max(id)>=100"](http://apijson.cn:8080/get/{"[]":{"count":10,"Moment":{"@column":"userId%253Bmax(id)","@group":"userId","@having":"max(id)>=100"}}})
对应SQL是`SELECT userId,max(id) ... GROUP BY userId HAVING max(id)>=100`
还可以指定函数返回名:
["@column":"userId;max(id):maxId",
"@group":"userId",
"@having":"maxId>=100"](http://apijson.cn:8080/get/{"[]":{"count":10,"Moment":{"@column":"userId%253Bmax(id):maxId","@group":"userId","@having":"maxId>=100"}}})
对应SQL是`SELECT userId,max(id) AS maxId ... GROUP BY userId HAVING maxId>=100`

⑥ 查询 sys 内的 User 表:
["@schema":"sys"](http://apijson.cn:8080/get/{"User":{"@schema":"sys"}})
对应SQL是`FROM sys.User`

⑦ 查询 PostgreSQL 数据库的 User 表:
["@database":"POSTGRESQL"](http://apijson.cn:8080/get/{"User":{"@database":"POSTGRESQL","@explain":true}})

⑧ 将 VARCHAR 字符串字段 get 转为 JSONArray 返回:
["@json":"get"](http://apijson.cn:8080/get/{"Access":{"@json":"get"}})

⑨ 查询当前用户的动态:
["@role":"OWNER"](http://apijson.cn:8080/get/{"[]":{"Moment":{"@role":"OWNER"}}})

⑩ 开启性能分析:
["@explain":true](http://apijson.cn:8080/get/{"[]":{"Moment":{"@explain":true}}})
对应SQL是`EXPLAIN`

⑪ 统计最近一周偶数userId的数量
["@column":"date;sum(if(userId%2=0,1,0))",
"@group":"date",
"@having":"to_days(now())-to_days(\`date\`)<=7",
"@raw":"@column,@having"](http://apijson.cn:8080/get/{"[]":{"Comment":{"@column":"date%3bsum(if(userId%252=0,1,0))","@group":"date","@having":"to_days(now())-to_days(\`date\`)<=7","@raw":"@column,@having"}}})
对应SQL是``SELECT date,sum(if(userId%2=0,1,0)) ... GROUP BY date HAVING to_days(now())-to_days(`date`)<=7``

⑫ 从pictureList获取第0张图片:
["@position":0, //自定义关键词
"firstPicture()":"getFromArray(pictureList,@position)"](http://apijson.cn:8080/get/{"User":{"id":38710,"@position":0,"firstPicture()":"getFromArray(pictureList,@position)"}})
From a890072145d9bf7aa7c713a40b2ab6992f0c8159 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Sun, 31 Jan 2021 01:29:00 +0800 Subject: [PATCH 0263/1181] Update Document.md --- Document.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Document.md b/Document.md index 8479881eb..202f9ff7f 100644 --- a/Document.md +++ b/Document.md @@ -371,6 +371,6 @@ DELETE:
删除数据 | base_url/delete/ | {
   TableName:{< 比较运算 | >, <, >=, <= 比较运算符,用于
① 提供 "id{}":"<=90000" 这种条件范围的简化写法

② 实现子查询相关比较运算

不支持 "key=":Object 和 "key!=":Object 这两种写法,直接用更简单的 "key":Object 和 "key!":Object 替代。 | ① ["id<=":90000](http://apijson.cn:8080/get/{"[]":{"User":{"id<=":90000}}}),对应SQL是`id<=90000`,查询符合id<=90000的一个User数组

② ["id>@":{
   "from":"Comment",
   "Comment":{
      "@column":"min(userId)"
   }
}](http://apijson.cn:8080/get/{"User":{"id>@":{"from":"Comment","Comment":{"@column":"min(userId)"}}}})
WHERE id>(SELECT min(userId) FROM Comment) 逻辑运算 | &, \|, ! 逻辑运算符,对应数据库 SQL 中的 AND, OR, NOT。
横或纵与:同一键值对的值内条件默认 \| 或连接,可以在 key 后加逻辑运算符来具体指定;不同键值对的条件默认 & 与连接,可以用下面说明的对象关键词 @combine 来具体指定。

① & 可用于"key&{}":"条件"等

② \| 可用于"key\|{}":"条件", "key\|{}":[]等,一般可省略

③ ! 可单独使用,如"key!":Object,也可像&,\|一样配合其他功能符使用
"key!":null 无效,null 值会导致整个键值对被忽略解析,可以用 "key{}":"!=null" 替代,
"key":null 同理,用 "key{}":"=null" 替代。 | ① ["id&{}":">80000,<=90000"](http://apijson.cn:8080/head/{"User":{"id&{}":">80000,<=90000"}}),对应SQL是`id>80000 AND id<=90000`,即id满足id>80000 & id<=90000

② ["id\|{}":">90000,<=80000"](http://apijson.cn:8080/head/{"User":{"id\|{}":">90000,<=80000"}}),同"id{}":">90000,<=80000",对应SQL是`id>80000 OR id<=90000`,即id满足id>90000 \| id<=80000

③ ["id!{}":[82001,38710]](http://apijson.cn:8080/head/{"User":{"id!{}":[82001,38710]}}),对应SQL是`id NOT IN(82001,38710)`,即id满足 ! (id=82001 \| id=38710),可过滤黑名单的消息 数组关键词,可自定义 | "key":Object,key为 "[]":{} 中{}内的关键词,Object的类型由key指定

① "count":Integer,查询数量,0 表示最大值,默认最大值为100

② "page":Integer,查询页码,从0开始,默认最大值为100,一般和count一起用

③ "query":Integer,查询内容
0-对象,1-总数和分页详情,2-以上全部
总数关键词为 total,分页详情关键词为 info,
它们都和 query 同级,通过引用赋值得到,例如
"total@":"/[]/total", "info@":"/[]/info"
这里query及total仅为GET类型的请求提供方便,
一般可直接用HEAD类型的请求获取总数

④ "join":"&/Table0/key0@,\多表连接方式:
"\<" - LEFT JOIN
">" - RIGHT JOIN
"&" - INNER JOIN
"\|" - FULL JOIN
"!" - OUTTER JOIN
"@" - APP JOIN
其中 @ APP JOIN 为应用层连表,会从已查出的主表里取得所有副表 key@ 关联的主表内的 refKey 作为一个数组 refKeys: [value0, value1...],然后把原来副表 count 次查询 key=$refKey 的 SQL 用 key IN($refKeys) 的方式合并为一条 SQL 来优化性能;
其它 JOIN 都是 SQL JOIN,具体功能和 MySQL,PostgreSQL 等数据库的 JOIN 一一对应
`"join":"`"MainTable":{},`
`"ViceTable":{"key@":"/MainTable/refKey"}`
会对应生成
`MainTable LEFT JOIN ViceTable`
`ON ViceTable.key=MainTable.refKey`

⑤ "otherKey":Object,自定义关键词,名称和以上系统关键词不一样,且原样返回上传的值 | ① 查询User数组,最多5个:
["count":5](http://apijson.cn:8080/get/{"[]":{"count":5,"User":{}}})
对应SQL是`LIMIT 5`

② 查询第3页的User数组,每页5个:
["count":5,
"page":3](http://apijson.cn:8080/get/{"[]":{"count":5,"page":3,"User":{}}})
对应SQL是`LIMIT 5 OFFSET 15`

③ 查询User数组和对应的User总数:
["[]":{
   "query":2,
   "User":{}
},
"total@":"/[]/total",
"info@":"/[]/info"](http://apijson.cn:8080/get/{"[]":{"query":2,"count":5,"User":{}},"total@":"%252F[]%252Ftotal","info@":"%252F[]%252Finfo"})
返回的数据中,总数及分页详情结构为:
"total":139, //总数
"info":{ //分页详情
   "total":139, //总数
   "count":5, //每页数量
   "page":0, //当前页码
   "max":27, //最大页码
   "more":true, //是否还有更多
   "first":true, //是否为首页
   "last":false //是否为尾页
}

④ Moment INNER JOIN User LEFT JOIN Comment:
["[]":{
   "join":"&/User/id@,\    "Moment":{},
   "User":{
     "name~":"t",
     "id@":"/Moment/userId"
   },
   "Comment":{
     "momentId@":"/Moment/id"
   }
}](http://apijson.cn:8080/get/{"[]":{"count":5,"join":"&%252FUser%252Fid@,\<%252FComment%252FmomentId@","Moment":{"@column":"id,userId,content"},"User":{"name~":"t","id@":"%252FMoment%252FuserId","@column":"id,name,head"},"Comment":{"momentId@":"%252FMoment%252Fid","@column":"id,momentId,content"}}})

⑤ 每一层都加当前用户名:
["User":{},
"[]":{
   "name@":"User/name", //自定义关键词
   "Moment":{}
}](http://apijson.cn:8080/get/{"User":{},"[]":{"name@":"User%252Fname","Moment":{}}}) - 对象关键词,可自定义 | "@key":Object,@key为 Table:{} 中{}内的关键词,Object的类型由@key指定

① "@combine":"&key0,&key1,\|key2,key3,
!key4,!key5,&key6,key7...",条件组合方式,\| 可省略。会自动把同类的合并,外层按照 & \| ! 顺序,内层的按传参顺序组合成
(key0 & key1 & key6 & 其它key) & (key2 \| key3 \| key7) & !(key4 \| key5)
这种连接方式,其中 "其它key" 是指与 @combine 在同一对象,且未被它声明的条件 key,默认都是 & 连接

② "@column":"column;function(arg)...",返回字段

③ "@order":"column0+,column1-...",排序方式

④ "@group":"column0,column1...",分组方式。如果@column里声明了Table的id,则id也必须在@group中声明;其它情况下必须满足至少一个条件:
1.分组的key在@column里声明
2.Table主键在@group中声明

⑤ "@having":"function0(...)?value0;function1(...)?value1;function2(...)?value2...",SQL函数条件,一般和@group一起用,函数一般在@column里声明

⑥ "@schema":"sys",集合空间(模式),非默认的值可通过它来指定,可以在最外层作为全局默认配置

⑦ "@database":"POSTGRESQL",跨数据库,非默认的值可通过它来指定,可以在最外层作为全局默认配置

⑧ "@json":"key0,key1...",转为 JSON 格式返回,符合 JSONObject 则转为 {...},符合 JSONArray 则转为 \[...]

⑨ "@role":"OWNER",来访角色,包括
UNKNOWN,LOGIN,CONTACT,CIRCLE,OWNER,ADMIN,
可以在最外层作为全局默认配置,
可自定义其它角色并重写 Verifier.verify 等相关方法来自定义校验

⑩ "@explain":true,性能分析,可以在最外层作为全局默认配置

⑪ "@raw":"key0,key1...",其中 key0, key1 都对应有键值对
"key0":"SQL片段或SQL片段的别名",
"key1":"SQL片段或SQL片段的别名"
自定义原始SQL片段,可扩展嵌套SQL函数等复杂语句,必须是后端已配置的,只有其它功能符都做不到才考虑,谨慎使用,注意防SQL注入

⑫ "@otherKey":Object,自定义关键词,名称和以上系统关键词不一样,且原样返回上传的值 | ① 搜索name或tag任何一个字段包含字符a的User列表:
["name~":"a",
"tag~":"a",
"@combine":"name~,tag~"](http://apijson.cn:8080/get/{"User[]":{"count":10,"User":{"@column":"id,name,tag","name~":"a","tag~":"a","@combine":"name~,tag~"}}})
对应SQL是`name REGEXP 'a' OR tag REGEXP 'a'`

② 只查询id,sex,name这几列并且请求结果也按照这个顺序:
["@column":"id,sex,name"](http://apijson.cn:8080/get/{"User":{"@column":"id,sex,name","id":38710}})
对应SQL是`SELECT id,sex,name`

③ 查询按 name降序、id默认顺序 排序的User数组:
["@order":"name-,id"](http://apijson.cn:8080/get/{"[]":{"count":10,"User":{"@column":"name,id","@order":"name-,id"}}})
对应SQL是`ORDER BY name DESC,id`

④ 查询按userId分组的Moment数组:
["@group":"userId,id"](http://apijson.cn:8080/get/{"[]":{"count":10,"Moment":%7B"@column":"userId,id","@group":"userId,id"}}})
对应SQL是`GROUP BY userId,id`

⑤ 查询 按userId分组、id最大值>=100 的Moment数组:
["@column":"userId;max(id)",
"@group":"userId",
"@having":"max(id)>=100"](http://apijson.cn:8080/get/{"[]":{"count":10,"Moment":{"@column":"userId%253Bmax(id)","@group":"userId","@having":"max(id)>=100"}}})
对应SQL是`SELECT userId,max(id) ... GROUP BY userId HAVING max(id)>=100`
还可以指定函数返回名:
["@column":"userId;max(id):maxId",
"@group":"userId",
"@having":"maxId>=100"](http://apijson.cn:8080/get/{"[]":{"count":10,"Moment":{"@column":"userId%253Bmax(id):maxId","@group":"userId","@having":"maxId>=100"}}})
对应SQL是`SELECT userId,max(id) AS maxId ... GROUP BY userId HAVING maxId>=100`

⑥ 查询 sys 内的 User 表:
["@schema":"sys"](http://apijson.cn:8080/get/{"User":{"@schema":"sys"}})
对应SQL是`FROM sys.User`

⑦ 查询 PostgreSQL 数据库的 User 表:
["@database":"POSTGRESQL"](http://apijson.cn:8080/get/{"User":{"@database":"POSTGRESQL","@explain":true}})

⑧ 将 VARCHAR 字符串字段 get 转为 JSONArray 返回:
["@json":"get"](http://apijson.cn:8080/get/{"Access":{"@json":"get"}})

⑨ 查询当前用户的动态:
["@role":"OWNER"](http://apijson.cn:8080/get/{"[]":{"Moment":{"@role":"OWNER"}}})

⑩ 开启性能分析:
["@explain":true](http://apijson.cn:8080/get/{"[]":{"Moment":{"@explain":true}}})
对应SQL是`EXPLAIN`

⑪ 统计最近一周偶数userId的数量
["@column":"date;sum(if(userId%2=0,1,0))",
"@group":"date",
"@having":"to_days(now())-to_days(\`date\`)<=7",
"@raw":"@column,@having"](http://apijson.cn:8080/get/{"[]":{"Comment":{"@column":"date%3bsum(if(userId%252=0,1,0))","@group":"date","@having":"to_days(now())-to_days(\`date\`)<=7","@raw":"@column,@having"}}})
对应SQL是``SELECT date,sum(if(userId%2=0,1,0)) ... GROUP BY date HAVING to_days(now())-to_days(`date`)<=7``

⑫ 从pictureList获取第0张图片:
["@position":0, //自定义关键词
"firstPicture()":"getFromArray(pictureList,@position)"](http://apijson.cn:8080/get/{"User":{"id":38710,"@position":0,"firstPicture()":"getFromArray(pictureList,@position)"}}) + 对象关键词,可自定义 | "@key":Object,@key为 Table:{} 中{}内的关键词,Object的类型由@key指定

① "@combine":"&key0,&key1,\|key2,key3,
!key4,!key5,&key6,key7...",条件组合方式,\| 可省略。会自动把同类的合并,外层按照 & \| ! 顺序,内层的按传参顺序组合成
(key0 & key1 & key6 & 其它key) & (key2 \| key3 \| key7) & !(key4 \| key5)
这种连接方式,其中 "其它key" 是指与 @combine 在同一对象,且未被它声明的条件 key,默认都是 & 连接

② "@column":"column;function(arg)...",返回字段

③ "@order":"column0+,column1-...",排序方式

④ "@group":"column0,column1...",分组方式。如果@column里声明了Table的id,则id也必须在@group中声明;其它情况下必须满足至少一个条件:
1.分组的key在@column里声明
2.Table主键在@group中声明

⑤ "@having":"function0(...)?value0;function1(...)?value1;function2(...)?value2...",SQL函数条件,一般和@group一起用,函数一般在@column里声明

⑥ "@schema":"sys",集合空间(模式),非默认的值可通过它来指定,可以在最外层作为全局默认配置

⑦ "@database":"POSTGRESQL",跨数据库,非默认的值可通过它来指定,可以在最外层作为全局默认配置

⑧ "@json":"key0,key1...",转为 JSON 格式返回,符合 JSONObject 则转为 {...},符合 JSONArray 则转为 \[...]

⑨ "@role":"OWNER",来访角色,包括
UNKNOWN,LOGIN,CONTACT,CIRCLE,OWNER,ADMIN,
可以在最外层作为全局默认配置,
可自定义其它角色并重写 Verifier.verify 等相关方法来自定义校验

⑩ "@explain":true,性能分析,可以在最外层作为全局默认配置

⑪ "@raw":"key0,key1...",其中 key0, key1 都对应有键值对
"key0":"SQL片段或SQL片段的别名",
"key1":"SQL片段或SQL片段的别名"
自定义原始SQL片段,可扩展嵌套SQL函数等复杂语句,必须是后端已配置的,只有其它功能符都做不到才考虑,谨慎使用,注意防SQL注入

⑫ "@otherKey":Object,自定义关键词,名称和以上系统关键词不一样,且原样返回上传的值 | ① 搜索name或tag任何一个字段包含字符a的User列表:
["name~":"a",
"tag~":"a",
"@combine":"name~,tag~"](http://apijson.cn:8080/get/{"User[]":{"count":10,"User":{"@column":"id,name,tag","name~":"a","tag~":"a","@combine":"name~,tag~"}}})
对应SQL是`name REGEXP 'a' OR tag REGEXP 'a'`

② 只查询id,sex,name这几列并且请求结果也按照这个顺序:
["@column":"id,sex,name"](http://apijson.cn:8080/get/{"User":{"@column":"id,sex,name","id":38710}})
对应SQL是`SELECT id,sex,name`

③ 查询按 name降序、id默认顺序 排序的User数组:
["@order":"name-,id"](http://apijson.cn:8080/get/{"[]":{"count":10,"User":{"@column":"name,id","@order":"name-,id"}}})
对应SQL是`ORDER BY name DESC,id`

④ 查询按userId分组的Moment数组:
["@group":"userId,id"](http://apijson.cn:8080/get/{"[]":{"count":10,"Moment":%7B"@column":"userId,id","@group":"userId,id"}}})
对应SQL是`GROUP BY userId,id`

⑤ 查询 按userId分组、id最大值>=100 的Moment数组:
["@column":"userId;max(id)",
"@group":"userId",
"@having":"max(id)>=100"](http://apijson.cn:8080/get/{"[]":{"count":10,"Moment":{"@column":"userId%253Bmax(id)","@group":"userId","@having":"max(id)>=100"}}})
对应SQL是`SELECT userId,max(id) ... GROUP BY userId HAVING max(id)>=100`
还可以指定函数返回名:
["@column":"userId;max(id):maxId",
"@group":"userId",
"@having":"maxId>=100"](http://apijson.cn:8080/get/{"[]":{"count":10,"Moment":{"@column":"userId%253Bmax(id):maxId","@group":"userId","@having":"maxId>=100"}}})
对应SQL是`SELECT userId,max(id) AS maxId ... GROUP BY userId HAVING maxId>=100`

⑥ 查询 sys 内的 User 表:
["@schema":"sys"](http://apijson.cn:8080/get/{"User":{"@schema":"sys"}})
对应SQL是`FROM sys.User`

⑦ 查询 PostgreSQL 数据库的 User 表:
["@database":"POSTGRESQL"](http://apijson.cn:8080/get/{"User":{"@database":"POSTGRESQL","@explain":true}})

⑧ 将 VARCHAR 字符串字段 get 转为 JSONArray 返回:
["@json":"get"](http://apijson.cn:8080/get/{"Access":{"@json":"get"}})

⑨ 查询当前用户的动态:
["@role":"OWNER"](http://apijson.cn:8080/get/{"[]":{"Moment":{"@role":"OWNER"}}})

⑩ 开启性能分析:
["@explain":true](http://apijson.cn:8080/get/{"[]":{"Moment":{"@explain":true}}})
对应SQL是`EXPLAIN`

⑪ 统计最近一周偶数userId的数量
["@column":"left(date,10):day;sum(if(userId%2=0,1,0))",
"@group":"day",
"@having":"to_days(now())-to_days(\`date\`)<=7",
"@raw":"@column,@having"](http://apijson.cn:8080/get/{"[]":{"Comment":{"@column":"left(date,10):day%3bsum(if(userId%252=0,1,0))","@group":"day","@having":"to_days(now())-to_days(\`date\`)<=7","@raw":"@column,@having"}}})
对应SQL是``SELECT left(date,10) AS day, sum(if(userId%2=0,1,0)) ... GROUP BY day HAVING to_days(now())-to_days(`date`)<=7``

⑫ 从pictureList获取第0张图片:
["@position":0, //自定义关键词
"firstPicture()":"getFromArray(pictureList,@position)"](http://apijson.cn:8080/get/{"User":{"id":38710,"@position":0,"firstPicture()":"getFromArray(pictureList,@position)"}})
From 859cdf5f60dbf97f24141eb37faaab91538d9db8 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Sun, 31 Jan 2021 01:30:23 +0800 Subject: [PATCH 0264/1181] Update Document.md --- Document.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Document.md b/Document.md index 202f9ff7f..e064a8a86 100644 --- a/Document.md +++ b/Document.md @@ -371,6 +371,6 @@ DELETE:
删除数据 | base_url/delete/ | {
   TableName:{< 比较运算 | >, <, >=, <= 比较运算符,用于
① 提供 "id{}":"<=90000" 这种条件范围的简化写法

② 实现子查询相关比较运算

不支持 "key=":Object 和 "key!=":Object 这两种写法,直接用更简单的 "key":Object 和 "key!":Object 替代。 | ① ["id<=":90000](http://apijson.cn:8080/get/{"[]":{"User":{"id<=":90000}}}),对应SQL是`id<=90000`,查询符合id<=90000的一个User数组

② ["id>@":{
   "from":"Comment",
   "Comment":{
      "@column":"min(userId)"
   }
}](http://apijson.cn:8080/get/{"User":{"id>@":{"from":"Comment","Comment":{"@column":"min(userId)"}}}})
WHERE id>(SELECT min(userId) FROM Comment) 逻辑运算 | &, \|, ! 逻辑运算符,对应数据库 SQL 中的 AND, OR, NOT。
横或纵与:同一键值对的值内条件默认 \| 或连接,可以在 key 后加逻辑运算符来具体指定;不同键值对的条件默认 & 与连接,可以用下面说明的对象关键词 @combine 来具体指定。

① & 可用于"key&{}":"条件"等

② \| 可用于"key\|{}":"条件", "key\|{}":[]等,一般可省略

③ ! 可单独使用,如"key!":Object,也可像&,\|一样配合其他功能符使用
"key!":null 无效,null 值会导致整个键值对被忽略解析,可以用 "key{}":"!=null" 替代,
"key":null 同理,用 "key{}":"=null" 替代。 | ① ["id&{}":">80000,<=90000"](http://apijson.cn:8080/head/{"User":{"id&{}":">80000,<=90000"}}),对应SQL是`id>80000 AND id<=90000`,即id满足id>80000 & id<=90000

② ["id\|{}":">90000,<=80000"](http://apijson.cn:8080/head/{"User":{"id\|{}":">90000,<=80000"}}),同"id{}":">90000,<=80000",对应SQL是`id>80000 OR id<=90000`,即id满足id>90000 \| id<=80000

③ ["id!{}":[82001,38710]](http://apijson.cn:8080/head/{"User":{"id!{}":[82001,38710]}}),对应SQL是`id NOT IN(82001,38710)`,即id满足 ! (id=82001 \| id=38710),可过滤黑名单的消息 数组关键词,可自定义 | "key":Object,key为 "[]":{} 中{}内的关键词,Object的类型由key指定

① "count":Integer,查询数量,0 表示最大值,默认最大值为100

② "page":Integer,查询页码,从0开始,默认最大值为100,一般和count一起用

③ "query":Integer,查询内容
0-对象,1-总数和分页详情,2-以上全部
总数关键词为 total,分页详情关键词为 info,
它们都和 query 同级,通过引用赋值得到,例如
"total@":"/[]/total", "info@":"/[]/info"
这里query及total仅为GET类型的请求提供方便,
一般可直接用HEAD类型的请求获取总数

④ "join":"&/Table0/key0@,\多表连接方式:
"\<" - LEFT JOIN
">" - RIGHT JOIN
"&" - INNER JOIN
"\|" - FULL JOIN
"!" - OUTTER JOIN
"@" - APP JOIN
其中 @ APP JOIN 为应用层连表,会从已查出的主表里取得所有副表 key@ 关联的主表内的 refKey 作为一个数组 refKeys: [value0, value1...],然后把原来副表 count 次查询 key=$refKey 的 SQL 用 key IN($refKeys) 的方式合并为一条 SQL 来优化性能;
其它 JOIN 都是 SQL JOIN,具体功能和 MySQL,PostgreSQL 等数据库的 JOIN 一一对应
`"join":"`"MainTable":{},`
`"ViceTable":{"key@":"/MainTable/refKey"}`
会对应生成
`MainTable LEFT JOIN ViceTable`
`ON ViceTable.key=MainTable.refKey`

⑤ "otherKey":Object,自定义关键词,名称和以上系统关键词不一样,且原样返回上传的值 | ① 查询User数组,最多5个:
["count":5](http://apijson.cn:8080/get/{"[]":{"count":5,"User":{}}})
对应SQL是`LIMIT 5`

② 查询第3页的User数组,每页5个:
["count":5,
"page":3](http://apijson.cn:8080/get/{"[]":{"count":5,"page":3,"User":{}}})
对应SQL是`LIMIT 5 OFFSET 15`

③ 查询User数组和对应的User总数:
["[]":{
   "query":2,
   "User":{}
},
"total@":"/[]/total",
"info@":"/[]/info"](http://apijson.cn:8080/get/{"[]":{"query":2,"count":5,"User":{}},"total@":"%252F[]%252Ftotal","info@":"%252F[]%252Finfo"})
返回的数据中,总数及分页详情结构为:
"total":139, //总数
"info":{ //分页详情
   "total":139, //总数
   "count":5, //每页数量
   "page":0, //当前页码
   "max":27, //最大页码
   "more":true, //是否还有更多
   "first":true, //是否为首页
   "last":false //是否为尾页
}

④ Moment INNER JOIN User LEFT JOIN Comment:
["[]":{
   "join":"&/User/id@,\    "Moment":{},
   "User":{
     "name~":"t",
     "id@":"/Moment/userId"
   },
   "Comment":{
     "momentId@":"/Moment/id"
   }
}](http://apijson.cn:8080/get/{"[]":{"count":5,"join":"&%252FUser%252Fid@,\<%252FComment%252FmomentId@","Moment":{"@column":"id,userId,content"},"User":{"name~":"t","id@":"%252FMoment%252FuserId","@column":"id,name,head"},"Comment":{"momentId@":"%252FMoment%252Fid","@column":"id,momentId,content"}}})

⑤ 每一层都加当前用户名:
["User":{},
"[]":{
   "name@":"User/name", //自定义关键词
   "Moment":{}
}](http://apijson.cn:8080/get/{"User":{},"[]":{"name@":"User%252Fname","Moment":{}}}) - 对象关键词,可自定义 | "@key":Object,@key为 Table:{} 中{}内的关键词,Object的类型由@key指定

① "@combine":"&key0,&key1,\|key2,key3,
!key4,!key5,&key6,key7...",条件组合方式,\| 可省略。会自动把同类的合并,外层按照 & \| ! 顺序,内层的按传参顺序组合成
(key0 & key1 & key6 & 其它key) & (key2 \| key3 \| key7) & !(key4 \| key5)
这种连接方式,其中 "其它key" 是指与 @combine 在同一对象,且未被它声明的条件 key,默认都是 & 连接

② "@column":"column;function(arg)...",返回字段

③ "@order":"column0+,column1-...",排序方式

④ "@group":"column0,column1...",分组方式。如果@column里声明了Table的id,则id也必须在@group中声明;其它情况下必须满足至少一个条件:
1.分组的key在@column里声明
2.Table主键在@group中声明

⑤ "@having":"function0(...)?value0;function1(...)?value1;function2(...)?value2...",SQL函数条件,一般和@group一起用,函数一般在@column里声明

⑥ "@schema":"sys",集合空间(模式),非默认的值可通过它来指定,可以在最外层作为全局默认配置

⑦ "@database":"POSTGRESQL",跨数据库,非默认的值可通过它来指定,可以在最外层作为全局默认配置

⑧ "@json":"key0,key1...",转为 JSON 格式返回,符合 JSONObject 则转为 {...},符合 JSONArray 则转为 \[...]

⑨ "@role":"OWNER",来访角色,包括
UNKNOWN,LOGIN,CONTACT,CIRCLE,OWNER,ADMIN,
可以在最外层作为全局默认配置,
可自定义其它角色并重写 Verifier.verify 等相关方法来自定义校验

⑩ "@explain":true,性能分析,可以在最外层作为全局默认配置

⑪ "@raw":"key0,key1...",其中 key0, key1 都对应有键值对
"key0":"SQL片段或SQL片段的别名",
"key1":"SQL片段或SQL片段的别名"
自定义原始SQL片段,可扩展嵌套SQL函数等复杂语句,必须是后端已配置的,只有其它功能符都做不到才考虑,谨慎使用,注意防SQL注入

⑫ "@otherKey":Object,自定义关键词,名称和以上系统关键词不一样,且原样返回上传的值 | ① 搜索name或tag任何一个字段包含字符a的User列表:
["name~":"a",
"tag~":"a",
"@combine":"name~,tag~"](http://apijson.cn:8080/get/{"User[]":{"count":10,"User":{"@column":"id,name,tag","name~":"a","tag~":"a","@combine":"name~,tag~"}}})
对应SQL是`name REGEXP 'a' OR tag REGEXP 'a'`

② 只查询id,sex,name这几列并且请求结果也按照这个顺序:
["@column":"id,sex,name"](http://apijson.cn:8080/get/{"User":{"@column":"id,sex,name","id":38710}})
对应SQL是`SELECT id,sex,name`

③ 查询按 name降序、id默认顺序 排序的User数组:
["@order":"name-,id"](http://apijson.cn:8080/get/{"[]":{"count":10,"User":{"@column":"name,id","@order":"name-,id"}}})
对应SQL是`ORDER BY name DESC,id`

④ 查询按userId分组的Moment数组:
["@group":"userId,id"](http://apijson.cn:8080/get/{"[]":{"count":10,"Moment":%7B"@column":"userId,id","@group":"userId,id"}}})
对应SQL是`GROUP BY userId,id`

⑤ 查询 按userId分组、id最大值>=100 的Moment数组:
["@column":"userId;max(id)",
"@group":"userId",
"@having":"max(id)>=100"](http://apijson.cn:8080/get/{"[]":{"count":10,"Moment":{"@column":"userId%253Bmax(id)","@group":"userId","@having":"max(id)>=100"}}})
对应SQL是`SELECT userId,max(id) ... GROUP BY userId HAVING max(id)>=100`
还可以指定函数返回名:
["@column":"userId;max(id):maxId",
"@group":"userId",
"@having":"maxId>=100"](http://apijson.cn:8080/get/{"[]":{"count":10,"Moment":{"@column":"userId%253Bmax(id):maxId","@group":"userId","@having":"maxId>=100"}}})
对应SQL是`SELECT userId,max(id) AS maxId ... GROUP BY userId HAVING maxId>=100`

⑥ 查询 sys 内的 User 表:
["@schema":"sys"](http://apijson.cn:8080/get/{"User":{"@schema":"sys"}})
对应SQL是`FROM sys.User`

⑦ 查询 PostgreSQL 数据库的 User 表:
["@database":"POSTGRESQL"](http://apijson.cn:8080/get/{"User":{"@database":"POSTGRESQL","@explain":true}})

⑧ 将 VARCHAR 字符串字段 get 转为 JSONArray 返回:
["@json":"get"](http://apijson.cn:8080/get/{"Access":{"@json":"get"}})

⑨ 查询当前用户的动态:
["@role":"OWNER"](http://apijson.cn:8080/get/{"[]":{"Moment":{"@role":"OWNER"}}})

⑩ 开启性能分析:
["@explain":true](http://apijson.cn:8080/get/{"[]":{"Moment":{"@explain":true}}})
对应SQL是`EXPLAIN`

⑪ 统计最近一周偶数userId的数量
["@column":"left(date,10):day;sum(if(userId%2=0,1,0))",
"@group":"day",
"@having":"to_days(now())-to_days(\`date\`)<=7",
"@raw":"@column,@having"](http://apijson.cn:8080/get/{"[]":{"Comment":{"@column":"left(date,10):day%3bsum(if(userId%252=0,1,0))","@group":"day","@having":"to_days(now())-to_days(\`date\`)<=7","@raw":"@column,@having"}}})
对应SQL是``SELECT left(date,10) AS day, sum(if(userId%2=0,1,0)) ... GROUP BY day HAVING to_days(now())-to_days(`date`)<=7``

⑫ 从pictureList获取第0张图片:
["@position":0, //自定义关键词
"firstPicture()":"getFromArray(pictureList,@position)"](http://apijson.cn:8080/get/{"User":{"id":38710,"@position":0,"firstPicture()":"getFromArray(pictureList,@position)"}}) + 对象关键词,可自定义 | "@key":Object,@key为 Table:{} 中{}内的关键词,Object的类型由@key指定

① "@combine":"&key0,&key1,\|key2,key3,
!key4,!key5,&key6,key7...",条件组合方式,\| 可省略。会自动把同类的合并,外层按照 & \| ! 顺序,内层的按传参顺序组合成
(key0 & key1 & key6 & 其它key) & (key2 \| key3 \| key7) & !(key4 \| key5)
这种连接方式,其中 "其它key" 是指与 @combine 在同一对象,且未被它声明的条件 key,默认都是 & 连接

② "@column":"column;function(arg)...",返回字段

③ "@order":"column0+,column1-...",排序方式

④ "@group":"column0,column1...",分组方式。如果@column里声明了Table的id,则id也必须在@group中声明;其它情况下必须满足至少一个条件:
1.分组的key在@column里声明
2.Table主键在@group中声明

⑤ "@having":"function0(...)?value0;function1(...)?value1;function2(...)?value2...",SQL函数条件,一般和@group一起用,函数一般在@column里声明

⑥ "@schema":"sys",集合空间(模式),非默认的值可通过它来指定,可以在最外层作为全局默认配置

⑦ "@database":"POSTGRESQL",跨数据库,非默认的值可通过它来指定,可以在最外层作为全局默认配置

⑧ "@json":"key0,key1...",转为 JSON 格式返回,符合 JSONObject 则转为 {...},符合 JSONArray 则转为 \[...]

⑨ "@role":"OWNER",来访角色,包括
UNKNOWN,LOGIN,CONTACT,CIRCLE,OWNER,ADMIN,
可以在最外层作为全局默认配置,
可自定义其它角色并重写 Verifier.verify 等相关方法来自定义校验

⑩ "@explain":true,性能分析,可以在最外层作为全局默认配置

⑪ "@raw":"key0,key1...",其中 key0, key1 都对应有键值对
"key0":"SQL片段或SQL片段的别名",
"key1":"SQL片段或SQL片段的别名"
自定义原始SQL片段,可扩展嵌套SQL函数等复杂语句,必须是后端已配置的,只有其它功能符都做不到才考虑,谨慎使用,注意防SQL注入

⑫ "@otherKey":Object,自定义关键词,名称和以上系统关键词不一样,且原样返回上传的值 | ① 搜索name或tag任何一个字段包含字符a的User列表:
["name~":"a",
"tag~":"a",
"@combine":"name~,tag~"](http://apijson.cn:8080/get/{"User[]":{"count":10,"User":{"@column":"id,name,tag","name~":"a","tag~":"a","@combine":"name~,tag~"}}})
对应SQL是`name REGEXP 'a' OR tag REGEXP 'a'`

② 只查询id,sex,name这几列并且请求结果也按照这个顺序:
["@column":"id,sex,name"](http://apijson.cn:8080/get/{"User":{"@column":"id,sex,name","id":38710}})
对应SQL是`SELECT id,sex,name`

③ 查询按 name降序、id默认顺序 排序的User数组:
["@order":"name-,id"](http://apijson.cn:8080/get/{"[]":{"count":10,"User":{"@column":"name,id","@order":"name-,id"}}})
对应SQL是`ORDER BY name DESC,id`

④ 查询按userId分组的Moment数组:
["@group":"userId,id"](http://apijson.cn:8080/get/{"[]":{"count":10,"Moment":%7B"@column":"userId,id","@group":"userId,id"}}})
对应SQL是`GROUP BY userId,id`

⑤ 查询 按userId分组、id最大值>=100 的Moment数组:
["@column":"userId;max(id)",
"@group":"userId",
"@having":"max(id)>=100"](http://apijson.cn:8080/get/{"[]":{"count":10,"Moment":{"@column":"userId%253Bmax(id)","@group":"userId","@having":"max(id)>=100"}}})
对应SQL是`SELECT userId,max(id) ... GROUP BY userId HAVING max(id)>=100`
还可以指定函数返回名:
["@column":"userId;max(id):maxId",
"@group":"userId",
"@having":"maxId>=100"](http://apijson.cn:8080/get/{"[]":{"count":10,"Moment":{"@column":"userId%253Bmax(id):maxId","@group":"userId","@having":"maxId>=100"}}})
对应SQL是`SELECT userId,max(id) AS maxId ... GROUP BY userId HAVING maxId>=100`

⑥ 查询 sys 内的 User 表:
["@schema":"sys"](http://apijson.cn:8080/get/{"User":{"@schema":"sys"}})
对应SQL是`FROM sys.User`

⑦ 查询 PostgreSQL 数据库的 User 表:
["@database":"POSTGRESQL"](http://apijson.cn:8080/get/{"User":{"@database":"POSTGRESQL","@explain":true}})

⑧ 将 VARCHAR 字符串字段 get 转为 JSONArray 返回:
["@json":"get"](http://apijson.cn:8080/get/{"Access":{"@json":"get"}})

⑨ 查询当前用户的动态:
["@role":"OWNER"](http://apijson.cn:8080/get/{"[]":{"Moment":{"@role":"OWNER"}}})

⑩ 开启性能分析:
["@explain":true](http://apijson.cn:8080/get/{"[]":{"Moment":{"@explain":true}}})
对应SQL是`EXPLAIN`

⑪ 统计最近一周偶数userId的数量
["@column":"date;left(date,10):day;sum(if(userId%2=0,1,0))",
"@group":"day",
"@having":"to_days(now())-to_days(\`date\`)<=7",
"@raw":"@column,@having"](http://apijson.cn:8080/get/{"[]":{"Comment":{"@column":"date%3bleft(date,10):day%3bsum(if(userId%252=0,1,0))","@group":"day","@having":"to_days(now())-to_days(\`date\`)<=7","@raw":"@column,@having"}}})
对应SQL是``SELECT date, left(date,10) AS day, sum(if(userId%2=0,1,0)) ... GROUP BY day HAVING to_days(now())-to_days(`date`)<=7``

⑫ 从pictureList获取第0张图片:
["@position":0, //自定义关键词
"firstPicture()":"getFromArray(pictureList,@position)"](http://apijson.cn:8080/get/{"User":{"id":38710,"@position":0,"firstPicture()":"getFromArray(pictureList,@position)"}})
From 5a73b23b7bdff764bf3579917f025ab9c18f099f Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Sun, 31 Jan 2021 01:32:21 +0800 Subject: [PATCH 0265/1181] Update Document.md --- Document.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Document.md b/Document.md index e064a8a86..f21c8153a 100644 --- a/Document.md +++ b/Document.md @@ -371,6 +371,6 @@ DELETE:
删除数据 | base_url/delete/ | {
   TableName:{< 比较运算 | >, <, >=, <= 比较运算符,用于
① 提供 "id{}":"<=90000" 这种条件范围的简化写法

② 实现子查询相关比较运算

不支持 "key=":Object 和 "key!=":Object 这两种写法,直接用更简单的 "key":Object 和 "key!":Object 替代。 | ① ["id<=":90000](http://apijson.cn:8080/get/{"[]":{"User":{"id<=":90000}}}),对应SQL是`id<=90000`,查询符合id<=90000的一个User数组

② ["id>@":{
   "from":"Comment",
   "Comment":{
      "@column":"min(userId)"
   }
}](http://apijson.cn:8080/get/{"User":{"id>@":{"from":"Comment","Comment":{"@column":"min(userId)"}}}})
WHERE id>(SELECT min(userId) FROM Comment) 逻辑运算 | &, \|, ! 逻辑运算符,对应数据库 SQL 中的 AND, OR, NOT。
横或纵与:同一键值对的值内条件默认 \| 或连接,可以在 key 后加逻辑运算符来具体指定;不同键值对的条件默认 & 与连接,可以用下面说明的对象关键词 @combine 来具体指定。

① & 可用于"key&{}":"条件"等

② \| 可用于"key\|{}":"条件", "key\|{}":[]等,一般可省略

③ ! 可单独使用,如"key!":Object,也可像&,\|一样配合其他功能符使用
"key!":null 无效,null 值会导致整个键值对被忽略解析,可以用 "key{}":"!=null" 替代,
"key":null 同理,用 "key{}":"=null" 替代。 | ① ["id&{}":">80000,<=90000"](http://apijson.cn:8080/head/{"User":{"id&{}":">80000,<=90000"}}),对应SQL是`id>80000 AND id<=90000`,即id满足id>80000 & id<=90000

② ["id\|{}":">90000,<=80000"](http://apijson.cn:8080/head/{"User":{"id\|{}":">90000,<=80000"}}),同"id{}":">90000,<=80000",对应SQL是`id>80000 OR id<=90000`,即id满足id>90000 \| id<=80000

③ ["id!{}":[82001,38710]](http://apijson.cn:8080/head/{"User":{"id!{}":[82001,38710]}}),对应SQL是`id NOT IN(82001,38710)`,即id满足 ! (id=82001 \| id=38710),可过滤黑名单的消息 数组关键词,可自定义 | "key":Object,key为 "[]":{} 中{}内的关键词,Object的类型由key指定

① "count":Integer,查询数量,0 表示最大值,默认最大值为100

② "page":Integer,查询页码,从0开始,默认最大值为100,一般和count一起用

③ "query":Integer,查询内容
0-对象,1-总数和分页详情,2-以上全部
总数关键词为 total,分页详情关键词为 info,
它们都和 query 同级,通过引用赋值得到,例如
"total@":"/[]/total", "info@":"/[]/info"
这里query及total仅为GET类型的请求提供方便,
一般可直接用HEAD类型的请求获取总数

④ "join":"&/Table0/key0@,\多表连接方式:
"\<" - LEFT JOIN
">" - RIGHT JOIN
"&" - INNER JOIN
"\|" - FULL JOIN
"!" - OUTTER JOIN
"@" - APP JOIN
其中 @ APP JOIN 为应用层连表,会从已查出的主表里取得所有副表 key@ 关联的主表内的 refKey 作为一个数组 refKeys: [value0, value1...],然后把原来副表 count 次查询 key=$refKey 的 SQL 用 key IN($refKeys) 的方式合并为一条 SQL 来优化性能;
其它 JOIN 都是 SQL JOIN,具体功能和 MySQL,PostgreSQL 等数据库的 JOIN 一一对应
`"join":"`"MainTable":{},`
`"ViceTable":{"key@":"/MainTable/refKey"}`
会对应生成
`MainTable LEFT JOIN ViceTable`
`ON ViceTable.key=MainTable.refKey`

⑤ "otherKey":Object,自定义关键词,名称和以上系统关键词不一样,且原样返回上传的值 | ① 查询User数组,最多5个:
["count":5](http://apijson.cn:8080/get/{"[]":{"count":5,"User":{}}})
对应SQL是`LIMIT 5`

② 查询第3页的User数组,每页5个:
["count":5,
"page":3](http://apijson.cn:8080/get/{"[]":{"count":5,"page":3,"User":{}}})
对应SQL是`LIMIT 5 OFFSET 15`

③ 查询User数组和对应的User总数:
["[]":{
   "query":2,
   "User":{}
},
"total@":"/[]/total",
"info@":"/[]/info"](http://apijson.cn:8080/get/{"[]":{"query":2,"count":5,"User":{}},"total@":"%252F[]%252Ftotal","info@":"%252F[]%252Finfo"})
返回的数据中,总数及分页详情结构为:
"total":139, //总数
"info":{ //分页详情
   "total":139, //总数
   "count":5, //每页数量
   "page":0, //当前页码
   "max":27, //最大页码
   "more":true, //是否还有更多
   "first":true, //是否为首页
   "last":false //是否为尾页
}

④ Moment INNER JOIN User LEFT JOIN Comment:
["[]":{
   "join":"&/User/id@,\    "Moment":{},
   "User":{
     "name~":"t",
     "id@":"/Moment/userId"
   },
   "Comment":{
     "momentId@":"/Moment/id"
   }
}](http://apijson.cn:8080/get/{"[]":{"count":5,"join":"&%252FUser%252Fid@,\<%252FComment%252FmomentId@","Moment":{"@column":"id,userId,content"},"User":{"name~":"t","id@":"%252FMoment%252FuserId","@column":"id,name,head"},"Comment":{"momentId@":"%252FMoment%252Fid","@column":"id,momentId,content"}}})

⑤ 每一层都加当前用户名:
["User":{},
"[]":{
   "name@":"User/name", //自定义关键词
   "Moment":{}
}](http://apijson.cn:8080/get/{"User":{},"[]":{"name@":"User%252Fname","Moment":{}}}) - 对象关键词,可自定义 | "@key":Object,@key为 Table:{} 中{}内的关键词,Object的类型由@key指定

① "@combine":"&key0,&key1,\|key2,key3,
!key4,!key5,&key6,key7...",条件组合方式,\| 可省略。会自动把同类的合并,外层按照 & \| ! 顺序,内层的按传参顺序组合成
(key0 & key1 & key6 & 其它key) & (key2 \| key3 \| key7) & !(key4 \| key5)
这种连接方式,其中 "其它key" 是指与 @combine 在同一对象,且未被它声明的条件 key,默认都是 & 连接

② "@column":"column;function(arg)...",返回字段

③ "@order":"column0+,column1-...",排序方式

④ "@group":"column0,column1...",分组方式。如果@column里声明了Table的id,则id也必须在@group中声明;其它情况下必须满足至少一个条件:
1.分组的key在@column里声明
2.Table主键在@group中声明

⑤ "@having":"function0(...)?value0;function1(...)?value1;function2(...)?value2...",SQL函数条件,一般和@group一起用,函数一般在@column里声明

⑥ "@schema":"sys",集合空间(模式),非默认的值可通过它来指定,可以在最外层作为全局默认配置

⑦ "@database":"POSTGRESQL",跨数据库,非默认的值可通过它来指定,可以在最外层作为全局默认配置

⑧ "@json":"key0,key1...",转为 JSON 格式返回,符合 JSONObject 则转为 {...},符合 JSONArray 则转为 \[...]

⑨ "@role":"OWNER",来访角色,包括
UNKNOWN,LOGIN,CONTACT,CIRCLE,OWNER,ADMIN,
可以在最外层作为全局默认配置,
可自定义其它角色并重写 Verifier.verify 等相关方法来自定义校验

⑩ "@explain":true,性能分析,可以在最外层作为全局默认配置

⑪ "@raw":"key0,key1...",其中 key0, key1 都对应有键值对
"key0":"SQL片段或SQL片段的别名",
"key1":"SQL片段或SQL片段的别名"
自定义原始SQL片段,可扩展嵌套SQL函数等复杂语句,必须是后端已配置的,只有其它功能符都做不到才考虑,谨慎使用,注意防SQL注入

⑫ "@otherKey":Object,自定义关键词,名称和以上系统关键词不一样,且原样返回上传的值 | ① 搜索name或tag任何一个字段包含字符a的User列表:
["name~":"a",
"tag~":"a",
"@combine":"name~,tag~"](http://apijson.cn:8080/get/{"User[]":{"count":10,"User":{"@column":"id,name,tag","name~":"a","tag~":"a","@combine":"name~,tag~"}}})
对应SQL是`name REGEXP 'a' OR tag REGEXP 'a'`

② 只查询id,sex,name这几列并且请求结果也按照这个顺序:
["@column":"id,sex,name"](http://apijson.cn:8080/get/{"User":{"@column":"id,sex,name","id":38710}})
对应SQL是`SELECT id,sex,name`

③ 查询按 name降序、id默认顺序 排序的User数组:
["@order":"name-,id"](http://apijson.cn:8080/get/{"[]":{"count":10,"User":{"@column":"name,id","@order":"name-,id"}}})
对应SQL是`ORDER BY name DESC,id`

④ 查询按userId分组的Moment数组:
["@group":"userId,id"](http://apijson.cn:8080/get/{"[]":{"count":10,"Moment":%7B"@column":"userId,id","@group":"userId,id"}}})
对应SQL是`GROUP BY userId,id`

⑤ 查询 按userId分组、id最大值>=100 的Moment数组:
["@column":"userId;max(id)",
"@group":"userId",
"@having":"max(id)>=100"](http://apijson.cn:8080/get/{"[]":{"count":10,"Moment":{"@column":"userId%253Bmax(id)","@group":"userId","@having":"max(id)>=100"}}})
对应SQL是`SELECT userId,max(id) ... GROUP BY userId HAVING max(id)>=100`
还可以指定函数返回名:
["@column":"userId;max(id):maxId",
"@group":"userId",
"@having":"maxId>=100"](http://apijson.cn:8080/get/{"[]":{"count":10,"Moment":{"@column":"userId%253Bmax(id):maxId","@group":"userId","@having":"maxId>=100"}}})
对应SQL是`SELECT userId,max(id) AS maxId ... GROUP BY userId HAVING maxId>=100`

⑥ 查询 sys 内的 User 表:
["@schema":"sys"](http://apijson.cn:8080/get/{"User":{"@schema":"sys"}})
对应SQL是`FROM sys.User`

⑦ 查询 PostgreSQL 数据库的 User 表:
["@database":"POSTGRESQL"](http://apijson.cn:8080/get/{"User":{"@database":"POSTGRESQL","@explain":true}})

⑧ 将 VARCHAR 字符串字段 get 转为 JSONArray 返回:
["@json":"get"](http://apijson.cn:8080/get/{"Access":{"@json":"get"}})

⑨ 查询当前用户的动态:
["@role":"OWNER"](http://apijson.cn:8080/get/{"[]":{"Moment":{"@role":"OWNER"}}})

⑩ 开启性能分析:
["@explain":true](http://apijson.cn:8080/get/{"[]":{"Moment":{"@explain":true}}})
对应SQL是`EXPLAIN`

⑪ 统计最近一周偶数userId的数量
["@column":"date;left(date,10):day;sum(if(userId%2=0,1,0))",
"@group":"day",
"@having":"to_days(now())-to_days(\`date\`)<=7",
"@raw":"@column,@having"](http://apijson.cn:8080/get/{"[]":{"Comment":{"@column":"date%3bleft(date,10):day%3bsum(if(userId%252=0,1,0))","@group":"day","@having":"to_days(now())-to_days(\`date\`)<=7","@raw":"@column,@having"}}})
对应SQL是``SELECT date, left(date,10) AS day, sum(if(userId%2=0,1,0)) ... GROUP BY day HAVING to_days(now())-to_days(`date`)<=7``

⑫ 从pictureList获取第0张图片:
["@position":0, //自定义关键词
"firstPicture()":"getFromArray(pictureList,@position)"](http://apijson.cn:8080/get/{"User":{"id":38710,"@position":0,"firstPicture()":"getFromArray(pictureList,@position)"}}) + 对象关键词,可自定义 | "@key":Object,@key为 Table:{} 中{}内的关键词,Object的类型由@key指定

① "@combine":"&key0,&key1,\|key2,key3,
!key4,!key5,&key6,key7...",条件组合方式,\| 可省略。会自动把同类的合并,外层按照 & \| ! 顺序,内层的按传参顺序组合成
(key0 & key1 & key6 & 其它key) & (key2 \| key3 \| key7) & !(key4 \| key5)
这种连接方式,其中 "其它key" 是指与 @combine 在同一对象,且未被它声明的条件 key,默认都是 & 连接

② "@column":"column;function(arg)...",返回字段

③ "@order":"column0+,column1-...",排序方式

④ "@group":"column0,column1...",分组方式。如果@column里声明了Table的id,则id也必须在@group中声明;其它情况下必须满足至少一个条件:
1.分组的key在@column里声明
2.Table主键在@group中声明

⑤ "@having":"function0(...)?value0;function1(...)?value1;function2(...)?value2...",SQL函数条件,一般和@group一起用,函数一般在@column里声明

⑥ "@schema":"sys",集合空间(模式),非默认的值可通过它来指定,可以在最外层作为全局默认配置

⑦ "@database":"POSTGRESQL",跨数据库,非默认的值可通过它来指定,可以在最外层作为全局默认配置

⑧ "@json":"key0,key1...",转为 JSON 格式返回,符合 JSONObject 则转为 {...},符合 JSONArray 则转为 \[...]

⑨ "@role":"OWNER",来访角色,包括
UNKNOWN,LOGIN,CONTACT,CIRCLE,OWNER,ADMIN,
可以在最外层作为全局默认配置,
可自定义其它角色并重写 Verifier.verify 等相关方法来自定义校验

⑩ "@explain":true,性能分析,可以在最外层作为全局默认配置

⑪ "@raw":"key0,key1...",其中 key0, key1 都对应有键值对
"key0":"SQL片段或SQL片段的别名",
"key1":"SQL片段或SQL片段的别名"
自定义原始SQL片段,可扩展嵌套SQL函数等复杂语句,必须是后端已配置的,只有其它功能符都做不到才考虑,谨慎使用,注意防SQL注入

⑫ "@otherKey":Object,自定义关键词,名称和以上系统关键词不一样,且原样返回上传的值 | ① 搜索name或tag任何一个字段包含字符a的User列表:
["name~":"a",
"tag~":"a",
"@combine":"name~,tag~"](http://apijson.cn:8080/get/{"User[]":{"count":10,"User":{"@column":"id,name,tag","name~":"a","tag~":"a","@combine":"name~,tag~"}}})
对应SQL是`name REGEXP 'a' OR tag REGEXP 'a'`

② 只查询id,sex,name这几列并且请求结果也按照这个顺序:
["@column":"id,sex,name"](http://apijson.cn:8080/get/{"User":{"@column":"id,sex,name","id":38710}})
对应SQL是`SELECT id,sex,name`

③ 查询按 name降序、id默认顺序 排序的User数组:
["@order":"name-,id"](http://apijson.cn:8080/get/{"[]":{"count":10,"User":{"@column":"name,id","@order":"name-,id"}}})
对应SQL是`ORDER BY name DESC,id`

④ 查询按userId分组的Moment数组:
["@group":"userId,id"](http://apijson.cn:8080/get/{"[]":{"count":10,"Moment":%7B"@column":"userId,id","@group":"userId,id"}}})
对应SQL是`GROUP BY userId,id`

⑤ 查询 按userId分组、id最大值>=100 的Moment数组:
["@column":"userId;max(id)",
"@group":"userId",
"@having":"max(id)>=100"](http://apijson.cn:8080/get/{"[]":{"count":10,"Moment":{"@column":"userId%253Bmax(id)","@group":"userId","@having":"max(id)>=100"}}})
对应SQL是`SELECT userId,max(id) ... GROUP BY userId HAVING max(id)>=100`
还可以指定函数返回名:
["@column":"userId;max(id):maxId",
"@group":"userId",
"@having":"maxId>=100"](http://apijson.cn:8080/get/{"[]":{"count":10,"Moment":{"@column":"userId%253Bmax(id):maxId","@group":"userId","@having":"maxId>=100"}}})
对应SQL是`SELECT userId,max(id) AS maxId ... GROUP BY userId HAVING maxId>=100`

⑥ 查询 sys 内的 User 表:
["@schema":"sys"](http://apijson.cn:8080/get/{"User":{"@schema":"sys"}})
对应SQL是`FROM sys.User`

⑦ 查询 PostgreSQL 数据库的 User 表:
["@database":"POSTGRESQL"](http://apijson.cn:8080/get/{"User":{"@database":"POSTGRESQL","@explain":true}})

⑧ 将 VARCHAR 字符串字段 get 转为 JSONArray 返回:
["@json":"get"](http://apijson.cn:8080/get/{"Access":{"@json":"get"}})

⑨ 查询当前用户的动态:
["@role":"OWNER"](http://apijson.cn:8080/get/{"[]":{"Moment":{"@role":"OWNER"}}})

⑩ 开启性能分析:
["@explain":true](http://apijson.cn:8080/get/{"[]":{"Moment":{"@explain":true}}})
对应SQL是`EXPLAIN`

⑪ 统计最近一周偶数userId的数量
["@column":"date;left(date,10):day;sum(if(userId%2=0,1,0))",
"@group":"day",
"@having":"to_days(now())-to_days(\`date\`)<=7",
"@raw":"@column,@having"](http://apijson.cn:8080/get/{"[]":{"Moment":{"@column":"date%3bleft(date,10):day%3bsum(if(userId%252=0,1,0))","@group":"day","@having":"to_days(now())-to_days(\`date\`)<=7","@raw":"@column,@having"}}})
对应SQL是``SELECT date, left(date,10) AS day, sum(if(userId%2=0,1,0)) ... GROUP BY day HAVING to_days(now())-to_days(`date`)<=7``

⑫ 从pictureList获取第0张图片:
["@position":0, //自定义关键词
"firstPicture()":"getFromArray(pictureList,@position)"](http://apijson.cn:8080/get/{"User":{"id":38710,"@position":0,"firstPicture()":"getFromArray(pictureList,@position)"}})
From cf1cca0254a47cf7d8a4f0b25fc56ba9c5ef1130 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Sun, 31 Jan 2021 02:24:37 +0800 Subject: [PATCH 0266/1181] =?UTF-8?q?=E8=A7=A3=E5=86=B3=20"toId%":=20"0,10?= =?UTF-8?q?"=20=E7=AD=89=E8=BF=9E=E7=BB=AD=E8=8C=83=E5=9B=B4=E6=8A=A5?= =?UTF-8?q?=E9=94=99=20value=20=E7=B1=BB=E5=9E=8B=E4=B8=8D=E5=90=88?= =?UTF-8?q?=E6=B3=95=EF=BC=9B=E8=A7=A3=E5=86=B3=20"id{}@":=20"[]/Moment/pr?= =?UTF-8?q?aiseUserIdList"=20=E7=AD=89=E5=BC=95=E7=94=A8=E8=B5=8B=E5=80=BC?= =?UTF-8?q?=E7=9A=84=E5=80=BC=E6=9C=89=E6=97=B6=E7=B1=BB=E5=9E=8B=E4=B8=BA?= =?UTF-8?q?=20List=20=E6=97=B6=E6=8A=A5=E9=94=99=20ArrayList=20cannot=20be?= =?UTF-8?q?=20cast=20to=20JSONArray=EF=BC=9B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/main/java/apijson/orm/AbstractSQLConfig.java | 10 ++++++++-- .../src/main/java/apijson/orm/AbstractVerifier.java | 3 ++- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java index e6f3fd9cc..169fe6d4e 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java @@ -1920,7 +1920,10 @@ public String getRegExpString(String key, String value, boolean ignoreCase) { * @throws IllegalArgumentException */ @JSONField(serialize = false) - public String getBetweenString(String key, Object value) throws IllegalArgumentException { + public String getBetweenString(String key, Object value, String rawSQL) throws IllegalArgumentException { + if (rawSQL != null) { + throw new UnsupportedOperationException("@raw:value 中 " + key + " 不合法!@raw 不支持 key% 这种功能符 !只支持 key, key!, key<, key{} 等比较运算 和 @column, @having !"); + } if (value == null) { return ""; } @@ -1960,7 +1963,7 @@ public String getBetweenString(String key, Object[] values, int type) throws Ill throw new IllegalArgumentException(key + "%:value 中 value 不合法!类型为 String 时必须包括1个逗号 , 且左右两侧都有值!类型为 String[] 里面每个元素要符合前面类型为 String 的规则 !"); } - condition += (i <= 0 ? "" : (Logic.isAnd(type) ? AND : OR)) + "(" + getBetweenString(key, vs[0], vs[1]) + ")"; + condition += (i <= 0 ? "" : (Logic.isAnd(type) ? AND : OR)) + "(" + getBetweenString(key, (Object) vs[0], (Object) vs[1]) + ")"; } return getCondition(Logic.isNot(type), condition); @@ -2149,6 +2152,9 @@ public String getExistsString(String key, Object value, String rawSQL) throws Ex */ @JSONField(serialize = false) public String getContainString(String key, Object value, String rawSQL) throws IllegalArgumentException { + if (rawSQL != null) { + throw new UnsupportedOperationException("@raw:value 中 " + key + " 不合法!@raw 不支持 key<> 这种功能符 !只支持 key, key!, key<, key{} 等比较运算 和 @column, @having !"); + } if (value == null) { return ""; } diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractVerifier.java b/APIJSONORM/src/main/java/apijson/orm/AbstractVerifier.java index 2e14f0881..b85c1e19f 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractVerifier.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractVerifier.java @@ -262,7 +262,8 @@ public boolean verifyAccess(SQLConfig config) throws Exception { //key!{}:[] 或 其它没有明确id的条件 等 可以和key{}:list组合。类型错误就报错 requestId = (Number) config.getWhere(visitorIdkey, true);//JSON里数值不能保证是Long,可能是Integer - JSONArray requestIdArray = (JSONArray) config.getWhere(visitorIdkey + "{}", true);//不能是 &{}, |{} 不要传,直接{} + @SuppressWarnings("unchecked") + Collection requestIdArray = (Collection) config.getWhere(visitorIdkey + "{}", true);//不能是 &{}, |{} 不要传,直接{} if (requestId != null) { if (requestIdArray == null) { requestIdArray = new JSONArray(); From 6831cb6a6d9b0db24b612628d5dc31243493b7a4 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Sun, 31 Jan 2021 04:20:26 +0800 Subject: [PATCH 0267/1181] =?UTF-8?q?=E5=A2=9E=E5=BC=BA=E5=AE=89=E5=85=A8?= =?UTF-8?q?=EF=BC=9A=E5=AF=B9=20DELETE=20=E5=92=8C=20PUT=20=E5=BC=BA?= =?UTF-8?q?=E5=88=B6=E5=8A=A0=20LIMIT=EF=BC=9B=E7=AE=80=E5=8C=96=E5=8C=85?= =?UTF-8?q?=E5=90=AB=E9=80=89=E9=A1=B9=E7=9A=84=E5=86=99=E6=B3=95=EF=BC=9A?= =?UTF-8?q?=E8=A7=A3=E5=86=B3=20"key<>":=20"a"=20=E8=BF=99=E7=A7=8D?= =?UTF-8?q?=E5=8C=85=E5=90=AB=E5=AD=97=E7=AC=A6=E4=B8=B2=E7=9A=84=E6=A0=BC?= =?UTF-8?q?=E5=BC=8F=E6=8A=A5=E9=94=99=20Data=20truncation:=20Invalid=20JS?= =?UTF-8?q?ON=20text=EF=BC=8C=E5=8E=9F=E6=9D=A5=E5=BF=85=E9=A1=BB=E9=87=8C?= =?UTF-8?q?=E9=9D=A2=E5=86=8D=E7=94=A8=20""=20=E5=8C=85=E8=A3=85=E4=B8=80?= =?UTF-8?q?=E6=AC=A1=EF=BC=8CJSON=20=E4=B8=AD=E8=BF=98=E5=BE=97=E8=BD=AC?= =?UTF-8?q?=E4=B9=89=EF=BC=8C=E7=8E=B0=E5=9C=A8=E7=9B=B4=E6=8E=A5=E5=86=99?= =?UTF-8?q?=E5=8D=B3=E5=8F=AF=EF=BC=9B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../apijson/orm/AbstractObjectParser.java | 4 +-- .../java/apijson/orm/AbstractSQLConfig.java | 29 +++++++++++++------ 2 files changed, 22 insertions(+), 11 deletions(-) diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractObjectParser.java b/APIJSONORM/src/main/java/apijson/orm/AbstractObjectParser.java index 2ac9447e6..b40c9ca0b 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractObjectParser.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractObjectParser.java @@ -650,7 +650,7 @@ public SQLConfig newSQLConfig(boolean isProcedure) throws Exception { */ @Override public AbstractObjectParser setSQLConfig() throws Exception { - return setSQLConfig(1, 0, 0); + return setSQLConfig(RequestMethod.isQueryMethod(method) ? 1 : 0, 0, 0); } @Override @@ -668,7 +668,7 @@ public AbstractObjectParser setSQLConfig(int count, int page, int position) thro return this; } } - sqlConfig.setCount(count).setPage(page).setPosition(position); + sqlConfig.setCount(sqlConfig.getCount() <= 0 ? count : sqlConfig.getCount()).setPage(page).setPosition(position); parser.onVerifyRole(sqlConfig); diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java index 169fe6d4e..e6b08a494 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java @@ -1284,11 +1284,11 @@ public String getLimitString() { public static String getLimitString(int page, int count, boolean isTSQL) { int offset = getOffset(page, count); - if (isTSQL) { + if (isTSQL) { // OFFSET FECTH 中所有关键词都不可省略 return " OFFSET " + offset + " ROWS FETCH FIRST " + count + " ROWS ONLY"; } - return " LIMIT " + count + " OFFSET " + offset; + return " LIMIT " + count + (offset <= 0 ? "" : " OFFSET " + offset); // DELETE, UPDATE 不支持 OFFSET } //WHERE <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< @@ -2179,20 +2179,23 @@ public String getContainString(String key, Object[] childs, int type) throws Ill String condition = ""; if (childs != null) { for (int i = 0; i < childs.length; i++) { - if (childs[i] != null) { - if (childs[i] instanceof JSON) { + Object c = childs[i]; + if (c != null) { + if (c instanceof JSON) { throw new IllegalArgumentException(key + "<>:value 中value类型不能为JSON!"); } condition += (i <= 0 ? "" : (Logic.isAnd(type) ? AND : OR)); if (isPostgreSQL()) { - condition += (getKey(key) + " @> " + getValue(newJSONArray(childs[i]))); //operator does not exist: jsonb @> character varying "[" + childs[i] + "]"); + condition += (getKey(key) + " @> " + getValue(newJSONArray(c))); //operator does not exist: jsonb @> character varying "[" + c + "]"); } else if (isOracle()) { - condition += ("json_textcontains(" + getKey(key) + ", '$', " + getValue(childs[i].toString()) + ")"); + condition += ("json_textcontains(" + getKey(key) + ", '$', " + getValue(c.toString()) + ")"); } else { - condition += ("json_contains(" + getKey(key) + ", " + getValue(childs[i].toString()) + ")"); + boolean isNum = c instanceof Number; + String v = (isNum ? "" : "\"") + childs[i] + (isNum ? "" : "\""); + condition += ("json_contains(" + getKey(key) + ", " + getValue(v) + ")"); } } } @@ -2390,9 +2393,9 @@ public static String getSQL(AbstractSQLConfig config) throws Exception { case POST: return "INSERT INTO " + tablePath + config.getColumnString() + " VALUES" + config.getValuesString(); case PUT: - return "UPDATE " + tablePath + config.getSetString() + config.getWhereString(true); + return "UPDATE " + tablePath + config.getSetString() + config.getWhereString(true) + config.getLimitString(); case DELETE: - return "DELETE FROM " + tablePath + config.getWhereString(true); + return "DELETE FROM " + tablePath + config.getWhereString(true) + config.getLimitString(); default: String explain = (config.isExplain() ? (config.isSQLServer() || config.isOracle() ? "SET STATISTICS PROFILE ON " : "EXPLAIN ") : ""); if (config.isTest() && RequestMethod.isGetMethod(config.getMethod(), true)) { @@ -2635,6 +2638,10 @@ public static SQLConfig newSQLConfig(RequestMethod method, String table, String throw new NotExistException(TAG + ": newSQLConfig idIn instanceof List >> 去掉无效 id 后 newIdIn.isEmpty()"); } idIn = newIdIn; + + if (method == DELETE || method == PUT) { + config.setCount(newIdIn.size()); + } } //对id和id{}处理,这两个一定会作为条件 @@ -2670,6 +2677,10 @@ else if (id instanceof Subquery) {} throw new NotExistException(TAG + ": newSQLConfig idIn != null && (((List) idIn).contains(id) == false"); } } + + if (method == DELETE || method == PUT) { + config.setCount(1); + } } From a406242a81f2b303a1c55e6a4f5c3c835e62e53a Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Sun, 31 Jan 2021 19:35:52 +0800 Subject: [PATCH 0268/1181] =?UTF-8?q?=E3=80=90=E6=80=A7=E8=83=BD=E3=80=91?= =?UTF-8?q?=E9=80=9A=E8=BF=87=E7=BC=93=E5=AD=98=E5=8F=8A=E5=A4=8D=E7=94=A8?= =?UTF-8?q?=E6=95=B0=E7=BB=84=E4=B8=BB=E8=A1=A8=20ObjectParser=20=E6=9D=A5?= =?UTF-8?q?=E5=A4=A7=E5=B9=85=E6=8F=90=E5=8D=87=E6=9F=A5=E8=AF=A2=E6=95=B0?= =?UTF-8?q?=E7=BB=84=E7=9A=84=E6=80=A7=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../apijson/orm/AbstractObjectParser.java | 457 ++++++++-------- .../main/java/apijson/orm/AbstractParser.java | 41 +- .../java/apijson/orm/AbstractSQLConfig.java | 501 +++++++++--------- .../main/java/apijson/orm/ObjectParser.java | 8 +- .../src/main/java/apijson/orm/Parser.java | 2 +- 5 files changed, 528 insertions(+), 481 deletions(-) diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractObjectParser.java b/APIJSONORM/src/main/java/apijson/orm/AbstractObjectParser.java index b40c9ca0b..f5a2e079f 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractObjectParser.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractObjectParser.java @@ -61,12 +61,6 @@ public AbstractObjectParser setParser(AbstractParser parser) { protected final List joinList; protected final boolean isTable; protected final boolean isArrayMainTable; - protected final boolean isReuse; - protected final String path; - protected final String name; - protected final String table; - protected final String alias; - protected final boolean tri; /** @@ -80,26 +74,23 @@ public AbstractObjectParser setParser(AbstractParser parser) { * @param name * @throws Exception */ - public AbstractObjectParser(@NotNull JSONObject request, String parentPath, String name, SQLConfig arrayConfig, boolean isSubquery) throws Exception { + public AbstractObjectParser(@NotNull JSONObject request, String parentPath, SQLConfig arrayConfig + , boolean isSubquery, boolean isTable, boolean isArrayMainTable) throws Exception { if (request == null) { throw new IllegalArgumentException(TAG + ".ObjectParser request == null!!!"); } this.request = request; this.parentPath = parentPath; + this.arrayConfig = arrayConfig; this.isSubquery = isSubquery; this.type = arrayConfig == null ? 0 : arrayConfig.getType(); this.joinList = arrayConfig == null ? null : arrayConfig.getJoinList(); - this.name = name; - this.path = AbstractParser.getAbsPath(parentPath, name); - - apijson.orm.Entry entry = Pair.parseEntry(name, true); - this.table = entry.getKey(); - this.alias = entry.getValue(); - this.isTable = apijson.JSONObject.isTableKey(table); - this.isArrayMainTable = isSubquery == false && this.isTable && this.type == SQLConfig.TYPE_ITEM_CHILD_0 && RequestMethod.isGetMethod(method, true); - this.isReuse = isArrayMainTable && arrayConfig != null && arrayConfig.getPosition() > 0; + + this.isTable = isTable; // apijson.JSONObject.isTableKey(table); + this.isArrayMainTable = isArrayMainTable; // isSubquery == false && this.isTable && this.type == SQLConfig.TYPE_ITEM_CHILD_0 && RequestMethod.isGetMethod(method, true); +// this.isReuse = isReuse; // isArrayMainTable && arrayConfig != null && arrayConfig.getPosition() > 0; this.objectCount = 0; this.arrayCount = 0; @@ -117,11 +108,10 @@ public AbstractObjectParser(@NotNull JSONObject request, String parentPath, Stri request.remove(KEY_DROP); } - - Log.d(TAG, "AbstractObjectParser table = " + table + "; isTable = " + isTable); - Log.d(TAG, "AbstractObjectParser isEmpty = " + isEmpty + "; tri = " + tri + "; drop = " + drop); } + + protected int position; public int getPosition() { return position; @@ -148,6 +138,12 @@ public boolean isBreakParse() { return breakParse || isInvalidate(); } + protected String name; + protected String table; + protected String alias; + protected boolean isReuse; + protected String parentName; + protected String path; protected JSONObject response; protected JSONObject sqlRequest; @@ -177,118 +173,135 @@ public boolean isBreakParse() { * @throws Exception */ @Override - public AbstractObjectParser parse() throws Exception { + public AbstractObjectParser parse(String name, boolean isReuse) throws Exception { if (isInvalidate() == false) { + this.isReuse = isReuse; + this.name = name; + this.path = AbstractParser.getAbsPath(parentPath, name); + + apijson.orm.Entry tentry = Pair.parseEntry(name, true); + this.table = tentry.getKey(); + this.alias = tentry.getValue(); + + Log.d(TAG, "AbstractObjectParser parentPath = " + parentPath + "; name = " + name + "; table = " + table + "; alias = " + alias); + Log.d(TAG, "AbstractObjectParser type = " + type + "; isTable = " + isTable + "; isArrayMainTable = " + isArrayMainTable); + Log.d(TAG, "AbstractObjectParser isEmpty = " + request.isEmpty() + "; tri = " + tri + "; drop = " + drop); + breakParse = false; response = new JSONObject(true);//must init + sqlReponse = null;//must init - sqlRequest = new JSONObject(true);//must init + if (isReuse == false) { + sqlRequest = new JSONObject(true);//must init - sqlReponse = null;//must init - customMap = null;//must init - functionMap = null;//must init - childMap = null;//must init - - Set> set = request.isEmpty() ? null : new LinkedHashSet>(request.entrySet()); - if (set != null && set.isEmpty() == false) {//判断换取少几个变量的初始化是否值得? - if (isTable) {//非Table下必须保证原有顺序!否则 count,page 会丢, total@:"/[]/total" 会在[]:{}前执行! - customMap = new LinkedHashMap(); - childMap = new LinkedHashMap(); - } - functionMap = new LinkedHashMap>();//必须执行 - - - //条件<<<<<<<<<<<<<<<<<<< - List whereList = null; - if (method == PUT) { //这里只有PUTArray需要处理 || method == DELETE) { - String[] combine = StringUtil.split(request.getString(KEY_COMBINE)); - if (combine != null) { - String w; - for (int i = 0; i < combine.length; i++) { //去除 &,|,! 前缀 - w = combine[i]; - if (w != null && (w.startsWith("&") || w.startsWith("|") || w.startsWith("!"))) { - combine[i] = w.substring(1); + customMap = null;//must init + functionMap = null;//must init + childMap = null;//must init + + Set> set = request.isEmpty() ? null : new LinkedHashSet>(request.entrySet()); + if (set != null && set.isEmpty() == false) {//判断换取少几个变量的初始化是否值得? + if (isTable) {//非Table下必须保证原有顺序!否则 count,page 会丢, total@:"/[]/total" 会在[]:{}前执行! + customMap = new LinkedHashMap(); + childMap = new LinkedHashMap(); + } + functionMap = new LinkedHashMap>();//必须执行 + + //条件<<<<<<<<<<<<<<<<<<< + List whereList = null; + if (method == PUT) { //这里只有PUTArray需要处理 || method == DELETE) { + String[] combine = StringUtil.split(request.getString(KEY_COMBINE)); + if (combine != null) { + String w; + for (int i = 0; i < combine.length; i++) { //去除 &,|,! 前缀 + w = combine[i]; + if (w != null && (w.startsWith("&") || w.startsWith("|") || w.startsWith("!"))) { + combine[i] = w.substring(1); + } } } + //Arrays.asList()返回值不支持add方法! + whereList = new ArrayList(Arrays.asList(combine != null ? combine : new String[]{})); + whereList.add(apijson.JSONRequest.KEY_ID); + whereList.add(apijson.JSONRequest.KEY_ID_IN); } - //Arrays.asList()返回值不支持add方法! - whereList = new ArrayList(Arrays.asList(combine != null ? combine : new String[]{})); - whereList.add(apijson.JSONRequest.KEY_ID); - whereList.add(apijson.JSONRequest.KEY_ID_IN); - } - //条件>>>>>>>>>>>>>>>>>>> + //条件>>>>>>>>>>>>>>>>>>> - String key; - Object value; - int index = 0; + String key; + Object value; + int index = 0; - for (Entry entry : set) { - if (isBreakParse()) { - break; - } + for (Entry entry : set) { + if (isBreakParse()) { + break; + } - value = entry.getValue(); - if (value == null) { - continue; - } - key = entry.getKey(); + value = entry.getValue(); + if (value == null) { + continue; + } + key = entry.getKey(); - try { - if (key.startsWith("@") || key.endsWith("@")) { - if (onParse(key, value) == false) { - invalidate(); + try { + if (key.startsWith("@") || key.endsWith("@")) { + if (onParse(key, value) == false) { + invalidate(); + } } - } - else if (value instanceof JSONObject) { // JSONObject,往下一级提取 - if (childMap != null) { // 添加到childMap,最后再解析 - childMap.put(key, (JSONObject)value); + else if (value instanceof JSONObject) { // JSONObject,往下一级提取 + if (childMap != null) { // 添加到childMap,最后再解析 + childMap.put(key, (JSONObject)value); + } + else { // 直接解析并替换原来的,[]:{} 内必须直接解析,否则会因为丢掉count等属性,并且total@:"/[]/total"必须在[]:{} 后! + response.put(key, onChildParse(index, key, (JSONObject)value)); + index ++; + } } - else { // 直接解析并替换原来的,[]:{} 内必须直接解析,否则会因为丢掉count等属性,并且total@:"/[]/total"必须在[]:{} 后! - response.put(key, onChildParse(index, key, (JSONObject)value)); - index ++; + else if ((method == POST || method == PUT) && value instanceof JSONArray + && JSONRequest.isTableArray(key)) { // JSONArray,批量新增或修改,往下一级提取 + onTableArrayParse(key, (JSONArray) value); } - } - else if ((method == POST || method == PUT) && value instanceof JSONArray - && JSONRequest.isTableArray(key)) { // JSONArray,批量新增或修改,往下一级提取 - onTableArrayParse(key, (JSONArray) value); - } - else if (method == PUT && value instanceof JSONArray - && (whereList == null || whereList.contains(key) == false)) { // PUT JSONArray - onPUTArrayParse(key, (JSONArray) value); - } - else { // JSONArray或其它Object,直接填充 - if (onParse(key, value) == false) { - invalidate(); + else if (method == PUT && value instanceof JSONArray + && (whereList == null || whereList.contains(key) == false)) { // PUT JSONArray + onPUTArrayParse(key, (JSONArray) value); } + else { // JSONArray或其它Object,直接填充 + if (onParse(key, value) == false) { + invalidate(); + } + } + } catch (Exception e) { + if (tri == false) { + throw e; // 不忽略错误,抛异常 + } + invalidate(); // 忽略错误,还原request } - } catch (Exception e) { - if (tri == false) { - throw e; // 不忽略错误,抛异常 - } - invalidate(); // 忽略错误,还原request } - } - // 非Table内的函数会被滞后在onChildParse后调用! onFunctionResponse("-"); - } - - if (isReuse == false && isTable) { - if (parser.getGlobleDatabase() != null && sqlRequest.get(JSONRequest.KEY_DATABASE) == null) { - sqlRequest.put(JSONRequest.KEY_DATABASE, parser.getGlobleDatabase()); - } - if (parser.getGlobleSchema() != null && sqlRequest.get(JSONRequest.KEY_SCHEMA) == null) { - sqlRequest.put(JSONRequest.KEY_SCHEMA, parser.getGlobleSchema()); } - if (isSubquery == false) { //解决 SQL 语法报错,子查询不能 EXPLAIN - if (parser.getGlobleExplain() != null && sqlRequest.get(JSONRequest.KEY_EXPLAIN) == null) { - sqlRequest.put(JSONRequest.KEY_EXPLAIN, parser.getGlobleExplain()); + + if (isTable) { + if (parser.getGlobleDatabase() != null && sqlRequest.get(JSONRequest.KEY_DATABASE) == null) { + sqlRequest.put(JSONRequest.KEY_DATABASE, parser.getGlobleDatabase()); } - if (parser.getGlobleCache() != null && sqlRequest.get(JSONRequest.KEY_CACHE) == null) { - sqlRequest.put(JSONRequest.KEY_CACHE, parser.getGlobleCache()); + if (parser.getGlobleSchema() != null && sqlRequest.get(JSONRequest.KEY_SCHEMA) == null) { + sqlRequest.put(JSONRequest.KEY_SCHEMA, parser.getGlobleSchema()); + } + if (isSubquery == false) { //解决 SQL 语法报错,子查询不能 EXPLAIN + if (parser.getGlobleExplain() != null && sqlRequest.get(JSONRequest.KEY_EXPLAIN) == null) { + sqlRequest.put(JSONRequest.KEY_EXPLAIN, parser.getGlobleExplain()); + } + if (parser.getGlobleCache() != null && sqlRequest.get(JSONRequest.KEY_CACHE) == null) { + sqlRequest.put(JSONRequest.KEY_CACHE, parser.getGlobleCache()); + } } } } + + if (isTable) { // 非Table内的函数会被滞后在onChildParse后调用 + onFunctionResponse("-"); + } + } if (isInvalidate()) { @@ -311,95 +324,93 @@ else if (method == PUT && value instanceof JSONArray @Override public boolean onParse(@NotNull String key, @NotNull Object value) throws Exception { if (key.endsWith("@")) { // StringUtil.isPath((String) value)) { - String replaceKey = key.substring(0, key.length() - 1); - // [] 内主表 position > 0 时,用来生成 SQLConfig 的键值对全都忽略,不解析 -// if (isReuse == false || replaceKey.endsWith("()") || (replaceKey.startsWith("@") && JSONRequest.TABLE_KEY_LIST.contains(replaceKey) == false)) { - if (value instanceof JSONObject) { // key{}@ getRealKey, SQL 子查询对象,JSONObject -> SQLConfig.getSQL + if (value instanceof JSONObject) { // key{}@ getRealKey, SQL 子查询对象,JSONObject -> SQLConfig.getSQL + String replaceKey = key.substring(0, key.length() - 1); - JSONObject subquery = (JSONObject) value; - String range = subquery.getString(JSONRequest.KEY_SUBQUERY_RANGE); - if (range != null && JSONRequest.SUBQUERY_RANGE_ALL.equals(range) == false && JSONRequest.SUBQUERY_RANGE_ANY.equals(range) == false) { - throw new IllegalArgumentException("子查询 " + path + "/" + key + ":{ range:value } 中 value 只能为 [" + JSONRequest.SUBQUERY_RANGE_ALL + ", " + JSONRequest.SUBQUERY_RANGE_ANY + "] 中的一个!"); - } + JSONObject subquery = (JSONObject) value; + String range = subquery.getString(JSONRequest.KEY_SUBQUERY_RANGE); + if (range != null && JSONRequest.SUBQUERY_RANGE_ALL.equals(range) == false && JSONRequest.SUBQUERY_RANGE_ANY.equals(range) == false) { + throw new IllegalArgumentException("子查询 " + path + "/" + key + ":{ range:value } 中 value 只能为 [" + JSONRequest.SUBQUERY_RANGE_ALL + ", " + JSONRequest.SUBQUERY_RANGE_ANY + "] 中的一个!"); + } - JSONArray arr = parser.onArrayParse(subquery, path, key, true); + JSONArray arr = parser.onArrayParse(subquery, path, key, true); - JSONObject obj = arr == null || arr.isEmpty() ? null : arr.getJSONObject(0); - if (obj == null) { - throw new Exception("服务器内部错误,解析子查询 " + path + "/" + key + ":{ } 为 Subquery 对象失败!"); - } + JSONObject obj = arr == null || arr.isEmpty() ? null : arr.getJSONObject(0); + if (obj == null) { + throw new Exception("服务器内部错误,解析子查询 " + path + "/" + key + ":{ } 为 Subquery 对象失败!"); + } - String from = subquery.getString(JSONRequest.KEY_SUBQUERY_FROM); - JSONObject arrObj = from == null ? null : obj.getJSONObject(from); - if (arrObj == null) { - throw new IllegalArgumentException("子查询 " + path + "/" + key + ":{ from:value } 中 value 对应的主表对象 " + from + ":{} 不存在!"); - } - // - SQLConfig cfg = (SQLConfig) arrObj.get(AbstractParser.KEY_CONFIG); - if (cfg == null) { - throw new NotExistException(TAG + ".onParse cfg == null"); - } + String from = subquery.getString(JSONRequest.KEY_SUBQUERY_FROM); + JSONObject arrObj = from == null ? null : obj.getJSONObject(from); + if (arrObj == null) { + throw new IllegalArgumentException("子查询 " + path + "/" + key + ":{ from:value } 中 value 对应的主表对象 " + from + ":{} 不存在!"); + } + // + SQLConfig cfg = (SQLConfig) arrObj.get(AbstractParser.KEY_CONFIG); + if (cfg == null) { + throw new NotExistException(TAG + ".onParse cfg == null"); + } + + Subquery s = new Subquery(); + s.setPath(path); + s.setOriginKey(key); + s.setOriginValue(subquery); + + s.setFrom(from); + s.setRange(range); + s.setKey(replaceKey); + s.setConfig(cfg); - Subquery s = new Subquery(); - s.setPath(path); - s.setOriginKey(key); - s.setOriginValue(subquery); + key = replaceKey; + value = s; //(range == null || range.isEmpty() ? "" : "range") + "(" + cfg.getSQL(false) + ") "; + + parser.putQueryResult(AbstractParser.getAbsPath(path, key), s); //字符串引用保证不了安全性 parser.getSQL(cfg)); + } + else if (value instanceof String) { // //key{}@ getRealKey, 引用赋值路径 + String replaceKey = key.substring(0, key.length() - 1); - s.setFrom(from); - s.setRange(range); - s.setKey(replaceKey); - s.setConfig(cfg); + // System.out.println("getObject key.endsWith(@) >> parseRelation = " + parseRelation); + String targetPath = AbstractParser.getValuePath(type == TYPE_ITEM ? path : parentPath, new String((String) value)); - key = replaceKey; - value = s; //(range == null || range.isEmpty() ? "" : "range") + "(" + cfg.getSQL(false) + ") "; + //先尝试获取,尽量保留缺省依赖路径,这样就不需要担心路径改变 + Object target = onReferenceParse(targetPath); + Log.i(TAG, "onParse targetPath = " + targetPath + "; target = " + target); - parser.putQueryResult(AbstractParser.getAbsPath(path, key), s); //字符串引用保证不了安全性 parser.getSQL(cfg)); + if (target == null) {//String#equals(null)会出错 + Log.d(TAG, "onParse target == null >> return true;"); + return true; } - else if (value instanceof String) { // //key{}@ getRealKey, 引用赋值路径 - // System.out.println("getObject key.endsWith(@) >> parseRelation = " + parseRelation); - String targetPath = AbstractParser.getValuePath(type == TYPE_ITEM - ? path : parentPath, new String((String) value)); - - //先尝试获取,尽量保留缺省依赖路径,这样就不需要担心路径改变 - Object target = onReferenceParse(targetPath); - Log.i(TAG, "onParse targetPath = " + targetPath + "; target = " + target); - - if (target == null) {//String#equals(null)会出错 - Log.d(TAG, "onParse target == null >> return true;"); - return true; + if (target instanceof Map) { //target可能是从requestObject里取出的 {} + if (isTable || targetPath.endsWith("[]/" + JSONResponse.KEY_INFO) == false) { + Log.d(TAG, "onParse target instanceof Map >> return false;"); + return false; //FIXME 这个判断现在来看是否还有必要?为啥不允许为 JSONObject ?以前可能因为防止二次遍历再解析,现在只有一次遍历 } - if (target instanceof Map) { //target可能是从requestObject里取出的 {} - if (isTable || targetPath.endsWith("[]/" + JSONResponse.KEY_INFO) == false) { - Log.d(TAG, "onParse target instanceof Map >> return false;"); - return false; //FIXME 这个判断现在来看是否还有必要?为啥不允许为 JSONObject ?以前可能因为防止二次遍历再解析,现在只有一次遍历 - } + } + if (targetPath.equals(target)) {//必须valuePath和保证getValueByPath传进去的一致! + Log.d(TAG, "onParse targetPath.equals(target) >>"); + + //非查询关键词 @key 不影响查询,直接跳过 + if (isTable && (key.startsWith("@") == false || JSONRequest.TABLE_KEY_LIST.contains(key))) { + Log.e(TAG, "onParse isTable && (key.startsWith(@) == false" + + " || JSONRequest.TABLE_KEY_LIST.contains(key)) >> return null;"); + return false;//获取不到就不用再做无效的query了。不考虑 Table:{Table:{}}嵌套 + } else { + Log.d(TAG, "onParse isTable(table) == false >> return true;"); + return true;//舍去,对Table无影响 } - if (targetPath.equals(target)) {//必须valuePath和保证getValueByPath传进去的一致! - Log.d(TAG, "onParse targetPath.equals(target) >>"); - - //非查询关键词 @key 不影响查询,直接跳过 - if (isTable && (key.startsWith("@") == false || JSONRequest.TABLE_KEY_LIST.contains(key))) { - Log.e(TAG, "onParse isTable && (key.startsWith(@) == false" - + " || JSONRequest.TABLE_KEY_LIST.contains(key)) >> return null;"); - return false;//获取不到就不用再做无效的query了。不考虑 Table:{Table:{}}嵌套 - } else { - Log.d(TAG, "onParse isTable(table) == false >> return true;"); - return true;//舍去,对Table无影响 - } - } + } - //直接替换原来的key@:path为key:target - Log.i(TAG, "onParse >> key = replaceKey; value = target;"); - key = replaceKey; - value = target; - Log.d(TAG, "onParse key = " + key + "; value = " + value); - } - else { - throw new IllegalArgumentException(path + "/" + key + ":value 中 value 必须为 依赖路径String 或 SQL子查询JSONObject !"); - } -// } + //直接替换原来的key@:path为key:target + Log.i(TAG, "onParse >> key = replaceKey; value = target;"); + key = replaceKey; + value = target; + Log.d(TAG, "onParse key = " + key + "; value = " + value); + } + else { + throw new IllegalArgumentException(path + "/" + key + ":value 中 value 必须为 依赖路径String 或 SQL子查询JSONObject !"); + } } if (key.endsWith("()")) { @@ -410,21 +421,24 @@ else if (value instanceof String) { // //key{}@ getRealKey, 引用赋值路径 String k = key.substring(0, key.length() - 2); String type; //远程函数比较少用,一般一个Table:{}内用到也就一两个,所以这里用 "-","0","+" 更直观,转用 -1,0,1 对性能提升不大。 - if (k.endsWith("-")) { //不能封装到functionMap后批量执行,否则会导致非Table内的 key-():function() 在onChildParse后执行! + boolean isMinus = k.endsWith("-"); + if (isMinus) { //不能封装到functionMap后批量执行,否则会导致非Table内的 key-():function() 在onChildParse后执行! type = "-"; k = k.substring(0, k.length() - 1); - parseFunction(k, (String) value, parentPath, name, request); + if (isTable == false) { + parseFunction(k, (String) value, parentPath, name, request); + } + } + else if (k.endsWith("+")) { + type = "+"; + k = k.substring(0, k.length() - 1); } else { - if (k.endsWith("+")) { - type = "+"; - k = k.substring(0, k.length() - 1); - } - else { - type = "0"; - } + type = "0"; + } + if (isMinus == false || isTable) { //远程函数比较少用,一般一个Table:{}内用到也就一两个,所以这里循环里new出来对性能影响不大。 Map map = functionMap.get(type); if (map == null) { @@ -439,9 +453,7 @@ else if (isTable && key.startsWith("@") && JSONRequest.TABLE_KEY_LIST.contains(k customMap.put(key, value); } else { - // 导致副表从 1 开始都不查了 if (isReuse == false) { sqlRequest.put(key, value); - // } } return true; @@ -590,7 +602,7 @@ public void onTableArrayParse(String key, JSONArray value) throws Exception { String idKey = parser.createSQLConfig().getIdKey(); //Table[]: [{}] arrayConfig 为 null boolean isNeedVerifyContent = parser.isNeedVerifyContent(); - + for (int i = 0; i < valueArray.size(); i++) { //只要有一条失败,则抛出异常,全部失败 //TODO 改成一条多 VALUES 的 SQL 性能更高,报错也更会更好处理,更人性化 JSONObject item; @@ -693,28 +705,27 @@ public AbstractObjectParser executeSQL() throws Exception { sqlReponse = new JSONObject(sqlRequest); } else { - try { - sqlReponse = onSQLExecute(); - - } - catch (NotExistException e) { - // Log.e(TAG, "getObject try { response = getSQLObject(config2); } catch (Exception e) {"); - // if (e instanceof NotExistException) {//非严重异常,有时候只是数据不存在 - // // e.printStackTrace(); - sqlReponse = null;//内部吃掉异常,put到最外层 - // requestObject.put(JSONResponse.KEY_MSG - // , StringUtil.getString(requestObject.get(JSONResponse.KEY_MSG) - // + "; query " + path + " cath NotExistException:" - // + newErrorResult(e).getString(JSONResponse.KEY_MSG))); - // } else { - // throw e; - // } - } + try { + sqlReponse = onSQLExecute(); } - - if (drop) {//丢弃Table,只为了向下提供条件 - sqlReponse = null; + catch (NotExistException e) { + // Log.e(TAG, "getObject try { response = getSQLObject(config2); } catch (Exception e) {"); + // if (e instanceof NotExistException) {//非严重异常,有时候只是数据不存在 + // // e.printStackTrace(); + sqlReponse = null;//内部吃掉异常,put到最外层 + // requestObject.put(JSONResponse.KEY_MSG + // , StringUtil.getString(requestObject.get(JSONResponse.KEY_MSG) + // + "; query " + path + " cath NotExistException:" + // + newErrorResult(e).getString(JSONResponse.KEY_MSG))); + // } else { + // throw e; + // } } + } + + if (drop) {//丢弃Table,只为了向下提供条件 + sqlReponse = null; + } return this; } @@ -799,10 +810,16 @@ public void onChildResponse() throws Exception { if (set != null) { int index = 0; for (Entry entry : set) { - if (entry != null) { - response.put(entry.getKey(), onChildParse(index, entry.getKey(), entry.getValue())); - index ++; + Object child = entry == null ? null : onChildParse(index, entry.getKey(), entry.getValue()); + if (child == null + || (child instanceof JSONObject && ((JSONObject) child).isEmpty()) + || (child instanceof JSONArray && ((JSONArray) child).isEmpty()) + ) { + continue; } + + response.put(entry.getKey(), child ); + index ++; } } } @@ -834,21 +851,21 @@ public JSONObject onSQLExecute() throws Exception { for (int i = 1; i < list.size(); i++) { // 从 1 开始,0 已经处理过 JSONObject obj = parser.parseCorrectResponse(table, list.get(i)); list.set(i, obj); - + if (obj != null) { parser.putQueryResult(arrayPath + "/" + i + "/" + table, obj); //解决获取关联数据时requestObject里不存在需要的关联数据 } } - + parser.putArrayMainCache(arrayPath, list); } } - + if (isSubquery == false && result != null) { parser.putQueryResult(path, result);//解决获取关联数据时requestObject里不存在需要的关联数据 } } - + return result; } diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java b/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java index 1f0880128..55ca32b0c 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java @@ -752,6 +752,8 @@ public JSONObject getStructure(@NotNull String table, String method, String tag, + protected Map arrayObjectParserCacheMap = new HashMap<>(); + // protected SQLConfig itemConfig; /**获取单个对象,该对象处于parentObject内 * @param parentPath parentObject的路径 @@ -774,9 +776,10 @@ public JSONObject onObjectParse(final JSONObject request } int type = arrayConfig == null ? 0 : arrayConfig.getType(); + int position = arrayConfig == null ? 0 : arrayConfig.getPosition(); String[] arr = StringUtil.split(parentPath, "/"); - if (arrayConfig == null || arrayConfig.getPosition() == 0) { + if (position == 0) { int d = arr == null ? 1 : arr.length + 1; if (queryDepth < d) { queryDepth = d; @@ -786,12 +789,24 @@ public JSONObject onObjectParse(final JSONObject request } } } + + boolean isTable = apijson.JSONObject.isTableKey(name); + boolean isArrayMainTable = isSubquery == false && isTable && type == SQLConfig.TYPE_ITEM_CHILD_0 && arrayConfig != null && RequestMethod.isGetMethod(arrayConfig.getMethod(), true); + boolean isReuse = isArrayMainTable && position > 0; - ObjectParser op = createObjectParser(request, parentPath, name, arrayConfig, isSubquery).parse(); - - + ObjectParser op = null; + if (isReuse) { // 数组主表使用专门的缓存数据 + op = arrayObjectParserCacheMap.get(parentPath.substring(0, parentPath.lastIndexOf("[]") + 2)); + } + + if (op == null) { + op = createObjectParser(request, parentPath, arrayConfig, isSubquery, isTable, isArrayMainTable); + } + op = op.parse(name, isReuse); + JSONObject response = null; if (op != null) {//SQL查询结果为空时,functionMap和customMap没有意义 + if (arrayConfig == null) { //Common response = op.setSQLConfig().executeSQL().response(); } @@ -799,9 +814,12 @@ public JSONObject onObjectParse(final JSONObject request int query = arrayConfig.getQuery(); //total 这里不能用arrayConfig.getType(),因为在createObjectParser.onChildParse传到onObjectParse时已被改掉 - if (type == SQLConfig.TYPE_ITEM_CHILD_0 && query != JSONRequest.QUERY_TABLE - && arrayConfig.getPosition() == 0) { + if (type == SQLConfig.TYPE_ITEM_CHILD_0 && query != JSONRequest.QUERY_TABLE && position == 0) { + + RequestMethod method = op.getMethod(); JSONObject rp = op.setMethod(RequestMethod.HEAD).setSQLConfig().executeSQL().getSqlReponse(); + op.setMethod(method); + if (rp != null) { int index = parentPath.lastIndexOf("]/"); if (index >= 0) { @@ -842,14 +860,21 @@ public JSONObject onObjectParse(final JSONObject request response = null;//不再往后查询 } else { response = op - .setSQLConfig(arrayConfig.getCount(), arrayConfig.getPage(), arrayConfig.getPosition()) + .setSQLConfig(arrayConfig.getCount(), arrayConfig.getPage(), position) .executeSQL() .response(); // itemConfig = op.getConfig(); } } - op.recycle(); + if (isArrayMainTable) { + if (position == 0) { // 提取并缓存数组主表的列表数据 + arrayObjectParserCacheMap.put(parentPath.substring(0, parentPath.lastIndexOf("[]") + 2), op); + } + } +// else { +// op.recycle(); +// } op = null; } diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java index e6b08a494..6defdd1e1 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java @@ -84,7 +84,7 @@ public abstract class AbstractSQLConfig implements SQLConfig { // * 和 / 不能同时出现,防止 /* */ 段注释! # 和 -- 不能出现,防止行注释! ; 不能出现,防止隔断SQL语句!空格不能出现,防止 CRUD,DROP,SHOW TABLES等语句! private static final Pattern PATTERN_RANGE; private static final Pattern PATTERN_FUNCTION; - + /** * 表名映射,隐藏真实表名,对安全要求很高的表可以这么做 */ @@ -96,8 +96,8 @@ public abstract class AbstractSQLConfig implements SQLConfig { static { // 凡是 SQL 边界符、分隔符、注释符 都不允许,例如 ' " ` ( ) ; # -- ,以免拼接 SQL 时被注入意外可执行指令 PATTERN_RANGE = Pattern.compile("^[0-9%,!=\\<\\>/\\.\\+\\-\\*\\^]+$"); // ^[a-zA-Z0-9_*%!=<>(),"]+$ 导致 exists(select*from(Comment)) 通过! PATTERN_FUNCTION = Pattern.compile("^[A-Za-z0-9%,:_@&~!=\\<\\>\\|\\[\\]\\{\\} /\\.\\+\\-\\*\\^\\?\\$]+$"); //TODO 改成更好的正则,校验前面为单词,中间为操作符,后面为值 - - + + TABLE_KEY_MAP = new HashMap(); TABLE_KEY_MAP.put(Table.class.getSimpleName(), Table.TABLE_NAME); TABLE_KEY_MAP.put(Column.class.getSimpleName(), Column.TABLE_NAME); @@ -530,13 +530,13 @@ public String getHavingString(boolean hasPrefix) { if (keys == null || keys.length <= 0) { return StringUtil.isEmpty(joinHaving, true) ? "" : (hasPrefix ? " HAVING " : "") + joinHaving; } - + String quote = getQuote(); String tableAlias = getAliasWithQuote(); List raw = getRaw(); boolean containRaw = raw != null && raw.contains(KEY_HAVING); - + String expression; String method; //暂时不允许 String prefix; @@ -560,7 +560,7 @@ public String getHavingString(boolean hasPrefix) { + "} catch (Exception e) = " + e.getMessage()); } } - + if (expression.length() > 50) { throw new UnsupportedOperationException("@having:value 的 value 中字符串 " + expression + " 不合法!" + "不允许传超过 50 个字符的函数或表达式!请用 @raw 简化传参!"); @@ -958,13 +958,13 @@ public String getColumnString(boolean inSQLJoin) throws Exception { method = expression.substring(0, start); boolean distinct = i <= 0 && method.startsWith(PREFFIX_DISTINCT); String fun = distinct ? method.substring(PREFFIX_DISTINCT.length()) : method; - + if (fun.isEmpty() == false && StringUtil.isName(fun) == false) { throw new IllegalArgumentException("字符 " + method + " 不合法!" + "预编译模式下 @column:\"column0,column1:alias;function0(arg0,arg1,...);function1(...):alias...\"" + " 中SQL函数名 function 必须符合正则表达式 ^[0-9a-zA-Z_]+$ !"); } - + } boolean isColumn = start < 0; @@ -1049,13 +1049,13 @@ else if (StringUtil.isName(origin)) { int index = suffix.lastIndexOf(":"); String alias = index < 0 ? "" : suffix.substring(index + 1); //contactCount suffix = index < 0 ? suffix : suffix.substring(0, index); - + if (alias.isEmpty() == false && StringUtil.isName(alias) == false) { throw new IllegalArgumentException("字符串 " + alias + " 不合法!" + "预编译模式下 @column:value 中 value里面用 ; 分割的每一项" + " function(arg0,arg1,...):alias 中 alias 必须是1个单词!并且不要有多余的空格!"); } - + if (suffix.isEmpty() == false && (((String) suffix).contains("--") || ((String) suffix).contains("/*") || PATTERN_RANGE.matcher((String) suffix).matches() == false)) { throw new UnsupportedOperationException("字符串 " + suffix + " 不合法!" + "预编译模式下 @column:\"column?value;function(arg0,arg1,...)?value...\"" @@ -2393,9 +2393,9 @@ public static String getSQL(AbstractSQLConfig config) throws Exception { case POST: return "INSERT INTO " + tablePath + config.getColumnString() + " VALUES" + config.getValuesString(); case PUT: - return "UPDATE " + tablePath + config.getSetString() + config.getWhereString(true) + config.getLimitString(); + return "UPDATE " + tablePath + config.getSetString() + config.getWhereString(true) + (config.isMySQL() ? config.getLimitString() : ""); case DELETE: - return "DELETE FROM " + tablePath + config.getWhereString(true) + config.getLimitString(); + return "DELETE FROM " + tablePath + config.getWhereString(true) + (config.isMySQL() ? config.getLimitString() : ""); // PostgreSQL 不允许 LIMIT default: String explain = (config.isExplain() ? (config.isSQLServer() || config.isOracle() ? "SET STATISTICS PROFILE ON " : "EXPLAIN ") : ""); if (config.isTest() && RequestMethod.isGetMethod(config.getMethod(), true)) { @@ -2615,15 +2615,9 @@ public static SQLConfig newSQLConfig(RequestMethod method, String table, String String userIdKey = callback.getUserIdKey(database, schema, table); String userIdInKey = userIdKey + "{}"; - Object idIn = request.get(idInKey); //可能是 id{}:">0" - - if (method == POST && request.get(idKey) == null) { - Object newId = callback.newId(method, database, schema, table); // null 表示数据库自增 id - if (newId != null) { - request.put(idKey, newId); - } - } + //对id和id{}处理,这两个一定会作为条件 + Object idIn = request.get(idInKey); //可能是 id{}:">0" if (idIn instanceof List) { // 排除掉 0, 负数, 空字符串 等无效 id 值 List ids = ((List) idIn); List newIdIn = new ArrayList<>(); @@ -2638,14 +2632,18 @@ public static SQLConfig newSQLConfig(RequestMethod method, String table, String throw new NotExistException(TAG + ": newSQLConfig idIn instanceof List >> 去掉无效 id 后 newIdIn.isEmpty()"); } idIn = newIdIn; - + if (method == DELETE || method == PUT) { config.setCount(newIdIn.size()); } } - - //对id和id{}处理,这两个一定会作为条件 + Object id = request.get(idKey); + boolean hasId = id != null; + if (method == POST && hasId == false) { + id = callback.newId(method, database, schema, table); // null 表示数据库自增 id + } + if (id != null) { //null无效 if (id instanceof Number) { if (((Number) id).longValue() <= 0) { //一定没有值 @@ -2677,7 +2675,7 @@ else if (id instanceof Subquery) {} throw new NotExistException(TAG + ": newSQLConfig idIn != null && (((List) idIn).contains(id) == false"); } } - + if (method == DELETE || method == PUT) { config.setCount(1); } @@ -2695,271 +2693,276 @@ else if (id instanceof Subquery) {} String raw = request.getString(KEY_RAW); String json = request.getString(KEY_JSON); - //强制作为条件且放在最前面优化性能 - request.remove(idKey); - request.remove(idInKey); - //关键词 - request.remove(KEY_ROLE); - request.remove(KEY_EXPLAIN); - request.remove(KEY_CACHE); - request.remove(KEY_DATABASE); - request.remove(KEY_SCHEMA); - request.remove(KEY_COMBINE); - request.remove(KEY_FROM); - request.remove(KEY_COLUMN); - request.remove(KEY_GROUP); - request.remove(KEY_HAVING); - request.remove(KEY_ORDER); - request.remove(KEY_RAW); - request.remove(KEY_JSON); - - String[] rawArr = StringUtil.split(raw); - config.setRaw(rawArr == null || rawArr.length <= 0 ? null : new ArrayList<>(Arrays.asList(rawArr))); - - Map tableWhere = new LinkedHashMap();//保证顺序好优化 WHERE id > 1 AND name LIKE... - - //已经remove了id和id{},以及@key - Set set = request.keySet(); //前面已经判断request是否为空 - if (method == POST) { //POST操作 - if (idIn != null) { - throw new IllegalArgumentException("POST 请求中不允许传 " + idInKey + " !"); - } - - if (set != null && set.isEmpty() == false) { //不能直接return,要走完下面的流程 - String[] columns = set.toArray(new String[]{}); - - Collection valueCollection = request.values(); - Object[] values = valueCollection == null ? null : valueCollection.toArray(); - - if (values == null || values.length != columns.length) { - throw new Exception("服务器内部错误:\n" + TAG - + " newSQLConfig values == null || values.length != columns.length !"); + try { + //强制作为条件且放在最前面优化性能 + request.remove(idKey); + request.remove(idInKey); + //关键词 + request.remove(KEY_ROLE); + request.remove(KEY_EXPLAIN); + request.remove(KEY_CACHE); + request.remove(KEY_DATABASE); + request.remove(KEY_SCHEMA); + request.remove(KEY_COMBINE); + request.remove(KEY_FROM); + request.remove(KEY_COLUMN); + request.remove(KEY_GROUP); + request.remove(KEY_HAVING); + request.remove(KEY_ORDER); + request.remove(KEY_RAW); + request.remove(KEY_JSON); + + String[] rawArr = StringUtil.split(raw); + config.setRaw(rawArr == null || rawArr.length <= 0 ? null : new ArrayList<>(Arrays.asList(rawArr))); + + Map tableWhere = new LinkedHashMap();//保证顺序好优化 WHERE id > 1 AND name LIKE... + + //已经remove了id和id{},以及@key + Set set = request.keySet(); //前面已经判断request是否为空 + if (method == POST) { //POST操作 + if (idIn != null) { + throw new IllegalArgumentException("POST 请求中不允许传 " + idInKey + " !"); } - column = (id == null ? "" : idKey + ",") + StringUtil.getString(columns); //set已经判断过不为空 - List> valuess = new ArrayList<>(1); - List items; //(item0, item1, ...) - if (id == null) { //数据库自增 id - items = Arrays.asList(values); //FIXME 是否还需要进行 add 或 remove 操作?Arrays.ArrayList 不允许修改,会抛异常 - } - else { - int size = columns.length + (id == null ? 0 : 1); //以key数量为准 + if (set != null && set.isEmpty() == false) { //不能直接return,要走完下面的流程 + String[] columns = set.toArray(new String[]{}); - items = new ArrayList<>(size); - items.add(id); //idList.get(i)); //第0个就是id + Collection valueCollection = request.values(); + Object[] values = valueCollection == null ? null : valueCollection.toArray(); - for (int j = 1; j < size; j++) { - items.add(values[j-1]); //从第1个开始,允许"null" + if (values == null || values.length != columns.length) { + throw new Exception("服务器内部错误:\n" + TAG + + " newSQLConfig values == null || values.length != columns.length !"); } - } + column = (id == null ? "" : idKey + ",") + StringUtil.getString(columns); //set已经判断过不为空 - valuess.add(items); - config.setValues(valuess); - } - } - else { //非POST操作 - final boolean isWhere = method != PUT;//除了POST,PUT,其它全是条件!!! - - //条件<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< - List whereList = null; + List> valuess = new ArrayList<>(1); + List items; //(item0, item1, ...) + if (id == null) { //数据库自增 id + items = Arrays.asList(values); //FIXME 是否还需要进行 add 或 remove 操作?Arrays.ArrayList 不允许修改,会抛异常 + } + else { + int size = columns.length + (id == null ? 0 : 1); //以key数量为准 - Map> combineMap = new LinkedHashMap<>(); - List andList = new ArrayList<>(); - List orList = new ArrayList<>(); - List notList = new ArrayList<>(); + items = new ArrayList<>(size); + items.add(id); //idList.get(i)); //第0个就是id - //强制作为条件且放在最前面优化性能 - if (id != null) { - tableWhere.put(idKey, id); - andList.add(idKey); - } - if (idIn != null) { - tableWhere.put(idInKey, idIn); - andList.add(idInKey); - } + for (int j = 1; j < size; j++) { + items.add(values[j-1]); //从第1个开始,允许"null" + } + } - String[] ws = StringUtil.split(combine); - if (ws != null) { - if (method == DELETE || method == GETS || method == HEADS) { - throw new IllegalArgumentException("DELETE,GETS,HEADS 请求不允许传 @combine:value !"); + valuess.add(items); + config.setValues(valuess); } - whereList = new ArrayList<>(); - - String w; - for (int i = 0; i < ws.length; i++) { //去除 &,|,! 前缀 - w = ws[i]; - if (w != null) { - if (w.startsWith("&")) { - w = w.substring(1); - andList.add(w); - } - else if (w.startsWith("|")) { - if (method == PUT) { - throw new IllegalArgumentException(table + ":{} 里的 @combine:value 中的value里条件 " + ws[i] + " 不合法!" - + "PUT请求的 @combine:\"key0,key1,...\" 不允许传 |key 或 !key !"); + } + else { //非POST操作 + final boolean isWhere = method != PUT;//除了POST,PUT,其它全是条件!!! + + //条件<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< + List whereList = null; + + Map> combineMap = new LinkedHashMap<>(); + List andList = new ArrayList<>(); + List orList = new ArrayList<>(); + List notList = new ArrayList<>(); + + //强制作为条件且放在最前面优化性能 + if (id != null) { + tableWhere.put(idKey, id); + andList.add(idKey); + } + if (idIn != null) { + tableWhere.put(idInKey, idIn); + andList.add(idInKey); + } + + String[] ws = StringUtil.split(combine); + if (ws != null) { + if (method == DELETE || method == GETS || method == HEADS) { + throw new IllegalArgumentException("DELETE,GETS,HEADS 请求不允许传 @combine:value !"); + } + whereList = new ArrayList<>(); + + String w; + for (int i = 0; i < ws.length; i++) { //去除 &,|,! 前缀 + w = ws[i]; + if (w != null) { + if (w.startsWith("&")) { + w = w.substring(1); + andList.add(w); } - w = w.substring(1); - orList.add(w); - } - else if (w.startsWith("!")) { - if (method == PUT) { - throw new IllegalArgumentException(table + ":{} 里的 @combine:value 中的value里条件 " + ws[i] + " 不合法!" - + "PUT请求的 @combine:\"key0,key1,...\" 不允许传 |key 或 !key !"); + else if (w.startsWith("|")) { + if (method == PUT) { + throw new IllegalArgumentException(table + ":{} 里的 @combine:value 中的value里条件 " + ws[i] + " 不合法!" + + "PUT请求的 @combine:\"key0,key1,...\" 不允许传 |key 或 !key !"); + } + w = w.substring(1); + orList.add(w); + } + else if (w.startsWith("!")) { + if (method == PUT) { + throw new IllegalArgumentException(table + ":{} 里的 @combine:value 中的value里条件 " + ws[i] + " 不合法!" + + "PUT请求的 @combine:\"key0,key1,...\" 不允许传 |key 或 !key !"); + } + w = w.substring(1); + notList.add(w); + } + else { + orList.add(w); } - w = w.substring(1); - notList.add(w); - } - else { - orList.add(w); - } - if (w.isEmpty()) { - throw new IllegalArgumentException(table + ":{} 里的 @combine:value 中的value里条件 " + ws[i] + " 不合法!不允许为空值!"); - } - else { - if (idKey.equals(w) || idInKey.equals(w) || userIdKey.equals(w) || userIdInKey.equals(w)) { - throw new UnsupportedOperationException(table + ":{} 里的 @combine:value 中的value里 " + ws[i] + " 不合法!" - + "不允许传 [" + idKey + ", " + idInKey + ", " + userIdKey + ", " + userIdInKey + "] 其中任何一个!"); + if (w.isEmpty()) { + throw new IllegalArgumentException(table + ":{} 里的 @combine:value 中的value里条件 " + ws[i] + " 不合法!不允许为空值!"); + } + else { + if (idKey.equals(w) || idInKey.equals(w) || userIdKey.equals(w) || userIdInKey.equals(w)) { + throw new UnsupportedOperationException(table + ":{} 里的 @combine:value 中的value里 " + ws[i] + " 不合法!" + + "不允许传 [" + idKey + ", " + idInKey + ", " + userIdKey + ", " + userIdInKey + "] 其中任何一个!"); + } } + + whereList.add(w); } - whereList.add(w); + // 可重写回调方法自定义处理 // 动态设置的场景似乎很少,而且去掉后不方便用户排错!//去掉判断,有时候不在没关系,如果是对增删改等非开放请求强制要求传对应的条件,可以用 Operation.NECESSARY + if (request.containsKey(w) == false) { //和 request.get(w) == null 没区别,前面 Parser 已经过滤了 null + // throw new IllegalArgumentException(table + ":{} 里的 @combine:value 中的value里 " + ws[i] + " 对应的 " + w + " 不在它里面!"); + callback.onMissingKey4Combine(table, request, combine, ws[i], w); + } } - // 可重写回调方法自定义处理 // 动态设置的场景似乎很少,而且去掉后不方便用户排错!//去掉判断,有时候不在没关系,如果是对增删改等非开放请求强制要求传对应的条件,可以用 Operation.NECESSARY - if (request.containsKey(w) == false) { //和 request.get(w) == null 没区别,前面 Parser 已经过滤了 null - // throw new IllegalArgumentException(table + ":{} 里的 @combine:value 中的value里 " + ws[i] + " 对应的 " + w + " 不在它里面!"); - callback.onMissingKey4Combine(table, request, combine, ws[i], w); - } } - } + //条件>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> - //条件>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> + Map tableContent = new LinkedHashMap(); + Object value; + for (String key : set) { + value = request.get(key); - Map tableContent = new LinkedHashMap(); - Object value; - for (String key : set) { - value = request.get(key); - - if (value instanceof Map) {//只允许常规Object - throw new IllegalArgumentException("不允许 " + key + " 等任何key的value类型为 {JSONObject} !"); - } + if (value instanceof Map) {//只允许常规Object + throw new IllegalArgumentException("不允许 " + key + " 等任何key的value类型为 {JSONObject} !"); + } - //解决AccessVerifier新增userId没有作为条件,而是作为内容,导致PUT,DELETE出错 - if (isWhere) { - tableWhere.put(key, value); - if (whereList == null || whereList.contains(key) == false) { - andList.add(key); + //解决AccessVerifier新增userId没有作为条件,而是作为内容,导致PUT,DELETE出错 + if (isWhere) { + tableWhere.put(key, value); + if (whereList == null || whereList.contains(key) == false) { + andList.add(key); + } + } + else if (whereList != null && whereList.contains(key)) { + tableWhere.put(key, value); + } + else { + tableContent.put(key, value);//一样 instanceof JSONArray ? JSON.toJSONString(value) : value); } } - else if (whereList != null && whereList.contains(key)) { - tableWhere.put(key, value); - } - else { - tableContent.put(key, value);//一样 instanceof JSONArray ? JSON.toJSONString(value) : value); - } - } - combineMap.put("&", andList); - combineMap.put("|", orList); - combineMap.put("!", notList); - config.setCombine(combineMap); + combineMap.put("&", andList); + combineMap.put("|", orList); + combineMap.put("!", notList); + config.setCombine(combineMap); - config.setContent(tableContent); - } + config.setContent(tableContent); + } - List cs = new ArrayList<>(); + List cs = new ArrayList<>(); - List rawList = config.getRaw(); - boolean containColumnRaw = rawList != null && rawList.contains(KEY_COLUMN); + List rawList = config.getRaw(); + boolean containColumnRaw = rawList != null && rawList.contains(KEY_COLUMN); - String rawColumnSQL = null; - if (containColumnRaw) { - try { - rawColumnSQL = config.getRawSQL(KEY_COLUMN, column); - if (rawColumnSQL != null) { - cs.add(rawColumnSQL); + String rawColumnSQL = null; + if (containColumnRaw) { + try { + rawColumnSQL = config.getRawSQL(KEY_COLUMN, column); + if (rawColumnSQL != null) { + cs.add(rawColumnSQL); + } + } catch (Exception e) { + Log.e(TAG, "newSQLConfig config instanceof AbstractSQLConfig >> try { " + + " rawColumnSQL = ((AbstractSQLConfig) config).getRawSQL(KEY_COLUMN, column); " + + "} catch (Exception e) = " + e.getMessage()); } - } catch (Exception e) { - Log.e(TAG, "newSQLConfig config instanceof AbstractSQLConfig >> try { " - + " rawColumnSQL = ((AbstractSQLConfig) config).getRawSQL(KEY_COLUMN, column); " - + "} catch (Exception e) = " + e.getMessage()); - } - } - - boolean distinct = column == null || rawColumnSQL != null ? false : column.startsWith(PREFFIX_DISTINCT); - if (rawColumnSQL == null) { - String[] fks = StringUtil.split(distinct ? column.substring(PREFFIX_DISTINCT.length()) : column, ";"); // key0,key1;fun0(key0,...);fun1(key0,...);key3;fun2(key0,...) - if (fks != null) { - String[] ks; - for (String fk : fks) { - if (containColumnRaw) { - try { - String rawSQL = config.getRawSQL(KEY_COLUMN, fk); - if (rawSQL != null) { - cs.add(rawSQL); - continue; + } + + boolean distinct = column == null || rawColumnSQL != null ? false : column.startsWith(PREFFIX_DISTINCT); + if (rawColumnSQL == null) { + String[] fks = StringUtil.split(distinct ? column.substring(PREFFIX_DISTINCT.length()) : column, ";"); // key0,key1;fun0(key0,...);fun1(key0,...);key3;fun2(key0,...) + if (fks != null) { + String[] ks; + for (String fk : fks) { + if (containColumnRaw) { + try { + String rawSQL = config.getRawSQL(KEY_COLUMN, fk); + if (rawSQL != null) { + cs.add(rawSQL); + continue; + } + } catch (Exception e) { + Log.e(TAG, "newSQLConfig rawColumnSQL == null >> try { " + + " String rawSQL = ((AbstractSQLConfig) config).getRawSQL(KEY_COLUMN, fk); ... " + + "} catch (Exception e) = " + e.getMessage()); } - } catch (Exception e) { - Log.e(TAG, "newSQLConfig rawColumnSQL == null >> try { " - + " String rawSQL = ((AbstractSQLConfig) config).getRawSQL(KEY_COLUMN, fk); ... " - + "} catch (Exception e) = " + e.getMessage()); } - } - if (fk.contains("(")) { // fun0(key0,...) - cs.add(fk); - } - else { //key0,key1... - ks = StringUtil.split(fk); - if (ks != null && ks.length > 0) { - cs.addAll(Arrays.asList(ks)); + if (fk.contains("(")) { // fun0(key0,...) + cs.add(fk); + } + else { //key0,key1... + ks = StringUtil.split(fk); + if (ks != null && ks.length > 0) { + cs.addAll(Arrays.asList(ks)); + } } } } } - } - config.setExplain(explain); - config.setCache(getCache(cache)); - config.setFrom(from); - config.setDistinct(distinct); - config.setColumn(column == null ? null : cs); //解决总是 config.column != null,总是不能得到 * - config.setWhere(tableWhere); - - config.setId(id); - //在 tableWhere 第0个 config.setIdIn(idIn); - - config.setRole(RequestRole.get(role)); - config.setGroup(group); - config.setHaving(having); - config.setOrder(order); - - String[] jsonArr = StringUtil.split(json); - config.setJson(jsonArr == null || jsonArr.length <= 0 ? null : new ArrayList<>(Arrays.asList(jsonArr))); - - //TODO 解析JOIN,包括 @column,@group 等要合并 - - //后面还可能用到,要还原 - //id或id{}条件 - request.put(idKey, id); - request.put(idInKey, idIn); - //关键词 - request.put(KEY_DATABASE, database); - request.put(KEY_ROLE, role); - request.put(KEY_EXPLAIN, explain); - request.put(KEY_CACHE, cache); - request.put(KEY_SCHEMA, schema); - request.put(KEY_COMBINE, combine); - request.put(KEY_FROM, from); - request.put(KEY_COLUMN, column); - request.put(KEY_GROUP, group); - request.put(KEY_HAVING, having); - request.put(KEY_ORDER, order); - request.put(KEY_RAW, raw); - request.put(KEY_JSON, json); + config.setExplain(explain); + config.setCache(getCache(cache)); + config.setFrom(from); + config.setDistinct(distinct); + config.setColumn(column == null ? null : cs); //解决总是 config.column != null,总是不能得到 * + config.setWhere(tableWhere); + + config.setId(id); + //在 tableWhere 第0个 config.setIdIn(idIn); + + config.setRole(RequestRole.get(role)); + config.setGroup(group); + config.setHaving(having); + config.setOrder(order); + + String[] jsonArr = StringUtil.split(json); + config.setJson(jsonArr == null || jsonArr.length <= 0 ? null : new ArrayList<>(Arrays.asList(jsonArr))); + + //TODO 解析JOIN,包括 @column,@group 等要合并 + + } + finally {//后面还可能用到,要还原 + //id或id{}条件 + if (hasId) { + request.put(idKey, id); + } + request.put(idInKey, idIn); + //关键词 + request.put(KEY_DATABASE, database); + request.put(KEY_ROLE, role); + request.put(KEY_EXPLAIN, explain); + request.put(KEY_CACHE, cache); + request.put(KEY_SCHEMA, schema); + request.put(KEY_COMBINE, combine); + request.put(KEY_FROM, from); + request.put(KEY_COLUMN, column); + request.put(KEY_GROUP, group); + request.put(KEY_HAVING, having); + request.put(KEY_ORDER, order); + request.put(KEY_RAW, raw); + request.put(KEY_JSON, json); + } return config; } diff --git a/APIJSONORM/src/main/java/apijson/orm/ObjectParser.java b/APIJSONORM/src/main/java/apijson/orm/ObjectParser.java index a43a2d879..452ced293 100755 --- a/APIJSONORM/src/main/java/apijson/orm/ObjectParser.java +++ b/APIJSONORM/src/main/java/apijson/orm/ObjectParser.java @@ -20,14 +20,15 @@ */ public interface ObjectParser { - /**解析成员 * response重新赋值 - * @param config 传递给第0个Table + * @param parentPath + * @param name + * @param isReuse * @return null or this * @throws Exception */ - ObjectParser parse() throws Exception; + ObjectParser parse(String name, boolean isReuse) throws Exception; /**调用 parser 的 sqlExecutor 来解析结果 * @param method @@ -159,4 +160,5 @@ public interface ObjectParser { Map> getFunctionMap(); Map getChildMap(); + } diff --git a/APIJSONORM/src/main/java/apijson/orm/Parser.java b/APIJSONORM/src/main/java/apijson/orm/Parser.java index 4e09c5738..dee098b29 100755 --- a/APIJSONORM/src/main/java/apijson/orm/Parser.java +++ b/APIJSONORM/src/main/java/apijson/orm/Parser.java @@ -93,7 +93,7 @@ JSONObject parseCorrectRequest(RequestMethod method, String tag, int version, St */ Object onFunctionParse(String key, String function, String parentPath, String currentName, JSONObject currentObject) throws Exception; - ObjectParser createObjectParser(JSONObject request, String parentPath, String name, SQLConfig arrayConfig, boolean isSubquery) throws Exception; + ObjectParser createObjectParser(JSONObject request, String parentPath, SQLConfig arrayConfig, boolean isSubquery, boolean isTable, boolean isArrayMainTable) throws Exception; int getDefaultQueryCount(); int getMaxQueryPage(); From 9cd448713a97163159f21bbd712f214c6420d254 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Mon, 1 Feb 2021 00:03:37 +0800 Subject: [PATCH 0269/1181] =?UTF-8?q?=E5=8D=87=E7=BA=A7=E7=89=88=E6=9C=AC?= =?UTF-8?q?=E5=8F=B7=E4=B8=BA=204.6.0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- APIJSONORM/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/APIJSONORM/pom.xml b/APIJSONORM/pom.xml index 6802c6753..2ae05b340 100755 --- a/APIJSONORM/pom.xml +++ b/APIJSONORM/pom.xml @@ -5,7 +5,7 @@ apijson.orm apijson-orm - 4.5.1 + 4.6.0 jar APIJSONORM From 68f847e81f4987fe975e305c063e3bf6d088cade Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Tue, 2 Feb 2021 16:12:55 +0800 Subject: [PATCH 0270/1181] Update Document.md --- Document.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Document.md b/Document.md index f21c8153a..f80e36c6a 100644 --- a/Document.md +++ b/Document.md @@ -370,7 +370,7 @@ DELETE:
删除数据 | base_url/delete/ | {
   TableName:{< 减少 或 去除 | "key-":Object,与"key+"相反 | "balance-":100.00,对应SQL是`balance = balance - 100.00`,余额减少100.00,即花费了100元 比较运算 | >, <, >=, <= 比较运算符,用于
① 提供 "id{}":"<=90000" 这种条件范围的简化写法

② 实现子查询相关比较运算

不支持 "key=":Object 和 "key!=":Object 这两种写法,直接用更简单的 "key":Object 和 "key!":Object 替代。 | ① ["id<=":90000](http://apijson.cn:8080/get/{"[]":{"User":{"id<=":90000}}}),对应SQL是`id<=90000`,查询符合id<=90000的一个User数组

② ["id>@":{
   "from":"Comment",
   "Comment":{
      "@column":"min(userId)"
   }
}](http://apijson.cn:8080/get/{"User":{"id>@":{"from":"Comment","Comment":{"@column":"min(userId)"}}}})
WHERE id>(SELECT min(userId) FROM Comment) 逻辑运算 | &, \|, ! 逻辑运算符,对应数据库 SQL 中的 AND, OR, NOT。
横或纵与:同一键值对的值内条件默认 \| 或连接,可以在 key 后加逻辑运算符来具体指定;不同键值对的条件默认 & 与连接,可以用下面说明的对象关键词 @combine 来具体指定。

① & 可用于"key&{}":"条件"等

② \| 可用于"key\|{}":"条件", "key\|{}":[]等,一般可省略

③ ! 可单独使用,如"key!":Object,也可像&,\|一样配合其他功能符使用
"key!":null 无效,null 值会导致整个键值对被忽略解析,可以用 "key{}":"!=null" 替代,
"key":null 同理,用 "key{}":"=null" 替代。 | ① ["id&{}":">80000,<=90000"](http://apijson.cn:8080/head/{"User":{"id&{}":">80000,<=90000"}}),对应SQL是`id>80000 AND id<=90000`,即id满足id>80000 & id<=90000

② ["id\|{}":">90000,<=80000"](http://apijson.cn:8080/head/{"User":{"id\|{}":">90000,<=80000"}}),同"id{}":">90000,<=80000",对应SQL是`id>80000 OR id<=90000`,即id满足id>90000 \| id<=80000

③ ["id!{}":[82001,38710]](http://apijson.cn:8080/head/{"User":{"id!{}":[82001,38710]}}),对应SQL是`id NOT IN(82001,38710)`,即id满足 ! (id=82001 \| id=38710),可过滤黑名单的消息 - 数组关键词,可自定义 | "key":Object,key为 "[]":{} 中{}内的关键词,Object的类型由key指定

① "count":Integer,查询数量,0 表示最大值,默认最大值为100

② "page":Integer,查询页码,从0开始,默认最大值为100,一般和count一起用

③ "query":Integer,查询内容
0-对象,1-总数和分页详情,2-以上全部
总数关键词为 total,分页详情关键词为 info,
它们都和 query 同级,通过引用赋值得到,例如
"total@":"/[]/total", "info@":"/[]/info"
这里query及total仅为GET类型的请求提供方便,
一般可直接用HEAD类型的请求获取总数

④ "join":"&/Table0/key0@,\多表连接方式:
"\<" - LEFT JOIN
">" - RIGHT JOIN
"&" - INNER JOIN
"\|" - FULL JOIN
"!" - OUTTER JOIN
"@" - APP JOIN
其中 @ APP JOIN 为应用层连表,会从已查出的主表里取得所有副表 key@ 关联的主表内的 refKey 作为一个数组 refKeys: [value0, value1...],然后把原来副表 count 次查询 key=$refKey 的 SQL 用 key IN($refKeys) 的方式合并为一条 SQL 来优化性能;
其它 JOIN 都是 SQL JOIN,具体功能和 MySQL,PostgreSQL 等数据库的 JOIN 一一对应
`"join":"`"MainTable":{},`
`"ViceTable":{"key@":"/MainTable/refKey"}`
会对应生成
`MainTable LEFT JOIN ViceTable`
`ON ViceTable.key=MainTable.refKey`

⑤ "otherKey":Object,自定义关键词,名称和以上系统关键词不一样,且原样返回上传的值 | ① 查询User数组,最多5个:
["count":5](http://apijson.cn:8080/get/{"[]":{"count":5,"User":{}}})
对应SQL是`LIMIT 5`

② 查询第3页的User数组,每页5个:
["count":5,
"page":3](http://apijson.cn:8080/get/{"[]":{"count":5,"page":3,"User":{}}})
对应SQL是`LIMIT 5 OFFSET 15`

③ 查询User数组和对应的User总数:
["[]":{
   "query":2,
   "User":{}
},
"total@":"/[]/total",
"info@":"/[]/info"](http://apijson.cn:8080/get/{"[]":{"query":2,"count":5,"User":{}},"total@":"%252F[]%252Ftotal","info@":"%252F[]%252Finfo"})
返回的数据中,总数及分页详情结构为:
"total":139, //总数
"info":{ //分页详情
   "total":139, //总数
   "count":5, //每页数量
   "page":0, //当前页码
   "max":27, //最大页码
   "more":true, //是否还有更多
   "first":true, //是否为首页
   "last":false //是否为尾页
}

④ Moment INNER JOIN User LEFT JOIN Comment:
["[]":{
   "join":"&/User/id@,\    "Moment":{},
   "User":{
     "name~":"t",
     "id@":"/Moment/userId"
   },
   "Comment":{
     "momentId@":"/Moment/id"
   }
}](http://apijson.cn:8080/get/{"[]":{"count":5,"join":"&%252FUser%252Fid@,\<%252FComment%252FmomentId@","Moment":{"@column":"id,userId,content"},"User":{"name~":"t","id@":"%252FMoment%252FuserId","@column":"id,name,head"},"Comment":{"momentId@":"%252FMoment%252Fid","@column":"id,momentId,content"}}})

⑤ 每一层都加当前用户名:
["User":{},
"[]":{
   "name@":"User/name", //自定义关键词
   "Moment":{}
}](http://apijson.cn:8080/get/{"User":{},"[]":{"name@":"User%252Fname","Moment":{}}}) + 数组关键词,可自定义 | "key":Object,key为 "[]":{} 中{}内的关键词,Object的类型由key指定

① "count":Integer,查询数量,0 表示最大值,默认最大值为100

② "page":Integer,查询页码,从0开始,默认最大值为100,一般和count一起用

③ "query":Integer,查询内容
0-对象,1-总数和分页详情,2-以上全部
总数关键词为 total,分页详情关键词为 info,
它们都和 query 同级,通过引用赋值得到,例如
"total@":"/[]/total", "info@":"/[]/info"
这里query及total仅为GET类型的请求提供方便,
一般可直接用HEAD类型的请求获取总数

④ "join":"&/Table0/key0@,\多表连接方式:
"\<" - LEFT JOIN
">" - RIGHT JOIN
"&" - INNER JOIN
"\|" - FULL JOIN
"!" - OUTTER JOIN
"@" - APP JOIN
其中 @ APP JOIN 为应用层连表,会从已查出的主表里取得所有副表 key@ 关联的主表内的 refKey 作为一个数组 refKeys: [value0, value1...],然后把原来副表 count 次查询 key=$refKey 的 SQL 用 key IN($refKeys) 的方式合并为一条 SQL 来优化性能;
其它 JOIN 都是 SQL JOIN,具体功能和 MySQL,PostgreSQL 等数据库的 JOIN 一一对应
`"join":"`"MainTable":{},`
`"ViceTable":{"key@":"/MainTable/refKey"}`
会对应生成
`MainTable LEFT JOIN ViceTable`
`ON ViceTable.key=MainTable.refKey`

⑤ "otherKey":Object,自定义关键词,名称和以上系统关键词不一样,且原样返回上传的值 | ① 查询User数组,最多5个:
["count":5](http://apijson.cn:8080/get/{"[]":{"count":5,"User":{}}})
对应SQL是`LIMIT 5`

② 查询第3页的User数组,每页5个:
["count":5,
"page":3](http://apijson.cn:8080/get/{"[]":{"count":5,"page":3,"User":{}}})
对应SQL是`LIMIT 5 OFFSET 15`

③ 查询User数组和对应的User总数:
["[]":{
   "query":2,
   "User":{}
},
"total@":"/[]/total",
"info@":"/[]/info"](http://apijson.cn:8080/get/{"[]":{"query":2,"count":5,"User":{}},"total@":"%252F[]%252Ftotal","info@":"%252F[]%252Finfo"})
返回的数据中,总数及分页详情结构为:
"total":139, //总数
"info":{ //分页详情
   "total":139, //总数
   "count":5, //每页数量
   "page":0, //当前页码
   "max":27, //最大页码
   "more":true, //是否还有更多
   "first":true, //是否为首页
   "last":false //是否为尾页
}

④ Moment INNER JOIN User LEFT JOIN Comment:
["[]":{
   "join":"&/User/id@,\    "Moment":{
     "@group":"id"
   },
   "User":{
     "name~":"t",
     "id@":"/Moment/userId"
   },
   "Comment":{
     "momentId@":"/Moment/id"
   }
}](http://apijson.cn:8080/get/{"[]":{"count":5,"join":"&%252FUser%252Fid@,\<%252FComment%252FmomentId@","Moment":{"@column":"id,userId,content","@group":"id"},"User":{"name~":"t","id@":"%252FMoment%252FuserId","@column":"id,name,head"},"Comment":{"momentId@":"%252FMoment%252Fid","@column":"id,momentId,content"}}})

⑤ 每一层都加当前用户名:
["User":{},
"[]":{
   "name@":"User/name", //自定义关键词
   "Moment":{}
}](http://apijson.cn:8080/get/{"User":{},"[]":{"name@":"User%252Fname","Moment":{}}}) 对象关键词,可自定义 | "@key":Object,@key为 Table:{} 中{}内的关键词,Object的类型由@key指定

① "@combine":"&key0,&key1,\|key2,key3,
!key4,!key5,&key6,key7...",条件组合方式,\| 可省略。会自动把同类的合并,外层按照 & \| ! 顺序,内层的按传参顺序组合成
(key0 & key1 & key6 & 其它key) & (key2 \| key3 \| key7) & !(key4 \| key5)
这种连接方式,其中 "其它key" 是指与 @combine 在同一对象,且未被它声明的条件 key,默认都是 & 连接

② "@column":"column;function(arg)...",返回字段

③ "@order":"column0+,column1-...",排序方式

④ "@group":"column0,column1...",分组方式。如果@column里声明了Table的id,则id也必须在@group中声明;其它情况下必须满足至少一个条件:
1.分组的key在@column里声明
2.Table主键在@group中声明

⑤ "@having":"function0(...)?value0;function1(...)?value1;function2(...)?value2...",SQL函数条件,一般和@group一起用,函数一般在@column里声明

⑥ "@schema":"sys",集合空间(模式),非默认的值可通过它来指定,可以在最外层作为全局默认配置

⑦ "@database":"POSTGRESQL",跨数据库,非默认的值可通过它来指定,可以在最外层作为全局默认配置

⑧ "@json":"key0,key1...",转为 JSON 格式返回,符合 JSONObject 则转为 {...},符合 JSONArray 则转为 \[...]

⑨ "@role":"OWNER",来访角色,包括
UNKNOWN,LOGIN,CONTACT,CIRCLE,OWNER,ADMIN,
可以在最外层作为全局默认配置,
可自定义其它角色并重写 Verifier.verify 等相关方法来自定义校验

⑩ "@explain":true,性能分析,可以在最外层作为全局默认配置

⑪ "@raw":"key0,key1...",其中 key0, key1 都对应有键值对
"key0":"SQL片段或SQL片段的别名",
"key1":"SQL片段或SQL片段的别名"
自定义原始SQL片段,可扩展嵌套SQL函数等复杂语句,必须是后端已配置的,只有其它功能符都做不到才考虑,谨慎使用,注意防SQL注入

⑫ "@otherKey":Object,自定义关键词,名称和以上系统关键词不一样,且原样返回上传的值 | ① 搜索name或tag任何一个字段包含字符a的User列表:
["name~":"a",
"tag~":"a",
"@combine":"name~,tag~"](http://apijson.cn:8080/get/{"User[]":{"count":10,"User":{"@column":"id,name,tag","name~":"a","tag~":"a","@combine":"name~,tag~"}}})
对应SQL是`name REGEXP 'a' OR tag REGEXP 'a'`

② 只查询id,sex,name这几列并且请求结果也按照这个顺序:
["@column":"id,sex,name"](http://apijson.cn:8080/get/{"User":{"@column":"id,sex,name","id":38710}})
对应SQL是`SELECT id,sex,name`

③ 查询按 name降序、id默认顺序 排序的User数组:
["@order":"name-,id"](http://apijson.cn:8080/get/{"[]":{"count":10,"User":{"@column":"name,id","@order":"name-,id"}}})
对应SQL是`ORDER BY name DESC,id`

④ 查询按userId分组的Moment数组:
["@group":"userId,id"](http://apijson.cn:8080/get/{"[]":{"count":10,"Moment":%7B"@column":"userId,id","@group":"userId,id"}}})
对应SQL是`GROUP BY userId,id`

⑤ 查询 按userId分组、id最大值>=100 的Moment数组:
["@column":"userId;max(id)",
"@group":"userId",
"@having":"max(id)>=100"](http://apijson.cn:8080/get/{"[]":{"count":10,"Moment":{"@column":"userId%253Bmax(id)","@group":"userId","@having":"max(id)>=100"}}})
对应SQL是`SELECT userId,max(id) ... GROUP BY userId HAVING max(id)>=100`
还可以指定函数返回名:
["@column":"userId;max(id):maxId",
"@group":"userId",
"@having":"maxId>=100"](http://apijson.cn:8080/get/{"[]":{"count":10,"Moment":{"@column":"userId%253Bmax(id):maxId","@group":"userId","@having":"maxId>=100"}}})
对应SQL是`SELECT userId,max(id) AS maxId ... GROUP BY userId HAVING maxId>=100`

⑥ 查询 sys 内的 User 表:
["@schema":"sys"](http://apijson.cn:8080/get/{"User":{"@schema":"sys"}})
对应SQL是`FROM sys.User`

⑦ 查询 PostgreSQL 数据库的 User 表:
["@database":"POSTGRESQL"](http://apijson.cn:8080/get/{"User":{"@database":"POSTGRESQL","@explain":true}})

⑧ 将 VARCHAR 字符串字段 get 转为 JSONArray 返回:
["@json":"get"](http://apijson.cn:8080/get/{"Access":{"@json":"get"}})

⑨ 查询当前用户的动态:
["@role":"OWNER"](http://apijson.cn:8080/get/{"[]":{"Moment":{"@role":"OWNER"}}})

⑩ 开启性能分析:
["@explain":true](http://apijson.cn:8080/get/{"[]":{"Moment":{"@explain":true}}})
对应SQL是`EXPLAIN`

⑪ 统计最近一周偶数userId的数量
["@column":"date;left(date,10):day;sum(if(userId%2=0,1,0))",
"@group":"day",
"@having":"to_days(now())-to_days(\`date\`)<=7",
"@raw":"@column,@having"](http://apijson.cn:8080/get/{"[]":{"Moment":{"@column":"date%3bleft(date,10):day%3bsum(if(userId%252=0,1,0))","@group":"day","@having":"to_days(now())-to_days(\`date\`)<=7","@raw":"@column,@having"}}})
对应SQL是``SELECT date, left(date,10) AS day, sum(if(userId%2=0,1,0)) ... GROUP BY day HAVING to_days(now())-to_days(`date`)<=7``

⑫ 从pictureList获取第0张图片:
["@position":0, //自定义关键词
"firstPicture()":"getFromArray(pictureList,@position)"](http://apijson.cn:8080/get/{"User":{"id":38710,"@position":0,"firstPicture()":"getFromArray(pictureList,@position)"}})
From d564cfca003456076b574784d16d79665c3d4a71 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Tue, 2 Feb 2021 16:18:24 +0800 Subject: [PATCH 0271/1181] Update Document.md --- Document.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Document.md b/Document.md index f80e36c6a..aeab529fb 100644 --- a/Document.md +++ b/Document.md @@ -370,7 +370,7 @@ DELETE:
删除数据 | base_url/delete/ | {
   TableName:{< 减少 或 去除 | "key-":Object,与"key+"相反 | "balance-":100.00,对应SQL是`balance = balance - 100.00`,余额减少100.00,即花费了100元 比较运算 | >, <, >=, <= 比较运算符,用于
① 提供 "id{}":"<=90000" 这种条件范围的简化写法

② 实现子查询相关比较运算

不支持 "key=":Object 和 "key!=":Object 这两种写法,直接用更简单的 "key":Object 和 "key!":Object 替代。 | ① ["id<=":90000](http://apijson.cn:8080/get/{"[]":{"User":{"id<=":90000}}}),对应SQL是`id<=90000`,查询符合id<=90000的一个User数组

② ["id>@":{
   "from":"Comment",
   "Comment":{
      "@column":"min(userId)"
   }
}](http://apijson.cn:8080/get/{"User":{"id>@":{"from":"Comment","Comment":{"@column":"min(userId)"}}}})
WHERE id>(SELECT min(userId) FROM Comment) 逻辑运算 | &, \|, ! 逻辑运算符,对应数据库 SQL 中的 AND, OR, NOT。
横或纵与:同一键值对的值内条件默认 \| 或连接,可以在 key 后加逻辑运算符来具体指定;不同键值对的条件默认 & 与连接,可以用下面说明的对象关键词 @combine 来具体指定。

① & 可用于"key&{}":"条件"等

② \| 可用于"key\|{}":"条件", "key\|{}":[]等,一般可省略

③ ! 可单独使用,如"key!":Object,也可像&,\|一样配合其他功能符使用
"key!":null 无效,null 值会导致整个键值对被忽略解析,可以用 "key{}":"!=null" 替代,
"key":null 同理,用 "key{}":"=null" 替代。 | ① ["id&{}":">80000,<=90000"](http://apijson.cn:8080/head/{"User":{"id&{}":">80000,<=90000"}}),对应SQL是`id>80000 AND id<=90000`,即id满足id>80000 & id<=90000

② ["id\|{}":">90000,<=80000"](http://apijson.cn:8080/head/{"User":{"id\|{}":">90000,<=80000"}}),同"id{}":">90000,<=80000",对应SQL是`id>80000 OR id<=90000`,即id满足id>90000 \| id<=80000

③ ["id!{}":[82001,38710]](http://apijson.cn:8080/head/{"User":{"id!{}":[82001,38710]}}),对应SQL是`id NOT IN(82001,38710)`,即id满足 ! (id=82001 \| id=38710),可过滤黑名单的消息 - 数组关键词,可自定义 | "key":Object,key为 "[]":{} 中{}内的关键词,Object的类型由key指定

① "count":Integer,查询数量,0 表示最大值,默认最大值为100

② "page":Integer,查询页码,从0开始,默认最大值为100,一般和count一起用

③ "query":Integer,查询内容
0-对象,1-总数和分页详情,2-以上全部
总数关键词为 total,分页详情关键词为 info,
它们都和 query 同级,通过引用赋值得到,例如
"total@":"/[]/total", "info@":"/[]/info"
这里query及total仅为GET类型的请求提供方便,
一般可直接用HEAD类型的请求获取总数

④ "join":"&/Table0/key0@,\多表连接方式:
"\<" - LEFT JOIN
">" - RIGHT JOIN
"&" - INNER JOIN
"\|" - FULL JOIN
"!" - OUTTER JOIN
"@" - APP JOIN
其中 @ APP JOIN 为应用层连表,会从已查出的主表里取得所有副表 key@ 关联的主表内的 refKey 作为一个数组 refKeys: [value0, value1...],然后把原来副表 count 次查询 key=$refKey 的 SQL 用 key IN($refKeys) 的方式合并为一条 SQL 来优化性能;
其它 JOIN 都是 SQL JOIN,具体功能和 MySQL,PostgreSQL 等数据库的 JOIN 一一对应
`"join":"`"MainTable":{},`
`"ViceTable":{"key@":"/MainTable/refKey"}`
会对应生成
`MainTable LEFT JOIN ViceTable`
`ON ViceTable.key=MainTable.refKey`

⑤ "otherKey":Object,自定义关键词,名称和以上系统关键词不一样,且原样返回上传的值 | ① 查询User数组,最多5个:
["count":5](http://apijson.cn:8080/get/{"[]":{"count":5,"User":{}}})
对应SQL是`LIMIT 5`

② 查询第3页的User数组,每页5个:
["count":5,
"page":3](http://apijson.cn:8080/get/{"[]":{"count":5,"page":3,"User":{}}})
对应SQL是`LIMIT 5 OFFSET 15`

③ 查询User数组和对应的User总数:
["[]":{
   "query":2,
   "User":{}
},
"total@":"/[]/total",
"info@":"/[]/info"](http://apijson.cn:8080/get/{"[]":{"query":2,"count":5,"User":{}},"total@":"%252F[]%252Ftotal","info@":"%252F[]%252Finfo"})
返回的数据中,总数及分页详情结构为:
"total":139, //总数
"info":{ //分页详情
   "total":139, //总数
   "count":5, //每页数量
   "page":0, //当前页码
   "max":27, //最大页码
   "more":true, //是否还有更多
   "first":true, //是否为首页
   "last":false //是否为尾页
}

④ Moment INNER JOIN User LEFT JOIN Comment:
["[]":{
   "join":"&/User/id@,\    "Moment":{
     "@group":"id"
   },
   "User":{
     "name~":"t",
     "id@":"/Moment/userId"
   },
   "Comment":{
     "momentId@":"/Moment/id"
   }
}](http://apijson.cn:8080/get/{"[]":{"count":5,"join":"&%252FUser%252Fid@,\<%252FComment%252FmomentId@","Moment":{"@column":"id,userId,content","@group":"id"},"User":{"name~":"t","id@":"%252FMoment%252FuserId","@column":"id,name,head"},"Comment":{"momentId@":"%252FMoment%252Fid","@column":"id,momentId,content"}}})

⑤ 每一层都加当前用户名:
["User":{},
"[]":{
   "name@":"User/name", //自定义关键词
   "Moment":{}
}](http://apijson.cn:8080/get/{"User":{},"[]":{"name@":"User%252Fname","Moment":{}}}) + 数组关键词,可自定义 | "key":Object,key为 "[]":{} 中{}内的关键词,Object的类型由key指定

① "count":Integer,查询数量,0 表示最大值,默认最大值为100

② "page":Integer,查询页码,从0开始,默认最大值为100,一般和count一起用

③ "query":Integer,查询内容
0-对象,1-总数和分页详情,2-以上全部
总数关键词为 total,分页详情关键词为 info,
它们都和 query 同级,通过引用赋值得到,例如
"total@":"/[]/total", "info@":"/[]/info"
这里query及total仅为GET类型的请求提供方便,
一般可直接用HEAD类型的请求获取总数

④ "join":"&/Table0/key0@,\多表连接方式:
"\<" - LEFT JOIN
">" - RIGHT JOIN
"&" - INNER JOIN
"\|" - FULL JOIN
"!" - OUTTER JOIN
"@" - APP JOIN
其中 @ APP JOIN 为应用层连表,会从已查出的主表里取得所有副表 key@ 关联的主表内的 refKey 作为一个数组 refKeys: [value0, value1...],然后把原来副表 count 次查询 key=$refKey 的 SQL 用 key IN($refKeys) 的方式合并为一条 SQL 来优化性能;
其它 JOIN 都是 SQL JOIN,具体功能和 MySQL,PostgreSQL 等数据库的 JOIN 一一对应
`"join":"`"MainTable":{},`
`"ViceTable":{"key@":"/MainTable/refKey"}`
会对应生成
`MainTable LEFT JOIN ViceTable`
`ON ViceTable.key=MainTable.refKey`

⑤ "otherKey":Object,自定义关键词,名称和以上系统关键词不一样,且原样返回上传的值 | ① 查询User数组,最多5个:
["count":5](http://apijson.cn:8080/get/{"[]":{"count":5,"User":{}}})
对应SQL是`LIMIT 5`

② 查询第3页的User数组,每页5个:
["count":5,
"page":3](http://apijson.cn:8080/get/{"[]":{"count":5,"page":3,"User":{}}})
对应SQL是`LIMIT 5 OFFSET 15`

③ 查询User数组和对应的User总数:
["[]":{
   "query":2,
   "User":{}
},
"total@":"/[]/total",
"info@":"/[]/info"](http://apijson.cn:8080/get/{"[]":{"query":2,"count":5,"User":{}},"total@":"%252F[]%252Ftotal","info@":"%252F[]%252Finfo"})
返回的数据中,总数及分页详情结构为:
"total":139, //总数
"info":{ //分页详情
   "total":139, //总数
   "count":5, //每页数量
   "page":0, //当前页码
   "max":27, //最大页码
   "more":true, //是否还有更多
   "first":true, //是否为首页
   "last":false //是否为尾页
}

④ Moment INNER JOIN User LEFT JOIN Comment:
["[]":{
   "join":"&/User/id@,\    "Moment":{
     "@group":"id" //去除重复数据
   },
   "User":{
     "name~":"t",
     "id@":"/Moment/userId"
   },
   "Comment":{
     "momentId@":"/Moment/id"
   }
}](http://apijson.cn:8080/get/{"[]":{"count":5,"join":"&%252FUser%252Fid@,\<%252FComment%252FmomentId@","Moment":{"@column":"id,userId,content","@group":"id"},"User":{"name~":"t","id@":"%252FMoment%252FuserId","@column":"id,name,head"},"Comment":{"momentId@":"%252FMoment%252Fid","@column":"id,momentId,content"}}})

⑤ 每一层都加当前用户名:
["User":{},
"[]":{
   "name@":"User/name", //自定义关键词
   "Moment":{}
}](http://apijson.cn:8080/get/{"User":{},"[]":{"name@":"User%252Fname","Moment":{}}}) 对象关键词,可自定义 | "@key":Object,@key为 Table:{} 中{}内的关键词,Object的类型由@key指定

① "@combine":"&key0,&key1,\|key2,key3,
!key4,!key5,&key6,key7...",条件组合方式,\| 可省略。会自动把同类的合并,外层按照 & \| ! 顺序,内层的按传参顺序组合成
(key0 & key1 & key6 & 其它key) & (key2 \| key3 \| key7) & !(key4 \| key5)
这种连接方式,其中 "其它key" 是指与 @combine 在同一对象,且未被它声明的条件 key,默认都是 & 连接

② "@column":"column;function(arg)...",返回字段

③ "@order":"column0+,column1-...",排序方式

④ "@group":"column0,column1...",分组方式。如果@column里声明了Table的id,则id也必须在@group中声明;其它情况下必须满足至少一个条件:
1.分组的key在@column里声明
2.Table主键在@group中声明

⑤ "@having":"function0(...)?value0;function1(...)?value1;function2(...)?value2...",SQL函数条件,一般和@group一起用,函数一般在@column里声明

⑥ "@schema":"sys",集合空间(模式),非默认的值可通过它来指定,可以在最外层作为全局默认配置

⑦ "@database":"POSTGRESQL",跨数据库,非默认的值可通过它来指定,可以在最外层作为全局默认配置

⑧ "@json":"key0,key1...",转为 JSON 格式返回,符合 JSONObject 则转为 {...},符合 JSONArray 则转为 \[...]

⑨ "@role":"OWNER",来访角色,包括
UNKNOWN,LOGIN,CONTACT,CIRCLE,OWNER,ADMIN,
可以在最外层作为全局默认配置,
可自定义其它角色并重写 Verifier.verify 等相关方法来自定义校验

⑩ "@explain":true,性能分析,可以在最外层作为全局默认配置

⑪ "@raw":"key0,key1...",其中 key0, key1 都对应有键值对
"key0":"SQL片段或SQL片段的别名",
"key1":"SQL片段或SQL片段的别名"
自定义原始SQL片段,可扩展嵌套SQL函数等复杂语句,必须是后端已配置的,只有其它功能符都做不到才考虑,谨慎使用,注意防SQL注入

⑫ "@otherKey":Object,自定义关键词,名称和以上系统关键词不一样,且原样返回上传的值 | ① 搜索name或tag任何一个字段包含字符a的User列表:
["name~":"a",
"tag~":"a",
"@combine":"name~,tag~"](http://apijson.cn:8080/get/{"User[]":{"count":10,"User":{"@column":"id,name,tag","name~":"a","tag~":"a","@combine":"name~,tag~"}}})
对应SQL是`name REGEXP 'a' OR tag REGEXP 'a'`

② 只查询id,sex,name这几列并且请求结果也按照这个顺序:
["@column":"id,sex,name"](http://apijson.cn:8080/get/{"User":{"@column":"id,sex,name","id":38710}})
对应SQL是`SELECT id,sex,name`

③ 查询按 name降序、id默认顺序 排序的User数组:
["@order":"name-,id"](http://apijson.cn:8080/get/{"[]":{"count":10,"User":{"@column":"name,id","@order":"name-,id"}}})
对应SQL是`ORDER BY name DESC,id`

④ 查询按userId分组的Moment数组:
["@group":"userId,id"](http://apijson.cn:8080/get/{"[]":{"count":10,"Moment":%7B"@column":"userId,id","@group":"userId,id"}}})
对应SQL是`GROUP BY userId,id`

⑤ 查询 按userId分组、id最大值>=100 的Moment数组:
["@column":"userId;max(id)",
"@group":"userId",
"@having":"max(id)>=100"](http://apijson.cn:8080/get/{"[]":{"count":10,"Moment":{"@column":"userId%253Bmax(id)","@group":"userId","@having":"max(id)>=100"}}})
对应SQL是`SELECT userId,max(id) ... GROUP BY userId HAVING max(id)>=100`
还可以指定函数返回名:
["@column":"userId;max(id):maxId",
"@group":"userId",
"@having":"maxId>=100"](http://apijson.cn:8080/get/{"[]":{"count":10,"Moment":{"@column":"userId%253Bmax(id):maxId","@group":"userId","@having":"maxId>=100"}}})
对应SQL是`SELECT userId,max(id) AS maxId ... GROUP BY userId HAVING maxId>=100`

⑥ 查询 sys 内的 User 表:
["@schema":"sys"](http://apijson.cn:8080/get/{"User":{"@schema":"sys"}})
对应SQL是`FROM sys.User`

⑦ 查询 PostgreSQL 数据库的 User 表:
["@database":"POSTGRESQL"](http://apijson.cn:8080/get/{"User":{"@database":"POSTGRESQL","@explain":true}})

⑧ 将 VARCHAR 字符串字段 get 转为 JSONArray 返回:
["@json":"get"](http://apijson.cn:8080/get/{"Access":{"@json":"get"}})

⑨ 查询当前用户的动态:
["@role":"OWNER"](http://apijson.cn:8080/get/{"[]":{"Moment":{"@role":"OWNER"}}})

⑩ 开启性能分析:
["@explain":true](http://apijson.cn:8080/get/{"[]":{"Moment":{"@explain":true}}})
对应SQL是`EXPLAIN`

⑪ 统计最近一周偶数userId的数量
["@column":"date;left(date,10):day;sum(if(userId%2=0,1,0))",
"@group":"day",
"@having":"to_days(now())-to_days(\`date\`)<=7",
"@raw":"@column,@having"](http://apijson.cn:8080/get/{"[]":{"Moment":{"@column":"date%3bleft(date,10):day%3bsum(if(userId%252=0,1,0))","@group":"day","@having":"to_days(now())-to_days(\`date\`)<=7","@raw":"@column,@having"}}})
对应SQL是``SELECT date, left(date,10) AS day, sum(if(userId%2=0,1,0)) ... GROUP BY day HAVING to_days(now())-to_days(`date`)<=7``

⑫ 从pictureList获取第0张图片:
["@position":0, //自定义关键词
"firstPicture()":"getFromArray(pictureList,@position)"](http://apijson.cn:8080/get/{"User":{"id":38710,"@position":0,"firstPicture()":"getFromArray(pictureList,@position)"}})
From e59238e0222d40d9227afb6cd6212eece109acbe Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Tue, 2 Feb 2021 16:20:25 +0800 Subject: [PATCH 0272/1181] Update Document.md --- Document.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Document.md b/Document.md index aeab529fb..f6ff8dc19 100644 --- a/Document.md +++ b/Document.md @@ -370,7 +370,7 @@ DELETE:
删除数据 | base_url/delete/ | {
   TableName:{< 减少 或 去除 | "key-":Object,与"key+"相反 | "balance-":100.00,对应SQL是`balance = balance - 100.00`,余额减少100.00,即花费了100元 比较运算 | >, <, >=, <= 比较运算符,用于
① 提供 "id{}":"<=90000" 这种条件范围的简化写法

② 实现子查询相关比较运算

不支持 "key=":Object 和 "key!=":Object 这两种写法,直接用更简单的 "key":Object 和 "key!":Object 替代。 | ① ["id<=":90000](http://apijson.cn:8080/get/{"[]":{"User":{"id<=":90000}}}),对应SQL是`id<=90000`,查询符合id<=90000的一个User数组

② ["id>@":{
   "from":"Comment",
   "Comment":{
      "@column":"min(userId)"
   }
}](http://apijson.cn:8080/get/{"User":{"id>@":{"from":"Comment","Comment":{"@column":"min(userId)"}}}})
WHERE id>(SELECT min(userId) FROM Comment) 逻辑运算 | &, \|, ! 逻辑运算符,对应数据库 SQL 中的 AND, OR, NOT。
横或纵与:同一键值对的值内条件默认 \| 或连接,可以在 key 后加逻辑运算符来具体指定;不同键值对的条件默认 & 与连接,可以用下面说明的对象关键词 @combine 来具体指定。

① & 可用于"key&{}":"条件"等

② \| 可用于"key\|{}":"条件", "key\|{}":[]等,一般可省略

③ ! 可单独使用,如"key!":Object,也可像&,\|一样配合其他功能符使用
"key!":null 无效,null 值会导致整个键值对被忽略解析,可以用 "key{}":"!=null" 替代,
"key":null 同理,用 "key{}":"=null" 替代。 | ① ["id&{}":">80000,<=90000"](http://apijson.cn:8080/head/{"User":{"id&{}":">80000,<=90000"}}),对应SQL是`id>80000 AND id<=90000`,即id满足id>80000 & id<=90000

② ["id\|{}":">90000,<=80000"](http://apijson.cn:8080/head/{"User":{"id\|{}":">90000,<=80000"}}),同"id{}":">90000,<=80000",对应SQL是`id>80000 OR id<=90000`,即id满足id>90000 \| id<=80000

③ ["id!{}":[82001,38710]](http://apijson.cn:8080/head/{"User":{"id!{}":[82001,38710]}}),对应SQL是`id NOT IN(82001,38710)`,即id满足 ! (id=82001 \| id=38710),可过滤黑名单的消息 - 数组关键词,可自定义 | "key":Object,key为 "[]":{} 中{}内的关键词,Object的类型由key指定

① "count":Integer,查询数量,0 表示最大值,默认最大值为100

② "page":Integer,查询页码,从0开始,默认最大值为100,一般和count一起用

③ "query":Integer,查询内容
0-对象,1-总数和分页详情,2-以上全部
总数关键词为 total,分页详情关键词为 info,
它们都和 query 同级,通过引用赋值得到,例如
"total@":"/[]/total", "info@":"/[]/info"
这里query及total仅为GET类型的请求提供方便,
一般可直接用HEAD类型的请求获取总数

④ "join":"&/Table0/key0@,\多表连接方式:
"\<" - LEFT JOIN
">" - RIGHT JOIN
"&" - INNER JOIN
"\|" - FULL JOIN
"!" - OUTTER JOIN
"@" - APP JOIN
其中 @ APP JOIN 为应用层连表,会从已查出的主表里取得所有副表 key@ 关联的主表内的 refKey 作为一个数组 refKeys: [value0, value1...],然后把原来副表 count 次查询 key=$refKey 的 SQL 用 key IN($refKeys) 的方式合并为一条 SQL 来优化性能;
其它 JOIN 都是 SQL JOIN,具体功能和 MySQL,PostgreSQL 等数据库的 JOIN 一一对应
`"join":"`"MainTable":{},`
`"ViceTable":{"key@":"/MainTable/refKey"}`
会对应生成
`MainTable LEFT JOIN ViceTable`
`ON ViceTable.key=MainTable.refKey`

⑤ "otherKey":Object,自定义关键词,名称和以上系统关键词不一样,且原样返回上传的值 | ① 查询User数组,最多5个:
["count":5](http://apijson.cn:8080/get/{"[]":{"count":5,"User":{}}})
对应SQL是`LIMIT 5`

② 查询第3页的User数组,每页5个:
["count":5,
"page":3](http://apijson.cn:8080/get/{"[]":{"count":5,"page":3,"User":{}}})
对应SQL是`LIMIT 5 OFFSET 15`

③ 查询User数组和对应的User总数:
["[]":{
   "query":2,
   "User":{}
},
"total@":"/[]/total",
"info@":"/[]/info"](http://apijson.cn:8080/get/{"[]":{"query":2,"count":5,"User":{}},"total@":"%252F[]%252Ftotal","info@":"%252F[]%252Finfo"})
返回的数据中,总数及分页详情结构为:
"total":139, //总数
"info":{ //分页详情
   "total":139, //总数
   "count":5, //每页数量
   "page":0, //当前页码
   "max":27, //最大页码
   "more":true, //是否还有更多
   "first":true, //是否为首页
   "last":false //是否为尾页
}

④ Moment INNER JOIN User LEFT JOIN Comment:
["[]":{
   "join":"&/User/id@,\    "Moment":{
     "@group":"id" //去除重复数据
   },
   "User":{
     "name~":"t",
     "id@":"/Moment/userId"
   },
   "Comment":{
     "momentId@":"/Moment/id"
   }
}](http://apijson.cn:8080/get/{"[]":{"count":5,"join":"&%252FUser%252Fid@,\<%252FComment%252FmomentId@","Moment":{"@column":"id,userId,content","@group":"id"},"User":{"name~":"t","id@":"%252FMoment%252FuserId","@column":"id,name,head"},"Comment":{"momentId@":"%252FMoment%252Fid","@column":"id,momentId,content"}}})

⑤ 每一层都加当前用户名:
["User":{},
"[]":{
   "name@":"User/name", //自定义关键词
   "Moment":{}
}](http://apijson.cn:8080/get/{"User":{},"[]":{"name@":"User%252Fname","Moment":{}}}) + 数组关键词,可自定义 | "key":Object,key为 "[]":{} 中{}内的关键词,Object的类型由key指定

① "count":Integer,查询数量,0 表示最大值,默认最大值为100

② "page":Integer,查询页码,从0开始,默认最大值为100,一般和count一起用

③ "query":Integer,查询内容
0-对象,1-总数和分页详情,2-以上全部
总数关键词为 total,分页详情关键词为 info,
它们都和 query 同级,通过引用赋值得到,例如
"total@":"/[]/total", "info@":"/[]/info"
这里query及total仅为GET类型的请求提供方便,
一般可直接用HEAD类型的请求获取总数

④ "join":"&/Table0/key0@,\多表连接方式:
"\<" - LEFT JOIN
">" - RIGHT JOIN
"&" - INNER JOIN
"\|" - FULL JOIN
"!" - OUTTER JOIN
"@" - APP JOIN
其中 @ APP JOIN 为应用层连表,会从已查出的主表里取得所有副表 key@ 关联的主表内的 refKey 作为一个数组 refKeys: [value0, value1...],然后把原来副表 count 次查询 key=$refKey 的 SQL 用 key IN($refKeys) 的方式合并为一条 SQL 来优化性能;
其它 JOIN 都是 SQL JOIN,具体功能和 MySQL,PostgreSQL 等数据库的 JOIN 一一对应
`"join":"`"MainTable":{},`
`"ViceTable":{"key@":"/MainTable/refKey"}`
会对应生成
`MainTable LEFT JOIN ViceTable`
`ON ViceTable.key=MainTable.refKey`

⑤ "otherKey":Object,自定义关键词,名称和以上系统关键词不一样,且原样返回上传的值 | ① 查询User数组,最多5个:
["count":5](http://apijson.cn:8080/get/{"[]":{"count":5,"User":{}}})
对应SQL是`LIMIT 5`

② 查询第3页的User数组,每页5个:
["count":5,
"page":3](http://apijson.cn:8080/get/{"[]":{"count":5,"page":3,"User":{}}})
对应SQL是`LIMIT 5 OFFSET 15`

③ 查询User数组和对应的User总数:
["[]":{
   "query":2,
   "User":{}
},
"total@":"/[]/total",
"info@":"/[]/info"](http://apijson.cn:8080/get/{"[]":{"query":2,"count":5,"User":{}},"total@":"%252F[]%252Ftotal","info@":"%252F[]%252Finfo"})
返回的数据中,总数及分页详情结构为:
"total":139, //总数
"info":{ //分页详情
   "total":139, //总数
   "count":5, //每页数量
   "page":0, //当前页码
   "max":27, //最大页码
   "more":true, //是否还有更多
   "first":true, //是否为首页
   "last":false //是否为尾页
}

④ Moment INNER JOIN User LEFT JOIN Comment:
["[]":{
   "join":"&/User/id@,\    "Moment":{
     "@group":"id" //主副表不是一对一,要去除重复数据
   },
   "User":{
     "name~":"t",
     "id@":"/Moment/userId"
   },
   "Comment":{
     "momentId@":"/Moment/id"
   }
}](http://apijson.cn:8080/get/{"[]":{"count":5,"join":"&%252FUser%252Fid@,\<%252FComment%252FmomentId@","Moment":{"@column":"id,userId,content","@group":"id"},"User":{"name~":"t","id@":"%252FMoment%252FuserId","@column":"id,name,head"},"Comment":{"momentId@":"%252FMoment%252Fid","@column":"id,momentId,content"}}})

⑤ 每一层都加当前用户名:
["User":{},
"[]":{
   "name@":"User/name", //自定义关键词
   "Moment":{}
}](http://apijson.cn:8080/get/{"User":{},"[]":{"name@":"User%252Fname","Moment":{}}}) 对象关键词,可自定义 | "@key":Object,@key为 Table:{} 中{}内的关键词,Object的类型由@key指定

① "@combine":"&key0,&key1,\|key2,key3,
!key4,!key5,&key6,key7...",条件组合方式,\| 可省略。会自动把同类的合并,外层按照 & \| ! 顺序,内层的按传参顺序组合成
(key0 & key1 & key6 & 其它key) & (key2 \| key3 \| key7) & !(key4 \| key5)
这种连接方式,其中 "其它key" 是指与 @combine 在同一对象,且未被它声明的条件 key,默认都是 & 连接

② "@column":"column;function(arg)...",返回字段

③ "@order":"column0+,column1-...",排序方式

④ "@group":"column0,column1...",分组方式。如果@column里声明了Table的id,则id也必须在@group中声明;其它情况下必须满足至少一个条件:
1.分组的key在@column里声明
2.Table主键在@group中声明

⑤ "@having":"function0(...)?value0;function1(...)?value1;function2(...)?value2...",SQL函数条件,一般和@group一起用,函数一般在@column里声明

⑥ "@schema":"sys",集合空间(模式),非默认的值可通过它来指定,可以在最外层作为全局默认配置

⑦ "@database":"POSTGRESQL",跨数据库,非默认的值可通过它来指定,可以在最外层作为全局默认配置

⑧ "@json":"key0,key1...",转为 JSON 格式返回,符合 JSONObject 则转为 {...},符合 JSONArray 则转为 \[...]

⑨ "@role":"OWNER",来访角色,包括
UNKNOWN,LOGIN,CONTACT,CIRCLE,OWNER,ADMIN,
可以在最外层作为全局默认配置,
可自定义其它角色并重写 Verifier.verify 等相关方法来自定义校验

⑩ "@explain":true,性能分析,可以在最外层作为全局默认配置

⑪ "@raw":"key0,key1...",其中 key0, key1 都对应有键值对
"key0":"SQL片段或SQL片段的别名",
"key1":"SQL片段或SQL片段的别名"
自定义原始SQL片段,可扩展嵌套SQL函数等复杂语句,必须是后端已配置的,只有其它功能符都做不到才考虑,谨慎使用,注意防SQL注入

⑫ "@otherKey":Object,自定义关键词,名称和以上系统关键词不一样,且原样返回上传的值 | ① 搜索name或tag任何一个字段包含字符a的User列表:
["name~":"a",
"tag~":"a",
"@combine":"name~,tag~"](http://apijson.cn:8080/get/{"User[]":{"count":10,"User":{"@column":"id,name,tag","name~":"a","tag~":"a","@combine":"name~,tag~"}}})
对应SQL是`name REGEXP 'a' OR tag REGEXP 'a'`

② 只查询id,sex,name这几列并且请求结果也按照这个顺序:
["@column":"id,sex,name"](http://apijson.cn:8080/get/{"User":{"@column":"id,sex,name","id":38710}})
对应SQL是`SELECT id,sex,name`

③ 查询按 name降序、id默认顺序 排序的User数组:
["@order":"name-,id"](http://apijson.cn:8080/get/{"[]":{"count":10,"User":{"@column":"name,id","@order":"name-,id"}}})
对应SQL是`ORDER BY name DESC,id`

④ 查询按userId分组的Moment数组:
["@group":"userId,id"](http://apijson.cn:8080/get/{"[]":{"count":10,"Moment":%7B"@column":"userId,id","@group":"userId,id"}}})
对应SQL是`GROUP BY userId,id`

⑤ 查询 按userId分组、id最大值>=100 的Moment数组:
["@column":"userId;max(id)",
"@group":"userId",
"@having":"max(id)>=100"](http://apijson.cn:8080/get/{"[]":{"count":10,"Moment":{"@column":"userId%253Bmax(id)","@group":"userId","@having":"max(id)>=100"}}})
对应SQL是`SELECT userId,max(id) ... GROUP BY userId HAVING max(id)>=100`
还可以指定函数返回名:
["@column":"userId;max(id):maxId",
"@group":"userId",
"@having":"maxId>=100"](http://apijson.cn:8080/get/{"[]":{"count":10,"Moment":{"@column":"userId%253Bmax(id):maxId","@group":"userId","@having":"maxId>=100"}}})
对应SQL是`SELECT userId,max(id) AS maxId ... GROUP BY userId HAVING maxId>=100`

⑥ 查询 sys 内的 User 表:
["@schema":"sys"](http://apijson.cn:8080/get/{"User":{"@schema":"sys"}})
对应SQL是`FROM sys.User`

⑦ 查询 PostgreSQL 数据库的 User 表:
["@database":"POSTGRESQL"](http://apijson.cn:8080/get/{"User":{"@database":"POSTGRESQL","@explain":true}})

⑧ 将 VARCHAR 字符串字段 get 转为 JSONArray 返回:
["@json":"get"](http://apijson.cn:8080/get/{"Access":{"@json":"get"}})

⑨ 查询当前用户的动态:
["@role":"OWNER"](http://apijson.cn:8080/get/{"[]":{"Moment":{"@role":"OWNER"}}})

⑩ 开启性能分析:
["@explain":true](http://apijson.cn:8080/get/{"[]":{"Moment":{"@explain":true}}})
对应SQL是`EXPLAIN`

⑪ 统计最近一周偶数userId的数量
["@column":"date;left(date,10):day;sum(if(userId%2=0,1,0))",
"@group":"day",
"@having":"to_days(now())-to_days(\`date\`)<=7",
"@raw":"@column,@having"](http://apijson.cn:8080/get/{"[]":{"Moment":{"@column":"date%3bleft(date,10):day%3bsum(if(userId%252=0,1,0))","@group":"day","@having":"to_days(now())-to_days(\`date\`)<=7","@raw":"@column,@having"}}})
对应SQL是``SELECT date, left(date,10) AS day, sum(if(userId%2=0,1,0)) ... GROUP BY day HAVING to_days(now())-to_days(`date`)<=7``

⑫ 从pictureList获取第0张图片:
["@position":0, //自定义关键词
"firstPicture()":"getFromArray(pictureList,@position)"](http://apijson.cn:8080/get/{"User":{"id":38710,"@position":0,"firstPicture()":"getFromArray(pictureList,@position)"}})
From 47c896a1adef34fc6ba14eb85b43f7af12deecff Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Wed, 3 Feb 2021 21:49:10 +0800 Subject: [PATCH 0273/1181] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E7=94=A8=E6=88=B7?= =?UTF-8?q?=E5=8F=91=E7=9A=84=E6=96=87=E7=AB=A0=20"apijson=E7=AE=80?= =?UTF-8?q?=E5=8D=95demo"?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 4981da0d3..8e10555ef 100644 --- a/README.md +++ b/README.md @@ -319,6 +319,8 @@ https://github.com/Tencent/APIJSON/blob/master/CONTRIBUTING.md [APIJSON对接分布式HTAP数据库TiDB](https://asktug.com/t/htap-tidb/395) +[apijson简单demo](https://blog.csdn.net/dmw412724/article/details/113558115) + [apijson简单使用](https://www.cnblogs.com/greyzeng/p/14311995.html) [APIJSON简单部署和使用](https://blog.csdn.net/m450744192/article/details/108462611) From 1ba92a4bdd48660617b3d146d6fd33495a9984e8 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Fri, 5 Feb 2021 15:33:04 +0800 Subject: [PATCH 0274/1181] Update Roadmap.md --- Roadmap.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Roadmap.md b/Roadmap.md index 6f0e3e5b7..55ce594fb 100644 --- a/Roadmap.md +++ b/Roadmap.md @@ -191,6 +191,9 @@ https://github.com/Tencent/APIJSON/blob/master/APIJSONORM/src/main/java/apijson/ ### 提高性能 +最近的两次大幅提升性能相关优化及 Release
+[4.6.0【性能】大幅提升数组内主表查询性能](https://github.com/Tencent/APIJSON/releases/tag/4.6.0)
+[4.4.5【性能】大幅提升增删改的性能](https://github.com/Tencent/APIJSON/releases)
#### 解析 JSON From ae4e52039f07b8ed03366a7d485429b473d03dc6 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Fri, 5 Feb 2021 15:39:55 +0800 Subject: [PATCH 0275/1181] Update Roadmap.md --- Roadmap.md | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/Roadmap.md b/Roadmap.md index 55ce594fb..74c3b4ca4 100644 --- a/Roadmap.md +++ b/Roadmap.md @@ -191,9 +191,9 @@ https://github.com/Tencent/APIJSON/blob/master/APIJSONORM/src/main/java/apijson/ ### 提高性能 -最近的两次大幅提升性能相关优化及 Release
+20200205 更新:最近的两次大幅提升性能相关优化及 Release
[4.6.0【性能】大幅提升数组内主表查询性能](https://github.com/Tencent/APIJSON/releases/tag/4.6.0)
-[4.4.5【性能】大幅提升增删改的性能](https://github.com/Tencent/APIJSON/releases)
+[4.4.5【性能】大幅提升增删改的性能](https://github.com/Tencent/APIJSON/releases/tag/4.4.5)
#### 解析 JSON @@ -222,6 +222,9 @@ APIJSON 代码经过商业分析软件 [源伞Pinpoint](https://www.sourcebrella https://github.com/Tencent/APIJSON/issues/48
但我们需要再接再厉,尽可能做到 99.999% 可靠度,降低使用风险,让用户放心和安心。
+20200205 更新:已经解决了 [源伞科技](https://www.sourcebrella.com) 以上报告中的大部分问题 及 [奇安信代码卫士](https://github.com/QiAnXinCodeSafe) 发现的部分问题 +https://github.com/Tencent/APIJSON/issues/created_by/QiAnXinCodeSafe + #### 减少 Bug ##### [APIAuto](https://github.com/TommyLemon/APIAuto) 上统计的 bug @@ -241,6 +244,9 @@ https://gitee.com/TommyLemon/UnitAuto
### 完善文档 +20200205 更新:最近完善及更新了通用文档、上手文档、图文入门文档等,还对首页引导文档加了导航目录 +https://github.com/Tencent/APIJSON/blob/master/Navigation.md + #### 中文文档 ##### 通用文档 @@ -294,6 +300,8 @@ https://github.com/APIJSON/APIJSON-Demo
https://github.com/Tencent/APIJSON#%E7%9B%B8%E5%85%B3%E6%8E%A8%E8%8D%90 +20200205 更新:最近首页相关推荐新增了 1 篇官方发的文章和 6 篇用户发的文章 +https://github.com/Tencent/APIJSON/blob/master/README.md#%E7%9B%B8%E5%85%B3%E6%8E%A8%E8%8D%90 #### 登记正在使用 APIJSON 的公司或项目 From 3847d2b3c242009d7cf77f88944fb15f353dfd73 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Sat, 6 Feb 2021 16:03:34 +0800 Subject: [PATCH 0276/1181] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 8e10555ef..b48279c5c 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ This source code is licensed under the Apache License Version 2.0
APIJSON -

🏆 码云最有价值开源项目
🚀 后端接口和文档自动化,前端(客户端) 定制返回JSON的数据和结构!

+

🏆 码云最有价值开源项目
🚀 后端接口和文档自动化,前端(客户端) 定制返回 JSON 的数据和结构!

From e7058a000487a9ff7a6c8f143528997ee0dc85fb Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Sat, 6 Feb 2021 16:30:03 +0800 Subject: [PATCH 0277/1181] =?UTF-8?q?"=E4=B8=BA=E4=BB=80=E4=B9=88=E4=B8=80?= =?UTF-8?q?=E5=AE=9A=E8=A6=81=E8=B4=A1=E7=8C=AE=E4=BB=A3=E7=A0=81=EF=BC=9F?= =?UTF-8?q?"=20=E6=96=B0=E5=A2=9E=E7=AE=80=E5=8E=86=E4=B8=8E=E9=9D=A2?= =?UTF-8?q?=E8=AF=95=E7=9B=B8=E5=85=B3=E8=AF=B4=E6=98=8E?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CONTRIBUTING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index af41fa23b..321f0e142 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -32,7 +32,7 @@ APIJSON 已开发近 4 年,在此感谢所有开发者对于 APIJSON 的喜欢 加入 APIJSON ,共同打造一个更棒的自动化 ORM 库!🍾🎉 ### 为什么一定要贡献代码? -贡献代码可以避免你碰到以下麻烦:
+APIJSON 作为腾讯开源的知名热门项目,贡献代码除了可以给简历加亮点、为面试加分,还可以避免你碰到以下麻烦:
1.你在 APIJSON 上更改的代码其他人看不到,不能帮你发现 Bug,更不可能帮你修复 Bug 甚至优化代码
2.作者和其它贡献者可能不兼容你更改的代码,导致你的项目在升级 APIJSON 版本后在功能甚至编译上出错
3.你需要自己维护你的代码,每次升级 APIJSON 版本时,你都需要下载 APIJSON 新代码再合并你自己的更改
From 4858f02d3f41f318e91ecb264d36b6c865b95c93 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Sat, 6 Feb 2021 19:46:09 +0800 Subject: [PATCH 0278/1181] =?UTF-8?q?=E8=A7=A3=E5=86=B3=E8=A1=A8=E5=AF=B9?= =?UTF-8?q?=E8=B1=A1=E5=BD=93=E8=A1=A8=E5=90=8D=E6=9C=89=E5=88=AB=E5=90=8D?= =?UTF-8?q?=E6=97=B6=E8=A2=AB=E5=BD=93=E6=88=90=E6=99=AE=E9=80=9A=E5=AF=B9?= =?UTF-8?q?=E8=B1=A1=E5=AF=BC=E8=87=B4=E6=9F=A5=E4=B8=8D=E5=88=B0=E6=95=B0?= =?UTF-8?q?=E6=8D=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/main/java/apijson/orm/AbstractObjectParser.java | 2 +- APIJSONORM/src/main/java/apijson/orm/AbstractParser.java | 6 +++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractObjectParser.java b/APIJSONORM/src/main/java/apijson/orm/AbstractObjectParser.java index f5a2e079f..f83c9e8bb 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractObjectParser.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractObjectParser.java @@ -853,7 +853,7 @@ public JSONObject onSQLExecute() throws Exception { list.set(i, obj); if (obj != null) { - parser.putQueryResult(arrayPath + "/" + i + "/" + table, obj); //解决获取关联数据时requestObject里不存在需要的关联数据 + parser.putQueryResult(arrayPath + "/" + i + "/" + name, obj); //解决获取关联数据时requestObject里不存在需要的关联数据 } } diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java b/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java index 55ca32b0c..9fb17ee05 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java @@ -790,7 +790,11 @@ public JSONObject onObjectParse(final JSONObject request } } - boolean isTable = apijson.JSONObject.isTableKey(name); + apijson.orm.Entry entry = Pair.parseEntry(name, true); + String table = entry.getKey(); //Comment + // String alias = entry.getValue(); //to + + boolean isTable = apijson.JSONObject.isTableKey(table); boolean isArrayMainTable = isSubquery == false && isTable && type == SQLConfig.TYPE_ITEM_CHILD_0 && arrayConfig != null && RequestMethod.isGetMethod(arrayConfig.getMethod(), true); boolean isReuse = isArrayMainTable && position > 0; From cb025812658ea8097bd3b1f17f1e48572a9baf15 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Sun, 7 Feb 2021 10:38:52 +0800 Subject: [PATCH 0279/1181] =?UTF-8?q?=E6=9B=B4=E6=96=B0=20Star=20=E6=95=B0?= =?UTF-8?q?=E4=B8=BA=2010K?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index b48279c5c..eed57eac4 100644 --- a/README.md +++ b/README.md @@ -149,7 +149,7 @@ https://github.com/Tencent/APIJSON/wiki * **解决十大痛点** (APIJSON 大幅提振开发效率、强力杜绝联调扯皮、巧妙规避文档缺陷、非常节省流量带宽 等) * **开发提速巨大** (CRUD 零代码热更新自动化,APIJSONBoot 对比 SSM、SSH 等保守估计可提速 20 倍以上) * **腾讯官方开源** (使用 GitHub、Gitee、工蜂 等平台的官方账号开源,微信公众号、腾讯云+社区 等官方公告) -* **社区影响力大** (GitHub 9.8K Star 在 350W Java 项目中排名前 150,远超 FLAG, BAT 等国内外绝大部分开源项目) +* **社区影响力大** (GitHub 10K Star 在 350W Java 项目中排名前 150,远超 FLAG, BAT 等国内外绝大部分开源项目) * **各项荣誉成就** (腾讯开源五个第一、腾讯首个 GVP 获奖项目、腾讯后端项目 Star 第一、GitHub Java 周榜第一 等) * **多样用户案例** (腾讯内部用户包含 互娱、音乐、云与智慧,外部用户包含 500 强上市公司、数千亿资本国企 等) * **适用场景广泛** (社交聊天、阅读资讯、影音视频、办公学习 等各种 App、网站、公众号、小程序 等非金融类项目) From 5877fcb27986db1550be6cd4810749ea4109f69c Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Wed, 10 Feb 2021 15:03:33 +0800 Subject: [PATCH 0280/1181] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E7=99=BB=E8=AE=B0?= =?UTF-8?q?=E7=94=A8=E6=88=B7=20=E6=8A=95=E6=8A=95=E7=A7=91=E6=8A=80-?= =?UTF-8?q?=E8=A1=8C=E4=B8=9A=E9=A2=86=E5=85=88=E7=9A=84=E5=B9=B3=E5=8F=B0?= =?UTF-8?q?=E5=9E=8B=E9=87=91=E8=9E=8D=E7=A7=91=E6=8A=80=E5=85=AC=E5=8F=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index eed57eac4..c254f948f 100644 --- a/README.md +++ b/README.md @@ -229,6 +229,7 @@ https://github.com/Tencent/APIJSON/issues/187 +
From 41fedcfe75027da8883bb800be1f9e49dbb0556e Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Wed, 10 Feb 2021 16:35:16 +0800 Subject: [PATCH 0281/1181] =?UTF-8?q?=E4=B8=BA=E7=94=A8=E6=88=B7=20?= =?UTF-8?q?=E6=8A=95=E6=8A=95=E7=A7=91=E6=8A=80-=E8=A1=8C=E4=B8=9A?= =?UTF-8?q?=E9=A2=86=E5=85=88=E7=9A=84=E5=B9=B3=E5=8F=B0=E5=9E=8B=E9=87=91?= =?UTF-8?q?=E8=9E=8D=E7=A7=91=E6=8A=80=E5=85=AC=E5=8F=B8=20=E6=9B=B4?= =?UTF-8?q?=E9=80=82=E5=90=88=E5=B1=95=E7=A4=BA=E7=9A=84=20logo?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index c254f948f..f60d6029d 100644 --- a/README.md +++ b/README.md @@ -218,18 +218,18 @@ https://github.com/Tencent/APIJSON/issues/36 如果您在使用 APIJSON,请让我们知道,您的使用对我们非常重要(按登记顺序排列):
https://github.com/Tencent/APIJSON/issues/187

- - - - - - - - - - - - + + + + + + + + + + + +
From 22d1861739113acf8770ae7b059c0e1afebfb26f Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Sat, 20 Feb 2021 15:58:53 +0800 Subject: [PATCH 0282/1181] =?UTF-8?q?=E9=80=9A=E7=94=A8=E6=96=87=E6=A1=A3?= =?UTF-8?q?=EF=BC=9A=E6=96=B0=E5=A2=9E=E5=AF=B9=E4=B8=87=E8=83=BD=E9=80=9A?= =?UTF-8?q?=E7=94=A8=20API=20=E5=AF=B9=E5=BA=94=E6=95=B0=E6=8D=AE=E5=BA=93?= =?UTF-8?q?=E5=A2=9E=E5=88=A0=E6=94=B9=E6=9F=A5=E7=9A=84=20SQL=20=E8=AF=B4?= =?UTF-8?q?=E6=98=8E?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Document.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Document.md b/Document.md index f6ff8dc19..fe2d02702 100644 --- a/Document.md +++ b/Document.md @@ -330,13 +330,13 @@  方法及说明 | URL | Request | Response ------------ | ------------ | ------------ | ------------ -GET:
普通获取数据,
可用浏览器调试 | base_url/get/ | {
   TableName:{
     …
   }
}
{…}内为限制条件

例如获取一个 id = 235 的 Moment:
{
   "Moment":{
     "id":235
   }
} | {
   TableName:{
     ...
   },
   "code":200,
   "msg":"success"
}
例如
{
   "Moment":{
     "id":235,
     "userId":38710,
     "content":"APIJSON,let interfaces and documents go to hell !"
   },
   "code":200,
   "msg":"success"
} -HEAD:
普通获取数量,
可用浏览器调试 | base_url/head/ | {
   TableName:{
     …
   }
}
{…}内为限制条件

例如获取一个 id = 38710 的 User 所发布的 Moment 总数:
{
   "Moment":{
     "userId":38710
   }
} | {
   TableName:{
     "code":200,
     "msg":"success",
     "count":10
   },
   "code":200,
   "msg":"success"
}
例如
{
   "Moment":{
     "code":200,
     "msg":"success",
     "count":10
   },
   "code":200,
   "msg":"success"
} +GET:
普通获取数据,
可用浏览器调试 | base_url/get/ | {
   TableName:{
     …
   }
}
{…}内为限制条件

例如获取一个 id = 235 的 Moment:
{
   "Moment":{
     "id":235
   }
}
后端校验通过后自动解析为 SQL 并执行:
`SELECT * FROM Moment WHERE id=235 LIMIT 1` | {
   TableName:{
     ...
   },
   "code":200,
   "msg":"success"
}
例如
{
   "Moment":{
     "id":235,
     "userId":38710,
     "content":"APIJSON,let interfaces and documents go to hell !"
   },
   "code":200,
   "msg":"success"
} +HEAD:
普通获取数量,
可用浏览器调试 | base_url/head/ | {
   TableName:{
     …
   }
}
{…}内为限制条件

例如获取一个 id = 38710 的 User 所发布的 Moment 总数:
{
   "Moment":{
     "userId":38710
   }
}
后端校验通过后自动解析为 SQL 并执行:
`SELECT count(*) FROM Moment WHERE userId=38710 LIMIT 1` | {
   TableName:{
     "code":200,
     "msg":"success",
     "count":10
   },
   "code":200,
   "msg":"success"
}
例如
{
   "Moment":{
     "code":200,
     "msg":"success",
     "count":10
   },
   "code":200,
   "msg":"success"
} GETS:
安全/私密获取数据,
用于获取钱包等
对安全性要求高的数据 | base_url/gets/ | 最外层加一个 "tag":tag,其它同GET | 同GET HEADS:
安全/私密获取数量,
用于获取银行卡数量等
对安全性要求高的数据总数 | base_url/heads/ | 最外层加一个 "tag":tag,其它同HEAD | 同HEAD -POST:
新增数据 | base_url/post/ | 单个:
{
   TableName:{
     …
   },
   "tag":tag
}
{…}中id由后端生成,不能传

例如一个 id = 38710 的 User 发布一个新 Moment:
{
   "Moment":{
     "userId":38710,
     "content":"APIJSON,let interfaces and documents go to hell !"
   },
   "tag":"Moment"
}

批量:
{
   TableName\[]:\[{
       …
     }, {
       …
     }
     …
   ],
   "tag":tag
}
{…}中id由后端生成,不能传

例如当前登录用户 82001 发布 2 个 Comment:
{
   "Comment\[]":\[{
     "momentId":12,
     "content":"APIJSON,let interfaces and documents go to hell !"
     }, {
     "momentId":15,
     "content":"APIJSON is a JSON transmision protocol."
   }],
   "tag":"Comment[]"
}
| 单个:
{
   TableName:{
     "code":200,
     "msg":"success",
     "id":38710
   },
   "code":200,
   "msg":"success"
}
例如
{
   "Moment":{
     "code":200,
     "msg":"success",
     "id":120
   },
   "code":200,
   "msg":"success"
}

批量:
{
   TableName:{
     "code":200,
     "msg":"success",
     "count":5,
     "id[]":[1, 2, 3, 4, 5]
   },
   "code":200,
   "msg":"success"
}
例如
{
   "Comment":{
     "code":200,
     "msg":"success",
     "count":2,
     "id[]":\[1, 2]
   },
   "code":200,
   "msg":"success"
} -PUT:
修改数据,
只修改所传的字段 | base_url/put/ | {
   TableName:{
     "id":id,
     …
   },
   "tag":tag
}
{…} 中 id 或 id{} 至少传一个

例如修改 id = 235 的 Moment 的 content:
{
   "Moment":{
     "id":235,
     "content":"APIJSON,let interfaces and documents go to hell !"
   },
   "tag":"Moment"
}

批量除了 id{}:\[] 也可类似批量 POST,只是每个 {...} 里面都必须有 id | 同POST -DELETE:
删除数据 | base_url/delete/ | {
   TableName:{
     "id":id
   },
   "tag":tag
}
{…} 中 id 或 id{} 至少传一个,一般只传 id 或 id{}

例如批量删除 id = 100,110,120 的 Comment:
{
   "Comment":{
     "id{}":[100,110,120]
   },
   "tag":"Comment[]"
} | {
   TableName:{
     "code":200,
     "msg":"success",
     "id[]":[100,110,120]
      "count":3
   },
   "code":200,
   "msg":"success"
}
例如
{
   "Comment":{
      "code":200,
      "msg":"success",
      "id[]":[100,110,120],
      "count":3
   },
   "code":200,
   "msg":"success"
} +POST:
新增数据 | base_url/post/ | 单个:
{
   TableName:{
     …
   },
   "tag":tag
}
{…}中id由后端生成,不能传

例如一个 id = 38710 的 User 发布一个新 Moment:
{
   "Moment":{
     "userId":38710,
     "content":"APIJSON,let interfaces and documents go to hell !"
   },
   "tag":"Moment"
}
后端校验通过后自动解析为 SQL 并执行:
`INSERT INTO Moment(userId,content) VALUES(38710,'APIJSON,let interfaces and documents go to hell !')`

批量:
{
   TableName\[]:\[{
       …
     }, {
       …
     }
     …
   ],
   "tag":tag
}
{…}中id由后端生成,不能传

例如当前登录用户 82001 发布 2 个 Comment:
{
   "Comment\[]":\[{
     "momentId":12,
     "content":"APIJSON,let interfaces and documents go to hell !"
     }, {
     "momentId":15,
     "content":"APIJSON is a JSON transmision protocol."
   }],
   "tag":"Comment[]"
}
后端校验通过后自动解析为 SQL 并执行:
`INSERT INTO Comment(userId,momentId,content) VALUES(82001,12,'APIJSON,let interfaces and documents go to hell !')`
`INSERT INTO Comment(userId,momentId,content) VALUES(82001,15,'APIJSON is a JSON transmision protocol.')` | 单个:
{
   TableName:{
     "code":200,
     "msg":"success",
     "id":38710
   },
   "code":200,
   "msg":"success"
}
例如
{
   "Moment":{
     "code":200,
     "msg":"success",
     "id":120
   },
   "code":200,
   "msg":"success"
}

批量:
{
   TableName:{
     "code":200,
     "msg":"success",
     "count":5,
     "id[]":[1, 2, 3, 4, 5]
   },
   "code":200,
   "msg":"success"
}
例如
{
   "Comment":{
     "code":200,
     "msg":"success",
     "count":2,
     "id[]":\[1, 2]
   },
   "code":200,
   "msg":"success"
} +PUT:
修改数据,
只修改所传的字段 | base_url/put/ | {
   TableName:{
     "id":id,
     …
   },
   "tag":tag
}
{…} 中 id 或 id{} 至少传一个

例如修改 id = 235 的 Moment 的 content:
{
   "Moment":{
     "id":235,
     "content":"APIJSON,let interfaces and documents go to hell !"
   },
   "tag":"Moment"
}
后端校验通过后自动解析为 SQL 并执行:
`UPDATE Moment SET content='APIJSON,let interfaces and documents go to hell !' WHERE id=235 AND userId=82001 LIMIT 1`
批量除了 id{}:\[] 也可类似批量 POST,只是每个 {...} 里面都必须有 id | 同POST +DELETE:
删除数据 | base_url/delete/ | {
   TableName:{
     "id":id
   },
   "tag":tag
}
{…} 中 id 或 id{} 至少传一个,一般只传 id 或 id{}

例如批量删除 id = 100,110,120 的 Comment:
{
   "Comment":{
     "id{}":[100,110,120]
   },
   "tag":"Comment[]"
}
后端校验通过后自动解析为 SQL 并执行:
`DELETE FROM Comment WHERE id IN(100,110,120) AND userId=82001 LIMIT 3` | {
   TableName:{
     "code":200,
     "msg":"success",
     "id[]":[100,110,120]
      "count":3
   },
   "code":200,
   "msg":"success"
}
例如
{
   "Comment":{
      "code":200,
      "msg":"success",
      "id[]":[100,110,120],
      "count":3
   },
   "code":200,
   "msg":"success"
} 1.TableName指要查询的数据库表Table的名称字符串。第一个字符为大写字母,剩下的字符要符合英语字母、数字、下划线中的任何一种。对应的值的类型为JSONObject,结构是 {...},里面放的是Table的字段(列名)。下同。
From 00f6843babab2b6425087f55f4ad73e0f65307ec Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Sat, 20 Feb 2021 16:11:37 +0800 Subject: [PATCH 0283/1181] Update Document.md --- Document.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Document.md b/Document.md index fe2d02702..3eb2bfd3a 100644 --- a/Document.md +++ b/Document.md @@ -334,9 +334,9 @@ GET:
普通获取数据,
可用浏览器调试 | base_url/get/ | {< HEAD:
普通获取数量,
可用浏览器调试 | base_url/head/ | {
   TableName:{
     …
   }
}
{…}内为限制条件

例如获取一个 id = 38710 的 User 所发布的 Moment 总数:
{
   "Moment":{
     "userId":38710
   }
}
后端校验通过后自动解析为 SQL 并执行:
`SELECT count(*) FROM Moment WHERE userId=38710 LIMIT 1` | {
   TableName:{
     "code":200,
     "msg":"success",
     "count":10
   },
   "code":200,
   "msg":"success"
}
例如
{
   "Moment":{
     "code":200,
     "msg":"success",
     "count":10
   },
   "code":200,
   "msg":"success"
} GETS:
安全/私密获取数据,
用于获取钱包等
对安全性要求高的数据 | base_url/gets/ | 最外层加一个 "tag":tag,其它同GET | 同GET HEADS:
安全/私密获取数量,
用于获取银行卡数量等
对安全性要求高的数据总数 | base_url/heads/ | 最外层加一个 "tag":tag,其它同HEAD | 同HEAD -POST:
新增数据 | base_url/post/ | 单个:
{
   TableName:{
     …
   },
   "tag":tag
}
{…}中id由后端生成,不能传

例如一个 id = 38710 的 User 发布一个新 Moment:
{
   "Moment":{
     "userId":38710,
     "content":"APIJSON,let interfaces and documents go to hell !"
   },
   "tag":"Moment"
}
后端校验通过后自动解析为 SQL 并执行:
`INSERT INTO Moment(userId,content) VALUES(38710,'APIJSON,let interfaces and documents go to hell !')`

批量:
{
   TableName\[]:\[{
       …
     }, {
       …
     }
     …
   ],
   "tag":tag
}
{…}中id由后端生成,不能传

例如当前登录用户 82001 发布 2 个 Comment:
{
   "Comment\[]":\[{
     "momentId":12,
     "content":"APIJSON,let interfaces and documents go to hell !"
     }, {
     "momentId":15,
     "content":"APIJSON is a JSON transmision protocol."
   }],
   "tag":"Comment[]"
}
后端校验通过后自动解析为 SQL 并执行:
`INSERT INTO Comment(userId,momentId,content) VALUES(82001,12,'APIJSON,let interfaces and documents go to hell !')`
`INSERT INTO Comment(userId,momentId,content) VALUES(82001,15,'APIJSON is a JSON transmision protocol.')` | 单个:
{
   TableName:{
     "code":200,
     "msg":"success",
     "id":38710
   },
   "code":200,
   "msg":"success"
}
例如
{
   "Moment":{
     "code":200,
     "msg":"success",
     "id":120
   },
   "code":200,
   "msg":"success"
}

批量:
{
   TableName:{
     "code":200,
     "msg":"success",
     "count":5,
     "id[]":[1, 2, 3, 4, 5]
   },
   "code":200,
   "msg":"success"
}
例如
{
   "Comment":{
     "code":200,
     "msg":"success",
     "count":2,
     "id[]":\[1, 2]
   },
   "code":200,
   "msg":"success"
} -PUT:
修改数据,
只修改所传的字段 | base_url/put/ | {
   TableName:{
     "id":id,
     …
   },
   "tag":tag
}
{…} 中 id 或 id{} 至少传一个

例如修改 id = 235 的 Moment 的 content:
{
   "Moment":{
     "id":235,
     "content":"APIJSON,let interfaces and documents go to hell !"
   },
   "tag":"Moment"
}
后端校验通过后自动解析为 SQL 并执行:
`UPDATE Moment SET content='APIJSON,let interfaces and documents go to hell !' WHERE id=235 AND userId=82001 LIMIT 1`
批量除了 id{}:\[] 也可类似批量 POST,只是每个 {...} 里面都必须有 id | 同POST -DELETE:
删除数据 | base_url/delete/ | {
   TableName:{
     "id":id
   },
   "tag":tag
}
{…} 中 id 或 id{} 至少传一个,一般只传 id 或 id{}

例如批量删除 id = 100,110,120 的 Comment:
{
   "Comment":{
     "id{}":[100,110,120]
   },
   "tag":"Comment[]"
}
后端校验通过后自动解析为 SQL 并执行:
`DELETE FROM Comment WHERE id IN(100,110,120) AND userId=82001 LIMIT 3` | {
   TableName:{
     "code":200,
     "msg":"success",
     "id[]":[100,110,120]
      "count":3
   },
   "code":200,
   "msg":"success"
}
例如
{
   "Comment":{
      "code":200,
      "msg":"success",
      "id[]":[100,110,120],
      "count":3
   },
   "code":200,
   "msg":"success"
} +POST:
新增数据 | base_url/post/ | 单个:
{
   TableName:{
     …
   },
   "tag":tag
}
{…}中id由后端生成,不能传

例如当前登录用户 38710 发布一个新 Moment:
{
   "Moment":{
     "userId":38710,
     "content":"APIJSON,let interfaces and documents go to hell !"
   },
   "tag":"Moment"
}
后端校验通过后自动解析为 SQL 并执行:
`INSERT INTO Moment(userId,content) VALUES(38710,'APIJSON,let interfaces and documents go to hell !')`

批量:
{
   TableName\[]:\[{
       …
     }, {
       …
     }
     …
   ],
   "tag":tag
}
{…}中id由后端生成,不能传

例如当前登录用户 82001 发布 2 个 Comment:
{
   "Comment\[]":\[{
     "momentId":12,
     "content":"APIJSON,let interfaces and documents go to hell !"
     }, {
     "momentId":15,
     "content":"APIJSON is a JSON transmision protocol."
   }],
   "tag":"Comment[]"
}
后端校验通过后自动解析为 SQL 并执行:
`INSERT INTO Comment(userId,momentId,content) VALUES(82001,12,'APIJSON,let interfaces and documents go to hell !')`
`INSERT INTO Comment(userId,momentId,content) VALUES(82001,15,'APIJSON is a JSON transmision protocol.')` | 单个:
{
   TableName:{
     "code":200,
     "msg":"success",
     "id":38710
   },
   "code":200,
   "msg":"success"
}
例如
{
   "Moment":{
     "code":200,
     "msg":"success",
     "id":120
   },
   "code":200,
   "msg":"success"
}

批量:
{
   TableName:{
     "code":200,
     "msg":"success",
     "count":5,
     "id[]":[1, 2, 3, 4, 5]
   },
   "code":200,
   "msg":"success"
}
例如
{
   "Comment":{
     "code":200,
     "msg":"success",
     "count":2,
     "id[]":\[1, 2]
   },
   "code":200,
   "msg":"success"
} +PUT:
修改数据,
只修改所传的字段 | base_url/put/ | {
   TableName:{
     "id":id,
     …
   },
   "tag":tag
}
{…} 中 id 或 id{} 至少传一个

例如当前登录用户 82001 修改 id = 235 的 Moment 的 content:
{
   "Moment":{
     "id":235,
     "content":"APIJSON,let interfaces and documents go to hell !"
   },
   "tag":"Moment"
}
后端校验通过后自动解析为 SQL 并执行:
`UPDATE Moment SET content='APIJSON,let interfaces and documents go to hell !' WHERE id=235 AND userId=82001 LIMIT 1`
批量除了 id{}:\[] 也可类似批量 POST,只是每个 {...} 里面都必须有 id | 同POST +DELETE:
删除数据 | base_url/delete/ | {
   TableName:{
     "id":id
   },
   "tag":tag
}
{…} 中 id 或 id{} 至少传一个,一般只传 id 或 id{}

例如当前登录用户 82001 批量删除 id = 100,110,120 的 Comment:
{
   "Comment":{
     "id{}":[100,110,120]
   },
   "tag":"Comment[]"
}
后端校验通过后自动解析为 SQL 并执行:
`DELETE FROM Comment WHERE id IN(100,110,120) AND userId=82001 LIMIT 3` | {
   TableName:{
     "code":200,
     "msg":"success",
     "id[]":[100,110,120]
      "count":3
   },
   "code":200,
   "msg":"success"
}
例如
{
   "Comment":{
      "code":200,
      "msg":"success",
      "id[]":[100,110,120],
      "count":3
   },
   "code":200,
   "msg":"success"
} 1.TableName指要查询的数据库表Table的名称字符串。第一个字符为大写字母,剩下的字符要符合英语字母、数字、下划线中的任何一种。对应的值的类型为JSONObject,结构是 {...},里面放的是Table的字段(列名)。下同。
From 784051b6211d3e61571824d29f4e5de1cf2dc131 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Sat, 20 Feb 2021 16:15:09 +0800 Subject: [PATCH 0284/1181] Update Document.md --- Document.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Document.md b/Document.md index 3eb2bfd3a..eb980951e 100644 --- a/Document.md +++ b/Document.md @@ -334,7 +334,7 @@ GET:
普通获取数据,
可用浏览器调试 | base_url/get/ | {< HEAD:
普通获取数量,
可用浏览器调试 | base_url/head/ | {
   TableName:{
     …
   }
}
{…}内为限制条件

例如获取一个 id = 38710 的 User 所发布的 Moment 总数:
{
   "Moment":{
     "userId":38710
   }
}
后端校验通过后自动解析为 SQL 并执行:
`SELECT count(*) FROM Moment WHERE userId=38710 LIMIT 1` | {
   TableName:{
     "code":200,
     "msg":"success",
     "count":10
   },
   "code":200,
   "msg":"success"
}
例如
{
   "Moment":{
     "code":200,
     "msg":"success",
     "count":10
   },
   "code":200,
   "msg":"success"
} GETS:
安全/私密获取数据,
用于获取钱包等
对安全性要求高的数据 | base_url/gets/ | 最外层加一个 "tag":tag,其它同GET | 同GET HEADS:
安全/私密获取数量,
用于获取银行卡数量等
对安全性要求高的数据总数 | base_url/heads/ | 最外层加一个 "tag":tag,其它同HEAD | 同HEAD -POST:
新增数据 | base_url/post/ | 单个:
{
   TableName:{
     …
   },
   "tag":tag
}
{…}中id由后端生成,不能传

例如当前登录用户 38710 发布一个新 Moment:
{
   "Moment":{
     "userId":38710,
     "content":"APIJSON,let interfaces and documents go to hell !"
   },
   "tag":"Moment"
}
后端校验通过后自动解析为 SQL 并执行:
`INSERT INTO Moment(userId,content) VALUES(38710,'APIJSON,let interfaces and documents go to hell !')`

批量:
{
   TableName\[]:\[{
       …
     }, {
       …
     }
     …
   ],
   "tag":tag
}
{…}中id由后端生成,不能传

例如当前登录用户 82001 发布 2 个 Comment:
{
   "Comment\[]":\[{
     "momentId":12,
     "content":"APIJSON,let interfaces and documents go to hell !"
     }, {
     "momentId":15,
     "content":"APIJSON is a JSON transmision protocol."
   }],
   "tag":"Comment[]"
}
后端校验通过后自动解析为 SQL 并执行:
`INSERT INTO Comment(userId,momentId,content) VALUES(82001,12,'APIJSON,let interfaces and documents go to hell !')`
`INSERT INTO Comment(userId,momentId,content) VALUES(82001,15,'APIJSON is a JSON transmision protocol.')` | 单个:
{
   TableName:{
     "code":200,
     "msg":"success",
     "id":38710
   },
   "code":200,
   "msg":"success"
}
例如
{
   "Moment":{
     "code":200,
     "msg":"success",
     "id":120
   },
   "code":200,
   "msg":"success"
}

批量:
{
   TableName:{
     "code":200,
     "msg":"success",
     "count":5,
     "id[]":[1, 2, 3, 4, 5]
   },
   "code":200,
   "msg":"success"
}
例如
{
   "Comment":{
     "code":200,
     "msg":"success",
     "count":2,
     "id[]":\[1, 2]
   },
   "code":200,
   "msg":"success"
} +POST:
新增数据 | base_url/post/ | 单个:
{
   TableName:{
     …
   },
   "tag":tag
}
{…}中id由后端生成,不能传

例如当前登录用户 38710 发布一个新 Moment:
{
   "Moment":{
     "content":"APIJSON,let interfaces and documents go to hell !",
     "pictureList":["http://apijson.cn/images/APIJSON_GVPAwardCertificate4Tencent-small.png"]
   },
   "tag":"Moment"
}
后端校验通过后自动解析为 SQL 并执行:
`INSERT INTO Moment(userId,content,pictureList) VALUES(38710,'APIJSON,let interfaces and documents go to hell !','["http://apijson.cn/images/APIJSON_GVPAwardCertificate4Tencent-small.png"]')`

批量:
{
   TableName\[]:\[{
       …
     }, {
       …
     }
     …
   ],
   "tag":tag
}
{…}中id由后端生成,不能传

例如当前登录用户 82001 发布 2 个 Comment:
{
   "Comment\[]":\[{
     "momentId":12,
     "content":"APIJSON,let interfaces and documents go to hell !"
     }, {
     "momentId":15,
     "content":"APIJSON is a JSON transmision protocol."
   }],
   "tag":"Comment[]"
}
后端校验通过后自动解析为 SQL 并执行:
`INSERT INTO Comment(userId,momentId,content) VALUES(82001,12,'APIJSON,let interfaces and documents go to hell !')`
`INSERT INTO Comment(userId,momentId,content) VALUES(82001,15,'APIJSON is a JSON transmision protocol.')` | 单个:
{
   TableName:{
     "code":200,
     "msg":"success",
     "id":38710
   },
   "code":200,
   "msg":"success"
}
例如
{
   "Moment":{
     "code":200,
     "msg":"success",
     "id":120
   },
   "code":200,
   "msg":"success"
}

批量:
{
   TableName:{
     "code":200,
     "msg":"success",
     "count":5,
     "id[]":[1, 2, 3, 4, 5]
   },
   "code":200,
   "msg":"success"
}
例如
{
   "Comment":{
     "code":200,
     "msg":"success",
     "count":2,
     "id[]":\[1, 2]
   },
   "code":200,
   "msg":"success"
} PUT:
修改数据,
只修改所传的字段 | base_url/put/ | {
   TableName:{
     "id":id,
     …
   },
   "tag":tag
}
{…} 中 id 或 id{} 至少传一个

例如当前登录用户 82001 修改 id = 235 的 Moment 的 content:
{
   "Moment":{
     "id":235,
     "content":"APIJSON,let interfaces and documents go to hell !"
   },
   "tag":"Moment"
}
后端校验通过后自动解析为 SQL 并执行:
`UPDATE Moment SET content='APIJSON,let interfaces and documents go to hell !' WHERE id=235 AND userId=82001 LIMIT 1`
批量除了 id{}:\[] 也可类似批量 POST,只是每个 {...} 里面都必须有 id | 同POST DELETE:
删除数据 | base_url/delete/ | {
   TableName:{
     "id":id
   },
   "tag":tag
}
{…} 中 id 或 id{} 至少传一个,一般只传 id 或 id{}

例如当前登录用户 82001 批量删除 id = 100,110,120 的 Comment:
{
   "Comment":{
     "id{}":[100,110,120]
   },
   "tag":"Comment[]"
}
后端校验通过后自动解析为 SQL 并执行:
`DELETE FROM Comment WHERE id IN(100,110,120) AND userId=82001 LIMIT 3` | {
   TableName:{
     "code":200,
     "msg":"success",
     "id[]":[100,110,120]
      "count":3
   },
   "code":200,
   "msg":"success"
}
例如
{
   "Comment":{
      "code":200,
      "msg":"success",
      "id[]":[100,110,120],
      "count":3
   },
   "code":200,
   "msg":"success"
} From b5daa2258b9ac9a1b9aec74b9390e582e78e49e8 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Sat, 20 Feb 2021 16:20:21 +0800 Subject: [PATCH 0285/1181] Update Document.md --- Document.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Document.md b/Document.md index eb980951e..6de724607 100644 --- a/Document.md +++ b/Document.md @@ -334,7 +334,7 @@ GET:
普通获取数据,
可用浏览器调试 | base_url/get/ | {< HEAD:
普通获取数量,
可用浏览器调试 | base_url/head/ | {
   TableName:{
     …
   }
}
{…}内为限制条件

例如获取一个 id = 38710 的 User 所发布的 Moment 总数:
{
   "Moment":{
     "userId":38710
   }
}
后端校验通过后自动解析为 SQL 并执行:
`SELECT count(*) FROM Moment WHERE userId=38710 LIMIT 1` | {
   TableName:{
     "code":200,
     "msg":"success",
     "count":10
   },
   "code":200,
   "msg":"success"
}
例如
{
   "Moment":{
     "code":200,
     "msg":"success",
     "count":10
   },
   "code":200,
   "msg":"success"
} GETS:
安全/私密获取数据,
用于获取钱包等
对安全性要求高的数据 | base_url/gets/ | 最外层加一个 "tag":tag,其它同GET | 同GET HEADS:
安全/私密获取数量,
用于获取银行卡数量等
对安全性要求高的数据总数 | base_url/heads/ | 最外层加一个 "tag":tag,其它同HEAD | 同HEAD -POST:
新增数据 | base_url/post/ | 单个:
{
   TableName:{
     …
   },
   "tag":tag
}
{…}中id由后端生成,不能传

例如当前登录用户 38710 发布一个新 Moment:
{
   "Moment":{
     "content":"APIJSON,let interfaces and documents go to hell !",
     "pictureList":["http://apijson.cn/images/APIJSON_GVPAwardCertificate4Tencent-small.png"]
   },
   "tag":"Moment"
}
后端校验通过后自动解析为 SQL 并执行:
`INSERT INTO Moment(userId,content,pictureList) VALUES(38710,'APIJSON,let interfaces and documents go to hell !','["http://apijson.cn/images/APIJSON_GVPAwardCertificate4Tencent-small.png"]')`

批量:
{
   TableName\[]:\[{
       …
     }, {
       …
     }
     …
   ],
   "tag":tag
}
{…}中id由后端生成,不能传

例如当前登录用户 82001 发布 2 个 Comment:
{
   "Comment\[]":\[{
     "momentId":12,
     "content":"APIJSON,let interfaces and documents go to hell !"
     }, {
     "momentId":15,
     "content":"APIJSON is a JSON transmision protocol."
   }],
   "tag":"Comment[]"
}
后端校验通过后自动解析为 SQL 并执行:
`INSERT INTO Comment(userId,momentId,content) VALUES(82001,12,'APIJSON,let interfaces and documents go to hell !')`
`INSERT INTO Comment(userId,momentId,content) VALUES(82001,15,'APIJSON is a JSON transmision protocol.')` | 单个:
{
   TableName:{
     "code":200,
     "msg":"success",
     "id":38710
   },
   "code":200,
   "msg":"success"
}
例如
{
   "Moment":{
     "code":200,
     "msg":"success",
     "id":120
   },
   "code":200,
   "msg":"success"
}

批量:
{
   TableName:{
     "code":200,
     "msg":"success",
     "count":5,
     "id[]":[1, 2, 3, 4, 5]
   },
   "code":200,
   "msg":"success"
}
例如
{
   "Comment":{
     "code":200,
     "msg":"success",
     "count":2,
     "id[]":\[1, 2]
   },
   "code":200,
   "msg":"success"
} +POST:
新增数据 | base_url/post/ | 单个:
{
   TableName:{
     …
   },
   "tag":tag
}
{…}中id由后端生成,不能传

例如当前登录用户 38710 发布一个新 Comment:
{
   "Comment":{
     "momentId":12,
     "content":"APIJSON,let interfaces and documents go to hell !",
   },
   "tag":"Comment"
}
后端校验通过后自动解析为 SQL 并执行:
`INSERT INTO Comment(userId,momentId,content) VALUES(38710,12,'APIJSON,let interfaces and documents go to hell !')`

批量:
{
   TableName\[]:\[{
       …
     }, {
       …
     }
     …
   ],
   "tag":tag
}
{…}中id由后端生成,不能传

例如当前登录用户 82001 发布 2 个 Comment:
{
   "Comment\[]":\[{
     "momentId":12,
     "content":"APIJSON,let interfaces and documents go to hell !"
     }, {
     "momentId":15,
     "content":"APIJSON is a JSON transmision protocol."
   }],
   "tag":"Comment[]"
}
后端校验通过后自动解析为 SQL 并执行:
`INSERT INTO Comment(userId,momentId,content) VALUES(82001,12,'APIJSON,let interfaces and documents go to hell !')`
`INSERT INTO Comment(userId,momentId,content) VALUES(82001,15,'APIJSON is a JSON transmision protocol.')` | 单个:
{
   TableName:{
     "code":200,
     "msg":"success",
     "id":38710
   },
   "code":200,
   "msg":"success"
}
例如
{
   "Comment":{
     "code":200,
     "msg":"success",
     "id":120
   },
   "code":200,
   "msg":"success"
}

批量:
{
   TableName:{
     "code":200,
     "msg":"success",
     "count":5,
     "id[]":[1, 2, 3, 4, 5]
   },
   "code":200,
   "msg":"success"
}
例如
{
   "Comment":{
     "code":200,
     "msg":"success",
     "count":2,
     "id[]":\[1, 2]
   },
   "code":200,
   "msg":"success"
} PUT:
修改数据,
只修改所传的字段 | base_url/put/ | {
   TableName:{
     "id":id,
     …
   },
   "tag":tag
}
{…} 中 id 或 id{} 至少传一个

例如当前登录用户 82001 修改 id = 235 的 Moment 的 content:
{
   "Moment":{
     "id":235,
     "content":"APIJSON,let interfaces and documents go to hell !"
   },
   "tag":"Moment"
}
后端校验通过后自动解析为 SQL 并执行:
`UPDATE Moment SET content='APIJSON,let interfaces and documents go to hell !' WHERE id=235 AND userId=82001 LIMIT 1`
批量除了 id{}:\[] 也可类似批量 POST,只是每个 {...} 里面都必须有 id | 同POST DELETE:
删除数据 | base_url/delete/ | {
   TableName:{
     "id":id
   },
   "tag":tag
}
{…} 中 id 或 id{} 至少传一个,一般只传 id 或 id{}

例如当前登录用户 82001 批量删除 id = 100,110,120 的 Comment:
{
   "Comment":{
     "id{}":[100,110,120]
   },
   "tag":"Comment[]"
}
后端校验通过后自动解析为 SQL 并执行:
`DELETE FROM Comment WHERE id IN(100,110,120) AND userId=82001 LIMIT 3` | {
   TableName:{
     "code":200,
     "msg":"success",
     "id[]":[100,110,120]
      "count":3
   },
   "code":200,
   "msg":"success"
}
例如
{
   "Comment":{
      "code":200,
      "msg":"success",
      "id[]":[100,110,120],
      "count":3
   },
   "code":200,
   "msg":"success"
} From 8012b9376746482fdc1653a8b8d6eda2fde18e10 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Sat, 20 Feb 2021 16:21:50 +0800 Subject: [PATCH 0286/1181] Update Document.md --- Document.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Document.md b/Document.md index 6de724607..acc2a0c95 100644 --- a/Document.md +++ b/Document.md @@ -335,7 +335,7 @@ HEAD:
普通获取数量,
可用浏览器调试 | base_url/head/ | GETS:
安全/私密获取数据,
用于获取钱包等
对安全性要求高的数据 | base_url/gets/ | 最外层加一个 "tag":tag,其它同GET | 同GET HEADS:
安全/私密获取数量,
用于获取银行卡数量等
对安全性要求高的数据总数 | base_url/heads/ | 最外层加一个 "tag":tag,其它同HEAD | 同HEAD POST:
新增数据 | base_url/post/ | 单个:
{
   TableName:{
     …
   },
   "tag":tag
}
{…}中id由后端生成,不能传

例如当前登录用户 38710 发布一个新 Comment:
{
   "Comment":{
     "momentId":12,
     "content":"APIJSON,let interfaces and documents go to hell !",
   },
   "tag":"Comment"
}
后端校验通过后自动解析为 SQL 并执行:
`INSERT INTO Comment(userId,momentId,content) VALUES(38710,12,'APIJSON,let interfaces and documents go to hell !')`

批量:
{
   TableName\[]:\[{
       …
     }, {
       …
     }
     …
   ],
   "tag":tag
}
{…}中id由后端生成,不能传

例如当前登录用户 82001 发布 2 个 Comment:
{
   "Comment\[]":\[{
     "momentId":12,
     "content":"APIJSON,let interfaces and documents go to hell !"
     }, {
     "momentId":15,
     "content":"APIJSON is a JSON transmision protocol."
   }],
   "tag":"Comment[]"
}
后端校验通过后自动解析为 SQL 并执行:
`INSERT INTO Comment(userId,momentId,content) VALUES(82001,12,'APIJSON,let interfaces and documents go to hell !')`
`INSERT INTO Comment(userId,momentId,content) VALUES(82001,15,'APIJSON is a JSON transmision protocol.')` | 单个:
{
   TableName:{
     "code":200,
     "msg":"success",
     "id":38710
   },
   "code":200,
   "msg":"success"
}
例如
{
   "Comment":{
     "code":200,
     "msg":"success",
     "id":120
   },
   "code":200,
   "msg":"success"
}

批量:
{
   TableName:{
     "code":200,
     "msg":"success",
     "count":5,
     "id[]":[1, 2, 3, 4, 5]
   },
   "code":200,
   "msg":"success"
}
例如
{
   "Comment":{
     "code":200,
     "msg":"success",
     "count":2,
     "id[]":\[1, 2]
   },
   "code":200,
   "msg":"success"
} -PUT:
修改数据,
只修改所传的字段 | base_url/put/ | {
   TableName:{
     "id":id,
     …
   },
   "tag":tag
}
{…} 中 id 或 id{} 至少传一个

例如当前登录用户 82001 修改 id = 235 的 Moment 的 content:
{
   "Moment":{
     "id":235,
     "content":"APIJSON,let interfaces and documents go to hell !"
   },
   "tag":"Moment"
}
后端校验通过后自动解析为 SQL 并执行:
`UPDATE Moment SET content='APIJSON,let interfaces and documents go to hell !' WHERE id=235 AND userId=82001 LIMIT 1`
批量除了 id{}:\[] 也可类似批量 POST,只是每个 {...} 里面都必须有 id | 同POST +PUT:
修改数据,
只修改所传的字段 | base_url/put/ | {
   TableName:{
     "id":id,
     …
   },
   "tag":tag
}
{…} 中 id 或 id{} 至少传一个

例如当前登录用户 82001 修改 id = 235 的 Moment 的 content:
{
   "Moment":{
     "id":235,
     "content":"APIJSON,let interfaces and documents go to hell !"
   },
   "tag":"Moment"
}
后端校验通过后自动解析为 SQL 并执行:
`UPDATE Moment SET content='APIJSON,let interfaces and documents go to hell !' WHERE id=235 AND userId=82001 LIMIT 1`

批量除了 id{}:\[] 也可类似批量 POST,只是每个 {...} 里面都必须有 id | 同POST DELETE:
删除数据 | base_url/delete/ | {
   TableName:{
     "id":id
   },
   "tag":tag
}
{…} 中 id 或 id{} 至少传一个,一般只传 id 或 id{}

例如当前登录用户 82001 批量删除 id = 100,110,120 的 Comment:
{
   "Comment":{
     "id{}":[100,110,120]
   },
   "tag":"Comment[]"
}
后端校验通过后自动解析为 SQL 并执行:
`DELETE FROM Comment WHERE id IN(100,110,120) AND userId=82001 LIMIT 3` | {
   TableName:{
     "code":200,
     "msg":"success",
     "id[]":[100,110,120]
      "count":3
   },
   "code":200,
   "msg":"success"
}
例如
{
   "Comment":{
      "code":200,
      "msg":"success",
      "id[]":[100,110,120],
      "count":3
   },
   "code":200,
   "msg":"success"
} From 46f82aa8ac7be054f3cf563611b203a3de7ede55 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Sat, 20 Feb 2021 16:26:02 +0800 Subject: [PATCH 0287/1181] Update Document.md --- Document.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Document.md b/Document.md index acc2a0c95..45cd44b5f 100644 --- a/Document.md +++ b/Document.md @@ -334,7 +334,7 @@ GET:
普通获取数据,
可用浏览器调试 | base_url/get/ | {< HEAD:
普通获取数量,
可用浏览器调试 | base_url/head/ | {
   TableName:{
     …
   }
}
{…}内为限制条件

例如获取一个 id = 38710 的 User 所发布的 Moment 总数:
{
   "Moment":{
     "userId":38710
   }
}
后端校验通过后自动解析为 SQL 并执行:
`SELECT count(*) FROM Moment WHERE userId=38710 LIMIT 1` | {
   TableName:{
     "code":200,
     "msg":"success",
     "count":10
   },
   "code":200,
   "msg":"success"
}
例如
{
   "Moment":{
     "code":200,
     "msg":"success",
     "count":10
   },
   "code":200,
   "msg":"success"
} GETS:
安全/私密获取数据,
用于获取钱包等
对安全性要求高的数据 | base_url/gets/ | 最外层加一个 "tag":tag,其它同GET | 同GET HEADS:
安全/私密获取数量,
用于获取银行卡数量等
对安全性要求高的数据总数 | base_url/heads/ | 最外层加一个 "tag":tag,其它同HEAD | 同HEAD -POST:
新增数据 | base_url/post/ | 单个:
{
   TableName:{
     …
   },
   "tag":tag
}
{…}中id由后端生成,不能传

例如当前登录用户 38710 发布一个新 Comment:
{
   "Comment":{
     "momentId":12,
     "content":"APIJSON,let interfaces and documents go to hell !",
   },
   "tag":"Comment"
}
后端校验通过后自动解析为 SQL 并执行:
`INSERT INTO Comment(userId,momentId,content) VALUES(38710,12,'APIJSON,let interfaces and documents go to hell !')`

批量:
{
   TableName\[]:\[{
       …
     }, {
       …
     }
     …
   ],
   "tag":tag
}
{…}中id由后端生成,不能传

例如当前登录用户 82001 发布 2 个 Comment:
{
   "Comment\[]":\[{
     "momentId":12,
     "content":"APIJSON,let interfaces and documents go to hell !"
     }, {
     "momentId":15,
     "content":"APIJSON is a JSON transmision protocol."
   }],
   "tag":"Comment[]"
}
后端校验通过后自动解析为 SQL 并执行:
`INSERT INTO Comment(userId,momentId,content) VALUES(82001,12,'APIJSON,let interfaces and documents go to hell !')`
`INSERT INTO Comment(userId,momentId,content) VALUES(82001,15,'APIJSON is a JSON transmision protocol.')` | 单个:
{
   TableName:{
     "code":200,
     "msg":"success",
     "id":38710
   },
   "code":200,
   "msg":"success"
}
例如
{
   "Comment":{
     "code":200,
     "msg":"success",
     "id":120
   },
   "code":200,
   "msg":"success"
}

批量:
{
   TableName:{
     "code":200,
     "msg":"success",
     "count":5,
     "id[]":[1, 2, 3, 4, 5]
   },
   "code":200,
   "msg":"success"
}
例如
{
   "Comment":{
     "code":200,
     "msg":"success",
     "count":2,
     "id[]":\[1, 2]
   },
   "code":200,
   "msg":"success"
} +POST:
新增数据 | base_url/post/ | 单个:
{
   TableName:{
     …
   },
   "tag":tag
}
{…}中id由后端生成,不能传

例如当前登录用户 38710 发布一个新 Comment:
{
   "Comment":{
     "momentId":12,
     "content":"APIJSON,let interfaces and documents go to hell !",
   },
   "tag":"Comment"
}
后端校验通过后自动解析为 SQL 并执行:
`INSERT INTO Comment(userId,momentId,content) VALUES(38710,12,'APIJSON,let interfaces and documents go to hell !')`

批量:
{
   TableName\[]:\[{
       …
     }, {
       …
     }
     …
   ],
   "tag":tag
}
{…}中id由后端生成,不能传

例如当前登录用户 82001 发布 2 个 Comment:
{
   "Comment\[]":\[{
     "momentId":12,
     "content":"APIJSON,let interfaces and documents go to hell !"
     }, {
     "momentId":15,
     "content":"APIJSON is a JSON transmision protocol."
   }],
   "tag":"Comment:[]" //Comment[] 表示所有项全部统一设置,Comment:[] 多了冒号标识每项分别单独设置
}
后端校验通过后自动解析为 SQL 并执行:
`INSERT INTO Comment(userId,momentId,content) VALUES(82001,12,'APIJSON,let interfaces and documents go to hell !')`
`INSERT INTO Comment(userId,momentId,content) VALUES(82001,15,'APIJSON is a JSON transmision protocol.')` | 单个:
{
   TableName:{
     "code":200,
     "msg":"success",
     "id":38710
   },
   "code":200,
   "msg":"success"
}
例如
{
   "Comment":{
     "code":200,
     "msg":"success",
     "id":120
   },
   "code":200,
   "msg":"success"
}

批量:
{
   TableName:{
     "code":200,
     "msg":"success",
     "count":5,
     "id[]":[1, 2, 3, 4, 5]
   },
   "code":200,
   "msg":"success"
}
例如
{
   "Comment":{
     "code":200,
     "msg":"success",
     "count":2,
     "id[]":\[1, 2]
   },
   "code":200,
   "msg":"success"
} PUT:
修改数据,
只修改所传的字段 | base_url/put/ | {
   TableName:{
     "id":id,
     …
   },
   "tag":tag
}
{…} 中 id 或 id{} 至少传一个

例如当前登录用户 82001 修改 id = 235 的 Moment 的 content:
{
   "Moment":{
     "id":235,
     "content":"APIJSON,let interfaces and documents go to hell !"
   },
   "tag":"Moment"
}
后端校验通过后自动解析为 SQL 并执行:
`UPDATE Moment SET content='APIJSON,let interfaces and documents go to hell !' WHERE id=235 AND userId=82001 LIMIT 1`

批量除了 id{}:\[] 也可类似批量 POST,只是每个 {...} 里面都必须有 id | 同POST DELETE:
删除数据 | base_url/delete/ | {
   TableName:{
     "id":id
   },
   "tag":tag
}
{…} 中 id 或 id{} 至少传一个,一般只传 id 或 id{}

例如当前登录用户 82001 批量删除 id = 100,110,120 的 Comment:
{
   "Comment":{
     "id{}":[100,110,120]
   },
   "tag":"Comment[]"
}
后端校验通过后自动解析为 SQL 并执行:
`DELETE FROM Comment WHERE id IN(100,110,120) AND userId=82001 LIMIT 3` | {
   TableName:{
     "code":200,
     "msg":"success",
     "id[]":[100,110,120]
      "count":3
   },
   "code":200,
   "msg":"success"
}
例如
{
   "Comment":{
      "code":200,
      "msg":"success",
      "id[]":[100,110,120],
      "count":3
   },
   "code":200,
   "msg":"success"
} From 1784f812e5ca1a507beedec3cae61f84a001c8d8 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Sat, 20 Feb 2021 16:27:03 +0800 Subject: [PATCH 0288/1181] Update Document.md --- Document.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Document.md b/Document.md index 45cd44b5f..1bd3e2980 100644 --- a/Document.md +++ b/Document.md @@ -334,7 +334,7 @@ GET:
普通获取数据,
可用浏览器调试 | base_url/get/ | {< HEAD:
普通获取数量,
可用浏览器调试 | base_url/head/ | {
   TableName:{
     …
   }
}
{…}内为限制条件

例如获取一个 id = 38710 的 User 所发布的 Moment 总数:
{
   "Moment":{
     "userId":38710
   }
}
后端校验通过后自动解析为 SQL 并执行:
`SELECT count(*) FROM Moment WHERE userId=38710 LIMIT 1` | {
   TableName:{
     "code":200,
     "msg":"success",
     "count":10
   },
   "code":200,
   "msg":"success"
}
例如
{
   "Moment":{
     "code":200,
     "msg":"success",
     "count":10
   },
   "code":200,
   "msg":"success"
} GETS:
安全/私密获取数据,
用于获取钱包等
对安全性要求高的数据 | base_url/gets/ | 最外层加一个 "tag":tag,其它同GET | 同GET HEADS:
安全/私密获取数量,
用于获取银行卡数量等
对安全性要求高的数据总数 | base_url/heads/ | 最外层加一个 "tag":tag,其它同HEAD | 同HEAD -POST:
新增数据 | base_url/post/ | 单个:
{
   TableName:{
     …
   },
   "tag":tag
}
{…}中id由后端生成,不能传

例如当前登录用户 38710 发布一个新 Comment:
{
   "Comment":{
     "momentId":12,
     "content":"APIJSON,let interfaces and documents go to hell !",
   },
   "tag":"Comment"
}
后端校验通过后自动解析为 SQL 并执行:
`INSERT INTO Comment(userId,momentId,content) VALUES(38710,12,'APIJSON,let interfaces and documents go to hell !')`

批量:
{
   TableName\[]:\[{
       …
     }, {
       …
     }
     …
   ],
   "tag":tag
}
{…}中id由后端生成,不能传

例如当前登录用户 82001 发布 2 个 Comment:
{
   "Comment\[]":\[{
     "momentId":12,
     "content":"APIJSON,let interfaces and documents go to hell !"
     }, {
     "momentId":15,
     "content":"APIJSON is a JSON transmision protocol."
   }],
   "tag":"Comment:[]" //Comment[] 表示所有项全部统一设置,Comment:[] 多了冒号标识每项分别单独设置
}
后端校验通过后自动解析为 SQL 并执行:
`INSERT INTO Comment(userId,momentId,content) VALUES(82001,12,'APIJSON,let interfaces and documents go to hell !')`
`INSERT INTO Comment(userId,momentId,content) VALUES(82001,15,'APIJSON is a JSON transmision protocol.')` | 单个:
{
   TableName:{
     "code":200,
     "msg":"success",
     "id":38710
   },
   "code":200,
   "msg":"success"
}
例如
{
   "Comment":{
     "code":200,
     "msg":"success",
     "id":120
   },
   "code":200,
   "msg":"success"
}

批量:
{
   TableName:{
     "code":200,
     "msg":"success",
     "count":5,
     "id[]":[1, 2, 3, 4, 5]
   },
   "code":200,
   "msg":"success"
}
例如
{
   "Comment":{
     "code":200,
     "msg":"success",
     "count":2,
     "id[]":\[1, 2]
   },
   "code":200,
   "msg":"success"
} +POST:
新增数据 | base_url/post/ | 单个:
{
   TableName:{
     …
   },
   "tag":tag
}
{…}中id由后端生成,不能传

例如当前登录用户 38710 发布一个新 Comment:
{
   "Comment":{
     "momentId":12,
     "content":"APIJSON,let interfaces and documents go to hell !",
   },
   "tag":"Comment"
}
后端校验通过后自动解析为 SQL 并执行:
`INSERT INTO Comment(userId,momentId,content) VALUES(38710,12,'APIJSON,let interfaces and documents go to hell !')`

批量:
{
   TableName\[]:\[{
       …
     }, {
       …
     }
     …
   ],
   "tag":tag
}
{…}中id由后端生成,不能传

例如当前登录用户 82001 发布 2 个 Comment:
{
   "Comment\[]":\[{
     "momentId":12,
     "content":"APIJSON,let interfaces and documents go to hell !"
     }, {
     "momentId":15,
     "content":"APIJSON is a JSON transmision protocol."
   }],
   "tag":"Comment:[]" //Comment[] 表示全部统一设置,Comment:[] 多了冒号表示每项单独设置
}
后端校验通过后自动解析为 SQL 并执行:
`INSERT INTO Comment(userId,momentId,content) VALUES(82001,12,'APIJSON,let interfaces and documents go to hell !')`
`INSERT INTO Comment(userId,momentId,content) VALUES(82001,15,'APIJSON is a JSON transmision protocol.')` | 单个:
{
   TableName:{
     "code":200,
     "msg":"success",
     "id":38710
   },
   "code":200,
   "msg":"success"
}
例如
{
   "Comment":{
     "code":200,
     "msg":"success",
     "id":120
   },
   "code":200,
   "msg":"success"
}

批量:
{
   TableName:{
     "code":200,
     "msg":"success",
     "count":5,
     "id[]":[1, 2, 3, 4, 5]
   },
   "code":200,
   "msg":"success"
}
例如
{
   "Comment":{
     "code":200,
     "msg":"success",
     "count":2,
     "id[]":\[1, 2]
   },
   "code":200,
   "msg":"success"
} PUT:
修改数据,
只修改所传的字段 | base_url/put/ | {
   TableName:{
     "id":id,
     …
   },
   "tag":tag
}
{…} 中 id 或 id{} 至少传一个

例如当前登录用户 82001 修改 id = 235 的 Moment 的 content:
{
   "Moment":{
     "id":235,
     "content":"APIJSON,let interfaces and documents go to hell !"
   },
   "tag":"Moment"
}
后端校验通过后自动解析为 SQL 并执行:
`UPDATE Moment SET content='APIJSON,let interfaces and documents go to hell !' WHERE id=235 AND userId=82001 LIMIT 1`

批量除了 id{}:\[] 也可类似批量 POST,只是每个 {...} 里面都必须有 id | 同POST DELETE:
删除数据 | base_url/delete/ | {
   TableName:{
     "id":id
   },
   "tag":tag
}
{…} 中 id 或 id{} 至少传一个,一般只传 id 或 id{}

例如当前登录用户 82001 批量删除 id = 100,110,120 的 Comment:
{
   "Comment":{
     "id{}":[100,110,120]
   },
   "tag":"Comment[]"
}
后端校验通过后自动解析为 SQL 并执行:
`DELETE FROM Comment WHERE id IN(100,110,120) AND userId=82001 LIMIT 3` | {
   TableName:{
     "code":200,
     "msg":"success",
     "id[]":[100,110,120]
      "count":3
   },
   "code":200,
   "msg":"success"
}
例如
{
   "Comment":{
      "code":200,
      "msg":"success",
      "id[]":[100,110,120],
      "count":3
   },
   "code":200,
   "msg":"success"
} From 48bb2409ece893827d0df2316d8be6faf4a49370 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Sat, 20 Feb 2021 16:31:31 +0800 Subject: [PATCH 0289/1181] Update Document.md --- Document.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Document.md b/Document.md index 1bd3e2980..5c3e763bf 100644 --- a/Document.md +++ b/Document.md @@ -334,7 +334,7 @@ GET:
普通获取数据,
可用浏览器调试 | base_url/get/ | {< HEAD:
普通获取数量,
可用浏览器调试 | base_url/head/ | {
   TableName:{
     …
   }
}
{…}内为限制条件

例如获取一个 id = 38710 的 User 所发布的 Moment 总数:
{
   "Moment":{
     "userId":38710
   }
}
后端校验通过后自动解析为 SQL 并执行:
`SELECT count(*) FROM Moment WHERE userId=38710 LIMIT 1` | {
   TableName:{
     "code":200,
     "msg":"success",
     "count":10
   },
   "code":200,
   "msg":"success"
}
例如
{
   "Moment":{
     "code":200,
     "msg":"success",
     "count":10
   },
   "code":200,
   "msg":"success"
} GETS:
安全/私密获取数据,
用于获取钱包等
对安全性要求高的数据 | base_url/gets/ | 最外层加一个 "tag":tag,其它同GET | 同GET HEADS:
安全/私密获取数量,
用于获取银行卡数量等
对安全性要求高的数据总数 | base_url/heads/ | 最外层加一个 "tag":tag,其它同HEAD | 同HEAD -POST:
新增数据 | base_url/post/ | 单个:
{
   TableName:{
     …
   },
   "tag":tag
}
{…}中id由后端生成,不能传

例如当前登录用户 38710 发布一个新 Comment:
{
   "Comment":{
     "momentId":12,
     "content":"APIJSON,let interfaces and documents go to hell !",
   },
   "tag":"Comment"
}
后端校验通过后自动解析为 SQL 并执行:
`INSERT INTO Comment(userId,momentId,content) VALUES(38710,12,'APIJSON,let interfaces and documents go to hell !')`

批量:
{
   TableName\[]:\[{
       …
     }, {
       …
     }
     …
   ],
   "tag":tag
}
{…}中id由后端生成,不能传

例如当前登录用户 82001 发布 2 个 Comment:
{
   "Comment\[]":\[{
     "momentId":12,
     "content":"APIJSON,let interfaces and documents go to hell !"
     }, {
     "momentId":15,
     "content":"APIJSON is a JSON transmision protocol."
   }],
   "tag":"Comment:[]" //Comment[] 表示全部统一设置,Comment:[] 多了冒号表示每项单独设置
}
后端校验通过后自动解析为 SQL 并执行:
`INSERT INTO Comment(userId,momentId,content) VALUES(82001,12,'APIJSON,let interfaces and documents go to hell !')`
`INSERT INTO Comment(userId,momentId,content) VALUES(82001,15,'APIJSON is a JSON transmision protocol.')` | 单个:
{
   TableName:{
     "code":200,
     "msg":"success",
     "id":38710
   },
   "code":200,
   "msg":"success"
}
例如
{
   "Comment":{
     "code":200,
     "msg":"success",
     "id":120
   },
   "code":200,
   "msg":"success"
}

批量:
{
   TableName:{
     "code":200,
     "msg":"success",
     "count":5,
     "id[]":[1, 2, 3, 4, 5]
   },
   "code":200,
   "msg":"success"
}
例如
{
   "Comment":{
     "code":200,
     "msg":"success",
     "count":2,
     "id[]":\[1, 2]
   },
   "code":200,
   "msg":"success"
} +POST:
新增数据 | base_url/post/ | 单个:
{
   TableName:{
     …
   },
   "tag":tag
}
{…}中id由后端生成,不能传

例如当前登录用户 38710 发布一个新 Comment:
{
   "Comment":{
     "momentId":12,
     "content":"APIJSON,let interfaces and documents go to hell !",
   },
   "tag":"Comment"
}
后端校验通过后自动解析为 SQL 并执行:
`INSERT INTO Comment(userId,momentId,content) VALUES(38710,12,'APIJSON,let interfaces and documents go to hell !')`

批量:
{
   TableName\[]:\[{
       …
     }, {
       …
     }
     …
   ],
   "tag":tag
}
{…}中id由后端生成,不能传

例如当前登录用户 82001 发布 2 个 Comment:
{
   "Comment\[]":\[{
     "momentId":12,
     "content":"APIJSON,let interfaces and documents go to hell !"
     }, {
     "momentId":15,
     "content":"APIJSON is a JSON transmision protocol."
   }],
   "tag":"Comment:[]" //Comment[] 表示 id{}:[] 指定记录全部统一设置,Comment:[] 多了冒号表示每项单独设置,两者对应结构不同
}
后端校验通过后自动解析为 SQL 并执行:
`INSERT INTO Comment(userId,momentId,content) VALUES(82001,12,'APIJSON,let interfaces and documents go to hell !')`
`INSERT INTO Comment(userId,momentId,content) VALUES(82001,15,'APIJSON is a JSON transmision protocol.')` | 单个:
{
   TableName:{
     "code":200,
     "msg":"success",
     "id":38710
   },
   "code":200,
   "msg":"success"
}
例如
{
   "Comment":{
     "code":200,
     "msg":"success",
     "id":120
   },
   "code":200,
   "msg":"success"
}

批量:
{
   TableName:{
     "code":200,
     "msg":"success",
     "count":5,
     "id[]":[1, 2, 3, 4, 5]
   },
   "code":200,
   "msg":"success"
}
例如
{
   "Comment":{
     "code":200,
     "msg":"success",
     "count":2,
     "id[]":\[1, 2]
   },
   "code":200,
   "msg":"success"
} PUT:
修改数据,
只修改所传的字段 | base_url/put/ | {
   TableName:{
     "id":id,
     …
   },
   "tag":tag
}
{…} 中 id 或 id{} 至少传一个

例如当前登录用户 82001 修改 id = 235 的 Moment 的 content:
{
   "Moment":{
     "id":235,
     "content":"APIJSON,let interfaces and documents go to hell !"
   },
   "tag":"Moment"
}
后端校验通过后自动解析为 SQL 并执行:
`UPDATE Moment SET content='APIJSON,let interfaces and documents go to hell !' WHERE id=235 AND userId=82001 LIMIT 1`

批量除了 id{}:\[] 也可类似批量 POST,只是每个 {...} 里面都必须有 id | 同POST DELETE:
删除数据 | base_url/delete/ | {
   TableName:{
     "id":id
   },
   "tag":tag
}
{…} 中 id 或 id{} 至少传一个,一般只传 id 或 id{}

例如当前登录用户 82001 批量删除 id = 100,110,120 的 Comment:
{
   "Comment":{
     "id{}":[100,110,120]
   },
   "tag":"Comment[]"
}
后端校验通过后自动解析为 SQL 并执行:
`DELETE FROM Comment WHERE id IN(100,110,120) AND userId=82001 LIMIT 3` | {
   TableName:{
     "code":200,
     "msg":"success",
     "id[]":[100,110,120]
      "count":3
   },
   "code":200,
   "msg":"success"
}
例如
{
   "Comment":{
      "code":200,
      "msg":"success",
      "id[]":[100,110,120],
      "count":3
   },
   "code":200,
   "msg":"success"
} From d064196adde941754c954a986bf611234af03f55 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Sat, 20 Feb 2021 16:33:23 +0800 Subject: [PATCH 0290/1181] Update Document.md --- Document.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Document.md b/Document.md index 5c3e763bf..51f4f6fd3 100644 --- a/Document.md +++ b/Document.md @@ -334,7 +334,7 @@ GET:
普通获取数据,
可用浏览器调试 | base_url/get/ | {< HEAD:
普通获取数量,
可用浏览器调试 | base_url/head/ | {
   TableName:{
     …
   }
}
{…}内为限制条件

例如获取一个 id = 38710 的 User 所发布的 Moment 总数:
{
   "Moment":{
     "userId":38710
   }
}
后端校验通过后自动解析为 SQL 并执行:
`SELECT count(*) FROM Moment WHERE userId=38710 LIMIT 1` | {
   TableName:{
     "code":200,
     "msg":"success",
     "count":10
   },
   "code":200,
   "msg":"success"
}
例如
{
   "Moment":{
     "code":200,
     "msg":"success",
     "count":10
   },
   "code":200,
   "msg":"success"
} GETS:
安全/私密获取数据,
用于获取钱包等
对安全性要求高的数据 | base_url/gets/ | 最外层加一个 "tag":tag,其它同GET | 同GET HEADS:
安全/私密获取数量,
用于获取银行卡数量等
对安全性要求高的数据总数 | base_url/heads/ | 最外层加一个 "tag":tag,其它同HEAD | 同HEAD -POST:
新增数据 | base_url/post/ | 单个:
{
   TableName:{
     …
   },
   "tag":tag
}
{…}中id由后端生成,不能传

例如当前登录用户 38710 发布一个新 Comment:
{
   "Comment":{
     "momentId":12,
     "content":"APIJSON,let interfaces and documents go to hell !",
   },
   "tag":"Comment"
}
后端校验通过后自动解析为 SQL 并执行:
`INSERT INTO Comment(userId,momentId,content) VALUES(38710,12,'APIJSON,let interfaces and documents go to hell !')`

批量:
{
   TableName\[]:\[{
       …
     }, {
       …
     }
     …
   ],
   "tag":tag
}
{…}中id由后端生成,不能传

例如当前登录用户 82001 发布 2 个 Comment:
{
   "Comment\[]":\[{
     "momentId":12,
     "content":"APIJSON,let interfaces and documents go to hell !"
     }, {
     "momentId":15,
     "content":"APIJSON is a JSON transmision protocol."
   }],
   "tag":"Comment:[]" //Comment[] 表示 id{}:[] 指定记录全部统一设置,Comment:[] 多了冒号表示每项单独设置,两者对应结构不同
}
后端校验通过后自动解析为 SQL 并执行:
`INSERT INTO Comment(userId,momentId,content) VALUES(82001,12,'APIJSON,let interfaces and documents go to hell !')`
`INSERT INTO Comment(userId,momentId,content) VALUES(82001,15,'APIJSON is a JSON transmision protocol.')` | 单个:
{
   TableName:{
     "code":200,
     "msg":"success",
     "id":38710
   },
   "code":200,
   "msg":"success"
}
例如
{
   "Comment":{
     "code":200,
     "msg":"success",
     "id":120
   },
   "code":200,
   "msg":"success"
}

批量:
{
   TableName:{
     "code":200,
     "msg":"success",
     "count":5,
     "id[]":[1, 2, 3, 4, 5]
   },
   "code":200,
   "msg":"success"
}
例如
{
   "Comment":{
     "code":200,
     "msg":"success",
     "count":2,
     "id[]":\[1, 2]
   },
   "code":200,
   "msg":"success"
} +POST:
新增数据 | base_url/post/ | 单个:
{
   TableName:{
     …
   },
   "tag":tag
}
{…}中id由后端生成,不能传

例如当前登录用户 38710 发布一个新 Comment:
{
   "Comment":{
     "momentId":12,
     "content":"APIJSON,let interfaces and documents go to hell !",
   },
   "tag":"Comment"
}
后端校验通过后自动解析为 SQL 并执行:
`INSERT INTO Comment(userId,momentId,content) VALUES(38710,12,'APIJSON,let interfaces and documents go to hell !')`

批量:
{
   TableName\[]:\[{
       …
     }, {
       …
     }
     …
   ],
   "tag":tag
}
{…}中id由后端生成,不能传

例如当前登录用户 82001 发布 2 个 Comment:
{
   "Comment\[]":\[{
     "momentId":12,
     "content":"APIJSON,let interfaces and documents go to hell !"
     }, {
     "momentId":15,
     "content":"APIJSON is a JSON transmision protocol."
   }],
   "tag":"Comment:[]" //Comment[] 对应有 id{}:[] 的对象,表示指定记录全部统一设置;Comment:[] 多了冒号,对应数组,表示每项单独设置
}
后端校验通过后自动解析为 SQL 并执行:
`INSERT INTO Comment(userId,momentId,content) VALUES(82001,12,'APIJSON,let interfaces and documents go to hell !')`
`INSERT INTO Comment(userId,momentId,content) VALUES(82001,15,'APIJSON is a JSON transmision protocol.')` | 单个:
{
   TableName:{
     "code":200,
     "msg":"success",
     "id":38710
   },
   "code":200,
   "msg":"success"
}
例如
{
   "Comment":{
     "code":200,
     "msg":"success",
     "id":120
   },
   "code":200,
   "msg":"success"
}

批量:
{
   TableName:{
     "code":200,
     "msg":"success",
     "count":5,
     "id[]":[1, 2, 3, 4, 5]
   },
   "code":200,
   "msg":"success"
}
例如
{
   "Comment":{
     "code":200,
     "msg":"success",
     "count":2,
     "id[]":\[1, 2]
   },
   "code":200,
   "msg":"success"
} PUT:
修改数据,
只修改所传的字段 | base_url/put/ | {
   TableName:{
     "id":id,
     …
   },
   "tag":tag
}
{…} 中 id 或 id{} 至少传一个

例如当前登录用户 82001 修改 id = 235 的 Moment 的 content:
{
   "Moment":{
     "id":235,
     "content":"APIJSON,let interfaces and documents go to hell !"
   },
   "tag":"Moment"
}
后端校验通过后自动解析为 SQL 并执行:
`UPDATE Moment SET content='APIJSON,let interfaces and documents go to hell !' WHERE id=235 AND userId=82001 LIMIT 1`

批量除了 id{}:\[] 也可类似批量 POST,只是每个 {...} 里面都必须有 id | 同POST DELETE:
删除数据 | base_url/delete/ | {
   TableName:{
     "id":id
   },
   "tag":tag
}
{…} 中 id 或 id{} 至少传一个,一般只传 id 或 id{}

例如当前登录用户 82001 批量删除 id = 100,110,120 的 Comment:
{
   "Comment":{
     "id{}":[100,110,120]
   },
   "tag":"Comment[]"
}
后端校验通过后自动解析为 SQL 并执行:
`DELETE FROM Comment WHERE id IN(100,110,120) AND userId=82001 LIMIT 3` | {
   TableName:{
     "code":200,
     "msg":"success",
     "id[]":[100,110,120]
      "count":3
   },
   "code":200,
   "msg":"success"
}
例如
{
   "Comment":{
      "code":200,
      "msg":"success",
      "id[]":[100,110,120],
      "count":3
   },
   "code":200,
   "msg":"success"
} From 42ad76397c99a7b26286cc7b46e150c797a48445 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Sat, 20 Feb 2021 16:39:53 +0800 Subject: [PATCH 0291/1181] Update Document.md --- Document.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Document.md b/Document.md index 51f4f6fd3..748919fbf 100644 --- a/Document.md +++ b/Document.md @@ -334,7 +334,7 @@ GET:
普通获取数据,
可用浏览器调试 | base_url/get/ | {< HEAD:
普通获取数量,
可用浏览器调试 | base_url/head/ | {
   TableName:{
     …
   }
}
{…}内为限制条件

例如获取一个 id = 38710 的 User 所发布的 Moment 总数:
{
   "Moment":{
     "userId":38710
   }
}
后端校验通过后自动解析为 SQL 并执行:
`SELECT count(*) FROM Moment WHERE userId=38710 LIMIT 1` | {
   TableName:{
     "code":200,
     "msg":"success",
     "count":10
   },
   "code":200,
   "msg":"success"
}
例如
{
   "Moment":{
     "code":200,
     "msg":"success",
     "count":10
   },
   "code":200,
   "msg":"success"
} GETS:
安全/私密获取数据,
用于获取钱包等
对安全性要求高的数据 | base_url/gets/ | 最外层加一个 "tag":tag,其它同GET | 同GET HEADS:
安全/私密获取数量,
用于获取银行卡数量等
对安全性要求高的数据总数 | base_url/heads/ | 最外层加一个 "tag":tag,其它同HEAD | 同HEAD -POST:
新增数据 | base_url/post/ | 单个:
{
   TableName:{
     …
   },
   "tag":tag
}
{…}中id由后端生成,不能传

例如当前登录用户 38710 发布一个新 Comment:
{
   "Comment":{
     "momentId":12,
     "content":"APIJSON,let interfaces and documents go to hell !",
   },
   "tag":"Comment"
}
后端校验通过后自动解析为 SQL 并执行:
`INSERT INTO Comment(userId,momentId,content) VALUES(38710,12,'APIJSON,let interfaces and documents go to hell !')`

批量:
{
   TableName\[]:\[{
       …
     }, {
       …
     }
     …
   ],
   "tag":tag
}
{…}中id由后端生成,不能传

例如当前登录用户 82001 发布 2 个 Comment:
{
   "Comment\[]":\[{
     "momentId":12,
     "content":"APIJSON,let interfaces and documents go to hell !"
     }, {
     "momentId":15,
     "content":"APIJSON is a JSON transmision protocol."
   }],
   "tag":"Comment:[]" //Comment[] 对应有 id{}:[] 的对象,表示指定记录全部统一设置;Comment:[] 多了冒号,对应数组,表示每项单独设置
}
后端校验通过后自动解析为 SQL 并执行:
`INSERT INTO Comment(userId,momentId,content) VALUES(82001,12,'APIJSON,let interfaces and documents go to hell !')`
`INSERT INTO Comment(userId,momentId,content) VALUES(82001,15,'APIJSON is a JSON transmision protocol.')` | 单个:
{
   TableName:{
     "code":200,
     "msg":"success",
     "id":38710
   },
   "code":200,
   "msg":"success"
}
例如
{
   "Comment":{
     "code":200,
     "msg":"success",
     "id":120
   },
   "code":200,
   "msg":"success"
}

批量:
{
   TableName:{
     "code":200,
     "msg":"success",
     "count":5,
     "id[]":[1, 2, 3, 4, 5]
   },
   "code":200,
   "msg":"success"
}
例如
{
   "Comment":{
     "code":200,
     "msg":"success",
     "count":2,
     "id[]":\[1, 2]
   },
   "code":200,
   "msg":"success"
} +POST:
新增数据 | base_url/post/ | 单个:
{
   TableName:{
     …
   },
   "tag":tag
}
{…}中id由后端生成,不能传

例如当前登录用户 38710 发布一个新 Comment:
{
   "Comment":{
     "momentId":12,
     "content":"APIJSON,let interfaces and documents go to hell !",
   },
   "tag":"Comment"
}
后端校验通过后自动解析为 SQL 并执行:
`INSERT INTO Comment(userId,momentId,content) VALUES(38710,12,'APIJSON,let interfaces and documents go to hell !')`

批量:
{
   TableName\[]:\[{
       …
     }, {
       …
     }
     …
   ],
   "tag":tag
}
{…}中id由后端生成,不能传

例如当前登录用户 82001 发布 2 个 Comment:
{
   "Comment\[]":\[{
     "momentId":12,
     "content":"APIJSON,let interfaces and documents go to hell !"
     }, {
     "momentId":15,
     "content":"APIJSON is a JSON transmision protocol."
   }],
   "tag":"Comment:[]" //Comment[] 对应对象 "Comment":{"id{}":[1,2,3]},表示指定记录全部统一设置;Comment:[] 多了冒号,对应数组 "Comment[]":[{"id":1},{"id":2},{"id":3}],表示每项单独设置
}
后端校验通过后自动解析为 SQL 并执行:
`INSERT INTO Comment(userId,momentId,content) VALUES(82001,12,'APIJSON,let interfaces and documents go to hell !')`
`INSERT INTO Comment(userId,momentId,content) VALUES(82001,15,'APIJSON is a JSON transmision protocol.')` | 单个:
{
   TableName:{
     "code":200,
     "msg":"success",
     "id":38710
   },
   "code":200,
   "msg":"success"
}
例如
{
   "Comment":{
     "code":200,
     "msg":"success",
     "id":120
   },
   "code":200,
   "msg":"success"
}

批量:
{
   TableName:{
     "code":200,
     "msg":"success",
     "count":5,
     "id[]":[1, 2, 3, 4, 5]
   },
   "code":200,
   "msg":"success"
}
例如
{
   "Comment":{
     "code":200,
     "msg":"success",
     "count":2,
     "id[]":\[1, 2]
   },
   "code":200,
   "msg":"success"
} PUT:
修改数据,
只修改所传的字段 | base_url/put/ | {
   TableName:{
     "id":id,
     …
   },
   "tag":tag
}
{…} 中 id 或 id{} 至少传一个

例如当前登录用户 82001 修改 id = 235 的 Moment 的 content:
{
   "Moment":{
     "id":235,
     "content":"APIJSON,let interfaces and documents go to hell !"
   },
   "tag":"Moment"
}
后端校验通过后自动解析为 SQL 并执行:
`UPDATE Moment SET content='APIJSON,let interfaces and documents go to hell !' WHERE id=235 AND userId=82001 LIMIT 1`

批量除了 id{}:\[] 也可类似批量 POST,只是每个 {...} 里面都必须有 id | 同POST DELETE:
删除数据 | base_url/delete/ | {
   TableName:{
     "id":id
   },
   "tag":tag
}
{…} 中 id 或 id{} 至少传一个,一般只传 id 或 id{}

例如当前登录用户 82001 批量删除 id = 100,110,120 的 Comment:
{
   "Comment":{
     "id{}":[100,110,120]
   },
   "tag":"Comment[]"
}
后端校验通过后自动解析为 SQL 并执行:
`DELETE FROM Comment WHERE id IN(100,110,120) AND userId=82001 LIMIT 3` | {
   TableName:{
     "code":200,
     "msg":"success",
     "id[]":[100,110,120]
      "count":3
   },
   "code":200,
   "msg":"success"
}
例如
{
   "Comment":{
      "code":200,
      "msg":"success",
      "id[]":[100,110,120],
      "count":3
   },
   "code":200,
   "msg":"success"
} From 706bf929fbd5063050f645bc8d5cd287565247a4 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Sat, 20 Feb 2021 16:42:48 +0800 Subject: [PATCH 0292/1181] Update Document.md --- Document.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Document.md b/Document.md index 748919fbf..701932c9b 100644 --- a/Document.md +++ b/Document.md @@ -334,8 +334,8 @@ GET:
普通获取数据,
可用浏览器调试 | base_url/get/ | {< HEAD:
普通获取数量,
可用浏览器调试 | base_url/head/ | {
   TableName:{
     …
   }
}
{…}内为限制条件

例如获取一个 id = 38710 的 User 所发布的 Moment 总数:
{
   "Moment":{
     "userId":38710
   }
}
后端校验通过后自动解析为 SQL 并执行:
`SELECT count(*) FROM Moment WHERE userId=38710 LIMIT 1` | {
   TableName:{
     "code":200,
     "msg":"success",
     "count":10
   },
   "code":200,
   "msg":"success"
}
例如
{
   "Moment":{
     "code":200,
     "msg":"success",
     "count":10
   },
   "code":200,
   "msg":"success"
} GETS:
安全/私密获取数据,
用于获取钱包等
对安全性要求高的数据 | base_url/gets/ | 最外层加一个 "tag":tag,其它同GET | 同GET HEADS:
安全/私密获取数量,
用于获取银行卡数量等
对安全性要求高的数据总数 | base_url/heads/ | 最外层加一个 "tag":tag,其它同HEAD | 同HEAD -POST:
新增数据 | base_url/post/ | 单个:
{
   TableName:{
     …
   },
   "tag":tag
}
{…}中id由后端生成,不能传

例如当前登录用户 38710 发布一个新 Comment:
{
   "Comment":{
     "momentId":12,
     "content":"APIJSON,let interfaces and documents go to hell !",
   },
   "tag":"Comment"
}
后端校验通过后自动解析为 SQL 并执行:
`INSERT INTO Comment(userId,momentId,content) VALUES(38710,12,'APIJSON,let interfaces and documents go to hell !')`

批量:
{
   TableName\[]:\[{
       …
     }, {
       …
     }
     …
   ],
   "tag":tag
}
{…}中id由后端生成,不能传

例如当前登录用户 82001 发布 2 个 Comment:
{
   "Comment\[]":\[{
     "momentId":12,
     "content":"APIJSON,let interfaces and documents go to hell !"
     }, {
     "momentId":15,
     "content":"APIJSON is a JSON transmision protocol."
   }],
   "tag":"Comment:[]" //Comment[] 对应对象 "Comment":{"id{}":[1,2,3]},表示指定记录全部统一设置;Comment:[] 多了冒号,对应数组 "Comment[]":[{"id":1},{"id":2},{"id":3}],表示每项单独设置
}
后端校验通过后自动解析为 SQL 并执行:
`INSERT INTO Comment(userId,momentId,content) VALUES(82001,12,'APIJSON,let interfaces and documents go to hell !')`
`INSERT INTO Comment(userId,momentId,content) VALUES(82001,15,'APIJSON is a JSON transmision protocol.')` | 单个:
{
   TableName:{
     "code":200,
     "msg":"success",
     "id":38710
   },
   "code":200,
   "msg":"success"
}
例如
{
   "Comment":{
     "code":200,
     "msg":"success",
     "id":120
   },
   "code":200,
   "msg":"success"
}

批量:
{
   TableName:{
     "code":200,
     "msg":"success",
     "count":5,
     "id[]":[1, 2, 3, 4, 5]
   },
   "code":200,
   "msg":"success"
}
例如
{
   "Comment":{
     "code":200,
     "msg":"success",
     "count":2,
     "id[]":\[1, 2]
   },
   "code":200,
   "msg":"success"
} -PUT:
修改数据,
只修改所传的字段 | base_url/put/ | {
   TableName:{
     "id":id,
     …
   },
   "tag":tag
}
{…} 中 id 或 id{} 至少传一个

例如当前登录用户 82001 修改 id = 235 的 Moment 的 content:
{
   "Moment":{
     "id":235,
     "content":"APIJSON,let interfaces and documents go to hell !"
   },
   "tag":"Moment"
}
后端校验通过后自动解析为 SQL 并执行:
`UPDATE Moment SET content='APIJSON,let interfaces and documents go to hell !' WHERE id=235 AND userId=82001 LIMIT 1`

批量除了 id{}:\[] 也可类似批量 POST,只是每个 {...} 里面都必须有 id | 同POST +POST:
新增数据 | base_url/post/ | 单个:
{
   TableName:{
     …
   },
   "tag":tag
}
{…}中id由后端生成,不能传

例如当前登录用户 38710 发布一个新 Comment:
{
   "Comment":{
     "momentId":12,
     "content":"APIJSON,let interfaces and documents go to hell !",
   },
   "tag":"Comment"
}
后端校验通过后自动解析为 SQL 并执行:
`INSERT INTO Comment(userId,momentId,content) VALUES(38710,12,'APIJSON,let interfaces and documents go to hell !')`

批量:
{
   TableName\[]:\[{
       …
     }, {
       …
     }
     …
   ],
   "tag":tag
}
{…}中id由后端生成,不能传

例如当前登录用户 82001 发布 2 个 Comment:
{
   "Comment\[]":\[{
     "momentId":12,
     "content":"APIJSON,let interfaces and documents go to hell !"
     }, {
     "momentId":15,
     "content":"APIJSON is a JSON transmision protocol."
   }],
   "tag":"Comment:[]"
}
后端校验通过后自动解析为 SQL 并执行:
`INSERT INTO Comment(userId,momentId,content) VALUES(82001,12,'APIJSON,let interfaces and documents go to hell !')`
`INSERT INTO Comment(userId,momentId,content) VALUES(82001,15,'APIJSON is a JSON transmision protocol.')` | 单个:
{
   TableName:{
     "code":200,
     "msg":"success",
     "id":38710
   },
   "code":200,
   "msg":"success"
}
例如
{
   "Comment":{
     "code":200,
     "msg":"success",
     "id":120
   },
   "code":200,
   "msg":"success"
}

批量:
{
   TableName:{
     "code":200,
     "msg":"success",
     "count":5,
     "id[]":[1, 2, 3, 4, 5]
   },
   "code":200,
   "msg":"success"
}
例如
{
   "Comment":{
     "code":200,
     "msg":"success",
     "count":2,
     "id[]":\[1, 2]
   },
   "code":200,
   "msg":"success"
} +PUT:
修改数据,
只修改所传的字段 | base_url/put/ | {
   TableName:{
     "id":id,
     …
   },
   "tag":tag
}
{…} 中 id 或 id{} 至少传一个

例如当前登录用户 82001 修改 id = 235 的 Moment 的 content:
{
   "Moment":{
     "id":235,
     "content":"APIJSON,let interfaces and documents go to hell !"
   },
   "tag":"Moment"
}
后端校验通过后自动解析为 SQL 并执行:
`UPDATE Moment SET content='APIJSON,let interfaces and documents go to hell !' WHERE id=235 AND userId=82001 LIMIT 1`

批量除了 id{}:\[] 也可类似批量 POST,只是每个 {...} 里面都必须有 id。
"tag":"Comment[]" 对应对象 "Comment":{"id{}":[1,2,3]},表示指定记录全部统一设置;
"tag":"Comment:[]" 多了冒号,对应数组 "Comment[]":[{"id":1},{"id":2},{"id":3}],表示每项单独设置 | 同POST DELETE:
删除数据 | base_url/delete/ | {
   TableName:{
     "id":id
   },
   "tag":tag
}
{…} 中 id 或 id{} 至少传一个,一般只传 id 或 id{}

例如当前登录用户 82001 批量删除 id = 100,110,120 的 Comment:
{
   "Comment":{
     "id{}":[100,110,120]
   },
   "tag":"Comment[]"
}
后端校验通过后自动解析为 SQL 并执行:
`DELETE FROM Comment WHERE id IN(100,110,120) AND userId=82001 LIMIT 3` | {
   TableName:{
     "code":200,
     "msg":"success",
     "id[]":[100,110,120]
      "count":3
   },
   "code":200,
   "msg":"success"
}
例如
{
   "Comment":{
      "code":200,
      "msg":"success",
      "id[]":[100,110,120],
      "count":3
   },
   "code":200,
   "msg":"success"
} From b11f138432bb31d40801d3ef11eb883c591ac8ff Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Fri, 26 Feb 2021 16:35:17 +0800 Subject: [PATCH 0293/1181] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index f60d6029d..7fa0cad82 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ This source code is licensed under the Apache License Version 2.0
APIJSON -

🏆 码云最有价值开源项目
🚀 后端接口和文档自动化,前端(客户端) 定制返回 JSON 的数据和结构!

+

🏆 码云最有价值开源项目
🚀 后端接口和文档零代码,前端(客户端) 定制返回 JSON 的数据和结构!

From b8c6ba1bbb067eab117da7048277a1a165d8eaac Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Sun, 7 Mar 2021 23:10:01 +0800 Subject: [PATCH 0294/1181] =?UTF-8?q?=E8=B4=A1=E7=8C=AE=E8=80=85=E6=96=B0?= =?UTF-8?q?=E5=A2=9E=E7=94=9F=E6=80=81=E9=A1=B9=E7=9B=AE=E5=8F=8A=E4=BD=9C?= =?UTF-8?q?=E8=80=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/README.md b/README.md index 7fa0cad82..41a7ac460 100644 --- a/README.md +++ b/README.md @@ -252,9 +252,12 @@ https://github.com/Tencent/APIJSON/issues/187
+ + + + + + + + + + + + + + +


From bf1fd02fa95771e8cc0980528381badc1ad635a6 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Sun, 7 Mar 2021 23:13:34 +0800 Subject: [PATCH 0295/1181] =?UTF-8?q?=E8=B4=A1=E7=8C=AE=E8=80=85=20?= =?UTF-8?q?=E6=96=B0=E5=A2=9E=E5=A4=9A=E4=B8=AA=E7=94=9F=E6=80=81=E9=A1=B9?= =?UTF-8?q?=E7=9B=AE=E5=8F=8A=E4=BD=9C=E8=80=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 41a7ac460..2fd4547a3 100644 --- a/README.md +++ b/README.md @@ -282,7 +282,7 @@ https://github.com/Tencent/APIJSON/issues/187 - +
From 3b82a7f783a07e19983dfc3554386588dd41a2ad Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Thu, 11 Mar 2021 13:09:40 +0800 Subject: [PATCH 0296/1181] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E5=AF=B9=E8=B4=A1?= =?UTF-8?q?=E7=8C=AE=E8=80=85=E7=9A=84=E8=AF=B4=E6=98=8E=EF=BC=8C=E5=8C=85?= =?UTF-8?q?=E6=8B=AC=E7=9F=A5=E4=B9=8E=E5=9F=BA=E7=A1=80=E7=A0=94=E5=8F=91?= =?UTF-8?q?=E6=9E=B6=E6=9E=84=E5=B8=88sunxiaoguang=E5=92=8C=E7=8E=B0?= =?UTF-8?q?=E5=B1=85=E7=BE=8E=E5=9B=BD=E6=B4=9B=E6=9D=89=E7=9F=B6=E7=9A=84?= =?UTF-8?q?ruoranw?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CONTRIBUTING.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 321f0e142..374f0c5ee 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -7,19 +7,19 @@ 非常感谢以下几位贡献者对于 APIJSON 的做出的贡献: -- [ruoranw](https://github.com/ruoranw) +- [ruoranw](https://github.com/ruoranw)(现居美国洛杉矶) - [zhoulingfengofcd](https://github.com/zhoulingfengofcd) - [Zerounary](https://github.com/Zerounary) - [vincentCheng](https://github.com/vincentCheng) - [justinfengchen](https://github.com/justinfengchen) - [linlwqq](https://github.com/linlwqq) -- [redcatmiss](https://github.com/redcatmiss) +- [redcatmiss](https://github.com/redcatmiss)(社保科技员工) - [linbren](https://github.com/linbren) - [jinzhongjian](https://github.com/jinzhongjian) - [CoolGeo2016](https://github.com/CoolGeo2016) - [1906522096](https://github.com/1906522096) - [github-ganyu](https://github.com/github-ganyu) -- [sunxiaoguang](https://github.com/sunxiaoguang) +- [sunxiaoguang](https://github.com/sunxiaoguang)(知乎基础研发架构师) #### 其中特别致谢:
justinfengchen 提交的 6 个 Commits, 对 APIJSON 做出了 3,130 增加和 0 处删减(截止 2020/11/04 日);
From da1b21d1662ce3ebf8dee4beebb73e4e59b23571 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Fri, 19 Mar 2021 15:35:37 +0800 Subject: [PATCH 0297/1181] update users and contributors --- README-English.md | 86 ++++++++++++++++++++++++++++------------------- 1 file changed, 52 insertions(+), 34 deletions(-) diff --git a/README-English.md b/README-English.md index 21b650aba..9d5cf49be 100644 --- a/README-English.md +++ b/README-English.md @@ -303,17 +303,18 @@ If you have any questions or suggestions, you can [create an issue](https://gith ### Users of this project:
- - - - - - - - - - - + + + + + + + + + + + +
[More APIJSON Users](https://github.com/Tencent/APIJSON/issues/73) @@ -322,35 +323,52 @@ If you have any questions or suggestions, you can [create an issue](https://gith Here are the contributers of this project and authors of other projects for ecosystem of APIJSON:
- - - - - - - - - - - - + height="54" width="54" > + + + + + + + + + + + + +
+ - + height="54" width="54" > + + + height="54" width="54" > - + height="54" width="54" > + - + height="54" width="54" > + + + + + - - + height="54" width="54" > + + + + + + + + + + +

Thanks to all contributers of APIJSON! From a32d5f420ee816472777471c312aaf223ed6f77c Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Sat, 20 Mar 2021 16:25:14 +0800 Subject: [PATCH 0298/1181] =?UTF-8?q?=E8=A7=A3=E5=86=B3=E5=AF=B9=20Respons?= =?UTF-8?q?e=20=E6=A0=A1=E9=AA=8C=E5=8F=AA=E5=AF=B9=E6=9C=80=E5=A4=96?= =?UTF-8?q?=E5=B1=82=E7=94=9F=E6=95=88=EF=BC=9B=E7=A7=BB=E9=99=A4=E5=B7=B2?= =?UTF-8?q?=E5=BA=9F=E5=BC=83=E7=9A=84=20StructureUtil=20=E5=92=8C=20Test?= =?UTF-8?q?=20=E7=9A=84=E7=9B=B8=E5=85=B3=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/main/java/apijson/orm/AbstractVerifier.java | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractVerifier.java b/APIJSONORM/src/main/java/apijson/orm/AbstractVerifier.java index b85c1e19f..3ef1d5c4a 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractVerifier.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractVerifier.java @@ -70,7 +70,6 @@ import apijson.orm.model.SysColumn; import apijson.orm.model.SysTable; import apijson.orm.model.Table; -import apijson.orm.model.Test; import apijson.orm.model.TestRecord; /**校验器(权限、请求参数、返回结果等) @@ -121,7 +120,6 @@ public abstract class AbstractVerifier implements Verifier, IdCallback { SYSTEM_ACCESS_MAP.put(Function.class.getSimpleName(), getAccessMap(Function.class.getAnnotation(MethodAccess.class))); SYSTEM_ACCESS_MAP.put(Request.class.getSimpleName(), getAccessMap(Request.class.getAnnotation(MethodAccess.class))); SYSTEM_ACCESS_MAP.put(Response.class.getSimpleName(), getAccessMap(Response.class.getAnnotation(MethodAccess.class))); - SYSTEM_ACCESS_MAP.put(Test.class.getSimpleName(), getAccessMap(Test.class.getAnnotation(MethodAccess.class))); if (Log.DEBUG) { SYSTEM_ACCESS_MAP.put(Table.class.getSimpleName(), getAccessMap(Table.class.getAnnotation(MethodAccess.class))); @@ -724,7 +722,12 @@ public static JSONObject verifyResponse(@NotNull final RequestMethod method, fin } //解析 - return parse(method, name, target, response, database, schema, idKeyCallback, creator, callback != null ? callback : new OnParseCallback() {}); + return parse(method, name, target, response, database, schema, idKeyCallback, creator, callback != null ? callback : new OnParseCallback() { + @Override + protected JSONObject onParseJSONObject(String key, JSONObject tobj, JSONObject robj) throws Exception { + return verifyResponse(method, key, tobj, robj, database, schema, idKeyCallback, creator, callback); + } + }); } From 0a2cbc111a58791ce261747da9b929a9d0617274 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Sat, 20 Mar 2021 17:30:17 +0800 Subject: [PATCH 0299/1181] =?UTF-8?q?=E5=8D=87=E7=BA=A7=E7=89=88=E6=9C=AC?= =?UTF-8?q?=E5=8F=B7=E4=B8=BA=204.6.1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- APIJSONORM/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/APIJSONORM/pom.xml b/APIJSONORM/pom.xml index 2ae05b340..cb3b5333e 100755 --- a/APIJSONORM/pom.xml +++ b/APIJSONORM/pom.xml @@ -5,7 +5,7 @@ apijson.orm apijson-orm - 4.6.0 + 4.6.1 jar APIJSONORM From ba6884446594f0b705a23b5d4e9eec509ef24e2f Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Sat, 20 Mar 2021 17:54:25 +0800 Subject: [PATCH 0300/1181] =?UTF-8?q?=E5=88=A0=E9=99=A4=E5=B7=B2=E5=BA=9F?= =?UTF-8?q?=E5=BC=83=E7=9A=84=20Test=20=E7=B1=BB=E7=9A=84=E7=9B=B8?= =?UTF-8?q?=E5=85=B3=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/apijson/orm/AbstractSQLConfig.java | 2 -- .../src/main/java/apijson/orm/model/Test.java | 17 ----------------- 2 files changed, 19 deletions(-) delete mode 100755 APIJSONORM/src/main/java/apijson/orm/model/Test.java diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java index 6defdd1e1..242224102 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java @@ -68,7 +68,6 @@ import apijson.orm.model.SysColumn; import apijson.orm.model.SysTable; import apijson.orm.model.Table; -import apijson.orm.model.Test; import apijson.orm.model.TestRecord; /**config sql for JSON Request @@ -111,7 +110,6 @@ public abstract class AbstractSQLConfig implements SQLConfig { CONFIG_TABLE_LIST.add(Function.class.getSimpleName()); CONFIG_TABLE_LIST.add(Request.class.getSimpleName()); CONFIG_TABLE_LIST.add(Response.class.getSimpleName()); - CONFIG_TABLE_LIST.add(Test.class.getSimpleName()); CONFIG_TABLE_LIST.add(Access.class.getSimpleName()); CONFIG_TABLE_LIST.add(Document.class.getSimpleName()); CONFIG_TABLE_LIST.add(TestRecord.class.getSimpleName()); diff --git a/APIJSONORM/src/main/java/apijson/orm/model/Test.java b/APIJSONORM/src/main/java/apijson/orm/model/Test.java deleted file mode 100755 index 5b887e383..000000000 --- a/APIJSONORM/src/main/java/apijson/orm/model/Test.java +++ /dev/null @@ -1,17 +0,0 @@ -/*Copyright (C) 2020 THL A29 Limited, a Tencent company. All rights reserved. - -This source code is licensed under the Apache License Version 2.0.*/ - - -package apijson.orm.model; - -import apijson.MethodAccess; - -/**条件测试。最早可能 4.5.0 移除。AbstractSQLConfig 已支持 SELECT 2>1 这种简单条件表达式, - * 相当于是 SELECT 后只有 WHERE 条件表达式,其它全都没有,这样就可以去掉仅用来动态执行校验逻辑 Test 表了。 - * @author Lemon - */ -@Deprecated -@MethodAccess(POST = {}, PUT = {}, DELETE = {}) -public class Test { -} From 35aee2c9031a37e70f4fd98bdf70616fcda3a4fe Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Sat, 27 Mar 2021 11:18:06 +0800 Subject: [PATCH 0301/1181] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E7=94=A8=E6=88=B7?= =?UTF-8?q?=E5=8F=91=E7=9A=84=E6=96=87=E7=AB=A0=20=E5=85=A8=E5=9B=BD?= =?UTF-8?q?=E8=A1=8C=E6=94=BF=E5=8C=BA=E5=88=92=E6=95=B0=E6=8D=AE=E6=8A=93?= =?UTF-8?q?=E5=8F=96=E4=B8=8E=E5=A4=84=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit https://www.mdeditor.tw/pl/gCVk --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 2fd4547a3..b3afbb6f6 100644 --- a/README.md +++ b/README.md @@ -354,6 +354,7 @@ https://github.com/Tencent/APIJSON/blob/master/CONTRIBUTING.md [APIJSON复杂业务深入实践(类似12306订票系统)](https://blog.csdn.net/aa330233789/article/details/105309571) +[全国行政区划数据抓取与处理](https://www.mdeditor.tw/pl/gCVk) ### 生态项目 [APIJSON-Demo](https://github.com/APIJSON/APIJSON-Demo) APIJSON 各种语言、各种框架 的 使用示例项目、上手文档、测试数据 SQL 文件 等 From 386a02f2f3baeb30a9fd6b31a1e1fabf27f67f32 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Sat, 27 Mar 2021 16:47:56 +0800 Subject: [PATCH 0302/1181] =?UTF-8?q?=E3=80=90=E5=AE=89=E5=85=A8=E3=80=91?= =?UTF-8?q?=EF=BC=9A=E5=8A=A0=E5=BC=BA=E5=AF=B9=20JOIN=20=E7=9B=B8?= =?UTF-8?q?=E5=85=B3=E9=94=AE=E5=80=BC=E5=AF=B9=E7=9A=84=E6=A0=A1=E9=AA=8C?= =?UTF-8?q?=EF=BC=9B=E5=8A=A0=E5=BC=BA=E5=AF=B9=E5=91=BD=E5=90=8D=E7=9A=84?= =?UTF-8?q?=E6=A0=A1=E9=AA=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/main/java/apijson/StringUtil.java | 21 ++++-- .../main/java/apijson/orm/AbstractParser.java | 70 ++++++++++++++----- .../java/apijson/orm/AbstractSQLConfig.java | 9 +-- .../src/main/java/apijson/orm/Join.java | 17 +++-- 4 files changed, 85 insertions(+), 32 deletions(-) diff --git a/APIJSONORM/src/main/java/apijson/StringUtil.java b/APIJSONORM/src/main/java/apijson/StringUtil.java index 7f8fa831c..e32b41dac 100755 --- a/APIJSONORM/src/main/java/apijson/StringUtil.java +++ b/APIJSONORM/src/main/java/apijson/StringUtil.java @@ -399,29 +399,36 @@ public static boolean isNumberOrAlpha(String s) { * @return */ public static boolean isName(String s) { - return s != null && PATTERN_NAME.matcher(s).matches(); + if (s == null || s.isEmpty()) { + return false; + } + + String first = s.substring(0, 1); + if ("_".equals(first) == false && PATTERN_ALPHA.matcher(first).matches() == false) { + return false; + } + + return s.length() <= 1 ? true : PATTERN_NAME.matcher(s.substring(1)).matches(); } /**判断是否为首字母大写的代码名称 * @param s * @return */ public static boolean isBigName(String s) { - s = getString(s); - if (s.isEmpty() || PATTERN_ALPHA_BIG.matcher(s.substring(0, 1)).matches() == false) { + if (s == null || s.isEmpty() || PATTERN_ALPHA_BIG.matcher(s.substring(0, 1)).matches() == false) { return false; } - return s.length() <= 1 ? true : isName(s.substring(1)); + return s.length() <= 1 ? true : PATTERN_NAME.matcher(s.substring(1)).matches(); } /**判断是否为首字母小写的代码名称 * @param s * @return */ public static boolean isSmallName(String s) { - s = getString(s); - if (s.isEmpty() || PATTERN_ALPHA_SMALL.matcher(s.substring(0, 1)).matches() == false) { + if (s == null || s.isEmpty() || PATTERN_ALPHA_SMALL.matcher(s.substring(0, 1)).matches() == false) { return false; } - return s.length() <= 1 ? true : isName(s.substring(1)); + return s.length() <= 1 ? true : PATTERN_NAME.matcher(s.substring(1)).matches(); } diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java b/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java index 9fb17ee05..a22c05d5c 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java @@ -1062,7 +1062,7 @@ else if (join != null){ Set> set = joinMap == null ? null : joinMap.entrySet(); if (set == null || set.isEmpty()) { - Log.e(TAG, "doJoin set == null || set.isEmpty() >> return null;"); + Log.e(TAG, "onJoinParse set == null || set.isEmpty() >> return null;"); return null; } @@ -1092,7 +1092,7 @@ else if (join != null){ int index = path.indexOf("/"); if (index < 0) { - throw new IllegalArgumentException(JSONRequest.KEY_JOIN + ":value 中value不合法!" + throw new IllegalArgumentException(JSONRequest.KEY_JOIN + ":value 中 value 值 " + path + " 不合法!" + "必须为 &/Table0/key0, ( ) <> () * @@ -1105,37 +1105,70 @@ else if (join != null){ String tableKey = index < 0 ? null : path.substring(0, index); //User:owner apijson.orm.Entry entry = Pair.parseEntry(tableKey, true); String table = entry.getKey(); //User + if (StringUtil.isName(table) == false) { + throw new IllegalArgumentException(JSONRequest.KEY_JOIN + ":value 中 value 的 Table 值 " + table + " 不合法!" + + "必须为 &/Table0/key0, targetEntry = Pair.parseEntry(targetTableKey, true); + // targetTable = targetEntry.getKey(); //User + // if (StringUtil.isName(targetTable) == false) { + // throw new IllegalArgumentException("/" + path + ":'/targetTable/targetKey' 中 targetTable 值 " + targetTable + " 不合法!必须满足大写字母开头的表对象英文单词 key 格式!"); + // } + // + // String targetAlias = targetEntry.getValue(); //owner + // if (StringUtil.isNotEmpty(targetAlias, true) && StringUtil.isName(targetAlias) == false) { + // throw new IllegalArgumentException("/" + path + ":'/targetTable:targetAlias/targetKey' 中 targetAlias 值 " + targetAlias + " 不合法!必须满足英文单词变量名格式!"); + // } + + targetTable = targetTableKey; // 主表不允许别名 + if (StringUtil.isName(targetTable) == false) { + throw new IllegalArgumentException("/" + path + ":'/targetTable/targetKey' 中 targetTable 值 " + targetTable + " 不合法!必须满足大写字母开头的表对象英文单词 key 格式!"); + } + //对引用的JSONObject添加条件 - targetObj = request.getJSONObject(targetTable); + try { + targetObj = request.getJSONObject(targetTableKey); + } + catch (Exception e2) { + throw new IllegalArgumentException("/" + path + ":'/targetTable/targetKey' 中路径对应的 '" + targetTableKey + "':value 中 value 类型不合法!必须是 {} 这种 JSONObject 格式!" + e2.getMessage()); + } + if (targetObj == null) { - throw new IllegalArgumentException(targetTable + "." + targetKey - + ":'/targetTable/targetKey' 中路径对应的对象不存在!"); + throw new IllegalArgumentException("/" + path + ":'/targetTable/targetKey' 中路径对应的对象 '" + targetTableKey + "':{} 不存在或值为 null !必须是 {} 这种 JSONObject 格式!"); } tableObj.put(key, tableObj.remove(key)); //保证和SQLExcecutor缓存的Config里where顺序一致,生成的SQL也就一致 @@ -1147,11 +1180,16 @@ else if (join != null){ j.setJoinType(joinType); j.setTable(table); j.setAlias(alias); - j.setTargetName(targetTable); + j.setTargetTable(targetTable); + // j.setTargetAlias(targetAlias); j.setTargetKey(targetKey); j.setKeyAndType(key); j.setRequest(getJoinObject(table, tableObj, key)); j.setOuter((JSONObject) e.getValue()); + + if (StringUtil.isName(j.getKey()) == false) { + throw new IllegalArgumentException(JSONRequest.KEY_JOIN + ":value 中 value 的 key@ 中 key 值 " + j.getKey() + " 不合法!必须满足英文单词变量名格式!"); + } joinList.add(j); diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java index 242224102..72b3b32ef 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java @@ -2489,7 +2489,8 @@ public String getJoinString() throws Exception { String sql = null; SQLConfig jc; String jt; - String tn; + String tt; + String ta; for (Join j : joinList) { if (j.isAppJoin()) { // APP JOIN,只是作为一个标记,执行完主表的查询后自动执行副表的查询 User.id IN($commentIdList) continue; @@ -2502,7 +2503,7 @@ public String getJoinString() throws Exception { jc.setPrepared(isPrepared()); jt = StringUtil.isEmpty(jc.getAlias(), true) ? jc.getTable() : jc.getAlias(); - tn = j.getTargetName(); + tt = j.getTargetTable(); //如果要强制小写,则可在子类重写这个方法再 toLowerCase // if (DATABASE_POSTGRESQL.equals(getDatabase())) { @@ -2522,7 +2523,7 @@ public String getJoinString() throws Exception { sql = ( "<".equals(type) ? " LEFT" : (">".equals(type) ? " RIGHT" : " CROSS") ) + " JOIN ( " + jc.getSQL(isPrepared()) + " ) AS " + quote + jt + quote + " ON " + quote + jt + quote + "." + quote + j.getKey() + quote + " = " - + quote + tn + quote + "." + quote + j.getTargetKey() + quote; + + quote + tt + quote + "." + quote + j.getTargetKey() + quote; jc.setMain(false).setKeyPrefix(true); pvl.addAll(jc.getPreparedValueList()); @@ -2537,7 +2538,7 @@ public String getJoinString() throws Exception { case "(": // ANTI JOIN: A & ! B case ")": // FOREIGN JOIN: B & ! A sql = " INNER JOIN " + jc.getTablePath() - + " ON " + quote + jt + quote + "." + quote + j.getKey() + quote + " = " + quote + tn + quote + "." + quote + j.getTargetKey() + quote; + + " ON " + quote + jt + quote + "." + quote + j.getKey() + quote + " = " + quote + tt + quote + "." + quote + j.getTargetKey() + quote; break; default: throw new UnsupportedOperationException( diff --git a/APIJSONORM/src/main/java/apijson/orm/Join.java b/APIJSONORM/src/main/java/apijson/orm/Join.java index 168e3e77d..01761037a 100644 --- a/APIJSONORM/src/main/java/apijson/orm/Join.java +++ b/APIJSONORM/src/main/java/apijson/orm/Join.java @@ -25,7 +25,8 @@ public class Join { private String table; //User private String alias; //owner private String key; //id - private String targetName; // Moment + private String targetTable; // Moment + private String targetAlias; //main private String targetKey; // userId private JSONObject outter; @@ -91,11 +92,17 @@ public String getKey() { public void setKey(String key) { this.key = key; } - public String getTargetName() { - return targetName; + public void setTargetTable(String targetTable) { + this.targetTable = targetTable; } - public void setTargetName(String targetName) { - this.targetName = targetName; + public String getTargetTable() { + return targetTable; + } + public void setTargetAlias(String targetAlias) { + this.targetAlias = targetAlias; + } + public String getTargetAlias() { + return targetAlias; } public String getTargetKey() { return targetKey; From bae6febdbb4abe612692234b2ee2715c6fad8647 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Sat, 27 Mar 2021 19:26:06 +0800 Subject: [PATCH 0303/1181] =?UTF-8?q?=E3=80=90=E5=AE=89=E5=85=A8=E3=80=91?= =?UTF-8?q?=EF=BC=9A=E8=B0=83=E7=94=A8=20SQL=20=E5=87=BD=E6=95=B0=E5=8F=AA?= =?UTF-8?q?=E5=85=81=E8=AE=B8=E7=94=A8=E5=90=8E=E7=AB=AF=E5=B7=B2=E9=85=8D?= =?UTF-8?q?=E7=BD=AE=E7=9A=84=EF=BC=8C=E9=81=BF=E5=85=8D=20sleep(10)=20?= =?UTF-8?q?=E8=BF=99=E7=A7=8D=E5=91=BD=E4=BB=A4=E5=87=BD=E6=95=B0=E5=AF=BC?= =?UTF-8?q?=E8=87=B4=E6=95=B0=E6=8D=AE=E5=BA=93=E5=BC=82=E5=B8=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/apijson/orm/AbstractSQLConfig.java | 217 +++++++++++++++++- 1 file changed, 207 insertions(+), 10 deletions(-) diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java index 72b3b32ef..0fdba0b81 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java @@ -92,6 +92,8 @@ public abstract class AbstractSQLConfig implements SQLConfig { public static final List DATABASE_LIST; // 自定义原始 SQL 片段 Map:当 substring 为 null 时忽略;当 substring 为 "" 时整个 value 是 raw SQL;其它情况则只是 substring 这段为 raw SQL public static final Map RAW_MAP; + // 允许调用的 SQL 函数:当 substring 为 null 时忽略;当 substring 为 "" 时整个 value 是 raw SQL;其它情况则只是 substring 这段为 raw SQL + public static final Map SQL_FUNCTION_MAP; static { // 凡是 SQL 边界符、分隔符、注释符 都不允许,例如 ' " ` ( ) ; # -- ,以免拼接 SQL 时被注入意外可执行指令 PATTERN_RANGE = Pattern.compile("^[0-9%,!=\\<\\>/\\.\\+\\-\\*\\^]+$"); // ^[a-zA-Z0-9_*%!=<>(),"]+$ 导致 exists(select*from(Comment)) 通过! PATTERN_FUNCTION = Pattern.compile("^[A-Za-z0-9%,:_@&~!=\\<\\>\\|\\[\\]\\{\\} /\\.\\+\\-\\*\\^\\?\\$]+$"); //TODO 改成更好的正则,校验前面为单词,中间为操作符,后面为值 @@ -122,7 +124,185 @@ public abstract class AbstractSQLConfig implements SQLConfig { DATABASE_LIST.add(DATABASE_ORACLE); DATABASE_LIST.add(DATABASE_DB2); + RAW_MAP = new LinkedHashMap<>(); // 保证顺序,避免配置冲突等意外情况 + + + SQL_FUNCTION_MAP = new LinkedHashMap<>(); // 保证顺序,避免配置冲突等意外情况 + + // MySQL 字符串函数 + SQL_FUNCTION_MAP.put("ascii", ""); // ASCII(s) 返回字符串 s 的第一个字符的 ASCII 码。 + SQL_FUNCTION_MAP.put("char_length", ""); // CHAR_LENGTH(s) 返回字符串 s 的字符数 + SQL_FUNCTION_MAP.put("character_length", ""); // CHARACTER_LENGTH(s) 返回字符串 s 的字符数 + SQL_FUNCTION_MAP.put("concat", ""); // CONCAT(s1, s2...sn) 字符串 s1,s2 等多个字符串合并为一个字符串 + SQL_FUNCTION_MAP.put("concat_ws", ""); // CONCAT_WS(x, s1, s2...sn) 同 CONCAT(s1, s2 ...) 函数,但是每个字符串之间要加上 x,x 可以是分隔符 + SQL_FUNCTION_MAP.put("field", ""); // FIELD(s, s1, s2...) 返回第一个字符串 s 在字符串列表 (s1, s2...)中的位置 + SQL_FUNCTION_MAP.put("find_in_set", ""); // FIND_IN_SET(s1, s2) 返回在字符串s2中与s1匹配的字符串的位置 + SQL_FUNCTION_MAP.put("format", ""); // FORMAT(x, n) 函数可以将数字 x 进行格式化 "#,###.##", 将 x 保留到小数点后 n 位,最后一位四舍五入。 + SQL_FUNCTION_MAP.put("insert", ""); // INSERT(s1, x, len, s2) 字符串 s2 替换 s1 的 x 位置开始长度为 len 的字符串 + SQL_FUNCTION_MAP.put("locate", ""); // LOCATE(s1, s) 从字符串 s 中获取 s1 的开始位置 + SQL_FUNCTION_MAP.put("lcase", ""); // LCASE(s) 将字符串 s 的所有字母变成小写字母 + SQL_FUNCTION_MAP.put("left", ""); // LEFT(s, n) 返回字符串 s 的前 n 个字符 + SQL_FUNCTION_MAP.put("lower", ""); // LOWER(s) 将字符串 s 的所有字母变成小写字母 + SQL_FUNCTION_MAP.put("lpad", ""); // LPAD(s1, len, s2) 在字符串 s1 的开始处填充字符串 s2,使字符串长度达到 len + SQL_FUNCTION_MAP.put("ltrim", ""); // LTRIM(s) 去掉字符串 s 开始处的空格 + SQL_FUNCTION_MAP.put("mid", ""); // MID(s, n, len) 从字符串 s 的 n 位置截取长度为 len 的子字符串,同 SUBSTRING(s, n, len) + SQL_FUNCTION_MAP.put("position", ""); // POSITION(s, s1); 从字符串 s 中获取 s1 的开始位置 + SQL_FUNCTION_MAP.put("repeat", ""); // REPEAT(s, n) 将字符串 s 重复 n 次 + SQL_FUNCTION_MAP.put("replace", ""); // REPLACE(s, s1, s2) 将字符串 s2 替代字符串 s 中的字符串 s1 + SQL_FUNCTION_MAP.put("reverse", ""); // REVERSE(s); // ) 将字符串s的顺序反过来 + SQL_FUNCTION_MAP.put("right", ""); // RIGHT(s, n) 返回字符串 s 的后 n 个字符 + SQL_FUNCTION_MAP.put("rpad", ""); // RPAD(s1, len, s2) 在字符串 s1 的结尾处添加字符串 s2,使字符串的长度达到 len + SQL_FUNCTION_MAP.put("rtrim", ""); // RTRIM", ""); // ) 去掉字符串 s 结尾处的空格 + SQL_FUNCTION_MAP.put("space", ""); // SPACE(n) 返回 n 个空格 + SQL_FUNCTION_MAP.put("strcmp", ""); // STRCMP(s1, s2) 比较字符串 s1 和 s2,如果 s1 与 s2 相等返回 0 ,如果 s1>s2 返回 1,如果 s1d2 之间相隔的天数 + SQL_FUNCTION_MAP.put("date_add", ""); // DATE_ADD(d,INTERVAL expr type) 计算起始日期 d 加上一个时间段后的日期 + SQL_FUNCTION_MAP.put("date_format", ""); // DATE_FORMAT(d,f) 按表达式 f的要求显示日期 d + SQL_FUNCTION_MAP.put("date_sub", ""); // DATE_SUB(date,INTERVAL expr type) 函数从日期减去指定的时间间隔。 + SQL_FUNCTION_MAP.put("day", ""); // DAY(d) 返回日期值 d 的日期部分 + SQL_FUNCTION_MAP.put("dayname", ""); // DAYNAME(d) 返回日期 d 是星期几,如 Monday,Tuesday + SQL_FUNCTION_MAP.put("dayofmonth", ""); // DAYOFMONTH(d) 计算日期 d 是本月的第几天 + SQL_FUNCTION_MAP.put("dayofweek", ""); // DAYOFWEEK(d) 日期 d 今天是星期几,1 星期日,2 星期一,以此类推 + SQL_FUNCTION_MAP.put("dayofyear", ""); // DAYOFYEAR(d) 计算日期 d 是本年的第几天 + SQL_FUNCTION_MAP.put("extract", ""); // EXTRACT(type FROM d) 从日期 d 中获取指定的值,type 指定返回的值。 + SQL_FUNCTION_MAP.put("from_days", ""); // FROM_DAYS(n) 计算从 0000 年 1 月 1 日开始 n 天后的日期 + SQL_FUNCTION_MAP.put("hour", ""); // 'HOUR(t) 返回 t 中的小时值 + SQL_FUNCTION_MAP.put("last_day", ""); // LAST_DAY(d) 返回给给定日期的那一月份的最后一天 + SQL_FUNCTION_MAP.put("localtime", ""); // LOCALTIME() 返回当前日期和时间 + SQL_FUNCTION_MAP.put("localtimestamp", ""); // LOCALTIMESTAMP() 返回当前日期和时间 + SQL_FUNCTION_MAP.put("makedate", ""); // MAKEDATE(year, day-of-year) 基于给定参数年份 year 和所在年中的天数序号 day-of-year 返回一个日期 + SQL_FUNCTION_MAP.put("maketime", ""); // MAKETIME(hour, minute, second) 组合时间,参数分别为小时、分钟、秒 + SQL_FUNCTION_MAP.put("microsecond", ""); // MICROSECOND(date) 返回日期参数所对应的微秒数 + SQL_FUNCTION_MAP.put("minute", ""); // MINUTE(t) 返回 t 中的分钟值 + SQL_FUNCTION_MAP.put("monthname", ""); // MONTHNAME(d) 返回日期当中的月份名称,如 November + SQL_FUNCTION_MAP.put("month", ""); // MONTH(d) 返回日期d中的月份值,1 到 12 + SQL_FUNCTION_MAP.put("now", ""); // NOW() 返回当前日期和时间 + SQL_FUNCTION_MAP.put("period_add", ""); // PERIOD_ADD(period, number) 为 年-月 组合日期添加一个时段 + SQL_FUNCTION_MAP.put("period_diff", ""); // PERIOD_DIFF(period1, period2) 返回两个时段之间的月份差值 + SQL_FUNCTION_MAP.put("quarter", ""); // QUARTER(d) 返回日期d是第几季节,返回 1 到 4 + SQL_FUNCTION_MAP.put("second", ""); // SECOND(t) 返回 t 中的秒钟值 + SQL_FUNCTION_MAP.put("sec_to_time", ""); // SEC_TO_TIME", ""); // ) 将以秒为单位的时间 s 转换为时分秒的格式 + SQL_FUNCTION_MAP.put("str_to_date", ""); // STR_TO_DATE", ""); // tring, format_mask) 将字符串转变为日期 + SQL_FUNCTION_MAP.put("subdate", ""); // SUBDATE(d,n) 日期 d 减去 n 天后的日期 + SQL_FUNCTION_MAP.put("subtime", ""); // SUBTIME(t,n) 时间 t 减去 n 秒的时间 + SQL_FUNCTION_MAP.put("sysdate", ""); // SYSDATE() 返回当前日期和时间 + SQL_FUNCTION_MAP.put("time", ""); // TIME(expression) 提取传入表达式的时间部分 + SQL_FUNCTION_MAP.put("time_format", ""); // TIME_FORMAT(t,f) 按表达式 f 的要求显示时间 t + SQL_FUNCTION_MAP.put("time_to_sec", ""); // TIME_TO_SEC(t) 将时间 t 转换为秒 + SQL_FUNCTION_MAP.put("timediff", ""); // TIMEDIFF(time1, time2) 计算时间差值 + SQL_FUNCTION_MAP.put("timestamp", ""); // TIMESTAMP(expression, interval) 单个参数时,函数返回日期或日期时间表达式;有2个参数时,将参数加和 + SQL_FUNCTION_MAP.put("to_days", ""); // TO_DAYS(d) 计算日期 d 距离 0000 年 1 月 1 日的天数 + SQL_FUNCTION_MAP.put("week", ""); // WEEK(d) 计算日期 d 是本年的第几个星期,范围是 0 到 53 + SQL_FUNCTION_MAP.put("weekday", ""); // WEEKDAY(d) 日期 d 是星期几,0 表示星期一,1 表示星期二 + SQL_FUNCTION_MAP.put("weekofyear", ""); // WEEKOFYEAR(d) 计算日期 d 是本年的第几个星期,范围是 0 到 53 + SQL_FUNCTION_MAP.put("year", ""); // YEAR(d) 返回年份 + SQL_FUNCTION_MAP.put("yearweek", ""); // YEARWEEK(date, mode) 返回年份及第几周(0到53),mode 中 0 表示周天,1表示周一,以此类推 + SQL_FUNCTION_MAP.put("unix_timestamp", ""); // UNIX_TIMESTAMP(date) 获取UNIX时间戳函数,返回一个以 UNIX 时间戳为基础的无符号整数 + SQL_FUNCTION_MAP.put("from_unixtime", ""); // FROM_UNIXTIME(date) 将 UNIX 时间戳转换为时间格式,与UNIX_TIMESTAMP互为反函数 + + // MYSQL JSON 函数 + SQL_FUNCTION_MAP.put("json_append", ""); // JSON_APPEND(json_doc, path, val[, path, val] ...)) 插入JSON数组 + SQL_FUNCTION_MAP.put("json_array", ""); // JSON_ARRAY(val1, val2...) 创建JSON数组 + SQL_FUNCTION_MAP.put("json_array_append", ""); // JSON_ARRAY_APPEND(json_doc, val) 将数据附加到JSON文档 + SQL_FUNCTION_MAP.put("json_array_insert", ""); // JSON_ARRAY_INSERT(json_doc, val) 插入JSON数组 + SQL_FUNCTION_MAP.put("json_contains", ""); // JSON_CONTAINS(json_doc, val) JSON文档是否在路径中包含特定对象 + SQL_FUNCTION_MAP.put("json_contains_path", ""); // JSON_CONTAINS_PATH(json_doc, path) JSON文档是否在路径中包含任何数据 + SQL_FUNCTION_MAP.put("json_depth", ""); // JSON_DEPTH(json_doc) JSON文档的最大深度 + SQL_FUNCTION_MAP.put("json_extract", ""); // JSON_EXTRACT(json_doc, path) 从JSON文档返回数据 + SQL_FUNCTION_MAP.put("json_insert", ""); // JSON_INSERT(json_doc, val) 将数据插入JSON文档 + SQL_FUNCTION_MAP.put("json_keys", ""); // JSON_KEYS(json_doc[, path]) JSON文档中的键数组 + SQL_FUNCTION_MAP.put("json_length", ""); // JSON_LENGTH(json_doc) JSON文档中的元素数 + SQL_FUNCTION_MAP.put("json_merge", ""); // JSON_MERGE(json_doc1, json_doc2) (已弃用) 合并JSON文档,保留重复的键。JSON_MERGE_PRESERVE()的已弃用同义词 + SQL_FUNCTION_MAP.put("json_merge_patch", ""); // JSON_MERGE_PATCH(json_doc1, json_doc2) 合并JSON文档,替换重复键的值 + SQL_FUNCTION_MAP.put("json_merge_preserve", ""); // JSON_MERGE_PRESERVE(json_doc1, json_doc2) 合并JSON文档,保留重复的键 + SQL_FUNCTION_MAP.put("json_object", ""); // JSON_OBJECT(key1, val1, key2, val2...) 创建JSON对象 + SQL_FUNCTION_MAP.put("json_overlaps", ""); // JSON_OVERLAPS(json_doc1, json_doc2) (引入8.0.17) 比较两个JSON文档,如果它们具有相同的键值对或数组元素,则返回TRUE(1),否则返回FALSE(0) + SQL_FUNCTION_MAP.put("json_pretty", ""); // JSON_PRETTY(json_doc) 以易于阅读的格式打印JSON文档 + SQL_FUNCTION_MAP.put("json_quote", ""); // JSON_QUOTE(json_doc1) 引用JSON文档 + SQL_FUNCTION_MAP.put("json_remove", ""); // JSON_REMOVE(json_doc1, path) 从JSON文档中删除数据 + SQL_FUNCTION_MAP.put("json_replace", ""); // JSON_REPLACE(json_doc1, val1, val2) 替换JSON文档中的值 + SQL_FUNCTION_MAP.put("json_schema_valid", ""); // JSON_SCHEMA_VALID(json_doc) (引入8.0.17) 根据JSON模式验证JSON文档;如果文档针对架构进行验证,则返回TRUE / 1;否则,则返回FALSE / 0 + SQL_FUNCTION_MAP.put("json_schema_validation_report", ""); // JSON_SCHEMA_VALIDATION_REPORT(json_doc, mode) (引入8.0.17) 根据JSON模式验证JSON文档;以JSON格式返回有关验证结果的报告,包括成功或失败以及失败原因 + SQL_FUNCTION_MAP.put("json_search", ""); // JSON_SEARCH(json_doc, val) JSON文档中值的路径 + SQL_FUNCTION_MAP.put("json_set", ""); // JSON_SET(json_doc, val) 将数据插入JSON文档 + // SQL_FUNCTION_MAP.put("json_storage_free", ""); // JSON_STORAGE_FREE() 部分更新后,JSON列值的二进制表示形式中的可用空间 + // SQL_FUNCTION_MAP.put("json_storage_size", ""); // JSON_STORAGE_SIZE() 用于存储JSON文档的二进制表示的空间 + SQL_FUNCTION_MAP.put("json_table", ""); // JSON_TABLE() 从JSON表达式返回数据作为关系表 + SQL_FUNCTION_MAP.put("json_type", ""); // JSON_TYPE(json_doc) JSON值类型 + SQL_FUNCTION_MAP.put("json_unquote", ""); // JSON_UNQUOTE(json_doc) 取消引用JSON值 + SQL_FUNCTION_MAP.put("json_valid", ""); // JSON_VALID(json_doc) JSON值是否有效 + SQL_FUNCTION_MAP.put("json_arrayagg", ""); // JSON_ARRAYAGG(key) 将每个表达式转换为 JSON 值,然后返回一个包含这些 JSON 值的 JSON 数组 + SQL_FUNCTION_MAP.put("json_objectagg", ""); // JSON_OBJECTAGG(key, val)) 将每个表达式转换为 JSON 值,然后返回一个包含这些 JSON 值的 JSON 对象 + + // MySQL 高级函数 + // SQL_FUNCTION_MAP.put("bin", ""); // BIN(x) 返回 x 的二进制编码 + // SQL_FUNCTION_MAP.put("binary", ""); // BINARY(s) 将字符串 s 转换为二进制字符串 + SQL_FUNCTION_MAP.put("case", ""); // CASE 表示函数开始,END 表示函数结束。如果 condition1 成立,则返回 result1, 如果 condition2 成立,则返回 result2,当全部不成立则返回 result,而当有一个成立之后,后面的就不执行了。 + SQL_FUNCTION_MAP.put("cast", ""); // CAST(x AS type) 转换数据类型 + SQL_FUNCTION_MAP.put("coalesce", ""); // COALESCE(expr1, expr2, ...., expr_n) 返回参数中的第一个非空表达式(从左向右) + // SQL_FUNCTION_MAP.put("conv", ""); // CONV(x,f1,f2) 返回 f1 进制数变成 f2 进制数 + // SQL_FUNCTION_MAP.put("convert", ""); // CONVERT(s, cs) 函数将字符串 s 的字符集变成 cs + SQL_FUNCTION_MAP.put("if", ""); // IF(expr,v1,v2) 如果表达式 expr 成立,返回结果 v1;否则,返回结果 v2。 + SQL_FUNCTION_MAP.put("ifnull", ""); // IFNULL(v1,v2) 如果 v1 的值不为 NULL,则返回 v1,否则返回 v2。 + SQL_FUNCTION_MAP.put("isnull", ""); // ISNULL(expression) 判断表达式是否为 NULL + SQL_FUNCTION_MAP.put("nullif", ""); // NULLIF(expr1, expr2) 比较两个字符串,如果字符串 expr1 与 expr2 相等 返回 NULL,否则返回 expr1 + SQL_FUNCTION_MAP.put("group_concat", ""); // GROUP_CONCAT([DISTINCT], s1, s2...) + } @@ -581,11 +761,19 @@ public String getHavingString(boolean hasPrefix) { } method = expression.substring(0, start); - - if (StringUtil.isName(method) == false) { - throw new IllegalArgumentException("字符 " + method + " 不合法!" - + "预编译模式下 @having:\"column?value;function(arg0,arg1,...)?value...\"" - + " 中SQL函数名 function 必须符合正则表达式 ^[0-9a-zA-Z_]+$ !"); + if (method.isEmpty() == false) { + if (SQL_FUNCTION_MAP == null || SQL_FUNCTION_MAP.isEmpty()) { + if (StringUtil.isName(method) == false) { + throw new IllegalArgumentException("字符 " + method + " 不合法!" + + "预编译模式下 @having:\"column?value;function(arg0,arg1,...)?value...\"" + + " 中 function 必须符合小写英文单词的 SQL 函数名格式!"); + } + } + else if (SQL_FUNCTION_MAP.containsKey(method) == false) { + throw new IllegalArgumentException("字符 " + method + " 不合法!" + + "预编译模式下 @column:\"column0,column1:alias;function0(arg0,arg1,...);function1(...):alias...\"" + + " 中 function 必须符合小写英文单词的 SQL 函数名格式!且必须是后端允许调用的 SQL 函数!"); + } } suffix = expression.substring(end + 1, expression.length()); @@ -957,10 +1145,19 @@ public String getColumnString(boolean inSQLJoin) throws Exception { boolean distinct = i <= 0 && method.startsWith(PREFFIX_DISTINCT); String fun = distinct ? method.substring(PREFFIX_DISTINCT.length()) : method; - if (fun.isEmpty() == false && StringUtil.isName(fun) == false) { - throw new IllegalArgumentException("字符 " + method + " 不合法!" - + "预编译模式下 @column:\"column0,column1:alias;function0(arg0,arg1,...);function1(...):alias...\"" - + " 中SQL函数名 function 必须符合正则表达式 ^[0-9a-zA-Z_]+$ !"); + if (fun.isEmpty() == false) { + if (SQL_FUNCTION_MAP == null || SQL_FUNCTION_MAP.isEmpty()) { + if (StringUtil.isName(fun) == false) { + throw new IllegalArgumentException("字符 " + method + " 不合法!" + + "预编译模式下 @column:\"column0,column1:alias;function0(arg0,arg1,...);function1(...):alias...\"" + + " 中 function 必须符合小写英文单词的 SQL 函数名格式!"); + } + } + else if (SQL_FUNCTION_MAP.containsKey(fun) == false) { + throw new IllegalArgumentException("字符 " + method + " 不合法!" + + "预编译模式下 @column:\"column0,column1:alias;function0(arg0,arg1,...);function1(...):alias...\"" + + " 中 function 必须符合小写英文单词的 SQL 函数名格式!且必须是后端允许调用的 SQL 函数!"); + } } } @@ -2490,7 +2687,7 @@ public String getJoinString() throws Exception { SQLConfig jc; String jt; String tt; - String ta; + // 主表不用别名 String ta; for (Join j : joinList) { if (j.isAppJoin()) { // APP JOIN,只是作为一个标记,执行完主表的查询后自动执行副表的查询 User.id IN($commentIdList) continue; From 786d326e6f5f5da2dad2e31490c91bec88ff34f5 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Sat, 27 Mar 2021 19:41:59 +0800 Subject: [PATCH 0304/1181] =?UTF-8?q?=E6=96=B0=E5=A2=9E=20=E6=95=B4?= =?UTF-8?q?=E5=90=88=20APIJSON=20=E5=92=8C=E5=BE=AE=E6=9C=8D=E5=8A=A1?= =?UTF-8?q?=E6=A1=86=E6=9E=B6=20light4j=20=E7=9A=84=20Demo(=E5=90=8C?= =?UTF-8?q?=E6=97=B6=E6=8E=A5=E5=85=A5=E4=BA=86=20Redis)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit https://github.com/xlongwei/light4j --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index b3afbb6f6..5fed97412 100644 --- a/README.md +++ b/README.md @@ -385,6 +385,8 @@ https://github.com/Tencent/APIJSON/blob/master/CONTRIBUTING.md [ApiJsonByJFinal](https://gitee.com/zhiyuexin/ApiJsonByJFinal) 整合 APIJSON 和 JFinal 的 Demo +[light4j](https://github.com/xlongwei/light4j) 整合 APIJSON 和微服务框架 light-4j 的 Demo,同时接入了 Redis + [SpringServer1.2-APIJSON](https://github.com/Airforce-1/SpringServer1.2-APIJSON) 智慧党建服务器端,提供 上传 和 下载 文件的接口 [apijson-builder](https://github.com/pengxianggui/apijson-builder) 一个方便为 APIJSON 构建 RESTful 请求的 JavaScript 库 From fe65470b5c0a819da1097a8658a5ec1cec3babb9 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Mon, 5 Apr 2021 19:24:42 +0800 Subject: [PATCH 0305/1181] =?UTF-8?q?SQL=20=E5=87=BD=E6=95=B0=E7=99=BD?= =?UTF-8?q?=E5=90=8D=E5=8D=95=E6=96=B0=E5=A2=9E=20length=EF=BC=9Bkey$=20?= =?UTF-8?q?=E6=A8=A1=E7=B3=8A=E6=90=9C=E7=B4=A2=E4=B8=8D=E5=85=81=E8=AE=B8?= =?UTF-8?q?=E8=BF=9E=E7=BB=AD=E7=9A=84=20%=EF=BC=9BAbstractSQLExecutor=20?= =?UTF-8?q?=E6=96=B0=E5=A2=9E=20getKey=20=E6=96=B9=E6=B3=95=EF=BC=9B?= =?UTF-8?q?=E6=96=B0=E5=A2=9E=E5=BE=85=E5=AE=9E=E7=8E=B0=E5=85=B3=E9=94=AE?= =?UTF-8?q?=E8=AF=8D=20@null=EF=BC=9B=E5=88=A0=E9=99=A4=20Structure.java,?= =?UTF-8?q?=20Operation=20=E4=B8=AD=20NECESSARY,=20DISALLOW=20=E7=AD=89?= =?UTF-8?q?=E5=B7=B2=E5=BA=9F=E5=BC=83=E7=9A=84=E9=83=A8=E5=88=86=E4=BB=A3?= =?UTF-8?q?=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/main/java/apijson/JSONObject.java | 1 + .../java/apijson/orm/AbstractSQLConfig.java | 14 +- .../java/apijson/orm/AbstractSQLExecutor.java | 11 +- .../java/apijson/orm/AbstractVerifier.java | 35 +-- .../src/main/java/apijson/orm/Operation.java | 15 +- .../src/main/java/apijson/orm/Structure.java | 241 ------------------ 6 files changed, 25 insertions(+), 292 deletions(-) delete mode 100755 APIJSONORM/src/main/java/apijson/orm/Structure.java diff --git a/APIJSONORM/src/main/java/apijson/JSONObject.java b/APIJSONORM/src/main/java/apijson/JSONObject.java index 75ca4256d..026d63efb 100755 --- a/APIJSONORM/src/main/java/apijson/JSONObject.java +++ b/APIJSONORM/src/main/java/apijson/JSONObject.java @@ -133,6 +133,7 @@ public JSONObject setUserIdIn(List list) { public static final String KEY_DROP = "@drop"; //丢弃,不返回,TODO 应该通过 fastjson 的 ignore 之类的机制来处理,避免导致下面的对象也不返回 // public static final String KEY_KEEP = "@keep"; //一定会返回,为 null 或 空对象时,会使用默认值(非空),解决其它对象因为不关联的第一个对为空导致也不返回 public static final String KEY_DEFULT = "@default"; //TODO 自定义默认值 { "@default":true },@default 可完全替代 @keep + public static final String KEY_NULL = "@null"; //TODO 值为 null 的键值对 "@null":"tag,pictureList",允许 is NULL 条件判断, SET tag = NULL 修改值为 NULL 等 public static final String KEY_ROLE = "@role"; //角色,拥有对某些数据的某些操作的权限 public static final String KEY_DATABASE = "@database"; //数据库类型,默认为MySQL diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java index 0fdba0b81..6f61b9c19 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java @@ -143,6 +143,7 @@ public abstract class AbstractSQLConfig implements SQLConfig { SQL_FUNCTION_MAP.put("locate", ""); // LOCATE(s1, s) 从字符串 s 中获取 s1 的开始位置 SQL_FUNCTION_MAP.put("lcase", ""); // LCASE(s) 将字符串 s 的所有字母变成小写字母 SQL_FUNCTION_MAP.put("left", ""); // LEFT(s, n) 返回字符串 s 的前 n 个字符 + SQL_FUNCTION_MAP.put("length", ""); // LENGTH(s) 返回字符串 s 的字符数 SQL_FUNCTION_MAP.put("lower", ""); // LOWER(s) 将字符串 s 的所有字母变成小写字母 SQL_FUNCTION_MAP.put("lpad", ""); // LPAD(s1, len, s2) 在字符串 s1 的开始处填充字符串 s2,使字符串长度达到 len SQL_FUNCTION_MAP.put("ltrim", ""); // LTRIM(s) 去掉字符串 s 开始处的空格 @@ -1013,6 +1014,8 @@ public String getColumnString() throws Exception { } @JSONField(serialize = false) public String getColumnString(boolean inSQLJoin) throws Exception { + List column = getColumn(); + switch (getMethod()) { case HEAD: case HEADS: //StringUtil.isEmpty(column, true) || column.contains(",") 时SQL.count(column)会return "*" @@ -2011,10 +2014,15 @@ public String getSearchString(String key, Object[] values, int type) throws Ille String condition = ""; for (int i = 0; i < values.length; i++) { - if (values[i] instanceof String == false) { - throw new IllegalArgumentException(key + "$:value 中value的类型只能为String或String[]!"); + Object v = values[i]; + if (v instanceof String == false) { + throw new IllegalArgumentException(key + "$:value 中 value 的类型只能为 String 或 String[]!"); + } + if (((String) v).contains("%%")) { + throw new IllegalArgumentException(key + "$:value 中 value 值 " + v + " 中包含 %% !不允许有连续的 % !"); } - condition += (i <= 0 ? "" : (Logic.isAnd(type) ? AND : OR)) + getLikeString(key, values[i]); + + condition += (i <= 0 ? "" : (Logic.isAnd(type) ? AND : OR)) + getLikeString(key, v); } return getCondition(Logic.isNot(type), condition); diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java index 47c2d46e5..a3ff117cf 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java @@ -249,7 +249,8 @@ public JSONObject execute(@NotNull SQLConfig config, boolean unknowType) throws // final boolean cache = config.getCount() != 1; - resultList = new ArrayList<>(); + // TODO 设置初始容量为查到的数据量,解决频繁扩容导致的延迟,貌似只有 rs.last 取 rs.getRow() ? 然后又得 rs.beforeFirst 重置位置以便下方取值 + resultList = new ArrayList<>(config.getCount() <= 0 ? Parser.MAX_QUERY_COUNT : config.getCount()); // Log.d(TAG, "select cache = " + cache + "; resultList" + (resultList == null ? "=" : "!=") + "null"); int index = -1; @@ -504,7 +505,7 @@ protected JSONObject onPutColumn(@NotNull SQLConfig config, @NotNull ResultSet r //已改为 rsmd.getTableName(columnIndex) 支持副表不传 @column , 但如何判断是副表?childMap != null // String lable = rsmd.getColumnLabel(columnIndex); // int dotIndex = lable.indexOf("."); - String lable = rsmd.getColumnLabel(columnIndex);//dotIndex < 0 ? lable : lable.substring(dotIndex + 1); + String lable = getKey(config, rs, rsmd, tablePosition, table, columnIndex, childMap); String childTable = childMap == null ? null : rsmd.getTableName(columnIndex); //dotIndex < 0 ? null : lable.substring(0, dotIndex); @@ -567,6 +568,12 @@ protected List onPutTable(@NotNull SQLConfig config, @NotNull Result return resultList; } + + + protected String getKey(@NotNull SQLConfig config, @NotNull ResultSet rs, @NotNull ResultSetMetaData rsmd + , final int tablePosition, @NotNull JSONObject table, final int columnIndex, Map childMap) throws Exception { + return rsmd.getColumnLabel(columnIndex); // dotIndex < 0 ? lable : lable.substring(dotIndex + 1); + } protected Object getValue(@NotNull SQLConfig config, @NotNull ResultSet rs, @NotNull ResultSetMetaData rsmd , final int tablePosition, @NotNull JSONObject table, final int columnIndex, String lable, Map childMap) throws Exception { diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractVerifier.java b/APIJSONORM/src/main/java/apijson/orm/AbstractVerifier.java index 3ef1d5c4a..633b5d645 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractVerifier.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractVerifier.java @@ -12,11 +12,9 @@ import static apijson.RequestMethod.HEADS; import static apijson.RequestMethod.POST; import static apijson.RequestMethod.PUT; -import static apijson.orm.Operation.DISALLOW; import static apijson.orm.Operation.EXIST; import static apijson.orm.Operation.INSERT; import static apijson.orm.Operation.MUST; -import static apijson.orm.Operation.NECESSARY; import static apijson.orm.Operation.REFUSE; import static apijson.orm.Operation.REMOVE; import static apijson.orm.Operation.REPLACE; @@ -96,6 +94,7 @@ public abstract class AbstractVerifier implements Verifier, IdCallback { @NotNull public static final Map> REQUEST_MAP; + // 正则匹配的别名快捷方式,例如用 "PHONE" 代替 "^((13[0-9])|(15[^4,\\D])|(18[0-2,5-9])|(17[0-9]))\\d{8}$" @NotNull public static final Map COMPILE_MAP; static { @@ -110,8 +109,6 @@ public abstract class AbstractVerifier implements Verifier, IdCallback { OPERATION_KEY_LIST.add(REMOVE.name()); OPERATION_KEY_LIST.add(MUST.name()); OPERATION_KEY_LIST.add(REFUSE.name()); - OPERATION_KEY_LIST.add(NECESSARY.name()); - OPERATION_KEY_LIST.add(DISALLOW.name()); SYSTEM_ACCESS_MAP = new HashMap>(); @@ -776,8 +773,6 @@ public static JSONObject parse(@NotNull final RequestMethod method, String name, String remove = StringUtil.getNoBlankString(target.getString(REMOVE.name())); String must = StringUtil.getNoBlankString(target.getString(MUST.name())); String refuse = StringUtil.getNoBlankString(target.getString(REFUSE.name())); - String necessary = StringUtil.getNoBlankString(target.getString(NECESSARY.name())); - String disallow = StringUtil.getNoBlankString(target.getString(DISALLOW.name())); // 移除字段<<<<<<<<<<<<<<<<<<< @@ -798,15 +793,6 @@ public static JSONObject parse(@NotNull final RequestMethod method, String name, + " 里面不能缺少 " + s + " 等[" + must + "]内的任何字段!"); } } - - String[] necessarys = StringUtil.split(necessary); - List necessaryList = necessarys == null ? new ArrayList() : Arrays.asList(necessarys); - for (String s : necessaryList) { - if (real.get(s) == null) {//可能传null进来,这里还会通过 real.containsKey(s) == false) { - throw new IllegalArgumentException(method + "请求," + name - + " 里面不能缺少 " + s + " 等[" + necessary + "]内的任何字段!"); - } - } //判断必要字段是否都有>>>>>>>>>>>>>>>>>>> @@ -879,21 +865,6 @@ public static JSONObject parse(@NotNull final RequestMethod method, String name, refuseList.addAll(Arrays.asList(refuses)); } } - - List disallowList = new ArrayList(); - if ("!".equals(disallow)) {//所有非necessary,改成 !necessary 更好 - for (String key : rkset) {//对@key放行,@role,@column,自定义@position等 - if (key != null && key.startsWith("@") == false - && necessaryList.contains(key) == false && objKeySet.contains(key) == false) { - disallowList.add(key); - } - } - } else { - String[] disallows = StringUtil.split(disallow); - if (disallows != null && disallows.length > 0) { - disallowList.addAll(Arrays.asList(disallows)); - } - } //解析不允许的字段>>>>>>>>>>>>>>>>>>> @@ -903,10 +874,6 @@ public static JSONObject parse(@NotNull final RequestMethod method, String name, throw new IllegalArgumentException(method + "请求," + name + " 里面不允许传 " + rk + " 等" + StringUtil.getString(refuseList) + "内的任何字段!"); } - if (disallowList.contains(rk)) { //不允许的字段 - throw new IllegalArgumentException(method + "请求," + name - + " 里面不允许传 " + rk + " 等" + StringUtil.getString(disallowList) + "内的任何字段!"); - } if (rk == null) { //无效的key real.remove(rk); diff --git a/APIJSONORM/src/main/java/apijson/orm/Operation.java b/APIJSONORM/src/main/java/apijson/orm/Operation.java index a0e2278b1..e6461bab5 100755 --- a/APIJSONORM/src/main/java/apijson/orm/Operation.java +++ b/APIJSONORM/src/main/java/apijson/orm/Operation.java @@ -5,7 +5,7 @@ package apijson.orm; -/**对请求JSON的操作 +/**对请求 JSON 的操作 * @author Lemon */ public enum Operation { @@ -14,21 +14,12 @@ public enum Operation { * "key0,key1,key2..." */ MUST, - /** - * @deprecated 用 MUST 代替,最早可能 4.5.0 移除 - */ - NECESSARY, /** * 不允许传的字段,结构是 * "key0,key1,key2..." */ REFUSE, - /** - * @deprecated 用 REFUSE 代替,最早可能 4.5.0 移除 - */ - DISALLOW, - /**TODO 是否应该把数组类型写成 BOOLEANS, NUMBERS 等复数单词,以便抽取 enum ?扩展用 VERIFY 或 INSERT/UPDATE 远程函数等 * 验证是否符合预设的类型: @@ -47,7 +38,7 @@ public enum Operation { * "id": "NUMBER", //id 类型必须为 NUMBER * "pictureList": "URL[]", //pictureList 类型必须为 URL[] * } - * @see {@link Structure#type(String, String, Object, boolean)} + * @see {@link AbstractVerifier#verifyType(String, String, Object, boolean)} */ TYPE, @@ -61,7 +52,7 @@ public enum Operation { * } * 例如 * { - * "phone~": "PHONE", //phone 必须满足 PHONE 的格式 + * "phone~": "PHONE", //phone 必须满足 PHONE 的格式,配置见 {@link AbstractVerifier#COMPILE_MAP} * "status{}": [1,2,3], //status 必须在给出的范围内 * "balance&{}":">0,<=10000" //必须满足 balance>0 & balance<=10000 * } diff --git a/APIJSONORM/src/main/java/apijson/orm/Structure.java b/APIJSONORM/src/main/java/apijson/orm/Structure.java deleted file mode 100755 index b5d146f90..000000000 --- a/APIJSONORM/src/main/java/apijson/orm/Structure.java +++ /dev/null @@ -1,241 +0,0 @@ -/*Copyright (C) 2020 THL A29 Limited, a Tencent company. All rights reserved. - -This source code is licensed under the Apache License Version 2.0.*/ - - -package apijson.orm; - -import java.util.Map; -import java.util.regex.Pattern; - -import javax.activation.UnsupportedDataTypeException; - -import com.alibaba.fastjson.JSONObject; - -import apijson.JSON; -import apijson.Log; -import apijson.NotNull; -import apijson.RequestMethod; -import apijson.orm.AbstractSQLConfig.Callback; -import apijson.orm.AbstractSQLConfig.IdCallback; - -/**结构类。已整合进 AbstractVerifier,最快 4.5.0 移除 - * 增删改查: OPERATION(ADD,REPLACE,PUT,REMOVE) OPERATION:{key0:value0, key1:value1 ...} - * 对值校验: VERIFY:{key0:value0, key1:value1 ...} (key{}:range,key$:"%m%"等) - * 对值重复性校验: UNIQUE:"key0:, key1 ..." (UNIQUE:"phone,email" 等) - * @author Lemon - */ -@Deprecated -public class Structure { - public static final String TAG = "Structure"; - - public static final Map COMPILE_MAP = AbstractVerifier.COMPILE_MAP; - - private Structure() {} - - - /**从request提取target指定的内容 - * @param method - * @param name - * @param target - * @param request - * @param creator - * @return - * @throws Exception - */ - public static JSONObject parseRequest(@NotNull final RequestMethod method, final String name - , final JSONObject target, final JSONObject request, final SQLCreator creator) throws Exception { - return parseRequest(method, name, target, request, Parser.MAX_UPDATE_COUNT, creator); - } - /**从request提取target指定的内容 - * @param method - * @param name - * @param target - * @param request - * @param maxUpdateCount - * @param creator - * @return - * @throws Exception - */ - public static JSONObject parseRequest(@NotNull final RequestMethod method, final String name - , final JSONObject target, final JSONObject request, final int maxUpdateCount, final SQLCreator creator) throws Exception { - return parseRequest(method, name, target, request, maxUpdateCount, null, null, null, creator); - } - /**从request提取target指定的内容 - * @param method - * @param name - * @param target - * @param request - * @param maxUpdateCount - * @param idKey - * @param userIdKey - * @param creator - * @return - * @throws Exception - */ - public static JSONObject parseRequest(@NotNull final RequestMethod method, final String name - , final JSONObject target, final JSONObject request, final int maxUpdateCount - , final String database, final String schema, final IdCallback idCallback, final SQLCreator creator) throws Exception { - - return AbstractVerifier.verifyRequest(method, name, target, request, maxUpdateCount, database, schema, idCallback, creator); - } - - /**校验并将response转换为指定的内容和结构 - * @param method - * @param name - * @param target - * @param response - * @param callback - * @param creator - * @return - * @throws Exception - */ - public static JSONObject parseResponse(@NotNull final RequestMethod method, final String name - , final JSONObject target, final JSONObject response, SQLCreator creator, OnParseCallback callback) throws Exception { - return parseResponse(method, name, target, response, null, null, null, creator, callback); - } - /**校验并将response转换为指定的内容和结构 - * @param method - * @param name - * @param target - * @param response - * @param idKey - * @param callback - * @param creator - * @return - * @throws Exception - */ - public static JSONObject parseResponse(@NotNull final RequestMethod method, final String name - , final JSONObject target, final JSONObject response, final String database, final String schema - , final Callback idKeyCallback, SQLCreator creator, OnParseCallback callback) throws Exception { - - Log.i(TAG, "parseResponse method = " + method + "; name = " + name - + "; target = \n" + JSON.toJSONString(target) - + "\n response = \n" + JSON.toJSONString(response)); - - if (target == null || response == null) {// || target.isEmpty() { - Log.i(TAG, "parseRequest target == null || response == null >> return response;"); - return response; - } - - //解析 - return parse(method, name, target, response, database, schema, idKeyCallback, creator, callback != null ? callback : new OnParseCallback() {}); - } - - - /**对request和response不同的解析用callback返回 - * @param method - * @param name - * @param target - * @param real - * @param creator - * @param callback - * @return - * @throws Exception - */ - public static JSONObject parse(@NotNull final RequestMethod method, String name, JSONObject target, JSONObject real - , SQLCreator creator, @NotNull OnParseCallback callback) throws Exception { - return parse(method, name, target, real, null, null, null, creator, callback); - } - - /**对request和response不同的解析用callback返回 - * @param method - * @param name - * @param target - * @param real - * @param idKey - * @param userIdKey - * @param creator - * @param callback - * @return - * @throws Exception - */ - public static JSONObject parse(@NotNull final RequestMethod method, String name, JSONObject target, JSONObject real - , final String database, final String schema, final IdCallback idCallback, SQLCreator creator, @NotNull OnParseCallback callback) throws Exception { - return AbstractVerifier.parse(method, name, target, real, database, schema, idCallback, creator, callback); - } - - - - /**验证值类型 - * @param tk - * @param tv {@link Operation} - * @param real - * @throws Exception - */ - public static void type(@NotNull String tk, Object tv, @NotNull JSONObject real) throws UnsupportedDataTypeException { - if (tv instanceof String == false) { - throw new UnsupportedDataTypeException("服务器内部错误," + tk + ":value 的value不合法!" - + "Request表校验规则中 TYPE:{ key:value } 中的value只能是String类型!"); - } - - type(tk, (String) tv, real.get(tk)); - } - /**验证值类型 - * @param tk - * @param tv {@link Operation} - * @param rv - * @throws Exception - */ - public static void type(@NotNull String tk, @NotNull String tv, Object rv) throws UnsupportedDataTypeException { - type(tk, tv, rv, false); - } - /**验证值类型 - * @param tk - * @param tv {@link Operation} - * @param rv - * @param isInArray - * @throws Exception - */ - public static void type(@NotNull String tk, @NotNull String tv, Object rv, boolean isInArray) throws UnsupportedDataTypeException { - AbstractVerifier.verifyType(tk, tv, rv, isInArray); - } - - - /**验证是否存在 - * @param table - * @param key - * @param value - * @throws Exception - */ - public static void verifyExist(String table, String key, Object value, long exceptId, @NotNull SQLCreator creator) throws Exception { - AbstractVerifier.verifyExist(table, key, value, exceptId, creator); - } - - /**验证是否重复 - * @param table - * @param key - * @param value - * @throws Exception - */ - public static void verifyRepeat(String table, String key, Object value, @NotNull SQLCreator creator) throws Exception { - verifyRepeat(table, key, value, 0, creator); - } - - /**验证是否重复 - * @param table - * @param key - * @param value - * @param exceptId 不包含id - * @throws Exception - */ - public static void verifyRepeat(String table, String key, Object value, long exceptId, @NotNull SQLCreator creator) throws Exception { - verifyRepeat(table, key, value, exceptId, null, creator); - } - - /**验证是否重复 - * TODO 与 AbstractVerifier.verifyRepeat 代码重复,需要简化 - * @param table - * @param key - * @param value - * @param exceptId 不包含id - * @param idKey - * @param creator - * @throws Exception - */ - public static void verifyRepeat(String table, String key, Object value, long exceptId, String idKey, @NotNull SQLCreator creator) throws Exception { - AbstractVerifier.verifyRepeat(table, key, value, exceptId, idKey, creator); - } - - -} From 8a6672b5f95ac8bb447742018068e842213d2469 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Mon, 5 Apr 2021 19:33:53 +0800 Subject: [PATCH 0306/1181] =?UTF-8?q?=E5=8D=87=E7=BA=A7=E7=89=88=E6=9C=AC?= =?UTF-8?q?=E5=8F=B7=E4=B8=BA=204.6.6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- APIJSONORM/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/APIJSONORM/pom.xml b/APIJSONORM/pom.xml index cb3b5333e..b892374e2 100755 --- a/APIJSONORM/pom.xml +++ b/APIJSONORM/pom.xml @@ -5,7 +5,7 @@ apijson.orm apijson-orm - 4.6.1 + 4.6.6 jar APIJSONORM From 60564651608a2abb487047a8b9b475356dabcbe0 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Mon, 5 Apr 2021 20:44:52 +0800 Subject: [PATCH 0307/1181] =?UTF-8?q?=E7=94=9F=E6=80=81=E9=A1=B9=E7=9B=AE?= =?UTF-8?q?=E6=96=B0=E5=A2=9E=EF=BC=9AAPIJSON=20=E7=9A=84=E5=AD=97?= =?UTF-8?q?=E6=AE=B5=E6=8F=92=E4=BB=B6=EF=BC=8C=E6=94=AF=E6=8C=81=20?= =?UTF-8?q?=E5=AD=97=E6=AE=B5=E5=90=8D=E6=98=A0=E5=B0=84=20=E5=92=8C=20!ke?= =?UTF-8?q?y=20=E5=8F=8D=E9=80=89=E5=AD=97=E6=AE=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit https://github.com/APIJSON/apijson-column --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 5fed97412..7c3ccc83e 100644 --- a/README.md +++ b/README.md @@ -363,6 +363,8 @@ https://github.com/Tencent/APIJSON/blob/master/CONTRIBUTING.md [apijson-framework](https://github.com/APIJSON/apijson-framework) APIJSON 服务端框架,可通过 Maven, Gradle 等远程依赖 +[apijson-column](https://github.com/APIJSON/apijson-column) APIJSON 的字段插件,支持 字段名映射 和 !key 反选字段 + [APIAuto](https://github.com/TommyLemon/APIAuto) 敏捷开发最强大易用的 HTTP 接口工具,机器学习零代码测试、生成代码与静态检查、生成文档与光标悬浮注释 [UnitAuto](https://github.com/TommyLemon/UnitAuto) 机器学习单元测试平台,零代码、全方位、自动化 测试 方法/函数 的正确性和可用性 From 3d210639c6d20b94679cd1268b549b64fa35288a Mon Sep 17 00:00:00 2001 From: iceewei Date: Mon, 5 Apr 2021 22:27:10 +0800 Subject: [PATCH 0308/1181] =?UTF-8?q?=E9=94=99=E8=AF=AF=E7=A0=81=E5=8F=AA?= =?UTF-8?q?=E5=9C=A8=E6=9C=80=E5=A4=96=E5=B1=82=E8=BF=94=E5=9B=9E=EF=BC=8C?= =?UTF-8?q?=E9=80=BB=E8=BE=91=E7=BB=9F=E4=B8=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/apijson/orm/AbstractSQLExecutor.java | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java index a3ff117cf..324cd6350 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java @@ -210,8 +210,11 @@ public JSONObject execute(@NotNull SQLConfig config, boolean unknowType) throws int updateCount = executeUpdate(config); - result = AbstractParser.newResult(updateCount > 0 ? JSONResponse.CODE_SUCCESS : JSONResponse.CODE_NOT_FOUND - , updateCount > 0 ? JSONResponse.MSG_SUCCEED : "没权限访问或对象不存在!"); + result = new JSONObject(); + if (config.getMethod() == RequestMethod.DELETE) { + result = AbstractParser.newResult(updateCount > 0 ? JSONResponse.CODE_SUCCESS : JSONResponse.CODE_NOT_FOUND, + updateCount > 0 ? JSONResponse.MSG_SUCCEED : "没权限访问或对象不存在!"); + } //id,id{}至少一个会有,一定会返回,不用抛异常来阻止关联写操作时前面错误导致后面无条件执行! result.put(JSONResponse.KEY_COUNT, updateCount);//返回修改的记录数 @@ -717,8 +720,8 @@ public Connection getConnection(@NotNull SQLConfig config) throws Exception { connection = connectionMap.get(config.getDatabase()); if (connection == null || connection.isClosed()) { Log.i(TAG, "select connection " + (connection == null ? " = null" : ("isClosed = " + connection.isClosed()))) ; - // PostgreSQL 不允许 cross-database - connection = DriverManager.getConnection(config.getDBUri(), config.getDBAccount(), config.getDBPassword()); + // PostgreSQL 不允许 cross-database + connection = DriverManager.getConnection(config.getDBUri(), config.getDBAccount(), config.getDBPassword()); connectionMap.put(config.getDatabase(), connection); } @@ -823,14 +826,14 @@ public ResultSet executeQuery(@NotNull SQLConfig config) throws Exception { public int executeUpdate(@NotNull SQLConfig config) throws Exception { PreparedStatement s = getStatement(config); int count = s.executeUpdate(); //PreparedStatement 不用传 SQL - + if (config.getMethod() == RequestMethod.POST && config.getId() == null) { //自增id ResultSet rs = s.getGeneratedKeys(); if (rs != null && rs.next()) { config.setId(rs.getLong(1));//返回插入的主键id } } - + return count; } From 737aa738efb8fb5ca362b537c8adc49b9d225f9c Mon Sep 17 00:00:00 2001 From: iceewei Date: Mon, 5 Apr 2021 22:29:18 +0800 Subject: [PATCH 0309/1181] =?UTF-8?q?=E9=94=99=E8=AF=AF=E7=A0=81=E5=8F=AA?= =?UTF-8?q?=E5=9C=A8=E6=9C=80=E5=A4=96=E5=B1=82=E8=BF=94=E5=9B=9E=EF=BC=8C?= =?UTF-8?q?=E9=80=BB=E8=BE=91=E7=BB=9F=E4=B8=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java | 1 + 1 file changed, 1 insertion(+) diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java index 324cd6350..043251a56 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java @@ -212,6 +212,7 @@ public JSONObject execute(@NotNull SQLConfig config, boolean unknowType) throws result = new JSONObject(); if (config.getMethod() == RequestMethod.DELETE) { + // 特别地,针对DELETE请求,如果需要提示code,可以用内部的code来判断。其余请求类型统一使用外层错误码。 result = AbstractParser.newResult(updateCount > 0 ? JSONResponse.CODE_SUCCESS : JSONResponse.CODE_NOT_FOUND, updateCount > 0 ? JSONResponse.MSG_SUCCEED : "没权限访问或对象不存在!"); } From 635d2413683b3e1eecb18a7af50cde7fb42be681 Mon Sep 17 00:00:00 2001 From: iceewei Date: Tue, 6 Apr 2021 15:37:16 +0800 Subject: [PATCH 0310/1181] =?UTF-8?q?feat:=20=E6=B7=BB=E5=8A=A0=E5=BC=82?= =?UTF-8?q?=E5=B8=B8=E4=BF=A1=E6=81=AF=E6=8E=A7=E5=88=B6=E9=9D=99=E6=80=81?= =?UTF-8?q?=E5=8F=98=E9=87=8F=EF=BC=8C=E6=94=B9=E9=80=A0=E4=B8=A4=E5=B1=82?= =?UTF-8?q?=E9=94=99=E8=AF=AF=E7=A0=81=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - AbstractParser类增加了isPrintErrorLog静态变量,暴露给用户控制错误信息抛出 - 内外两层错误码改造,增加抛出异常逻辑(updateCount <= 0) --- .../src/main/java/apijson/orm/AbstractParser.java | 8 ++++++-- .../main/java/apijson/orm/AbstractSQLExecutor.java | 12 ++++++------ Document.md | 4 ++-- 3 files changed, 14 insertions(+), 10 deletions(-) diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java b/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java index a22c05d5c..be8ef09e3 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java @@ -301,6 +301,9 @@ public JSONObject parseResponse(String request) { private int queryDepth; + // 打印异常日志的标识。线上环境比较敏感,可以通过切换该变量来控制异常栈抛出、错误日志打印。保守起见,该值默认为false。 + public static boolean isPrintErrorLog = false; + /**解析请求json并获取对应结果 * @param request * @return requestObject @@ -383,11 +386,12 @@ public JSONObject parseResponse(JSONObject request) { long endTime = System.currentTimeMillis(); long duration = endTime - startTime; - if (Log.DEBUG) { //用 | 替代 /,避免 APIJSON ORM,APIAuto 等解析路径错误 + if (isPrintErrorLog) { //用 | 替代 /,避免 APIJSON ORM,APIAuto 等解析路径错误 requestObject.put("sql:generate|cache|execute|maxExecute", getSQLExecutor().getGeneratedSQLCount() + "|" + getSQLExecutor().getCachedSQLCount() + "|" + getSQLExecutor().getExecutedSQLCount() + "|" + getMaxSQLCount()); requestObject.put("depth:count|max", queryDepth + "|" + getMaxQueryDepth()); requestObject.put("time:start|duration|end", startTime + "|" + duration + "|" + endTime); if (error != null) { + Log.d(TAG, String.format("onObjectParse error, error is %s", error.getMessage())); requestObject.put("throw", error.getClass().getName()); requestObject.put("trace", error.getStackTrace()); } @@ -397,7 +401,7 @@ public JSONObject parseResponse(JSONObject request) { //会不会导致原来的session = null? session = null; - if (Log.DEBUG) { + if (isPrintErrorLog) { Log.d(TAG, "\n\n\n\n<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<\n " + requestMethod + "/parseResponse request = \n" + requestString + "\n\n"); diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java index 043251a56..85b4162dc 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java @@ -27,6 +27,7 @@ import java.util.Map.Entry; import java.util.Set; +import apijson.orm.exception.NotExistException; import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSONObject; @@ -209,14 +210,13 @@ public JSONObject execute(@NotNull SQLConfig config, boolean unknowType) throws executedSQLCount ++; int updateCount = executeUpdate(config); - - result = new JSONObject(); - if (config.getMethod() == RequestMethod.DELETE) { - // 特别地,针对DELETE请求,如果需要提示code,可以用内部的code来判断。其余请求类型统一使用外层错误码。 - result = AbstractParser.newResult(updateCount > 0 ? JSONResponse.CODE_SUCCESS : JSONResponse.CODE_NOT_FOUND, - updateCount > 0 ? JSONResponse.MSG_SUCCEED : "没权限访问或对象不存在!"); + if (updateCount <= 0) { + throw new NotExistException("没权限访问或对象不存在!"); } + // 更新成功后收集结果。例如更新操作成功时,返回count(affected rows)、id字段 + result = new JSONObject(true); + //id,id{}至少一个会有,一定会返回,不用抛异常来阻止关联写操作时前面错误导致后面无条件执行! result.put(JSONResponse.KEY_COUNT, updateCount);//返回修改的记录数 if (config.getId() != null) { diff --git a/Document.md b/Document.md index 701932c9b..c7e7be53c 100644 --- a/Document.md +++ b/Document.md @@ -318,7 +318,7 @@ 3.请求中的 / 需要转义。JSONRequest.java已经用URLEncoder.encode转义,不需要再写;但如果是浏览器或Postman等直接输入url/request,需要把request中的所有 / 都改成 %252F 。下同。
4.code,指返回结果中的状态码,200表示成功,其它都是错误码,值全部都是HTTP标准状态码。下同。
5.msg,指返回结果中的状态信息,对成功结果或错误原因的详细说明。下同。
-6.code和msg总是在返回结果的同一层级成对出现。对所有请求的返回结果都会在最外层有一对总结式code和msg。对非GET类型的请求,返回结果里面的每个JSONObject里都会有一对code和msg说明这个JSONObject的状态。下同。
+6.code和msg总是在返回结果的同一层级成对出现。对所有请求的返回结果都会在最外层有一对总结式code和msg。下同。
7.id等字段对应的值仅供说明,不一定是数据库里存在的,请求里用的是真实存在的值。下同。
@@ -334,7 +334,7 @@ GET:
普通获取数据,
可用浏览器调试 | base_url/get/ | {< HEAD:
普通获取数量,
可用浏览器调试 | base_url/head/ | {
   TableName:{
     …
   }
}
{…}内为限制条件

例如获取一个 id = 38710 的 User 所发布的 Moment 总数:
{
   "Moment":{
     "userId":38710
   }
}
后端校验通过后自动解析为 SQL 并执行:
`SELECT count(*) FROM Moment WHERE userId=38710 LIMIT 1` | {
   TableName:{
     "code":200,
     "msg":"success",
     "count":10
   },
   "code":200,
   "msg":"success"
}
例如
{
   "Moment":{
     "code":200,
     "msg":"success",
     "count":10
   },
   "code":200,
   "msg":"success"
} GETS:
安全/私密获取数据,
用于获取钱包等
对安全性要求高的数据 | base_url/gets/ | 最外层加一个 "tag":tag,其它同GET | 同GET HEADS:
安全/私密获取数量,
用于获取银行卡数量等
对安全性要求高的数据总数 | base_url/heads/ | 最外层加一个 "tag":tag,其它同HEAD | 同HEAD -POST:
新增数据 | base_url/post/ | 单个:
{
   TableName:{
     …
   },
   "tag":tag
}
{…}中id由后端生成,不能传

例如当前登录用户 38710 发布一个新 Comment:
{
   "Comment":{
     "momentId":12,
     "content":"APIJSON,let interfaces and documents go to hell !",
   },
   "tag":"Comment"
}
后端校验通过后自动解析为 SQL 并执行:
`INSERT INTO Comment(userId,momentId,content) VALUES(38710,12,'APIJSON,let interfaces and documents go to hell !')`

批量:
{
   TableName\[]:\[{
       …
     }, {
       …
     }
     …
   ],
   "tag":tag
}
{…}中id由后端生成,不能传

例如当前登录用户 82001 发布 2 个 Comment:
{
   "Comment\[]":\[{
     "momentId":12,
     "content":"APIJSON,let interfaces and documents go to hell !"
     }, {
     "momentId":15,
     "content":"APIJSON is a JSON transmision protocol."
   }],
   "tag":"Comment:[]"
}
后端校验通过后自动解析为 SQL 并执行:
`INSERT INTO Comment(userId,momentId,content) VALUES(82001,12,'APIJSON,let interfaces and documents go to hell !')`
`INSERT INTO Comment(userId,momentId,content) VALUES(82001,15,'APIJSON is a JSON transmision protocol.')` | 单个:
{
   TableName:{
     "code":200,
     "msg":"success",
     "id":38710
   },
   "code":200,
   "msg":"success"
}
例如
{
   "Comment":{
     "code":200,
     "msg":"success",
     "id":120
   },
   "code":200,
   "msg":"success"
}

批量:
{
   TableName:{
     "code":200,
     "msg":"success",
     "count":5,
     "id[]":[1, 2, 3, 4, 5]
   },
   "code":200,
   "msg":"success"
}
例如
{
   "Comment":{
     "code":200,
     "msg":"success",
     "count":2,
     "id[]":\[1, 2]
   },
   "code":200,
   "msg":"success"
} +POST:
新增数据 | base_url/post/ | 单个:
{
   TableName:{
     …
   },
   "tag":tag
}
{…}中id由后端生成,不能传

例如当前登录用户 38710 发布一个新 Comment:
{
   "Comment":{
     "momentId":12,
     "content":"APIJSON,let interfaces and documents go to hell !"
   },
   "tag":"Comment"
}
后端校验通过后自动解析为 SQL 并执行:
`INSERT INTO Comment(userId,momentId,content) VALUES(38710,12,'APIJSON,let interfaces and documents go to hell !')`

批量:
{
   TableName\[]:\[{
       …
     }, {
       …
     }
     …
   ],
   "tag":tag
}
{…}中id由后端生成,不能传

例如当前登录用户 82001 发布 2 个 Comment:
{
   "Comment\[]":\[{
     "momentId":12,
     "content":"APIJSON,let interfaces and documents go to hell !"
     }, {
     "momentId":15,
     "content":"APIJSON is a JSON transmision protocol."
   }],
   "tag":"Comment:[]"
}
后端校验通过后自动解析为 SQL 并执行:
`INSERT INTO Comment(userId,momentId,content) VALUES(82001,12,'APIJSON,let interfaces and documents go to hell !')`
`INSERT INTO Comment(userId,momentId,content) VALUES(82001,15,'APIJSON is a JSON transmision protocol.')` | 单个:
{
   TableName:{
     "code":200,
     "msg":"success",
     "id":38710
   },
   "code":200,
   "msg":"success"
}
例如
{
   "Comment":{
     "code":200,
     "msg":"success",
     "id":120
   },
   "code":200,
   "msg":"success"
}

批量:
{
   TableName:{
     "code":200,
     "msg":"success",
     "count":5,
     "id[]":[1, 2, 3, 4, 5]
   },
   "code":200,
   "msg":"success"
}
例如
{
   "Comment":{
     "code":200,
     "msg":"success",
     "count":2,
     "id[]":\[1, 2]
   },
   "code":200,
   "msg":"success"
} PUT:
修改数据,
只修改所传的字段 | base_url/put/ | {
   TableName:{
     "id":id,
     …
   },
   "tag":tag
}
{…} 中 id 或 id{} 至少传一个

例如当前登录用户 82001 修改 id = 235 的 Moment 的 content:
{
   "Moment":{
     "id":235,
     "content":"APIJSON,let interfaces and documents go to hell !"
   },
   "tag":"Moment"
}
后端校验通过后自动解析为 SQL 并执行:
`UPDATE Moment SET content='APIJSON,let interfaces and documents go to hell !' WHERE id=235 AND userId=82001 LIMIT 1`

批量除了 id{}:\[] 也可类似批量 POST,只是每个 {...} 里面都必须有 id。
"tag":"Comment[]" 对应对象 "Comment":{"id{}":[1,2,3]},表示指定记录全部统一设置;
"tag":"Comment:[]" 多了冒号,对应数组 "Comment[]":[{"id":1},{"id":2},{"id":3}],表示每项单独设置 | 同POST DELETE:
删除数据 | base_url/delete/ | {
   TableName:{
     "id":id
   },
   "tag":tag
}
{…} 中 id 或 id{} 至少传一个,一般只传 id 或 id{}

例如当前登录用户 82001 批量删除 id = 100,110,120 的 Comment:
{
   "Comment":{
     "id{}":[100,110,120]
   },
   "tag":"Comment[]"
}
后端校验通过后自动解析为 SQL 并执行:
`DELETE FROM Comment WHERE id IN(100,110,120) AND userId=82001 LIMIT 3` | {
   TableName:{
     "code":200,
     "msg":"success",
     "id[]":[100,110,120]
      "count":3
   },
   "code":200,
   "msg":"success"
}
例如
{
   "Comment":{
      "code":200,
      "msg":"success",
      "id[]":[100,110,120],
      "count":3
   },
   "code":200,
   "msg":"success"
} From 873afa8470d189648e5e5dc30fc0827da01746c5 Mon Sep 17 00:00:00 2001 From: iceewei Date: Tue, 6 Apr 2021 16:51:57 +0800 Subject: [PATCH 0311/1181] =?UTF-8?q?fix:=20=E6=B7=BB=E5=8A=A0=E5=BC=82?= =?UTF-8?q?=E5=B8=B8=E4=BF=A1=E6=81=AF=E6=8E=A7=E5=88=B6=E9=9D=99=E6=80=81?= =?UTF-8?q?=E5=8F=98=E9=87=8F=EF=BC=8C=E6=94=B9=E9=80=A0=E4=B8=A4=E5=B1=82?= =?UTF-8?q?=E9=94=99=E8=AF=AF=E7=A0=81=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - AbstractParser类增加了isPrintErrorLog静态变量,暴露给用户控制错误信息抛出 - 内外两层错误码改造,增加抛出异常逻辑(updateCount <= 0) --- APIJSONORM/pom.xml | 2 +- APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/APIJSONORM/pom.xml b/APIJSONORM/pom.xml index b892374e2..4c1041bc9 100755 --- a/APIJSONORM/pom.xml +++ b/APIJSONORM/pom.xml @@ -5,7 +5,7 @@ apijson.orm apijson-orm - 4.6.6 + 4.6.7 jar APIJSONORM diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java index 85b4162dc..8966d0132 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java @@ -214,7 +214,7 @@ public JSONObject execute(@NotNull SQLConfig config, boolean unknowType) throws throw new NotExistException("没权限访问或对象不存在!"); } - // 更新成功后收集结果。例如更新操作成功时,返回count(affected rows)、id字段 + // updateCount>0时收集结果。例如更新操作成功时,返回count(affected rows)、id字段 result = new JSONObject(true); //id,id{}至少一个会有,一定会返回,不用抛异常来阻止关联写操作时前面错误导致后面无条件执行! From b67280d8c7765115ef0537715bd9eaf7b9a91017 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Tue, 6 Apr 2021 17:42:02 +0800 Subject: [PATCH 0312/1181] =?UTF-8?q?=E8=B4=A1=E7=8C=AE=E8=80=85=E6=96=B0?= =?UTF-8?q?=E5=A2=9E=E6=9D=A5=E8=87=AA=E8=85=BE=E8=AE=AF=20CSIG=20?= =?UTF-8?q?=E7=9A=84=E5=90=8C=E4=BA=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit https://github.com/Tencent/APIJSON/pull/212 --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 7c3ccc83e..ed92e297d 100644 --- a/README.md +++ b/README.md @@ -242,6 +242,7 @@ https://github.com/Tencent/APIJSON/issues/187 + From 1a75bfba0e8a542f8921ba6d20d920302089d932 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Tue, 6 Apr 2021 17:52:01 +0800 Subject: [PATCH 0313/1181] =?UTF-8?q?=E8=B4=A1=E7=8C=AE=E8=80=85=E5=88=97?= =?UTF-8?q?=E8=A1=A8=E6=96=B0=E5=A2=9E=20=E8=85=BE=E8=AE=AF=E5=90=8E?= =?UTF-8?q?=E5=8F=B0=E5=B7=A5=E7=A8=8B=E5=B8=88=20fineday009?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit https://github.com/Tencent/APIJSON/edit/master/CONTRIBUTING.md --- CONTRIBUTING.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 374f0c5ee..0900ca076 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -10,10 +10,11 @@ - [ruoranw](https://github.com/ruoranw)(现居美国洛杉矶) - [zhoulingfengofcd](https://github.com/zhoulingfengofcd) - [Zerounary](https://github.com/Zerounary) +- [fineday009](https://github.com/fineday009)(腾讯后台工程师) - [vincentCheng](https://github.com/vincentCheng) - [justinfengchen](https://github.com/justinfengchen) - [linlwqq](https://github.com/linlwqq) -- [redcatmiss](https://github.com/redcatmiss)(社保科技员工) +- [redcatmiss](https://github.com/redcatmiss)(社保科技后端工程师) - [linbren](https://github.com/linbren) - [jinzhongjian](https://github.com/jinzhongjian) - [CoolGeo2016](https://github.com/CoolGeo2016) From 8cf170c0bfb71052a09baad4e43029954352442a Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Wed, 7 Apr 2021 01:28:36 +0800 Subject: [PATCH 0314/1181] =?UTF-8?q?AbstractSQLExecutor=20=E4=BC=98?= =?UTF-8?q?=E5=8C=96=E5=A2=9E=E5=88=A0=E6=94=B9=E6=9C=AA=E6=88=90=E5=8A=9F?= =?UTF-8?q?=E4=B9=9F=E6=9C=AA=E6=8A=9B=E5=BC=82=E5=B8=B8=E7=9A=84=20code?= =?UTF-8?q?=20=E5=92=8C=20msg=EF=BC=9BAbstractParser=20=E4=BC=98=E5=8C=96?= =?UTF-8?q?=E8=AF=B7=E6=B1=82=E5=8F=8A=E5=93=8D=E5=BA=94=E7=9A=84=E6=97=A5?= =?UTF-8?q?=E5=BF=97=E6=89=93=E5=8D=B0=EF=BC=9BAbstractSQLConfig=20?= =?UTF-8?q?=E4=BC=98=E5=8C=96=20key$=20=E7=9A=84=E6=A0=BC=E5=BC=8F?= =?UTF-8?q?=E6=A0=A1=E9=AA=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../main/java/apijson/orm/AbstractParser.java | 29 +++++++++---------- .../java/apijson/orm/AbstractSQLConfig.java | 7 +++-- .../java/apijson/orm/AbstractSQLExecutor.java | 5 ++-- 3 files changed, 21 insertions(+), 20 deletions(-) diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java b/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java index be8ef09e3..7f3a2f3c2 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java @@ -48,6 +48,11 @@ public abstract class AbstractParser implements Parser, ParserCreator, VerifierCreator, SQLCreator { protected static final String TAG = "AbstractParser"; + /** + * 打印大数据量日志的标识。线上环境比较敏感,可以通过切换该变量来控制异常栈抛出、错误日志打印。保守起见,该值默认为false。 + * 与 {@link Log#DEBUG} 任何一个为 true 都会打印关键的接口请求及响应信息。 + */ + public static boolean IS_PRINT_BIG_LOG = false; /** * method = null @@ -301,9 +306,6 @@ public JSONObject parseResponse(String request) { private int queryDepth; - // 打印异常日志的标识。线上环境比较敏感,可以通过切换该变量来控制异常栈抛出、错误日志打印。保守起见,该值默认为false。 - public static boolean isPrintErrorLog = false; - /**解析请求json并获取对应结果 * @param request * @return requestObject @@ -386,12 +388,11 @@ public JSONObject parseResponse(JSONObject request) { long endTime = System.currentTimeMillis(); long duration = endTime - startTime; - if (isPrintErrorLog) { //用 | 替代 /,避免 APIJSON ORM,APIAuto 等解析路径错误 + if (Log.DEBUG) { requestObject.put("sql:generate|cache|execute|maxExecute", getSQLExecutor().getGeneratedSQLCount() + "|" + getSQLExecutor().getCachedSQLCount() + "|" + getSQLExecutor().getExecutedSQLCount() + "|" + getMaxSQLCount()); requestObject.put("depth:count|max", queryDepth + "|" + getMaxQueryDepth()); requestObject.put("time:start|duration|end", startTime + "|" + duration + "|" + endTime); if (error != null) { - Log.d(TAG, String.format("onObjectParse error, error is %s", error.getMessage())); requestObject.put("throw", error.getClass().getName()); requestObject.put("trace", error.getStackTrace()); } @@ -399,17 +400,15 @@ public JSONObject parseResponse(JSONObject request) { onClose(); - //会不会导致原来的session = null? session = null; - - if (isPrintErrorLog) { - Log.d(TAG, "\n\n\n\n<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<\n " - + requestMethod + "/parseResponse request = \n" + requestString + "\n\n"); - - Log.d(TAG, "parseResponse return response = \n" + JSON.toJSONString(requestObject) - + "\n >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> \n\n\n"); + System.err.println("\n\n\n\n<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<\n " + + TAG + ".DEBUG: " + requestMethod + "/parseResponse request = \n" + requestString + "\n\n"); + + if (Log.DEBUG || IS_PRINT_BIG_LOG || error != null) { // 日志仅存服务器,所以不太敏感,而且这些日志虽然量大但非常重要,对排查 bug 很关键 + System.err.println(TAG + ".DEBUG: " + requestMethod + "/parseResponse return response = \n" + JSON.toJSONString(requestObject) + "\n\n"); } - Log.d(TAG, "parseResponse endTime = " + endTime + "; duration = " + duration - + ">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>\n\n\n"); + + System.err.println(TAG + ".DEBUG: " + requestMethod + "/parseResponse endTime = " + endTime + "; duration = " + duration + + "\n>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> \n\n\n"); return res; } diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java index 6f61b9c19..968128011 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java @@ -2018,9 +2018,12 @@ public String getSearchString(String key, Object[] values, int type) throws Ille if (v instanceof String == false) { throw new IllegalArgumentException(key + "$:value 中 value 的类型只能为 String 或 String[]!"); } - if (((String) v).contains("%%")) { - throw new IllegalArgumentException(key + "$:value 中 value 值 " + v + " 中包含 %% !不允许有连续的 % !"); + if (((String) v).isEmpty()) { // 允许查空格 StringUtil.isEmpty((String) v, true) + throw new IllegalArgumentException(key + "$:value 中 value 值 " + v + "是空字符串,没有意义,不允许这样传!"); } + // if (((String) v).contains("%%")) { // 需要通过 %\%% 来模糊搜索 % + // throw new IllegalArgumentException(key + "$:value 中 value 值 " + v + " 中包含 %% !不允许有连续的 % !"); + // } condition += (i <= 0 ? "" : (Logic.isAnd(type) ? AND : OR)) + getLikeString(key, v); } diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java index 8966d0132..68bd8fe7e 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java @@ -27,7 +27,6 @@ import java.util.Map.Entry; import java.util.Set; -import apijson.orm.exception.NotExistException; import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSONObject; @@ -211,11 +210,11 @@ public JSONObject execute(@NotNull SQLConfig config, boolean unknowType) throws int updateCount = executeUpdate(config); if (updateCount <= 0) { - throw new NotExistException("没权限访问或对象不存在!"); + throw new IllegalAccessException("没权限访问或对象不存在!"); // NotExistException 会被 catch 转为成功状态 } // updateCount>0时收集结果。例如更新操作成功时,返回count(affected rows)、id字段 - result = new JSONObject(true); + result = AbstractParser.newSuccessResult(); // TODO 对 APIAuto 及其它现有的前端/客户端影响比较大,暂时还是返回 code 和 msg,5.0 再移除 new JSONObject(true); //id,id{}至少一个会有,一定会返回,不用抛异常来阻止关联写操作时前面错误导致后面无条件执行! result.put(JSONResponse.KEY_COUNT, updateCount);//返回修改的记录数 From 7d6226bdd976885aa59796b2958f658cf36c3e16 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Wed, 7 Apr 2021 01:43:07 +0800 Subject: [PATCH 0315/1181] =?UTF-8?q?=E8=B4=A1=E7=8C=AE=E8=80=85=E5=88=97?= =?UTF-8?q?=E8=A1=A8=E6=96=B0=E5=A2=9E=20=E8=85=BE=E8=AE=AF=20CSIG=20?= =?UTF-8?q?=E5=90=8C=E4=BA=8B=20fineday009?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CONTRIBUTING.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 0900ca076..091950a18 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -10,11 +10,11 @@ - [ruoranw](https://github.com/ruoranw)(现居美国洛杉矶) - [zhoulingfengofcd](https://github.com/zhoulingfengofcd) - [Zerounary](https://github.com/Zerounary) -- [fineday009](https://github.com/fineday009)(腾讯后台工程师) +- [fineday009](https://github.com/fineday009)(腾讯工程师) - [vincentCheng](https://github.com/vincentCheng) - [justinfengchen](https://github.com/justinfengchen) - [linlwqq](https://github.com/linlwqq) -- [redcatmiss](https://github.com/redcatmiss)(社保科技后端工程师) +- [redcatmiss](https://github.com/redcatmiss)(社保科技工程师) - [linbren](https://github.com/linbren) - [jinzhongjian](https://github.com/jinzhongjian) - [CoolGeo2016](https://github.com/CoolGeo2016) From f4d8775acba7b0ed4145e270a12a2c5bff8413b8 Mon Sep 17 00:00:00 2001 From: 403f <1292451605@qq.com> Date: Sat, 10 Apr 2021 22:44:04 +0800 Subject: [PATCH 0316/1181] =?UTF-8?q?=E5=AF=B9JSONResponse.java=E4=B8=AD?= =?UTF-8?q?=E7=9A=84formatHyphen=E6=96=B9=E6=B3=95=E7=9A=84=E4=BC=98?= =?UTF-8?q?=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/main/java/apijson/JSONResponse.java | 23 ++++++++----------- 1 file changed, 9 insertions(+), 14 deletions(-) diff --git a/APIJSONORM/src/main/java/apijson/JSONResponse.java b/APIJSONORM/src/main/java/apijson/JSONResponse.java index 5cc3a0b6e..ae60fd247 100755 --- a/APIJSONORM/src/main/java/apijson/JSONResponse.java +++ b/APIJSONORM/src/main/java/apijson/JSONResponse.java @@ -7,6 +7,7 @@ import java.util.List; import java.util.Set; +import java.util.StringTokenizer; import com.alibaba.fastjson.JSONArray; import com.alibaba.fastjson.JSONObject; @@ -502,23 +503,17 @@ public static String formatColon(@NotNull String key) { * @return */ public static String formatHyphen(@NotNull String key, boolean firstCase) { - boolean first = true; - int index; - String name = ""; - String 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); + StringTokenizer parts = new StringTokenizer(key, "-"); + name += parts.nextToken(); + while(parts.hasMoreTokens()) + { + String part = parts.nextToken(); + name += firstCase ? StringUtil.firstCase(part, true) : part; + } - return name; + return name; } From 8646eab6f4ef379f404ebaab23501aaa71755ab6 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Tue, 13 Apr 2021 22:49:51 +0800 Subject: [PATCH 0317/1181] =?UTF-8?q?=E8=B4=A1=E7=8C=AE=E8=80=85=E4=BB=AC?= =?UTF-8?q?=E6=96=B0=E5=A2=9E=20403f=EF=BC=8C=E6=84=9F=E8=B0=A2=E5=85=B3?= =?UTF-8?q?=E4=BA=8E=E6=80=A7=E8=83=BD=E4=BC=98=E5=8C=96=E7=9A=84=E4=BB=A3?= =?UTF-8?q?=E7=A0=81=E8=B4=A1=E7=8C=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit https://github.com/Tencent/APIJSON/pull/217 --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index ed92e297d..dabe909ad 100644 --- a/README.md +++ b/README.md @@ -248,6 +248,7 @@ https://github.com/Tencent/APIJSON/issues/187 + From ef41ebb2cb61ddaeb5a4d6ccfc01abf2f0d22135 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Tue, 13 Apr 2021 22:52:56 +0800 Subject: [PATCH 0318/1181] =?UTF-8?q?=E8=B4=A1=E7=8C=AE=E8=80=85=E5=90=8D?= =?UTF-8?q?=E5=8D=95=E6=96=B0=E5=A2=9E=20403f=EF=BC=8C=E6=84=9F=E8=B0=A2?= =?UTF-8?q?=E5=85=B3=E4=BA=8E=E6=80=A7=E8=83=BD=E4=BC=98=E5=8C=96=E7=9A=84?= =?UTF-8?q?=E4=BB=A3=E7=A0=81=E8=B4=A1=E7=8C=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit https://github.com/Tencent/APIJSON/pull/217 --- CONTRIBUTING.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 091950a18..69ea4ac49 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -5,7 +5,7 @@ ## Acknowledgements -非常感谢以下几位贡献者对于 APIJSON 的做出的贡献: +非常感谢以下贡献者们对于 APIJSON 的做出的贡献: - [ruoranw](https://github.com/ruoranw)(现居美国洛杉矶) - [zhoulingfengofcd](https://github.com/zhoulingfengofcd) @@ -21,6 +21,7 @@ - [1906522096](https://github.com/1906522096) - [github-ganyu](https://github.com/github-ganyu) - [sunxiaoguang](https://github.com/sunxiaoguang)(知乎基础研发架构师) +- [403f](https://github.com/Tencent/APIJSON/pull/217) #### 其中特别致谢:
justinfengchen 提交的 6 个 Commits, 对 APIJSON 做出了 3,130 增加和 0 处删减(截止 2020/11/04 日);
From 6f80013878331b19a689e6b0bbafcdf0e87b4aa6 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Sat, 17 Apr 2021 17:56:27 +0800 Subject: [PATCH 0319/1181] =?UTF-8?q?=E8=A7=A3=E5=86=B3=20JOIN=20=E5=89=AF?= =?UTF-8?q?=E8=A1=A8=E6=9C=89=20=E5=BC=95=E7=94=A8=E8=B5=8B=E5=80=BC=20?= =?UTF-8?q?=E5=A4=96=E7=9A=84=E6=9D=A1=E4=BB=B6=E6=97=B6=E5=9B=A0=E4=B8=BA?= =?UTF-8?q?=E7=BC=93=E5=AD=98=20SQL=20WHERE=20=E4=B8=AD=E6=9D=A1=E4=BB=B6?= =?UTF-8?q?=E9=A1=BA=E5=BA=8F=E4=B8=8D=E4=B8=80=E8=87=B4=E5=AF=BC=E8=87=B4?= =?UTF-8?q?=E5=A4=9A=E4=BD=99=E6=9F=A5=E8=AF=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/main/java/apijson/orm/AbstractParser.java | 14 +++++++++++++- .../main/java/apijson/orm/AbstractSQLConfig.java | 4 +++- .../main/java/apijson/orm/AbstractSQLExecutor.java | 8 ++++---- 3 files changed, 20 insertions(+), 6 deletions(-) diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java b/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java index 7f3a2f3c2..d068104bf 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java @@ -1174,7 +1174,19 @@ else if (join != null){ throw new IllegalArgumentException("/" + path + ":'/targetTable/targetKey' 中路径对应的对象 '" + targetTableKey + "':{} 不存在或值为 null !必须是 {} 这种 JSONObject 格式!"); } - tableObj.put(key, tableObj.remove(key)); //保证和SQLExcecutor缓存的Config里where顺序一致,生成的SQL也就一致 + // 保证和 SQLExcecutor 缓存的 Config 里 where 顺序一致,生成的 SQL 也就一致 <<<<<<<<< + // AbstractSQLConfig.newSQLConfig 中强制把 id, id{}, userId, userId{} 放到了最前面 tableObj.put(key, tableObj.remove(key)); + + if (tableObj.size() > 1) { // 把 key 强制放最前,AbstractSQLExcecutor 中 config.putWhere 也是放尽可能最前 + JSONObject newTableObj = new JSONObject(tableObj.size(), true); + newTableObj.put(key, tableObj.remove(key)); + newTableObj.putAll(tableObj); + + tableObj = newTableObj; + request.put(tableKey, tableObj); + } + // 保证和 SQLExcecutor 缓存的 Config 里 where 顺序一致,生成的 SQL 也就一致 >>>>>>>>> + Join j = new Join(); j.setPath(path); diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java index 968128011..4ed74b0f5 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java @@ -1570,7 +1570,9 @@ public AbstractSQLConfig putWhere(String key, Object value, boolean prior) { combine = getCombine(); List andList = combine.get("&"); if (value == null) { - andList.remove(key); + if (andList != null) { + andList.remove(key); + } } else if (andList == null || andList.contains(key) == false) { int i = 0; diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java index 68bd8fe7e..05521ba0f 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java @@ -408,8 +408,8 @@ protected void executeAppJoin(SQLConfig config, List resultList, Map //替换为 "id{}": [userId1, userId2, userId3...] - jc.putWhere(j.getOriginKey(), null, false); - jc.putWhere(j.getKey() + "{}", targetValueList, false); + jc.putWhere(j.getOriginKey(), null, false); // remove orginKey + jc.putWhere(j.getKey() + "{}", targetValueList, true); // add orginKey{} jc.setMain(true).setPreparedValueList(new ArrayList<>()); @@ -456,7 +456,7 @@ protected void executeAppJoin(SQLConfig config, List resultList, Map + "\n >>>>>>>>>>>>>>>>>>>>>>>>>>> \n\n"); //缓存到 childMap - cc.putWhere(j.getKey(), result.get(j.getKey()), false); + cc.putWhere(j.getKey(), result.get(j.getKey()), true); cacheSql = cc.getSQL(false); childMap.put(cacheSql, result); @@ -531,7 +531,7 @@ protected JSONObject onPutColumn(@NotNull SQLConfig config, @NotNull ResultSet r if (childConfig != null && childTable.equalsIgnoreCase(childConfig.getSQLTable())) { - childConfig.putWhere(j.getKey(), table.get(j.getTargetKey()), false); + childConfig.putWhere(j.getKey(), table.get(j.getTargetKey()), true); childSql = childConfig.getSQL(false); if (StringUtil.isEmpty(childSql, true)) { From b900c5819c3bc641845fffce3e205da6b64bccda Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Sun, 18 Apr 2021 01:38:29 +0800 Subject: [PATCH 0320/1181] =?UTF-8?q?=E8=A7=A3=E5=86=B3=20>=20RIGHT=20JOIN?= =?UTF-8?q?,=20^=20SIDE=20JOIN,=20!=20ANTI=20JOIN,=20)=20FOREIGN=20JOIN=20?= =?UTF-8?q?=E7=AD=89=E4=B8=8D=E8=BF=94=E5=9B=9E=E5=89=AF=E8=A1=A8=E6=95=B0?= =?UTF-8?q?=E6=8D=AE=EF=BC=9B=E8=A7=A3=E5=86=B3=20|=20FULL=20JOIN=20?= =?UTF-8?q?=E8=BF=94=E5=9B=9E=E7=9A=84=E5=89=AF=E8=A1=A8=E6=95=B0=E6=8D=AE?= =?UTF-8?q?=E9=83=A8=E5=88=86=E6=98=AF=E9=94=99=E7=9A=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/apijson/orm/AbstractSQLConfig.java | 17 +++--- .../java/apijson/orm/AbstractSQLExecutor.java | 18 +++++-- .../src/main/java/apijson/orm/Join.java | 52 +++++++++++++++---- 3 files changed, 67 insertions(+), 20 deletions(-) diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java index 4ed74b0f5..d4ac822f8 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java @@ -1769,8 +1769,8 @@ else if ("!".equals(ce.getKey())) { if (isAntiJoin) { // ( ANTI JOIN: A & ! B newWs += " ( " + ( isWsEmpty ? "" : ws + AND ) + NOT + " ( " + js + " ) " + " ) "; } - else if (isForeignJoin) { // ) FOREIGN JOIN: B & ! A - newWs += " ( " + " ( " + js + " ) " + ( isWsEmpty ? "" : AND + NOT + ws ) + " ) "; + else if (isForeignJoin) { // ) FOREIGN JOIN: (! A) & B // preparedValueList.add 不好反过来 B & ! A + newWs += " ( " + NOT + " ( " + ws + " ) ) " + AND + " ( " + js + " ) "; } else if (isSideJoin) { // ^ SIDE JOIN: ! (A & B) //MySQL 因为 NULL 值处理问题,(A & ! B) | (B & ! A) 与 ! (A & B) 返回结果不一样,后者往往更多 @@ -3203,7 +3203,7 @@ public static SQLConfig parseJoin(RequestMethod method, SQLConfig config, List resultList, Map continue; } - jc = j.getJoinConfig(); cc = j.getCacheConfig(); //这里用config改了getSQL后再还原很麻烦,所以提前给一个config2更好 + if (cc == null) { + if (Log.DEBUG) { + throw new NullPointerException("服务器内部错误, executeAppJoin cc == null ! 导致不能缓存 @ APP JOIN 的副表数据!"); + } + continue; + } + + jc = j.getJoinConfig(); //取出 "id@": "@/User/userId" 中所有 userId 的值 List targetValueList = new ArrayList<>(); @@ -543,15 +550,18 @@ protected JSONObject onPutColumn(@NotNull SQLConfig config, @NotNull ResultSet r } } } + + } + Object value = getValue(config, rs, rsmd, tablePosition, table, columnIndex, lable, childMap); + if (value != null) { if (finalTable == null) { finalTable = new JSONObject(true); childMap.put(childSql, finalTable); } + finalTable.put(lable, value); } - - finalTable.put(lable, getValue(config, rs, rsmd, tablePosition, table, columnIndex, lable, childMap)); - + return table; } diff --git a/APIJSONORM/src/main/java/apijson/orm/Join.java b/APIJSONORM/src/main/java/apijson/orm/Join.java index 01761037a..70c4401b9 100644 --- a/APIJSONORM/src/main/java/apijson/orm/Join.java +++ b/APIJSONORM/src/main/java/apijson/orm/Join.java @@ -162,7 +162,48 @@ else if (originKey.endsWith("<>")) { } + public boolean isAppJoin() { + return "@".equals(getJoinType()); + } + public boolean isLeftJoin() { + return "<".equals(getJoinType()); + } + public boolean isRightJoin() { + return ">".equals(getJoinType()); + } + public boolean isCrossJoin() { + return "*".equals(getJoinType()); + } + public boolean isInnerJoin() { + return "&".equals(getJoinType()); + } + public boolean isFullJoin() { + String jt = getJoinType(); + return "".equals(jt) || "|".equals(jt); + } + public boolean isOuterJoin() { + return "!".equals(getJoinType()); + } + public boolean isSideJoin() { + return "^".equals(getJoinType()); + } + public boolean isAntiJoin() { + return "(".equals(getJoinType()); + } + public boolean isForeignJoin() { + return ")".equals(getJoinType()); + } + public boolean isLeftOrRightJoin() { + String jt = getJoinType(); + return "<".equals(jt) || ">".equals(jt); + } + + public boolean canCacheViceTable() { + String jt = getJoinType(); + return "@".equals(jt) || "<".equals(jt) || ">".equals(jt) || "&".equals(jt) || "*".equals(jt) || ")".equals(jt); + } + public boolean isSQLJoin() { return ! isAppJoin(); } @@ -171,22 +212,13 @@ public static boolean isSQLJoin(Join j) { return j != null && j.isSQLJoin(); } - public boolean isAppJoin() { - return "@".equals(getJoinType()); - } - public static boolean isAppJoin(Join j) { return j != null && j.isAppJoin(); } - - public boolean isLeftOrRightJoin() { - return "<".equals(getJoinType()) || ">".equals(getJoinType()); - } - + public static boolean isLeftOrRightJoin(Join j) { return j != null && j.isLeftOrRightJoin(); } - } From 4ebc995f91e0ca42cc206de0ef555393d6be8b87 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Sun, 18 Apr 2021 01:45:59 +0800 Subject: [PATCH 0321/1181] =?UTF-8?q?=E8=B0=83=E6=95=B4=E7=94=A8=E6=88=B7?= =?UTF-8?q?=E5=8F=91=E6=96=87=E7=9A=84=E9=93=BE=E6=8E=A5=EF=BC=9A=E5=85=A8?= =?UTF-8?q?=E5=9B=BD=E8=A1=8C=E6=94=BF=E5=8C=BA=E5=88=92=E6=95=B0=E6=8D=AE?= =?UTF-8?q?=E6=8A=93=E5=8F=96=E4=B8=8E=E5=A4=84=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit https://my.oschina.net/hwxia/blog/4999897 --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index dabe909ad..5194f1b3e 100644 --- a/README.md +++ b/README.md @@ -73,7 +73,7 @@ APIJSON 是一种专为 API 而生的 JSON 网络传输协议 以及 基于这 * 自动生成文档,不用再编写和维护 * 自动校验权限、自动管理版本、自动防 SQL 注入 * 开放 API 无需划分版本,始终保持兼容 -* 支持增删改查、模糊搜索、正则匹配、远程函数等 +* 支持增删改查、复杂查询、跨库连表、远程函数等
@@ -356,7 +356,7 @@ https://github.com/Tencent/APIJSON/blob/master/CONTRIBUTING.md [APIJSON复杂业务深入实践(类似12306订票系统)](https://blog.csdn.net/aa330233789/article/details/105309571) -[全国行政区划数据抓取与处理](https://www.mdeditor.tw/pl/gCVk) +[全国行政区划数据抓取与处理](https://my.oschina.net/hwxia/blog/4999897) ### 生态项目 [APIJSON-Demo](https://github.com/APIJSON/APIJSON-Demo) APIJSON 各种语言、各种框架 的 使用示例项目、上手文档、测试数据 SQL 文件 等 From 838b3b08c8b7353748380c6c03a3b1dc08af6263 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=A1=BE=E5=8A=A0=E6=98=A5?= Date: Tue, 20 Apr 2021 10:56:20 +0800 Subject: [PATCH 0322/1181] =?UTF-8?q?fix:=20@explain=E5=9C=A8=E6=9B=B4?= =?UTF-8?q?=E6=96=B0=E6=96=B9=E6=B3=95=E5=BA=94=E7=94=A8=E7=9A=84=E9=94=99?= =?UTF-8?q?=E8=AF=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit explain只应用在select请求中,如果是更新请求 不需要执行explain,但可以返回sql语句 issue #218 closes #218 --- .../main/java/apijson/orm/AbstractParser.java | 26 +++++++++++++------ 1 file changed, 18 insertions(+), 8 deletions(-) diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java b/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java index d068104bf..697c145b7 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java @@ -1512,6 +1512,8 @@ public static JSONObject getJSONObject(JSONObject object, String key) { public static final String KEY_CONFIG = "config"; + + public static final String KEY_SQL = "sql"; protected Map> arrayMainCacheMap = new HashMap<>(); public void putArrayMainCache(String arrayPath, List mainTableDataList) { @@ -1549,19 +1551,27 @@ public JSONObject executeSQL(SQLConfig config, boolean isSubquery) throws Except JSONObject result; boolean explain = config.isExplain(); - if (explain) { //如果先执行 explain,则 execute 会死循环,所以只能先执行非 explain + if (explain) { + //如果先执行 explain,则 execute 会死循环,所以只能先执行非 explain config.setExplain(false); //对下面 config.getSQL(false); 生效 JSONObject res = getSQLExecutor().execute(config, false); - config.setExplain(explain); - JSONObject explainResult = config.isMain() && config.getPosition() != 0 ? null : getSQLExecutor().execute(config, false); + //如果是查询方法,才能执行explain + if (RequestMethod.isQueryMethod(config.getMethod())){ + config.setExplain(explain); + JSONObject explainResult = config.isMain() && config.getPosition() != 0 ? null : getSQLExecutor().execute(config, false); - if (explainResult == null) { - result = res; - } - else { + if (explainResult == null) { + result = res; + } + else { + result = new JSONObject(true); + result.put(KEY_EXPLAIN, explainResult); + result.putAll(res); + } + }else{//如果是更新请求,不执行explain,但可以返回sql result = new JSONObject(true); - result.put(KEY_EXPLAIN, explainResult); + result.put(KEY_SQL, config.getSQL(false)); result.putAll(res); } } From 93efd13a8b3b7974d4e9b864dcfd6f56d10b0c8d Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Tue, 20 Apr 2021 23:23:08 +0800 Subject: [PATCH 0323/1181] =?UTF-8?q?=E8=B4=A1=E7=8C=AE=E8=80=85=E6=96=B0?= =?UTF-8?q?=E5=A2=9E=20gujiachun=EF=BC=8C=E6=84=9F=E8=B0=A2=E8=A7=A3?= =?UTF-8?q?=E5=86=B3=E5=AF=B9=E5=A2=9E=E5=88=A0=E6=94=B9=E7=94=A8=20@expla?= =?UTF-8?q?in=20=E7=9A=84=20bug?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit https://github.com/Tencent/APIJSON/pull/219 --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 5194f1b3e..d120c0289 100644 --- a/README.md +++ b/README.md @@ -248,6 +248,7 @@ https://github.com/Tencent/APIJSON/issues/187 + From 3c9084479876748055ebe048d388098ef05c3e23 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Tue, 20 Apr 2021 23:25:00 +0800 Subject: [PATCH 0324/1181] =?UTF-8?q?=E8=B4=A1=E7=8C=AE=E8=80=85=E5=90=8D?= =?UTF-8?q?=E5=8D=95=E6=96=B0=E5=A2=9E=20gujiachun=EF=BC=8C=E6=84=9F?= =?UTF-8?q?=E8=B0=A2=E8=A7=A3=E5=86=B3=E5=AF=B9=E5=A2=9E=E5=88=A0=E6=94=B9?= =?UTF-8?q?=E7=94=A8=20@explain=20=E7=9A=84=20bug?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit https://github.com/Tencent/APIJSON/pull/219 --- CONTRIBUTING.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 69ea4ac49..8ef74b84e 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -21,7 +21,8 @@ - [1906522096](https://github.com/1906522096) - [github-ganyu](https://github.com/github-ganyu) - [sunxiaoguang](https://github.com/sunxiaoguang)(知乎基础研发架构师) -- [403f](https://github.com/Tencent/APIJSON/pull/217) +- [403f](https://github.com/403f) +- [gujiachun](https://github.com/gujiachun) #### 其中特别致谢:
justinfengchen 提交的 6 个 Commits, 对 APIJSON 做出了 3,130 增加和 0 处删减(截止 2020/11/04 日);
From 68d94d43b291eae6f51f5db7ace8d8ee308c7cf3 Mon Sep 17 00:00:00 2001 From: gdjs2 Date: Thu, 22 Apr 2021 07:43:03 +0000 Subject: [PATCH 0325/1181] Using Arrays.toString() to deal with methods array --- .../src/main/java/apijson/orm/AbstractFunctionParser.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractFunctionParser.java b/APIJSONORM/src/main/java/apijson/orm/AbstractFunctionParser.java index d712b15f5..3debe096a 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractFunctionParser.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractFunctionParser.java @@ -167,7 +167,7 @@ public static Object invoke(@NotNull AbstractFunctionParser parser, @NotNull Str String[] methods = StringUtil.split(row.getString("methods")); List ml = methods == null || methods.length <= 0 ? null : Arrays.asList(methods); if (ml != null && ml.contains(parser.getMethod().toString()) == false) { - throw new UnsupportedOperationException("不允许 method = " + parser.getMethod() + " 的请求调用远程函数 " + fb.getMethod() + " ! 必须满足 method 在 " + methods + "内 !"); + throw new UnsupportedOperationException("不允许 method = " + parser.getMethod() + " 的请求调用远程函数 " + fb.getMethod() + " ! 必须满足 method 在 " + Arrays.toString(methods) + "内 !"); } try { From ab5c047d3160c6cc788aae8e8d536f4773b42099 Mon Sep 17 00:00:00 2001 From: Rkyzzy <982993741@qq.com> Date: Thu, 22 Apr 2021 17:22:34 +0800 Subject: [PATCH 0326/1181] =?UTF-8?q?feat:=20=E4=BD=BF=E7=94=A8StringBuild?= =?UTF-8?q?er=E4=BC=98=E5=8C=96=E4=BA=86=E5=AD=97=E7=AC=A6=E4=B8=B2?= =?UTF-8?q?=E6=8B=BC=E6=8E=A5=20=E5=B0=86StringUtil.java=E7=B1=BB=E4=B8=AD?= =?UTF-8?q?=E4=B8=89=E5=A4=84=E5=AD=97=E7=AC=A6=E4=B8=B2=E7=9B=B4=E6=8E=A5?= =?UTF-8?q?=E6=8B=BC=E6=8E=A5=E4=BC=98=E5=8C=96=E4=B8=BA=E4=BD=BF=E7=94=A8?= =?UTF-8?q?StringBuilder=E6=8B=BC=E6=8E=A5=20issue=20#182?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/main/java/apijson/StringUtil.java | 21 ++++++++++--------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/APIJSONORM/src/main/java/apijson/StringUtil.java b/APIJSONORM/src/main/java/apijson/StringUtil.java index 7f8fa831c..fed6d9933 100755 --- a/APIJSONORM/src/main/java/apijson/StringUtil.java +++ b/APIJSONORM/src/main/java/apijson/StringUtil.java @@ -118,7 +118,7 @@ public static String getString(Object[] array, String split) { * @return */ public static String getString(Object[] array, String split, boolean ignoreEmptyItem) { - String s = ""; + StringBuilder s = new StringBuilder(""); if (array != null) { if (split == null) { split = ","; @@ -127,10 +127,10 @@ public static String getString(Object[] array, String split, boolean ignoreEmpty if (ignoreEmptyItem && isEmpty(array[i], true)) { continue; } - s += ((i > 0 ? split : "") + array[i]); + s.append(((i > 0 ? split : "") + array[i])); } } - return getString(s); + return getString(s.toString()); } //获取string,为null时返回"" >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> @@ -540,20 +540,19 @@ public static String getNumber(String s, boolean onlyStart) { return ""; } - String numberString = ""; + StringBuilder numberString = new StringBuilder(""); String single; for (int i = 0; i < s.length(); i++) { single = s.substring(i, i + 1); if (isNumer(single)) { - numberString += single; + numberString.append(single); } else { if (onlyStart) { - return numberString; + return numberString.toString(); } } } - - return numberString; + return numberString.toString(); } //提取特殊字符>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> @@ -643,14 +642,16 @@ public static String getPrice(String price, int formatType) { } //单独写到getCorrectPrice? <<<<<<<<<<<<<<<<<<<<<< - String correctPrice = ""; + String correctPrice; + StringBuilder correctPriceBuilder = new StringBuilder(""); String s; for (int i = 0; i < price.length(); i++) { s = price.substring(i, i + 1); if (".".equals(s) || isNumer(s)) { - correctPrice += s; + correctPriceBuilder.append(s); } } + correctPrice = correctPriceBuilder.toString(); //单独写到getCorrectPrice? >>>>>>>>>>>>>>>>>>>>>> Log.i(TAG, "getPrice <<<<<<<<<<<<<<<<<< correctPrice = " + correctPrice); From a8b388b0bab9b7a0c5288bd1813b27902fc8bf37 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Fri, 23 Apr 2021 10:56:06 +0800 Subject: [PATCH 0327/1181] Update CONTRIBUTING.md --- CONTRIBUTING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 8ef74b84e..bae8c1cc4 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -62,7 +62,7 @@ APIJSON 作为腾讯开源的知名热门项目,贡献代码除了可以给简 我们除了希望听到您的反馈和建议外,我们也希望您接受代码形式的直接帮助,对我们的 GitHub 发出 Pull Request 请求。 -以下是具体步骤: +以下是具体步骤:(注意本步骤将不会自动记录贡献者的 GitHub 账号到 contributors 中,推荐用以下 [详细的图文步骤](https://github.com/Tencent/APIJSON/blob/master/CONTRIBUTING.md#%E8%AF%A6%E7%BB%86%E7%9A%84%E5%9B%BE%E6%96%87%E6%AD%A5%E9%AA%A4%E5%8F%AF%E5%8F%82%E8%80%83%E4%BB%A5%E4%B8%8B%E4%BB%BB%E6%84%8F%E4%B8%80%E7%AF%87)) #### Fork 仓库 From 5826b45a82dfd7caa321d4e23be90f337b69aeee Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Fri, 23 Apr 2021 10:57:48 +0800 Subject: [PATCH 0328/1181] Update CONTRIBUTING.md --- CONTRIBUTING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index bae8c1cc4..40cee5241 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -62,7 +62,7 @@ APIJSON 作为腾讯开源的知名热门项目,贡献代码除了可以给简 我们除了希望听到您的反馈和建议外,我们也希望您接受代码形式的直接帮助,对我们的 GitHub 发出 Pull Request 请求。 -以下是具体步骤:(注意本步骤将不会自动记录贡献者的 GitHub 账号到 contributors 中,推荐用以下 [详细的图文步骤](https://github.com/Tencent/APIJSON/blob/master/CONTRIBUTING.md#%E8%AF%A6%E7%BB%86%E7%9A%84%E5%9B%BE%E6%96%87%E6%AD%A5%E9%AA%A4%E5%8F%AF%E5%8F%82%E8%80%83%E4%BB%A5%E4%B8%8B%E4%BB%BB%E6%84%8F%E4%B8%80%E7%AF%87)) +以下是具体步骤:(注意本步骤将不会自动把贡献者添加到 Contributors 中,推荐用以下 [详细的图文步骤](https://github.com/Tencent/APIJSON/blob/master/CONTRIBUTING.md#%E8%AF%A6%E7%BB%86%E7%9A%84%E5%9B%BE%E6%96%87%E6%AD%A5%E9%AA%A4%E5%8F%AF%E5%8F%82%E8%80%83%E4%BB%A5%E4%B8%8B%E4%BB%BB%E6%84%8F%E4%B8%80%E7%AF%87)) #### Fork 仓库 From 373dae0fc8656cafdcab5ebe3ac1a0cde478ab5c Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Fri, 23 Apr 2021 10:59:04 +0800 Subject: [PATCH 0329/1181] Update CONTRIBUTING.md --- CONTRIBUTING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 40cee5241..30e47f32c 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -62,7 +62,7 @@ APIJSON 作为腾讯开源的知名热门项目,贡献代码除了可以给简 我们除了希望听到您的反馈和建议外,我们也希望您接受代码形式的直接帮助,对我们的 GitHub 发出 Pull Request 请求。 -以下是具体步骤:(注意本步骤将不会自动把贡献者添加到 Contributors 中,推荐用以下 [详细的图文步骤](https://github.com/Tencent/APIJSON/blob/master/CONTRIBUTING.md#%E8%AF%A6%E7%BB%86%E7%9A%84%E5%9B%BE%E6%96%87%E6%AD%A5%E9%AA%A4%E5%8F%AF%E5%8F%82%E8%80%83%E4%BB%A5%E4%B8%8B%E4%BB%BB%E6%84%8F%E4%B8%80%E7%AF%87)) +以下是具体步骤:(注意本步骤未 Fork 本项目,GitHub 不会把贡献者添加到 Contributors 中,推荐用以下 [详细的图文步骤](https://github.com/Tencent/APIJSON/blob/master/CONTRIBUTING.md#%E8%AF%A6%E7%BB%86%E7%9A%84%E5%9B%BE%E6%96%87%E6%AD%A5%E9%AA%A4%E5%8F%AF%E5%8F%82%E8%80%83%E4%BB%A5%E4%B8%8B%E4%BB%BB%E6%84%8F%E4%B8%80%E7%AF%87)) #### Fork 仓库 From 46436c5cc0dc7971ae34757da787cf9df7b00235 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Fri, 23 Apr 2021 11:00:33 +0800 Subject: [PATCH 0330/1181] Update CONTRIBUTING.md --- CONTRIBUTING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 30e47f32c..a6bef6ca7 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -62,7 +62,7 @@ APIJSON 作为腾讯开源的知名热门项目,贡献代码除了可以给简 我们除了希望听到您的反馈和建议外,我们也希望您接受代码形式的直接帮助,对我们的 GitHub 发出 Pull Request 请求。 -以下是具体步骤:(注意本步骤未 Fork 本项目,GitHub 不会把贡献者添加到 Contributors 中,推荐用以下 [详细的图文步骤](https://github.com/Tencent/APIJSON/blob/master/CONTRIBUTING.md#%E8%AF%A6%E7%BB%86%E7%9A%84%E5%9B%BE%E6%96%87%E6%AD%A5%E9%AA%A4%E5%8F%AF%E5%8F%82%E8%80%83%E4%BB%A5%E4%B8%8B%E4%BB%BB%E6%84%8F%E4%B8%80%E7%AF%87)) +以下是具体步骤:(如果使用本步骤,GitHub 可能不会把贡献者添加到 Contributors 中,推荐用以下 [详细的图文步骤](https://github.com/Tencent/APIJSON/blob/master/CONTRIBUTING.md#%E8%AF%A6%E7%BB%86%E7%9A%84%E5%9B%BE%E6%96%87%E6%AD%A5%E9%AA%A4%E5%8F%AF%E5%8F%82%E8%80%83%E4%BB%A5%E4%B8%8B%E4%BB%BB%E6%84%8F%E4%B8%80%E7%AF%87)) #### Fork 仓库 From 08a3125adf96509ae8361095c18e54818e2e4711 Mon Sep 17 00:00:00 2001 From: kxlv2000 <49295281+kxlv2000@users.noreply.github.com> Date: Fri, 23 Apr 2021 15:26:26 +0800 Subject: [PATCH 0331/1181] =?UTF-8?q?=E5=B0=9D=E8=AF=95=E4=BF=AE=E5=A4=8D?= =?UTF-8?q?=20oracle=20select=20=E5=88=86=E9=A1=B5=E8=AF=AD=E6=B3=95?= =?UTF-8?q?=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/apijson/orm/AbstractSQLConfig.java | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java index d4ac822f8..4f036900e 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java @@ -1473,17 +1473,17 @@ public String getLimitString() { if (count <= 0 || RequestMethod.isHeadMethod(getMethod(), true)) { return ""; } - return getLimitString(getPage(), getCount(), isOracle() || isSQLServer() || isDb2()); + return getLimitString(getPage(), getCount(), isOracle() || isSQLServer() || isDb2(), isOracle()); } /**获取限制数量 * @param limit * @return */ - public static String getLimitString(int page, int count, boolean isTSQL) { + public static String getLimitString(int page, int count, boolean isTSQL, boolean isOracle) { int offset = getOffset(page, count); - if (isTSQL) { // OFFSET FECTH 中所有关键词都不可省略 - return " OFFSET " + offset + " ROWS FETCH FIRST " + count + " ROWS ONLY"; + if (isTSQL) { // OFFSET FECTH 中所有关键词都不可省略, 另外 Oracle 数据库使用子查询加 where 分页 + return isOracle? " WHERE ROWNUM BETWEEN "+ offset +" AND "+ (offset + count): " OFFSET " + offset + " ROWS FETCH FIRST " + count + " ROWS ONLY"; } return " LIMIT " + count + (offset <= 0 ? "" : " OFFSET " + offset); // DELETE, UPDATE 不支持 OFFSET @@ -2613,8 +2613,12 @@ public static String getSQL(AbstractSQLConfig config) throws Exception { config.setPreparedValueList(new ArrayList()); String column = config.getColumnString(); - return explain + "SELECT " + (config.getCache() == JSONRequest.CACHE_RAM ? "SQL_NO_CACHE " : "") + column + " FROM " + getConditionString(column, tablePath, config); - } + if(config.isOracle()){ + //When config's database is oracle,Using subquery since Oracle12 below does not support OFFSET FETCH paging syntax. + return explain + "SELECT * FROM (SELECT"+ (config.getCache() == JSONRequest.CACHE_RAM ? "SQL_NO_CACHE " : "") + column + " FROM "+getConditionString(column, tablePath, config)+ ") "+config.getLimitString(); + }else + return explain + "SELECT " + (config.getCache() == JSONRequest.CACHE_RAM ? "SQL_NO_CACHE " : "") + column + " FROM " + getConditionString(column, tablePath, config); + } } /**获取条件SQL字符串 @@ -2641,7 +2645,7 @@ private static String getConditionString(String column, String table, AbstractSQ //no need to optimize // if (config.getPage() <= 0 || ID.equals(column.trim())) { - return condition + config.getLimitString(); + return config.isOracle()? condition:condition + config.getLimitString(); // } // // From c38ad8cebbb49374e4d31bde3f6846c5343a8da8 Mon Sep 17 00:00:00 2001 From: Rkyzzy <982993741@qq.com> Date: Sat, 24 Apr 2021 21:27:23 +0800 Subject: [PATCH 0332/1181] =?UTF-8?q?=E4=BD=BF=E7=94=A8entrySet=E8=BF=AD?= =?UTF-8?q?=E4=BB=A3=E5=99=A8=E6=9B=BF=E4=BB=A3keySet=E8=BF=AD=E4=BB=A3?= =?UTF-8?q?=E5=99=A8=E6=8F=90=E9=AB=98=E6=95=88=E7=8E=87=20#48?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../main/java/apijson/orm/AbstractParser.java | 4 ++-- .../java/apijson/orm/AbstractSQLConfig.java | 24 ++++++++++--------- 2 files changed, 15 insertions(+), 13 deletions(-) diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java b/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java index 9fb17ee05..b45619369 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java @@ -1392,10 +1392,10 @@ public Object getValueByPath(String valuePath) { } //取出key被valuePath包含的result,再从里面获取key对应的value - Set set = queryResultMap.keySet(); JSONObject parent = null; String[] keys = null; - for (String path : set) { + for (Map.Entry entry : queryResultMap.entrySet()){ + String path = entry.getKey(); if (valuePath.startsWith(path + "/")) { try { parent = (JSONObject) queryResultMap.get(path); diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java index 6defdd1e1..bdbadc864 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java @@ -1341,16 +1341,17 @@ public Object getWhere(String key, boolean exactMatch) { return where == null ? null : where.get(key); } - Set set = key == null || where == null ? null : where.keySet(); - if (set != null) { - synchronized (where) { - if (where != null) { - int index; - for (String k : set) { - index = k.indexOf(key); - if (index >= 0 && StringUtil.isName(k.substring(index)) == false) { - return where.get(k); - } + if (key == null || where == null){ + return null; + } + synchronized (where) { + if (where != null) { + int index; + for (Map.Entry entry : where.entrySet()) { + String k = entry.getKey(); + index = k.indexOf(key); + if (index >= 0 && StringUtil.isName(k.substring(index)) == false) { + return where.get(k); } } } @@ -2289,7 +2290,8 @@ public String getSetString(RequestMethod method, Map content, bo Object value; String idKey = getIdKey(); - for (String key : set) { + for (Map.Entry entry : content.entrySet()) { + String key = entry.getKey(); //避免筛选到全部 value = key == null ? null : content.get(key); if (key == null || idKey.equals(key)) { continue; From 6b5ecb2f1dde23ba56dae93dbd905906116a59fc Mon Sep 17 00:00:00 2001 From: Rkyzzy <982993741@qq.com> Date: Sat, 24 Apr 2021 22:42:35 +0800 Subject: [PATCH 0333/1181] =?UTF-8?q?=E4=BD=BF=E7=94=A8entrySet=E4=BB=A3?= =?UTF-8?q?=E6=9B=BFkeySet=E6=8F=90=E9=AB=98=E6=95=88=E7=8E=87=20#48?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- APIJSONORM/src/main/java/apijson/orm/AbstractParser.java | 2 +- APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java b/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java index b45619369..98052f7ee 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java @@ -1398,7 +1398,7 @@ public Object getValueByPath(String valuePath) { String path = entry.getKey(); if (valuePath.startsWith(path + "/")) { try { - parent = (JSONObject) queryResultMap.get(path); + parent = (JSONObject) entry.getValue(); } catch (Exception e) { Log.e(TAG, "getValueByPath try { parent = (JSONObject) queryResultMap.get(path); } catch { " + "\n parent not instanceof JSONObject!"); diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java index bdbadc864..81acbf455 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java @@ -1351,7 +1351,7 @@ public Object getWhere(String key, boolean exactMatch) { String k = entry.getKey(); index = k.indexOf(key); if (index >= 0 && StringUtil.isName(k.substring(index)) == false) { - return where.get(k); + return entry.getValue(); } } } @@ -2304,7 +2304,7 @@ public String getSetString(RequestMethod method, Map content, bo } else { keyType = 0; //注意重置类型,不然不该加减的字段会跟着加减 } - value = content.get(key); + value = entry.getValue(); key = getRealKey(method, key, false, true, verifyName); setString += (isFirst ? "" : ", ") + (getKey(key) + " = " + (keyType == 1 ? getAddString(key, value) : (keyType == 2 From 0fcbdfab03aa9bcb8e0a35dc7276f327a34e7aca Mon Sep 17 00:00:00 2001 From: Rkyzzy <982993741@qq.com> Date: Sun, 25 Apr 2021 01:26:57 +0800 Subject: [PATCH 0334/1181] further wrote javadoc --- .../src/main/java/apijson/StringUtil.java | 23 ++++++++++++------- .../main/java/apijson/orm/AbstractParser.java | 4 +++- .../java/apijson/orm/AbstractSQLConfig.java | 14 +++++++---- 3 files changed, 27 insertions(+), 14 deletions(-) diff --git a/APIJSONORM/src/main/java/apijson/StringUtil.java b/APIJSONORM/src/main/java/apijson/StringUtil.java index fed6d9933..23e49f181 100755 --- a/APIJSONORM/src/main/java/apijson/StringUtil.java +++ b/APIJSONORM/src/main/java/apijson/StringUtil.java @@ -111,11 +111,13 @@ public static String getString(Object[] array, boolean ignoreEmptyItem) { public static String getString(Object[] array, String split) { return getString(array, split, false); } + //CS304 Issue link: https://github.com/Tencent/APIJSON/issues/182 /**获取string,为null则返回"" - * @param array - * @param split - * @param ignoreEmptyItem - * @return + * @param array -the str array given + * @param split -the token used to split + * @param ignoreEmptyItem -whether to ignore empty item or not + * @return {@link #getString(Object[], String, boolean)} + *

Here we replace the simple "+" way of concatenating with Stringbuilder 's append

*/ public static String getString(Object[] array, String split, boolean ignoreEmptyItem) { StringBuilder s = new StringBuilder(""); @@ -530,10 +532,13 @@ public static String getNumber(CharSequence cs) { public static String getNumber(String s) { return getNumber(s, false); } + + //CS304 Issue link: https://github.com/Tencent/APIJSON/issues/182 /**去掉string内所有非数字类型字符 - * @param s + * @param s -string passed in * @param onlyStart 中间有非数字时只获取前面的数字 - * @return + * @return limit String + *

Here we replace the simple "+" way of concatenating with Stringbuilder 's append

*/ public static String getNumber(String s, boolean onlyStart) { if (isNotEmpty(s, true) == false) { @@ -631,10 +636,12 @@ public static String getCorrectEmail(String email) { public static String getPrice(String price) { return getPrice(price, PRICE_FORMAT_DEFAULT); } + //CS304 Issue link: https://github.com/Tencent/APIJSON/issues/182 /**获取价格,保留两位小数 - * @param price + * @param price -price passed in * @param formatType 添加单位(元) - * @return + * @return limit String + *

Here we replace the simple "+" way of concatenating with Stringbuilder 's append

*/ public static String getPrice(String price, int formatType) { if (isNotEmpty(price, true) == false) { diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java b/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java index 98052f7ee..3684c9dce 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java @@ -1375,9 +1375,11 @@ public synchronized void putQueryResult(String path, Object result) { queryResultMap.put(path, result); // } } + //CS304 Issue link: https://github.com/Tencent/APIJSON/issues/48 /**根据路径获取值 - * @param valuePath + * @param valuePath -the path need to get value * @return parent == null ? valuePath : parent.get(keys[keys.length - 1]) + *

use entrySet+getValue() to replace keySet+get() to enhance efficiency

*/ @Override public Object getValueByPath(String valuePath) { diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java index 81acbf455..51152f81f 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java @@ -1329,10 +1329,12 @@ public AbstractSQLConfig setCombine(Map> combine) { public Object getWhere(String key) { return getWhere(key, false); } + //CS304 Issue link: https://github.com/Tencent/APIJSON/issues/48 /** - * @param key - * @param exactMatch + * @param key - the key passed in + * @param exactMatch - whether it is exact match * @return + *

use entrySet+getValue() to replace keySet+get() to enhance efficiency

*/ @JSONField(serialize = false) @Override @@ -2273,11 +2275,13 @@ public static JSONArray newJSONArray(Object obj) { public String getSetString() throws Exception { return getSetString(getMethod(), getContent(), ! isTest()); } + //CS304 Issue link: https://github.com/Tencent/APIJSON/issues/48 /**获取SET - * @param method - * @param content + * @param method -the method used + * @param content -the content map * @return - * @throws Exception + * @throws Exception + *

use entrySet+getValue() to replace keySet+get() to enhance efficiency

*/ @JSONField(serialize = false) public String getSetString(RequestMethod method, Map content, boolean verifyName) throws Exception { From 39f21f6f0f5a2428bac546e70abe391a68d73afa Mon Sep 17 00:00:00 2001 From: gdjs2 Date: Sun, 25 Apr 2021 04:12:46 +0000 Subject: [PATCH 0335/1181] Format the code; Use valueOf(String) instead of the deprecated Long(String) --- .../src/main/java/apijson/JSONResponse.java | 18 +++++++----------- .../java/apijson/orm/AbstractVerifier.java | 2 +- 2 files changed, 8 insertions(+), 12 deletions(-) diff --git a/APIJSONORM/src/main/java/apijson/JSONResponse.java b/APIJSONORM/src/main/java/apijson/JSONResponse.java index ae60fd247..d979c7328 100755 --- a/APIJSONORM/src/main/java/apijson/JSONResponse.java +++ b/APIJSONORM/src/main/java/apijson/JSONResponse.java @@ -505,16 +505,12 @@ public static String formatColon(@NotNull String key) { public static String formatHyphen(@NotNull String key, boolean firstCase) { String name = ""; - StringTokenizer parts = new StringTokenizer(key, "-"); - name += parts.nextToken(); - while(parts.hasMoreTokens()) - { - String part = parts.nextToken(); - name += firstCase ? StringUtil.firstCase(part, true) : part; - } - - return name; + StringTokenizer parts = new StringTokenizer(key, "-"); + name += parts.nextToken(); + while(parts.hasMoreTokens()) { + String part = parts.nextToken(); + name += firstCase ? StringUtil.firstCase(part, true) : part; + } + return name; } - - } diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractVerifier.java b/APIJSONORM/src/main/java/apijson/orm/AbstractVerifier.java index 633b5d645..926a037f8 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractVerifier.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractVerifier.java @@ -277,7 +277,7 @@ public boolean verifyAccess(SQLConfig config) throws Exception { if (id instanceof Number == false) {//不能准确地判断Long,可能是Integer throw new UnsupportedDataTypeException(table + ".id类型错误,id类型必须是Long!"); } - if (list.contains(new Long("" + id)) == false) {//Integer等转为Long才能正确判断。强转崩溃 + if (list.contains(Long.valueOf("" + id)) == false) {//Integer等转为Long才能正确判断。强转崩溃 throw new IllegalAccessException(visitorIdkey + " = " + id + " 的 " + table + " 不允许 " + role.name() + " 用户的 " + method.name() + " 请求!"); } From 6d8b7d3289b1304c55a498bdb87db62b1fb06df5 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Mon, 26 Apr 2021 16:22:22 +0800 Subject: [PATCH 0336/1181] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E4=B8=80=E4=B8=AA?= =?UTF-8?q?=20PHP=20=E7=89=88=E6=9C=AC=E7=9A=84=20APIJSON=EF=BC=9AAPIJSON-?= =?UTF-8?q?php?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit https://github.com/xianglong111/APIJSON-php --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index d120c0289..e6619a2dd 100644 --- a/README.md +++ b/README.md @@ -380,6 +380,8 @@ https://github.com/Tencent/APIJSON/blob/master/CONTRIBUTING.md [APIJSON.NET](https://github.com/liaozb/APIJSON.NET) C# 版 APIJSON ,支持 MySQL, PostgreSQL, SQL Server, Oracle, SQLite +[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,支持 MySQL, PostgreSQL, SQL Server, Oracle From c6eb4463608a0d2bc0fddda1e0c9a6ff0c3253d7 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Wed, 28 Apr 2021 11:26:53 +0800 Subject: [PATCH 0337/1181] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index e6619a2dd..07cc655db 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ This source code is licensed under the Apache License Version 2.0
APIJSON -

🏆 码云最有价值开源项目
🚀 后端接口和文档零代码,前端(客户端) 定制返回 JSON 的数据和结构!

+

🏆 腾讯内外四个奖项、腾讯开源五个第一
🚀 后端接口和文档零代码,前端(客户端) 定制返回 JSON 的数据和结构!

From 2162ac6b5a1787b11382fbee97d327bcffaf0865 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Wed, 28 Apr 2021 11:35:04 +0800 Subject: [PATCH 0338/1181] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E5=8C=85=E6=8B=AC?= =?UTF-8?q?=E6=9D=A5=E8=87=AA=20SUSTech=20=E5=9C=A8=E5=86=85=E7=9A=84=204?= =?UTF-8?q?=20=E4=B8=AA=E8=B4=A1=E7=8C=AE=E8=80=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit https://github.com/Tencent/APIJSON#%E8%B4%A1%E7%8C%AE%E8%80%85%E4%BB%AC --- README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.md b/README.md index 07cc655db..13c64d960 100644 --- a/README.md +++ b/README.md @@ -240,6 +240,7 @@ https://github.com/Tencent/APIJSON/issues/187 height="54" width="54" > + @@ -249,6 +250,8 @@ https://github.com/Tencent/APIJSON/issues/187 + + From 905fb1139ff5bea868dbb6049982653f169e6566 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Wed, 28 Apr 2021 11:41:40 +0800 Subject: [PATCH 0339/1181] =?UTF-8?q?=E8=B4=A1=E7=8C=AE=E8=80=85=E5=90=8D?= =?UTF-8?q?=E5=8D=95=E6=96=B0=E5=A2=9E=E5=8C=85=E6=8B=AC=E6=9D=A5=E8=87=AA?= =?UTF-8?q?=20=E8=85=BE=E8=AE=AF=E3=80=81SUSTech=20=E7=9A=84=204=20?= =?UTF-8?q?=E4=BA=BA=EF=BC=8C=E9=9D=9E=E5=B8=B8=E6=84=9F=E8=B0=A2=E5=A4=A7?= =?UTF-8?q?=E5=AE=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit https://github.com/Tencent/APIJSON/edit/master/CONTRIBUTING.md --- CONTRIBUTING.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index a6bef6ca7..011ee9502 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -7,6 +7,7 @@ 非常感谢以下贡献者们对于 APIJSON 的做出的贡献: +- [TommyLemon](https://github.com/TommyLemon)(腾讯工程师) - [ruoranw](https://github.com/ruoranw)(现居美国洛杉矶) - [zhoulingfengofcd](https://github.com/zhoulingfengofcd) - [Zerounary](https://github.com/Zerounary) @@ -23,6 +24,9 @@ - [sunxiaoguang](https://github.com/sunxiaoguang)(知乎基础研发架构师) - [403f](https://github.com/403f) - [gujiachun](https://github.com/gujiachun) +- [gdjs2](https://github.com/gdjs2) +- [Rkyzzy](https://github.com/Rkyzzy)(SUSTech) +- [kxlv2000](https://github.com/kxlv2000)(SUSTech) #### 其中特别致谢:
justinfengchen 提交的 6 个 Commits, 对 APIJSON 做出了 3,130 增加和 0 处删减(截止 2020/11/04 日);
From 95932f7687dbfb7b2b4e3166621805fc5020ce01 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=A1=BE=E5=8A=A0=E6=98=A5?= Date: Thu, 29 Apr 2021 16:27:50 +0800 Subject: [PATCH 0340/1181] =?UTF-8?q?=E5=8E=BB=E9=99=A4final=E5=85=B3?= =?UTF-8?q?=E9=94=AE=E5=AD=97=EF=BC=8C=E6=96=B9=E4=BE=BF=E4=B8=9A=E5=8A=A1?= =?UTF-8?q?=E4=BB=A3=E7=A0=81=E9=87=8D=E6=96=B0=E5=AE=9A=E4=B9=89ok?= =?UTF-8?q?=E3=80=81code=E3=80=81msg=E5=AD=97=E6=AE=B5=E5=90=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- APIJSONORM/src/main/java/apijson/JSONResponse.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/APIJSONORM/src/main/java/apijson/JSONResponse.java b/APIJSONORM/src/main/java/apijson/JSONResponse.java index ae60fd247..33da83ca6 100755 --- a/APIJSONORM/src/main/java/apijson/JSONResponse.java +++ b/APIJSONORM/src/main/java/apijson/JSONResponse.java @@ -57,9 +57,9 @@ public JSONResponse(JSONObject object) { public static final String MSG_SERVER_ERROR = "Internal Server Error!"; //服务器内部错误 - public static final String KEY_OK = "ok"; - public static final String KEY_CODE = "code"; - public static final String KEY_MSG = "msg"; + public static String KEY_OK = "ok"; + public static String KEY_CODE = "code"; + public static String KEY_MSG = "msg"; public static final String KEY_COUNT = "count"; public static final String KEY_TOTAL = "total"; public static final String KEY_INFO = "info"; //详细的分页信息 From eb5fe3204a3ac5ef499311888fe88397de099b02 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Tue, 4 May 2021 17:06:06 +0800 Subject: [PATCH 0341/1181] =?UTF-8?q?=E2=80=9C=E8=85=BE=E8=AE=AF=E5=86=85?= =?UTF-8?q?=E5=A4=96=E5=9B=9B=E4=B8=AA=E5=A5=96=E9=A1=B9=E2=80=9D=20?= =?UTF-8?q?=E6=94=B9=E4=B8=BA=20=E2=80=9C=E8=85=BE=E8=AE=AF=E5=86=85?= =?UTF-8?q?=E5=A4=96=E4=BA=94=E4=B8=AA=E5=A5=96=E9=A1=B9=E2=80=9D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 13c64d960..fd6dbd232 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ This source code is licensed under the Apache License Version 2.0
APIJSON -

🏆 腾讯内外四个奖项、腾讯开源五个第一
🚀 后端接口和文档零代码,前端(客户端) 定制返回 JSON 的数据和结构!

+

🏆 腾讯内外五个奖项、腾讯开源五个第一
🚀 后端接口和文档零代码,前端(客户端) 定制返回 JSON 的数据和结构!

From 6a0871ffea25fe9ca848c0bd4d171d76185343a7 Mon Sep 17 00:00:00 2001 From: smallhowcao Date: Thu, 13 May 2021 15:53:44 +0800 Subject: [PATCH 0342/1181] =?UTF-8?q?=E4=BF=AE=E6=94=B9=20Document=20=20?= =?UTF-8?q?=E6=96=87=E6=A1=A3=20OUTER=20JOIN=20=E6=8B=BC=E5=86=99=E9=94=99?= =?UTF-8?q?=E8=AF=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 修改 Document 文档 OUTER JOIN 拼写错误 --- Document-English.md | 2 +- Document.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Document-English.md b/Document-English.md index 9dd2eb141..eb737bb4b 100644 --- a/Document-English.md +++ b/Document-English.md @@ -42,7 +42,7 @@ Add / expand an item | `"key+":Object`
The type of Object is decided by *key*. Types can be Number, String, JSONArray. Froms are 82001,"apijson",["url0","url1"] respectively. It’s only applicable to PUT request.| "praiseUserIdList+":[82001]. In SQL, it's
`json_insert(praiseUserIdList,82001)`.
Add an *id* that praised the Moment. Delete / decrease an item | `"Key-":Object`
It’s the contrary of "key+" | "balance-":100.00. In SQL, it's
`balance = balance - 100.00`,
meaning there's 100 less in balance. Operations | &, \|, !
They're used in logic operations. It’s the same as AND, OR, NOT in SQL respectively.
By default, for the same key, it’s ‘\|’ (OR)operation among conditions; for different keys, the default operation among conditions is ‘&’(AND).
| ① ["id&{}":">80000,<=90000"](http://apijson.cn:8080/head/{"User":{"id&{}":">80000,<=90000"}})
In SQL, it's
`id>80000 AND id<=90000`,
meaning *id* needs to be id>80000 & id<=90000

② ["id\|{}":">90000,<=80000"](http://apijson.cn:8080/head/{"User":{"id\|{}":">90000,<=80000"}})
It's the same as "id{}":">90000,<=80000".
In SQL, it's
`id>80000 OR id<=90000`,
meaning that *id* needs to be id>90000 \| id<=80000

③ ["id!{}":[82001,38710]](http://apijson.cn:8080/head/{"User":{"id!{}":[82001,38710]}})
In SQL, it's
`id NOT IN(82001,38710)`,
meaning id needs to be ! (id=82001 \| id=38710). - Keywords in an Array: It can be self-defined. | As for `"key":Object`, *key* is the keyword of *{}* in *"[]":{}*. The type of *Object* is up to *key*.

① `"count":Integer` It's used to count the number. The default largest number is 100.

② `"page":Integer` It’s used for getting data from which page, starting from 0. The default largest number is 100. It’s usually used with COUNT.

③ `"query":Integer` Get the number of items that match conditions
When to get the object, the integer should be 0; when to get the total number, it’s 1; when both above, it’s 2.
You can get the total number with keyword total. It can be referred to other values.
Eg.
`"total@":"/[]/total"`
Put it as the same level of query.
*Query* and *total* are used in GET requests just for convenience. Generally, HEAD request is for getting numbers like the total number.

④ `"join":"&/Table0/key0@,Join tables:
"\<" - LEFT JOIN
">" - RIGHT JOIN
"&" - INNER JOIN
"\|" - FULL JOIN
"!" - OUTTER JOIN
"@" - APP JOIN
Where @ APP JOIN is in application layer.It’ll get all the keys in tables that refKeys in result tables are referred to, like refKeys:[value0, value1….]. Then, as the results get data according to `key=$refKey` a number of times (COUNT), it uses key `IN($refKeys)` to put these counts together in just one SQL query, in order to improve the performance.
Other JOIN functions are the same as those in SQL.
`"join":"`"MainTable":{},`
`"ViceTable":{"key@":"/MainTable/refKey"}`
will return
`MainTable LEFT JOIN ViceTable`
`ON ViceTable.key=MainTable.refKey`

⑤ `"otherKey":Object` Self-defined keyword other than those that already in the system. It also returns with self-defined keywords.| ① Get User arrays with maximum of 5:
["count":5](http://apijson.cn:8080/get/{"[]":{"count":5,"User":{}}})

② Look into User arrays on page 3. Show 5 of them each page.
["count":5,
"page":3](http://apijson.cn:8080/get/{"[]":{"count":5,"page":3,"User":{}}})

③ Get User Arrays and count the total number of Users:
["[]":{
   "query":2,
   "User":{}
},
"total@":"/[]/total"](http://apijson.cn:8080/get/{"[]":{"query":2,"count":5,"User":{}},"total@":"%252F[]%252Ftotal"})
Questions like total page numbers or if there's next page can be solved by total,count,page functions,
Total page number:
`int totalPage = Math.ceil(total / count)`
If this is the last page:
`boolean hasNextPage = total > count*page`
If this is the first page:
`boolean isFirstPage = page <= 0`
If it's the last page:
`boolean isLastPage = total <= count*page`
...

④ Moment INNER JOIN User LEFT JOIN Comment:
["[]":{
   "join": "&/User/id@,\    "Moment":{},
   "User":{
     "name~":"t",
     "id@": "/Moment/userId"
   },
   "Comment":{
     "momentId@": "/Moment/id"
   }
}](http://apijson.cn:8080/get/{"[]":{"count":5,"join":"&%252FUser%252Fid@,\<%252FComment%252FmomentId@","Moment":{"@column":"id,userId,content"},"User":{"name~":"t","id@":"%252FMoment%252FuserId","@column":"id,name,head"},"Comment":{"momentId@":"%252FMoment%252Fid","@column":"id,momentId,content"}}})

⑤ Add the current user to every level:
["User":{},
"[]":{
   "name@":"User/name", //self-defined keyword
   "Moment":{}
}](http://apijson.cn:8080/get/{"User":{},"[]":{"name@":"User%252Fname","Moment":{}}}) + Keywords in an Array: It can be self-defined. | As for `"key":Object`, *key* is the keyword of *{}* in *"[]":{}*. The type of *Object* is up to *key*.

① `"count":Integer` It's used to count the number. The default largest number is 100.

② `"page":Integer` It’s used for getting data from which page, starting from 0. The default largest number is 100. It’s usually used with COUNT.

③ `"query":Integer` Get the number of items that match conditions
When to get the object, the integer should be 0; when to get the total number, it’s 1; when both above, it’s 2.
You can get the total number with keyword total. It can be referred to other values.
Eg.
`"total@":"/[]/total"`
Put it as the same level of query.
*Query* and *total* are used in GET requests just for convenience. Generally, HEAD request is for getting numbers like the total number.

④ `"join":"&/Table0/key0@,Join tables:
"\<" - LEFT JOIN
">" - RIGHT JOIN
"&" - INNER JOIN
"\|" - FULL JOIN
"!" - OUTER JOIN
"@" - APP JOIN
Where @ APP JOIN is in application layer.It’ll get all the keys in tables that refKeys in result tables are referred to, like refKeys:[value0, value1….]. Then, as the results get data according to `key=$refKey` a number of times (COUNT), it uses key `IN($refKeys)` to put these counts together in just one SQL query, in order to improve the performance.
Other JOIN functions are the same as those in SQL.
`"join":"`"MainTable":{},`
`"ViceTable":{"key@":"/MainTable/refKey"}`
will return
`MainTable LEFT JOIN ViceTable`
`ON ViceTable.key=MainTable.refKey`

⑤ `"otherKey":Object` Self-defined keyword other than those that already in the system. It also returns with self-defined keywords.| ① Get User arrays with maximum of 5:
["count":5](http://apijson.cn:8080/get/{"[]":{"count":5,"User":{}}})

② Look into User arrays on page 3. Show 5 of them each page.
["count":5,
"page":3](http://apijson.cn:8080/get/{"[]":{"count":5,"page":3,"User":{}}})

③ Get User Arrays and count the total number of Users:
["[]":{
   "query":2,
   "User":{}
},
"total@":"/[]/total"](http://apijson.cn:8080/get/{"[]":{"query":2,"count":5,"User":{}},"total@":"%252F[]%252Ftotal"})
Questions like total page numbers or if there's next page can be solved by total,count,page functions,
Total page number:
`int totalPage = Math.ceil(total / count)`
If this is the last page:
`boolean hasNextPage = total > count*page`
If this is the first page:
`boolean isFirstPage = page <= 0`
If it's the last page:
`boolean isLastPage = total <= count*page`
...

④ Moment INNER JOIN User LEFT JOIN Comment:
["[]":{
   "join": "&/User/id@,\    "Moment":{},
   "User":{
     "name~":"t",
     "id@": "/Moment/userId"
   },
   "Comment":{
     "momentId@": "/Moment/id"
   }
}](http://apijson.cn:8080/get/{"[]":{"count":5,"join":"&%252FUser%252Fid@,\<%252FComment%252FmomentId@","Moment":{"@column":"id,userId,content"},"User":{"name~":"t","id@":"%252FMoment%252FuserId","@column":"id,name,head"},"Comment":{"momentId@":"%252FMoment%252Fid","@column":"id,momentId,content"}}})

⑤ Add the current user to every level:
["User":{},
"[]":{
   "name@":"User/name", //self-defined keyword
   "Moment":{}
}](http://apijson.cn:8080/get/{"User":{},"[]":{"name@":"User%252Fname","Moment":{}}}) Keywords in Objects: It can be self-defined. | `"@key":Object` @key is the keyword of {} in Table:{}. The type of Object is decided by @key

① `"@combine":"&key0,&key1,\|key2,key3,`
`!key4,!key5,&key6,key7..."`
First, it’ll group data with same operators. Within one group, it operates from left to right. Then it’ll follow the order of & \| ! to do the operation. Different groups are connected with &. So the expression above will be :
(key0 & key1 & key6 & other key) & (key2 \| key3 \| key7) & !(key4 \| key5)
\| is optional.

② `"@column":"column;function(arg)..."` Return with specific columns.

③ `"@order":"column0+,column1-..."` Decide the order of returning results:

④ `"@group":"column0,column1..."` How to group data. If @column has declared Table id, this id need to be included in @group. In other situations, at least one of the following needs to be done:
1.Group id is declared in @column
2.Primary Key of the table is declared in @group.

⑤ `@having":"function0(...)?value0;function1(...)?value1;function2(...)?value2..."` Add conditions on return results with @having. Usually working with@group, it’s declared in @column.

⑥ `"@schema":"sys"` Can be set as default setting.

⑦ `"@database":"POSTGRESQL"` Get data from a different database.Can be set as default setting.

⑧ `"@role":"OWNER"` Get information of the user, including
UNKNOWN,LOGIN,CONTACT,CIRCLE,OWNER,ADMIN,
Can be set as default setting.
You can self-define a new role or rewrite a role. Use`Verifier.verify` etc. to self-define validation methods.

⑨ `"@explain":true` Profiling. Can be set as default setting.

⑩ `"@otherKey":Object` Self-define keyword | ① Search *Users* that *name* or *tag* contains the letter "a":
["name~":"a",
"tag~":"a",
"@combine":"name~,tag~"](http://apijson.cn:8080/get/{"User[]":{"count":10,"User":{"@column":"id,name,tag","name~":"a","tag~":"a","@combine":"name~,tag~"}}})

② Only search column id,sex,name and return with the same order:
["@column":"id,sex,name"](http://apijson.cn:8080/get/{"User":{"@column":"id,sex,name","id":38710}})

③ Search Users that have descending order of name and default order of id:
["@order":"name-,id"](http://apijson.cn:8080/get/{"[]":{"count":10,"User":{"@column":"name,id","@order":"name-,id"}}})

④ Search Moment grouped with userId:
["@group":"userId,id"](http://apijson.cn:8080/get/{"[]":{"count":10,"Moment":%7B"@column":"userId,id","@group":"userId,id"}}})

⑤ Search Moments that id equals or less than 100 and group with userId:
["@column":"userId;max(id)",
"@group":"userId",
"@having":"max(id)>=100"](http://apijson.cn:8080/get/{"[]":{"count":10,"Moment":{"@column":"userId%253Bmax(id)","@group":"userId","@having":"max(id)>=100"}}})
You can also define the name of the returned function:
["@column":"userId;max(id):maxId",
"@group":"userId",
"@having":"maxId>=100"](http://apijson.cn:8080/get/{"[]":{"count":10,"Moment":{"@column":"userId%253Bmax(id):maxId","@group":"userId","@having":"maxId>=100"}}})

⑥ Check Users table in sys:
["@schema":"sys"](http://apijson.cn:8080/get/{"User":{"@schema":"sys"}})

⑦ Check Users table in PostgreSQL:
["@database":"POSTGRESQL"](http://apijson.cn:8080/get/{"User":{"@database":"POSTGRESQL"}})

⑧ Check the current user's activity:
["@role":"OWNER"](http://apijson.cn:8080/get/{"[]":{"Moment":{"@role":"OWNER"}}})

⑨ Turn on profiling:
["@explain":true](http://apijson.cn:8080/get/{"[]":{"Moment":{"@explain":true}}})

⑩ Get the No.0 picture from pictureList:
["@position":0, //self-defined keyword
"firstPicture()":"getFromArray(pictureList,@position)"](http://apijson.cn:8080/get/{"User":{"id":38710,"@position":0,"firstPicture()":"getFromArray(pictureList,@position)"}})
diff --git a/Document.md b/Document.md index c7e7be53c..d1fd5dbce 100644 --- a/Document.md +++ b/Document.md @@ -370,7 +370,7 @@ DELETE:
删除数据 | base_url/delete/ | {
   TableName:{< 减少 或 去除 | "key-":Object,与"key+"相反 | "balance-":100.00,对应SQL是`balance = balance - 100.00`,余额减少100.00,即花费了100元 比较运算 | >, <, >=, <= 比较运算符,用于
① 提供 "id{}":"<=90000" 这种条件范围的简化写法

② 实现子查询相关比较运算

不支持 "key=":Object 和 "key!=":Object 这两种写法,直接用更简单的 "key":Object 和 "key!":Object 替代。 | ① ["id<=":90000](http://apijson.cn:8080/get/{"[]":{"User":{"id<=":90000}}}),对应SQL是`id<=90000`,查询符合id<=90000的一个User数组

② ["id>@":{
   "from":"Comment",
   "Comment":{
      "@column":"min(userId)"
   }
}](http://apijson.cn:8080/get/{"User":{"id>@":{"from":"Comment","Comment":{"@column":"min(userId)"}}}})
WHERE id>(SELECT min(userId) FROM Comment) 逻辑运算 | &, \|, ! 逻辑运算符,对应数据库 SQL 中的 AND, OR, NOT。
横或纵与:同一键值对的值内条件默认 \| 或连接,可以在 key 后加逻辑运算符来具体指定;不同键值对的条件默认 & 与连接,可以用下面说明的对象关键词 @combine 来具体指定。

① & 可用于"key&{}":"条件"等

② \| 可用于"key\|{}":"条件", "key\|{}":[]等,一般可省略

③ ! 可单独使用,如"key!":Object,也可像&,\|一样配合其他功能符使用
"key!":null 无效,null 值会导致整个键值对被忽略解析,可以用 "key{}":"!=null" 替代,
"key":null 同理,用 "key{}":"=null" 替代。 | ① ["id&{}":">80000,<=90000"](http://apijson.cn:8080/head/{"User":{"id&{}":">80000,<=90000"}}),对应SQL是`id>80000 AND id<=90000`,即id满足id>80000 & id<=90000

② ["id\|{}":">90000,<=80000"](http://apijson.cn:8080/head/{"User":{"id\|{}":">90000,<=80000"}}),同"id{}":">90000,<=80000",对应SQL是`id>80000 OR id<=90000`,即id满足id>90000 \| id<=80000

③ ["id!{}":[82001,38710]](http://apijson.cn:8080/head/{"User":{"id!{}":[82001,38710]}}),对应SQL是`id NOT IN(82001,38710)`,即id满足 ! (id=82001 \| id=38710),可过滤黑名单的消息 - 数组关键词,可自定义 | "key":Object,key为 "[]":{} 中{}内的关键词,Object的类型由key指定

① "count":Integer,查询数量,0 表示最大值,默认最大值为100

② "page":Integer,查询页码,从0开始,默认最大值为100,一般和count一起用

③ "query":Integer,查询内容
0-对象,1-总数和分页详情,2-以上全部
总数关键词为 total,分页详情关键词为 info,
它们都和 query 同级,通过引用赋值得到,例如
"total@":"/[]/total", "info@":"/[]/info"
这里query及total仅为GET类型的请求提供方便,
一般可直接用HEAD类型的请求获取总数

④ "join":"&/Table0/key0@,\多表连接方式:
"\<" - LEFT JOIN
">" - RIGHT JOIN
"&" - INNER JOIN
"\|" - FULL JOIN
"!" - OUTTER JOIN
"@" - APP JOIN
其中 @ APP JOIN 为应用层连表,会从已查出的主表里取得所有副表 key@ 关联的主表内的 refKey 作为一个数组 refKeys: [value0, value1...],然后把原来副表 count 次查询 key=$refKey 的 SQL 用 key IN($refKeys) 的方式合并为一条 SQL 来优化性能;
其它 JOIN 都是 SQL JOIN,具体功能和 MySQL,PostgreSQL 等数据库的 JOIN 一一对应
`"join":"`"MainTable":{},`
`"ViceTable":{"key@":"/MainTable/refKey"}`
会对应生成
`MainTable LEFT JOIN ViceTable`
`ON ViceTable.key=MainTable.refKey`

⑤ "otherKey":Object,自定义关键词,名称和以上系统关键词不一样,且原样返回上传的值 | ① 查询User数组,最多5个:
["count":5](http://apijson.cn:8080/get/{"[]":{"count":5,"User":{}}})
对应SQL是`LIMIT 5`

② 查询第3页的User数组,每页5个:
["count":5,
"page":3](http://apijson.cn:8080/get/{"[]":{"count":5,"page":3,"User":{}}})
对应SQL是`LIMIT 5 OFFSET 15`

③ 查询User数组和对应的User总数:
["[]":{
   "query":2,
   "User":{}
},
"total@":"/[]/total",
"info@":"/[]/info"](http://apijson.cn:8080/get/{"[]":{"query":2,"count":5,"User":{}},"total@":"%252F[]%252Ftotal","info@":"%252F[]%252Finfo"})
返回的数据中,总数及分页详情结构为:
"total":139, //总数
"info":{ //分页详情
   "total":139, //总数
   "count":5, //每页数量
   "page":0, //当前页码
   "max":27, //最大页码
   "more":true, //是否还有更多
   "first":true, //是否为首页
   "last":false //是否为尾页
}

④ Moment INNER JOIN User LEFT JOIN Comment:
["[]":{
   "join":"&/User/id@,\    "Moment":{
     "@group":"id" //主副表不是一对一,要去除重复数据
   },
   "User":{
     "name~":"t",
     "id@":"/Moment/userId"
   },
   "Comment":{
     "momentId@":"/Moment/id"
   }
}](http://apijson.cn:8080/get/{"[]":{"count":5,"join":"&%252FUser%252Fid@,\<%252FComment%252FmomentId@","Moment":{"@column":"id,userId,content","@group":"id"},"User":{"name~":"t","id@":"%252FMoment%252FuserId","@column":"id,name,head"},"Comment":{"momentId@":"%252FMoment%252Fid","@column":"id,momentId,content"}}})

⑤ 每一层都加当前用户名:
["User":{},
"[]":{
   "name@":"User/name", //自定义关键词
   "Moment":{}
}](http://apijson.cn:8080/get/{"User":{},"[]":{"name@":"User%252Fname","Moment":{}}}) + 数组关键词,可自定义 | "key":Object,key为 "[]":{} 中{}内的关键词,Object的类型由key指定

① "count":Integer,查询数量,0 表示最大值,默认最大值为100

② "page":Integer,查询页码,从0开始,默认最大值为100,一般和count一起用

③ "query":Integer,查询内容
0-对象,1-总数和分页详情,2-以上全部
总数关键词为 total,分页详情关键词为 info,
它们都和 query 同级,通过引用赋值得到,例如
"total@":"/[]/total", "info@":"/[]/info"
这里query及total仅为GET类型的请求提供方便,
一般可直接用HEAD类型的请求获取总数

④ "join":"&/Table0/key0@,\多表连接方式:
"\<" - LEFT JOIN
">" - RIGHT JOIN
"&" - INNER JOIN
"\|" - FULL JOIN
"!" - OUTER JOIN
"@" - APP JOIN
其中 @ APP JOIN 为应用层连表,会从已查出的主表里取得所有副表 key@ 关联的主表内的 refKey 作为一个数组 refKeys: [value0, value1...],然后把原来副表 count 次查询 key=$refKey 的 SQL 用 key IN($refKeys) 的方式合并为一条 SQL 来优化性能;
其它 JOIN 都是 SQL JOIN,具体功能和 MySQL,PostgreSQL 等数据库的 JOIN 一一对应
`"join":"`"MainTable":{},`
`"ViceTable":{"key@":"/MainTable/refKey"}`
会对应生成
`MainTable LEFT JOIN ViceTable`
`ON ViceTable.key=MainTable.refKey`

⑤ "otherKey":Object,自定义关键词,名称和以上系统关键词不一样,且原样返回上传的值 | ① 查询User数组,最多5个:
["count":5](http://apijson.cn:8080/get/{"[]":{"count":5,"User":{}}})
对应SQL是`LIMIT 5`

② 查询第3页的User数组,每页5个:
["count":5,
"page":3](http://apijson.cn:8080/get/{"[]":{"count":5,"page":3,"User":{}}})
对应SQL是`LIMIT 5 OFFSET 15`

③ 查询User数组和对应的User总数:
["[]":{
   "query":2,
   "User":{}
},
"total@":"/[]/total",
"info@":"/[]/info"](http://apijson.cn:8080/get/{"[]":{"query":2,"count":5,"User":{}},"total@":"%252F[]%252Ftotal","info@":"%252F[]%252Finfo"})
返回的数据中,总数及分页详情结构为:
"total":139, //总数
"info":{ //分页详情
   "total":139, //总数
   "count":5, //每页数量
   "page":0, //当前页码
   "max":27, //最大页码
   "more":true, //是否还有更多
   "first":true, //是否为首页
   "last":false //是否为尾页
}

④ Moment INNER JOIN User LEFT JOIN Comment:
["[]":{
   "join":"&/User/id@,\    "Moment":{
     "@group":"id" //主副表不是一对一,要去除重复数据
   },
   "User":{
     "name~":"t",
     "id@":"/Moment/userId"
   },
   "Comment":{
     "momentId@":"/Moment/id"
   }
}](http://apijson.cn:8080/get/{"[]":{"count":5,"join":"&%252FUser%252Fid@,\<%252FComment%252FmomentId@","Moment":{"@column":"id,userId,content","@group":"id"},"User":{"name~":"t","id@":"%252FMoment%252FuserId","@column":"id,name,head"},"Comment":{"momentId@":"%252FMoment%252Fid","@column":"id,momentId,content"}}})

⑤ 每一层都加当前用户名:
["User":{},
"[]":{
   "name@":"User/name", //自定义关键词
   "Moment":{}
}](http://apijson.cn:8080/get/{"User":{},"[]":{"name@":"User%252Fname","Moment":{}}}) 对象关键词,可自定义 | "@key":Object,@key为 Table:{} 中{}内的关键词,Object的类型由@key指定

① "@combine":"&key0,&key1,\|key2,key3,
!key4,!key5,&key6,key7...",条件组合方式,\| 可省略。会自动把同类的合并,外层按照 & \| ! 顺序,内层的按传参顺序组合成
(key0 & key1 & key6 & 其它key) & (key2 \| key3 \| key7) & !(key4 \| key5)
这种连接方式,其中 "其它key" 是指与 @combine 在同一对象,且未被它声明的条件 key,默认都是 & 连接

② "@column":"column;function(arg)...",返回字段

③ "@order":"column0+,column1-...",排序方式

④ "@group":"column0,column1...",分组方式。如果@column里声明了Table的id,则id也必须在@group中声明;其它情况下必须满足至少一个条件:
1.分组的key在@column里声明
2.Table主键在@group中声明

⑤ "@having":"function0(...)?value0;function1(...)?value1;function2(...)?value2...",SQL函数条件,一般和@group一起用,函数一般在@column里声明

⑥ "@schema":"sys",集合空间(模式),非默认的值可通过它来指定,可以在最外层作为全局默认配置

⑦ "@database":"POSTGRESQL",跨数据库,非默认的值可通过它来指定,可以在最外层作为全局默认配置

⑧ "@json":"key0,key1...",转为 JSON 格式返回,符合 JSONObject 则转为 {...},符合 JSONArray 则转为 \[...]

⑨ "@role":"OWNER",来访角色,包括
UNKNOWN,LOGIN,CONTACT,CIRCLE,OWNER,ADMIN,
可以在最外层作为全局默认配置,
可自定义其它角色并重写 Verifier.verify 等相关方法来自定义校验

⑩ "@explain":true,性能分析,可以在最外层作为全局默认配置

⑪ "@raw":"key0,key1...",其中 key0, key1 都对应有键值对
"key0":"SQL片段或SQL片段的别名",
"key1":"SQL片段或SQL片段的别名"
自定义原始SQL片段,可扩展嵌套SQL函数等复杂语句,必须是后端已配置的,只有其它功能符都做不到才考虑,谨慎使用,注意防SQL注入

⑫ "@otherKey":Object,自定义关键词,名称和以上系统关键词不一样,且原样返回上传的值 | ① 搜索name或tag任何一个字段包含字符a的User列表:
["name~":"a",
"tag~":"a",
"@combine":"name~,tag~"](http://apijson.cn:8080/get/{"User[]":{"count":10,"User":{"@column":"id,name,tag","name~":"a","tag~":"a","@combine":"name~,tag~"}}})
对应SQL是`name REGEXP 'a' OR tag REGEXP 'a'`

② 只查询id,sex,name这几列并且请求结果也按照这个顺序:
["@column":"id,sex,name"](http://apijson.cn:8080/get/{"User":{"@column":"id,sex,name","id":38710}})
对应SQL是`SELECT id,sex,name`

③ 查询按 name降序、id默认顺序 排序的User数组:
["@order":"name-,id"](http://apijson.cn:8080/get/{"[]":{"count":10,"User":{"@column":"name,id","@order":"name-,id"}}})
对应SQL是`ORDER BY name DESC,id`

④ 查询按userId分组的Moment数组:
["@group":"userId,id"](http://apijson.cn:8080/get/{"[]":{"count":10,"Moment":%7B"@column":"userId,id","@group":"userId,id"}}})
对应SQL是`GROUP BY userId,id`

⑤ 查询 按userId分组、id最大值>=100 的Moment数组:
["@column":"userId;max(id)",
"@group":"userId",
"@having":"max(id)>=100"](http://apijson.cn:8080/get/{"[]":{"count":10,"Moment":{"@column":"userId%253Bmax(id)","@group":"userId","@having":"max(id)>=100"}}})
对应SQL是`SELECT userId,max(id) ... GROUP BY userId HAVING max(id)>=100`
还可以指定函数返回名:
["@column":"userId;max(id):maxId",
"@group":"userId",
"@having":"maxId>=100"](http://apijson.cn:8080/get/{"[]":{"count":10,"Moment":{"@column":"userId%253Bmax(id):maxId","@group":"userId","@having":"maxId>=100"}}})
对应SQL是`SELECT userId,max(id) AS maxId ... GROUP BY userId HAVING maxId>=100`

⑥ 查询 sys 内的 User 表:
["@schema":"sys"](http://apijson.cn:8080/get/{"User":{"@schema":"sys"}})
对应SQL是`FROM sys.User`

⑦ 查询 PostgreSQL 数据库的 User 表:
["@database":"POSTGRESQL"](http://apijson.cn:8080/get/{"User":{"@database":"POSTGRESQL","@explain":true}})

⑧ 将 VARCHAR 字符串字段 get 转为 JSONArray 返回:
["@json":"get"](http://apijson.cn:8080/get/{"Access":{"@json":"get"}})

⑨ 查询当前用户的动态:
["@role":"OWNER"](http://apijson.cn:8080/get/{"[]":{"Moment":{"@role":"OWNER"}}})

⑩ 开启性能分析:
["@explain":true](http://apijson.cn:8080/get/{"[]":{"Moment":{"@explain":true}}})
对应SQL是`EXPLAIN`

⑪ 统计最近一周偶数userId的数量
["@column":"date;left(date,10):day;sum(if(userId%2=0,1,0))",
"@group":"day",
"@having":"to_days(now())-to_days(\`date\`)<=7",
"@raw":"@column,@having"](http://apijson.cn:8080/get/{"[]":{"Moment":{"@column":"date%3bleft(date,10):day%3bsum(if(userId%252=0,1,0))","@group":"day","@having":"to_days(now())-to_days(\`date\`)<=7","@raw":"@column,@having"}}})
对应SQL是``SELECT date, left(date,10) AS day, sum(if(userId%2=0,1,0)) ... GROUP BY day HAVING to_days(now())-to_days(`date`)<=7``

⑫ 从pictureList获取第0张图片:
["@position":0, //自定义关键词
"firstPicture()":"getFromArray(pictureList,@position)"](http://apijson.cn:8080/get/{"User":{"id":38710,"@position":0,"firstPicture()":"getFromArray(pictureList,@position)"}})
From 0aa7281398bf52aa71dac3dad9d447d055778c4f Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Thu, 13 May 2021 16:39:20 +0800 Subject: [PATCH 0343/1181] =?UTF-8?q?=E6=96=B0=E5=A2=9E=20=E3=80=90?= =?UTF-8?q?=E8=85=BE=E8=AE=AF=E7=8A=80=E7=89=9B=E9=B8=9F=E5=BC=80=E6=BA=90?= =?UTF-8?q?=E4=BA=BA=E6=89=8D=E5=9F=B9=E5=85=BB=E8=AE=A1=E5=88=92=E3=80=91?= =?UTF-8?q?=E5=85=AC=E5=91=8A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index fd6dbd232..70fc32d17 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,5 @@ +[【腾讯犀牛鸟开源人才培养计划】开始啦!加入我们开源共建吧~](https://github.com/Tencent/APIJSON/issues/229) + Tencent is pleased to support the open source community by making APIJSON available.
Copyright (C) 2020 THL A29 Limited, a Tencent company. All rights reserved.
This source code is licensed under the Apache License Version 2.0
From b3704c9f75a63f9db878519f76851eb55ec376c2 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Thu, 13 May 2021 20:07:21 +0800 Subject: [PATCH 0344/1181] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E4=B8=80=E4=B8=AA?= =?UTF-8?q?=E8=B4=A1=E7=8C=AE=E8=80=85=EF=BC=8C=E6=84=9F=E8=B0=A2=E8=85=BE?= =?UTF-8?q?=E8=AE=AF=E5=90=8C=E4=BA=8B=E7=9A=84=E8=B4=A1=E7=8C=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 70fc32d17..c178329b6 100644 --- a/README.md +++ b/README.md @@ -259,6 +259,7 @@ https://github.com/Tencent/APIJSON/issues/187 +
From d78967a05d8b6a51ae3f7f28266b8cd87f2cc42f Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Thu, 13 May 2021 20:08:32 +0800 Subject: [PATCH 0345/1181] =?UTF-8?q?=E8=B4=A1=E7=8C=AE=E8=80=85=E5=90=8D?= =?UTF-8?q?=E5=8D=95=E6=96=B0=E5=A2=9E=E8=85=BE=E8=AE=AF=E5=B7=A5=E7=A8=8B?= =?UTF-8?q?=E5=B8=88=20caohao-php=EF=BC=8C=E6=84=9F=E8=B0=A2=E8=B4=A1?= =?UTF-8?q?=E7=8C=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CONTRIBUTING.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 011ee9502..ac57d8d9d 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -27,6 +27,8 @@ - [gdjs2](https://github.com/gdjs2) - [Rkyzzy](https://github.com/Rkyzzy)(SUSTech) - [kxlv2000](https://github.com/kxlv2000)(SUSTech) +- [caohao-php](https://github.com/caohao-php)(腾讯工程师) + #### 其中特别致谢:
justinfengchen 提交的 6 个 Commits, 对 APIJSON 做出了 3,130 增加和 0 处删减(截止 2020/11/04 日);
From 5750a556be3cb220848192527d6699306932e5a5 Mon Sep 17 00:00:00 2001 From: Eno Yao Date: Fri, 14 May 2021 16:29:16 +0800 Subject: [PATCH 0346/1181] Update README-English.md Update README-English.md --- README-English.md | 63 +++++++++++++++++++++++++---------------------- 1 file changed, 34 insertions(+), 29 deletions(-) diff --git a/README-English.md b/README-English.md index 9d5cf49be..eab64c560 100644 --- a/README-English.md +++ b/README-English.md @@ -75,19 +75,22 @@ With APIJSON, client developers will no longer be suffered from possible errors Server developers no longer need to worry about compatibility of APIs and documents with legacy apps. ### Examples: + #### Get a User Request: -


+
+```json
 {
   "User":{
   }
 }
-
+``` [Click here to test](http://apijson.cn:8080/get/{"User":{}}) Response: -

+
+```json
 {
   "User":{
     "id":38710,
@@ -106,13 +109,14 @@ Response:
   "code":200,
   "msg":"success"
 }
-
- +```
#### Get an Array of Users + Request: -

+
+```json
 {
   "[]":{
     "count":3,             //just get 3 results
@@ -121,12 +125,13 @@ Request:
     }
   }
 }
-
+``` [Click here to test](http://apijson.cn:8080/get/{"[]":{"count":3,"User":{"@column":"id,name"}}}) Response: -

+
+```json
 {
   "[]":[
     {
@@ -151,7 +156,7 @@ Response:
   "code":200,
   "msg":"success"
 }
-
+```
@@ -188,28 +193,28 @@ In the menu at the right, click libs, right click apijson-orm.jar,click add as l Open apijson.demo.server.DemoSQLConfig. In line 40-61, change return values of `getDBUri`,`getDBAccount`,`getDBPassword`,`getSchema` to your own database.
-

+```java
+@Override
+public String getDBUri() {
+    //TODO: Change the return value to your own
+    return DATABASE_POSTGRESQL.equalsIgnoreCase(getDatabase()) ? "jdbc:postgresql://localhost:5432/postgres" : "jdbc:mysql://192.168.71.146:3306/";
+}
+@Override
+public String getDBAccount() {
+    //TODO: Change the return value to your own
+    return DATABASE_POSTGRESQL.equalsIgnoreCase(getDatabase()) ? "postgres" : "root";
+}
 @Override
-	public String getDBUri() {
-		//TODO: Change the return value to your own
-		return DATABASE_POSTGRESQL.equalsIgnoreCase(getDatabase()) ? "jdbc:postgresql://localhost:5432/postgres" : "jdbc:mysql://192.168.71.146:3306/";
-	}
-	@Override
-	public String getDBAccount() {
+public String getDBPassword() {
     //TODO: Change the return value to your own
-		return DATABASE_POSTGRESQL.equalsIgnoreCase(getDatabase()) ? "postgres" : "root";
-	}
-	@Override
-	public String getDBPassword() {
-  	//TODO: Change the return value to your own
-		return DATABASE_POSTGRESQL.equalsIgnoreCase(getDatabase()) ? null : "root"; 
-	}
-	@Override
-	public String getSchema() {
-		String s = super.getSchema();
-		return StringUtil.isEmpty(s, true) ? "thea" : s; //TODO: Change the return value to your own. For here,change "thea" to "your database's name"
-	}
-
+ return DATABASE_POSTGRESQL.equalsIgnoreCase(getDatabase()) ? null : "root"; +} +@Override +public String getSchema() { + String s = super.getSchema(); + return StringUtil.isEmpty(s, true) ? "thea" : s; //TODO: Change the return value to your own. For here,change "thea" to "your database's name" +} +``` **Note**: Instead of this step, you can also [import your database](#2.2). From 3ac696efb732daf4100badabb9ce256d48632015 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Fri, 14 May 2021 18:40:50 +0800 Subject: [PATCH 0347/1181] =?UTF-8?q?=E8=B4=A1=E7=8C=AE=E8=80=85=E4=BB=AC?= =?UTF-8?q?=E6=96=B0=E5=A2=9E=E8=85=BE=E8=AE=AF=20AlloyTeam=20=E7=9A=84?= =?UTF-8?q?=E5=90=8C=E4=BA=8B=EF=BC=8C=E9=9D=9E=E5=B8=B8=E6=84=9F=E8=B0=A2?= =?UTF-8?q?=E5=AF=B9=E8=8B=B1=E6=96=87=E6=96=87=E6=A1=A3=E7=9A=84=E4=BC=98?= =?UTF-8?q?=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit https://github.com/Tencent/APIJSON/pull/235 --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index c178329b6..a5d11536e 100644 --- a/README.md +++ b/README.md @@ -259,7 +259,9 @@ https://github.com/Tencent/APIJSON/issues/187 + +
From dedd0d16b6c7c085fa1fc259df212049eb966d9f Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Fri, 14 May 2021 18:41:52 +0800 Subject: [PATCH 0348/1181] =?UTF-8?q?=E8=B4=A1=E7=8C=AE=E8=80=85=E5=90=8D?= =?UTF-8?q?=E5=8D=95=E6=96=B0=E5=A2=9E=E8=85=BE=E8=AE=AF=20AlloyTeam=20?= =?UTF-8?q?=E7=9A=84=E5=90=8C=E4=BA=8B=EF=BC=8C=E9=9D=9E=E5=B8=B8=E6=84=9F?= =?UTF-8?q?=E8=B0=A2=E5=AF=B9=E8=8B=B1=E6=96=87=E6=96=87=E6=A1=A3=E7=9A=84?= =?UTF-8?q?=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit https://github.com/Tencent/APIJSON/pull/235 --- CONTRIBUTING.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index ac57d8d9d..b20176fcb 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -28,6 +28,7 @@ - [Rkyzzy](https://github.com/Rkyzzy)(SUSTech) - [kxlv2000](https://github.com/kxlv2000)(SUSTech) - [caohao-php](https://github.com/caohao-php)(腾讯工程师) +- [Wscats](https://github.com/Wscats)(腾讯工程师) #### 其中特别致谢:
From 20b16a86933fb1d616ca431f96e9e4a0bccf6958 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Sat, 15 May 2021 22:23:06 +0800 Subject: [PATCH 0349/1181] Update README.md --- README.md | 13 ++----------- 1 file changed, 2 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index a5d11536e..edda5555d 100644 --- a/README.md +++ b/README.md @@ -56,11 +56,6 @@ APIJSON 是一种专为 API 而生的 JSON 网络传输协议 以及 基于这 前端再也不用和后端沟通接口或文档问题了!再也不会被文档各种错误坑了!
后端再也不用为了兼容旧接口写新版接口和文档了!再也不会被前端随时随地没完没了地烦了! -

- -

- - ### 特点功能 #### 对于前端 @@ -134,13 +129,9 @@ APIJSON 是一种专为 API 而生的 JSON 网络传输协议 以及 基于这
### APIJSON 分享演讲 -https://github.com/TommyLemon/StaticResources/tree/master/APIJSON/Share/GiteeGVPMeetup2020
-https://my.oschina.net/u/4570368/blog/4818203
-https://my.oschina.net/gitosc/blog/4864607
- -
-演讲录播视频 https://www.bilibili.com/video/BV1Tv411t74v?p=4 +![image](https://user-images.githubusercontent.com/5738175/118364689-18440980-b5cc-11eb-9291-51053954f805.png) +
From 2577f02f9e741150a61a8d7df890ca03d235a825 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Mon, 17 May 2021 00:01:20 +0800 Subject: [PATCH 0350/1181] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index edda5555d..c90935626 100644 --- a/README.md +++ b/README.md @@ -129,7 +129,7 @@ APIJSON 是一种专为 API 而生的 JSON 网络传输协议 以及 基于这
### APIJSON 分享演讲 -https://www.bilibili.com/video/BV1Tv411t74v?p=4 +https://www.bilibili.com/video/BV1Tv411t74v ![image](https://user-images.githubusercontent.com/5738175/118364689-18440980-b5cc-11eb-9291-51053954f805.png) From 8519c1b3d61f6c651362c8c900930a5a51d73ea6 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Mon, 17 May 2021 11:36:36 +0800 Subject: [PATCH 0351/1181] Update CONTRIBUTING.md --- CONTRIBUTING.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index b20176fcb..ebef62c74 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -5,14 +5,14 @@ ## Acknowledgements -非常感谢以下贡献者们对于 APIJSON 的做出的贡献: +非常感谢以下贡献者们对于 APIJSON 本项目做出的贡献: - [TommyLemon](https://github.com/TommyLemon)(腾讯工程师) - [ruoranw](https://github.com/ruoranw)(现居美国洛杉矶) - [zhoulingfengofcd](https://github.com/zhoulingfengofcd) -- [Zerounary](https://github.com/Zerounary) +- [Zerounary](https://github.com/Zerounary)(还贡献了 APIJSONParser) - [fineday009](https://github.com/fineday009)(腾讯工程师) -- [vincentCheng](https://github.com/vincentCheng) +- [vincentCheng](https://github.com/vincentCheng)(还贡献了 apijson-doc) - [justinfengchen](https://github.com/justinfengchen) - [linlwqq](https://github.com/linlwqq) - [redcatmiss](https://github.com/redcatmiss)(社保科技工程师) From 81632232ffcf67e22eb71b6643992b40ddb00f7f Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Mon, 17 May 2021 11:38:31 +0800 Subject: [PATCH 0352/1181] Update CONTRIBUTING.md --- CONTRIBUTING.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index ebef62c74..4687648ed 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -10,9 +10,9 @@ - [TommyLemon](https://github.com/TommyLemon)(腾讯工程师) - [ruoranw](https://github.com/ruoranw)(现居美国洛杉矶) - [zhoulingfengofcd](https://github.com/zhoulingfengofcd) -- [Zerounary](https://github.com/Zerounary)(还贡献了 APIJSONParser) -- [fineday009](https://github.com/fineday009)(腾讯工程师) -- [vincentCheng](https://github.com/vincentCheng)(还贡献了 apijson-doc) +- [Zerounary](https://github.com/Zerounary)(还开源了 APIJSONParser) +- [fineday009](https://github.com/fineday009)(腾讯工程师,还贡献了 apijson-framework, APIJSON-Demo) +- [vincentCheng](https://github.com/vincentCheng)(还开源了 apijson-doc) - [justinfengchen](https://github.com/justinfengchen) - [linlwqq](https://github.com/linlwqq) - [redcatmiss](https://github.com/redcatmiss)(社保科技工程师) From 0de66738791ddb8a732b8b22d0da67b3a4fa3014 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Mon, 17 May 2021 11:39:21 +0800 Subject: [PATCH 0353/1181] Update CONTRIBUTING.md --- CONTRIBUTING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 4687648ed..1638d3cae 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -7,7 +7,7 @@ 非常感谢以下贡献者们对于 APIJSON 本项目做出的贡献: -- [TommyLemon](https://github.com/TommyLemon)(腾讯工程师) +- [TommyLemon](https://github.com/TommyLemon)(腾讯工程师,还开源了 apijson-framework, APIJSON-Demo, apijson-column 等) - [ruoranw](https://github.com/ruoranw)(现居美国洛杉矶) - [zhoulingfengofcd](https://github.com/zhoulingfengofcd) - [Zerounary](https://github.com/Zerounary)(还开源了 APIJSONParser) From 6ef703230fca0b1eec22fd6786914d5bd564a17f Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Mon, 17 May 2021 11:41:20 +0800 Subject: [PATCH 0354/1181] Update CONTRIBUTING.md --- CONTRIBUTING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 1638d3cae..7613337a3 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -8,7 +8,7 @@ 非常感谢以下贡献者们对于 APIJSON 本项目做出的贡献: - [TommyLemon](https://github.com/TommyLemon)(腾讯工程师,还开源了 apijson-framework, APIJSON-Demo, apijson-column 等) -- [ruoranw](https://github.com/ruoranw)(现居美国洛杉矶) +- [ruoranw](https://github.com/ruoranw)(现居美国洛杉矶,还开源了 APIJSONdocs) - [zhoulingfengofcd](https://github.com/zhoulingfengofcd) - [Zerounary](https://github.com/Zerounary)(还开源了 APIJSONParser) - [fineday009](https://github.com/fineday009)(腾讯工程师,还贡献了 apijson-framework, APIJSON-Demo) From 43da615fad71d6f6f3a6b0863958a4fc24b24f86 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Mon, 17 May 2021 13:02:45 +0800 Subject: [PATCH 0355/1181] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index c90935626..507121400 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@ This source code is licensed under the Apache License Version 2.0
APIJSON -

🏆 腾讯内外五个奖项、腾讯开源五个第一
🚀 后端接口和文档零代码,前端(客户端) 定制返回 JSON 的数据和结构!

+

零代码、热更新、自动化 ORM 库
🚀 后端接口和文档零代码,前端(客户端) 定制返回 JSON 的数据和结构!

From deaa91d806ab57c83da29ecba170172bd56631a8 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Mon, 17 May 2021 13:03:15 +0800 Subject: [PATCH 0356/1181] Update README-English.md --- README-English.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README-English.md b/README-English.md index eab64c560..596b7c11c 100644 --- a/README-English.md +++ b/README-English.md @@ -6,7 +6,7 @@ This source code is licensed under the Apache License Version 2.0
APIJSON -

🏆Gitee Most Valuable Project
🚀A JSON Transmission Protocol and an ORM Library for providing APIs and Documents automatically.

+

🚀A JSON Transmission Protocol and an ORM Library for providing APIs and Documents automatically.

From 3b419c231b8d20dc367d83b27d85b90a2db146d0 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Mon, 17 May 2021 13:03:29 +0800 Subject: [PATCH 0357/1181] Update README-English.md --- README-English.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README-English.md b/README-English.md index 596b7c11c..9ed377326 100644 --- a/README-English.md +++ b/README-English.md @@ -6,7 +6,7 @@ This source code is licensed under the Apache License Version 2.0
APIJSON -

🚀A JSON Transmission Protocol and an ORM Library for providing APIs and Documents automatically.

+

🚀 A JSON Transmission Protocol and an ORM Library for providing APIs and Documents automatically.

From 575f58b9cbf98ac7ef19f8cb81a042bc028e2b83 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Mon, 17 May 2021 13:11:17 +0800 Subject: [PATCH 0358/1181] Update README.md --- README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 507121400..87a109268 100644 --- a/README.md +++ b/README.md @@ -139,11 +139,11 @@ https://www.bilibili.com/video/BV1Tv411t74v 前后端 关于接口的 开发、文档、联调 等 10 大痛点解析
https://github.com/Tencent/APIJSON/wiki -* **解决十大痛点** (APIJSON 大幅提振开发效率、强力杜绝联调扯皮、巧妙规避文档缺陷、非常节省流量带宽 等) +* **解决十大痛点** (APIJSON 可帮助用户 提振开发效率、杜绝联调扯皮、规避文档缺陷、节省流量带宽 等) * **开发提速巨大** (CRUD 零代码热更新自动化,APIJSONBoot 对比 SSM、SSH 等保守估计可提速 20 倍以上) * **腾讯官方开源** (使用 GitHub、Gitee、工蜂 等平台的官方账号开源,微信公众号、腾讯云+社区 等官方公告) -* **社区影响力大** (GitHub 10K Star 在 350W Java 项目中排名前 150,远超 FLAG, BAT 等国内外绝大部分开源项目) -* **各项荣誉成就** (腾讯开源五个第一、腾讯首个 GVP 获奖项目、腾讯后端项目 Star 第一、GitHub Java 周榜第一 等) +* **社区影响力大** (GitHub 1W+ Star 在 450W Java 项目中排名前 140,远超 FLAG, BAT 等国内外绝大部分开源项目) +* **各项荣誉成就** (腾讯内三个奖项、腾讯外两个奖项、腾讯后端项目 Star 第一、登上 GitHub Java 日周月榜 等) * **多样用户案例** (腾讯内部用户包含 互娱、音乐、云与智慧,外部用户包含 500 强上市公司、数千亿资本国企 等) * **适用场景广泛** (社交聊天、阅读资讯、影音视频、办公学习 等各种 App、网站、公众号、小程序 等非金融类项目) * **周边生态丰富** (Android, iOS, Web 等各种 Demo、继承 JSON 的海量生态、零代码 接口测试 和 单元测试 工具等) @@ -152,7 +152,7 @@ https://github.com/Tencent/APIJSON/wiki * **使用安全简单** (自动增删改查、自动生成文档、自动管理版本、自动控制权限、自动校验参数、自动防SQL注入等) * **灵活定制业务** (在后端编写 远程函数,可以拿到 session、version、当前 JSON 对象 等,然后自定义处理) * **高质可靠代码** (代码严谨规范,商业分析软件源伞 Pinpoint 代码扫描报告平均每行代码 Bug 率低至 0.15%) -* **兼容各种项目** (对各类 Web 框架集成友好且提供 SpringBoot, JFinal 的 Demo,协议不限 HTTP,与其它库无冲突) +* **兼容各种项目** (协议不限 HTTP,与其它库无冲突,对各类 Web 框架集成友好且提供 SpringBoot, JFinal 的 Demo) * **工程轻量小巧** (仅依赖 fastjson,Jar 仅 280KB,Java 文件仅 59 个共 13719 行代码,例如 APIJSONORM 4.3.1) * **多年持续迭代** (自 2016 年开源至今已连续维护 4 年,累计 2000+ Commits、70+ Releases,不断更新迭代中...) From cc61ca64841184324babea280312f8d53e18947d Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Mon, 17 May 2021 13:13:23 +0800 Subject: [PATCH 0359/1181] Update README.md --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 87a109268..8a321b8e8 100644 --- a/README.md +++ b/README.md @@ -130,7 +130,7 @@ APIJSON 是一种专为 API 而生的 JSON 网络传输协议 以及 基于这 ### APIJSON 分享演讲 https://www.bilibili.com/video/BV1Tv411t74v -![image](https://user-images.githubusercontent.com/5738175/118364689-18440980-b5cc-11eb-9291-51053954f805.png) +![image](http://apijson.cn/images/comparison/APIJSON_vs_PreviousWays.jpg)
@@ -143,7 +143,7 @@ https://github.com/Tencent/APIJSON/wiki * **开发提速巨大** (CRUD 零代码热更新自动化,APIJSONBoot 对比 SSM、SSH 等保守估计可提速 20 倍以上) * **腾讯官方开源** (使用 GitHub、Gitee、工蜂 等平台的官方账号开源,微信公众号、腾讯云+社区 等官方公告) * **社区影响力大** (GitHub 1W+ Star 在 450W Java 项目中排名前 140,远超 FLAG, BAT 等国内外绝大部分开源项目) -* **各项荣誉成就** (腾讯内三个奖项、腾讯外两个奖项、腾讯后端项目 Star 第一、登上 GitHub Java 日周月榜 等) +* **各项荣誉成就** (腾讯内三个奖项、腾讯外两个奖项、腾讯后端项目 Star 第一、多次登上 GitHub Java 日周月榜 等) * **多样用户案例** (腾讯内部用户包含 互娱、音乐、云与智慧,外部用户包含 500 强上市公司、数千亿资本国企 等) * **适用场景广泛** (社交聊天、阅读资讯、影音视频、办公学习 等各种 App、网站、公众号、小程序 等非金融类项目) * **周边生态丰富** (Android, iOS, Web 等各种 Demo、继承 JSON 的海量生态、零代码 接口测试 和 单元测试 工具等) From 1c867f761637f56d22565db6bbfca7a5477382f0 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Mon, 17 May 2021 13:14:13 +0800 Subject: [PATCH 0360/1181] Update README.md --- README.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 8a321b8e8..95fa3dc26 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@ This source code is licensed under the Apache License Version 2.0
APIJSON -

零代码、热更新、自动化 ORM 库
🚀 后端接口和文档零代码,前端(客户端) 定制返回 JSON 的数据和结构!

+

零代码、热更新、自动化 ORM 库
🚀 后端接口和文档零代码,前端(客户端) 定制返回 JSON 的数据和结构

@@ -51,10 +51,10 @@ APIJSON 是一种专为 API 而生的 JSON 网络传输协议 以及 基于这 能大幅降低开发和沟通成本,简化开发流程,缩短开发周期。
适合中小型前后端分离的项目,尤其是 BaaS、Serverless、互联网创业项目和企业自用项目。
-通过万能的 API,前端可以定制任何数据、任何结构!
-大部分 HTTP 请求后端再也不用写接口了,更不用写文档了!
-前端再也不用和后端沟通接口或文档问题了!再也不会被文档各种错误坑了!
-后端再也不用为了兼容旧接口写新版接口和文档了!再也不会被前端随时随地没完没了地烦了! +通过万能的 API,前端可以定制任何数据、任何结构。
+大部分 HTTP 请求后端再也不用写接口了,更不用写文档了。
+前端再也不用和后端沟通接口或文档问题了。再也不会被文档各种错误坑了。
+后端再也不用为了兼容旧接口写新版接口和文档了。再也不会被前端随时随地没完没了地烦了。 ### 特点功能 From 9f64a46dc7dac1160fdb67e28c4c01a33eb66c69 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Mon, 17 May 2021 14:21:24 +0800 Subject: [PATCH 0361/1181] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 95fa3dc26..0a969deb8 100644 --- a/README.md +++ b/README.md @@ -142,7 +142,7 @@ https://github.com/Tencent/APIJSON/wiki * **解决十大痛点** (APIJSON 可帮助用户 提振开发效率、杜绝联调扯皮、规避文档缺陷、节省流量带宽 等) * **开发提速巨大** (CRUD 零代码热更新自动化,APIJSONBoot 对比 SSM、SSH 等保守估计可提速 20 倍以上) * **腾讯官方开源** (使用 GitHub、Gitee、工蜂 等平台的官方账号开源,微信公众号、腾讯云+社区 等官方公告) -* **社区影响力大** (GitHub 1W+ Star 在 450W Java 项目中排名前 140,远超 FLAG, BAT 等国内外绝大部分开源项目) +* **社区影响力大** (GitHub 1W+ Star 在 350W Java 项目中排名前 140,远超 FLAG, BAT 等国内外绝大部分开源项目) * **各项荣誉成就** (腾讯内三个奖项、腾讯外两个奖项、腾讯后端项目 Star 第一、多次登上 GitHub Java 日周月榜 等) * **多样用户案例** (腾讯内部用户包含 互娱、音乐、云与智慧,外部用户包含 500 强上市公司、数千亿资本国企 等) * **适用场景广泛** (社交聊天、阅读资讯、影音视频、办公学习 等各种 App、网站、公众号、小程序 等非金融类项目) From 358afde0b581f4be371f984422aa6289d38575e1 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Tue, 18 May 2021 12:00:58 +0800 Subject: [PATCH 0362/1181] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 0a969deb8..79b18a8f9 100644 --- a/README.md +++ b/README.md @@ -139,7 +139,7 @@ https://www.bilibili.com/video/BV1Tv411t74v 前后端 关于接口的 开发、文档、联调 等 10 大痛点解析
https://github.com/Tencent/APIJSON/wiki -* **解决十大痛点** (APIJSON 可帮助用户 提振开发效率、杜绝联调扯皮、规避文档缺陷、节省流量带宽 等) +* **解决多个痛点** (APIJSON 可帮助用户 提振开发效率、杜绝联调扯皮、规避文档缺陷、节省流量带宽 等) * **开发提速巨大** (CRUD 零代码热更新自动化,APIJSONBoot 对比 SSM、SSH 等保守估计可提速 20 倍以上) * **腾讯官方开源** (使用 GitHub、Gitee、工蜂 等平台的官方账号开源,微信公众号、腾讯云+社区 等官方公告) * **社区影响力大** (GitHub 1W+ Star 在 350W Java 项目中排名前 140,远超 FLAG, BAT 等国内外绝大部分开源项目) From f8592c5b274b0b4c0d4ab4b63bff4a8ff0e769a7 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Tue, 18 May 2021 12:01:33 +0800 Subject: [PATCH 0363/1181] Update README.md --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 79b18a8f9..0b5af5af4 100644 --- a/README.md +++ b/README.md @@ -136,10 +136,10 @@ https://www.bilibili.com/video/BV1Tv411t74v
### 为什么选择 APIJSON? -前后端 关于接口的 开发、文档、联调 等 10 大痛点解析
+前后端 关于接口的 开发、文档、联调 等 10 个痛点解析
https://github.com/Tencent/APIJSON/wiki -* **解决多个痛点** (APIJSON 可帮助用户 提振开发效率、杜绝联调扯皮、规避文档缺陷、节省流量带宽 等) +* **解决十个痛点** (APIJSON 可帮助用户 提振开发效率、杜绝联调扯皮、规避文档缺陷、节省流量带宽 等) * **开发提速巨大** (CRUD 零代码热更新自动化,APIJSONBoot 对比 SSM、SSH 等保守估计可提速 20 倍以上) * **腾讯官方开源** (使用 GitHub、Gitee、工蜂 等平台的官方账号开源,微信公众号、腾讯云+社区 等官方公告) * **社区影响力大** (GitHub 1W+ Star 在 350W Java 项目中排名前 140,远超 FLAG, BAT 等国内外绝大部分开源项目) From 97942228dd3b49713dcad9952dd13fb896741f49 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Tue, 18 May 2021 12:03:05 +0800 Subject: [PATCH 0364/1181] Update README.md --- README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/README.md b/README.md index 0b5af5af4..6e1f8a87a 100644 --- a/README.md +++ b/README.md @@ -143,7 +143,6 @@ https://github.com/Tencent/APIJSON/wiki * **开发提速巨大** (CRUD 零代码热更新自动化,APIJSONBoot 对比 SSM、SSH 等保守估计可提速 20 倍以上) * **腾讯官方开源** (使用 GitHub、Gitee、工蜂 等平台的官方账号开源,微信公众号、腾讯云+社区 等官方公告) * **社区影响力大** (GitHub 1W+ Star 在 350W Java 项目中排名前 140,远超 FLAG, BAT 等国内外绝大部分开源项目) -* **各项荣誉成就** (腾讯内三个奖项、腾讯外两个奖项、腾讯后端项目 Star 第一、多次登上 GitHub Java 日周月榜 等) * **多样用户案例** (腾讯内部用户包含 互娱、音乐、云与智慧,外部用户包含 500 强上市公司、数千亿资本国企 等) * **适用场景广泛** (社交聊天、阅读资讯、影音视频、办公学习 等各种 App、网站、公众号、小程序 等非金融类项目) * **周边生态丰富** (Android, iOS, Web 等各种 Demo、继承 JSON 的海量生态、零代码 接口测试 和 单元测试 工具等) From 1fca1270da303431ba00f329a7851e993d2f89b8 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Tue, 18 May 2021 12:05:32 +0800 Subject: [PATCH 0365/1181] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 6e1f8a87a..7adf5e1b7 100644 --- a/README.md +++ b/README.md @@ -140,7 +140,7 @@ https://www.bilibili.com/video/BV1Tv411t74v https://github.com/Tencent/APIJSON/wiki * **解决十个痛点** (APIJSON 可帮助用户 提振开发效率、杜绝联调扯皮、规避文档缺陷、节省流量带宽 等) -* **开发提速巨大** (CRUD 零代码热更新自动化,APIJSONBoot 对比 SSM、SSH 等保守估计可提速 20 倍以上) +* **开发提速很大** (CRUD 零代码热更新自动化,APIJSONBoot 对比 SSM、SSH 等保守估计可提速 20 倍以上) * **腾讯官方开源** (使用 GitHub、Gitee、工蜂 等平台的官方账号开源,微信公众号、腾讯云+社区 等官方公告) * **社区影响力大** (GitHub 1W+ Star 在 350W Java 项目中排名前 140,远超 FLAG, BAT 等国内外绝大部分开源项目) * **多样用户案例** (腾讯内部用户包含 互娱、音乐、云与智慧,外部用户包含 500 强上市公司、数千亿资本国企 等) From a719ceb2d7433c163f87695c7568efcda3e7cdef Mon Sep 17 00:00:00 2001 From: kxlv2000 <49295281+kxlv2000@users.noreply.github.com> Date: Wed, 19 May 2021 21:54:05 +0800 Subject: [PATCH 0366/1181] =?UTF-8?q?=E4=BC=98=E5=8C=96=20system.err.print?= =?UTF-8?q?lin=20=E8=BE=93=E5=87=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit issure #232 --- .../main/java/apijson/orm/AbstractParser.java | 35 +++++++++++++------ 1 file changed, 25 insertions(+), 10 deletions(-) diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java b/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java index e109b5a04..1525941b9 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java @@ -48,11 +48,24 @@ public abstract class AbstractParser implements Parser, ParserCreator, VerifierCreator, SQLCreator { protected static final String TAG = "AbstractParser"; + /** + * 可以通过切换该变量来控制是否打印关键的接口请求内容。保守起见,该值默认为false。 + * 与 {@link Log#DEBUG} 任何一个为 true 都会打印关键的接口请求内容。 + */ + public static boolean IS_PRINT_REQUEST_STRING_LOG = true; + /** * 打印大数据量日志的标识。线上环境比较敏感,可以通过切换该变量来控制异常栈抛出、错误日志打印。保守起见,该值默认为false。 * 与 {@link Log#DEBUG} 任何一个为 true 都会打印关键的接口请求及响应信息。 */ - public static boolean IS_PRINT_BIG_LOG = false; + public static boolean IS_PRINT_BIG_LOG = true; + + /** + * 可以通过切换该变量来控制是否打印关键的接口请求结束时间。保守起见,该值默认为false。 + * 与 {@link Log#DEBUG} 任何一个为 true 都会打印关键的接口请求结束时间。 + */ + public static boolean IS_PRINT_REQUEST_ENDTIME_LOG = true; + /** * method = null @@ -400,16 +413,18 @@ public JSONObject parseResponse(JSONObject request) { onClose(); - System.err.println("\n\n\n\n<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<\n " - + TAG + ".DEBUG: " + requestMethod + "/parseResponse request = \n" + requestString + "\n\n"); - - if (Log.DEBUG || IS_PRINT_BIG_LOG || error != null) { // 日志仅存服务器,所以不太敏感,而且这些日志虽然量大但非常重要,对排查 bug 很关键 - System.err.println(TAG + ".DEBUG: " + requestMethod + "/parseResponse return response = \n" + JSON.toJSONString(requestObject) + "\n\n"); + //CS304 Issue link: https://github.com/Tencent/APIJSON/issues/232 + if (IS_PRINT_REQUEST_STRING_LOG||Log.DEBUG||error != null) { + Log.sl("\n\n\n",'<',""); + Log.fd(TAG , requestMethod + "/parseResponse request = \n" + requestString + "\n\n"); + } + if (IS_PRINT_BIG_LOG||Log.DEBUG||error != null) { // 日志仅存服务器,所以不太敏感,而且这些日志虽然量大但非常重要,对排查 bug 很关键 + Log.fd(TAG,requestMethod + "/parseResponse return response = \n" + JSON.toJSONString(requestObject) + "\n\n"); + } + if (IS_PRINT_REQUEST_ENDTIME_LOG||Log.DEBUG||error != null) { + Log.fd(TAG , requestMethod + "/parseResponse endTime = " + endTime + "; duration = " + duration); + Log.sl("",'>',"\n\n\n"); } - - System.err.println(TAG + ".DEBUG: " + requestMethod + "/parseResponse endTime = " + endTime + "; duration = " + duration - + "\n>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> \n\n\n"); - return res; } From 204ffe55fa873a08abe09e397299324498a1d40f Mon Sep 17 00:00:00 2001 From: kxlv2000 <49295281+kxlv2000@users.noreply.github.com> Date: Wed, 19 May 2021 22:09:27 +0800 Subject: [PATCH 0367/1181] =?UTF-8?q?=E4=BC=98=E5=8C=96=20system.err.print?= =?UTF-8?q?lin=20=E8=BE=93=E5=87=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit issue Tencent#232 --- APIJSONORM/src/main/java/apijson/Log.java | 19 +++++++++++++++++++ .../main/java/apijson/orm/AbstractParser.java | 6 +++--- 2 files changed, 22 insertions(+), 3 deletions(-) diff --git a/APIJSONORM/src/main/java/apijson/Log.java b/APIJSONORM/src/main/java/apijson/Log.java index d37691f8e..eecc7e1c2 100755 --- a/APIJSONORM/src/main/java/apijson/Log.java +++ b/APIJSONORM/src/main/java/apijson/Log.java @@ -22,6 +22,25 @@ public static void d(String TAG, String msg) { } } + /** + * Forced debug + * @param TAG tag + * @param msg debug messages + */ + public static void fd(String TAG, String msg) { + System.err.println(TAG + ".DEBUG: " + msg); + } + + /** + * Generate separation line + * @param pre prefix + * @param symbol used for generating separation line + * @param post postfix + */ + public static void sl(String pre,char symbol ,String post) { + System.err.println(pre+new String(new char[48]).replace('\u0000', symbol)+post); + } + /** * @param TAG * @param msg diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java b/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java index 1525941b9..c46e82e4e 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java @@ -52,19 +52,19 @@ public abstract class AbstractParser implements Parser, ParserCreator, * 可以通过切换该变量来控制是否打印关键的接口请求内容。保守起见,该值默认为false。 * 与 {@link Log#DEBUG} 任何一个为 true 都会打印关键的接口请求内容。 */ - public static boolean IS_PRINT_REQUEST_STRING_LOG = true; + public static boolean IS_PRINT_REQUEST_STRING_LOG = false; /** * 打印大数据量日志的标识。线上环境比较敏感,可以通过切换该变量来控制异常栈抛出、错误日志打印。保守起见,该值默认为false。 * 与 {@link Log#DEBUG} 任何一个为 true 都会打印关键的接口请求及响应信息。 */ - public static boolean IS_PRINT_BIG_LOG = true; + public static boolean IS_PRINT_BIG_LOG = false; /** * 可以通过切换该变量来控制是否打印关键的接口请求结束时间。保守起见,该值默认为false。 * 与 {@link Log#DEBUG} 任何一个为 true 都会打印关键的接口请求结束时间。 */ - public static boolean IS_PRINT_REQUEST_ENDTIME_LOG = true; + public static boolean IS_PRINT_REQUEST_ENDTIME_LOG = false; /** From 4116e5e5385c1d700bb64451f33b8bd11fcec64c Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Thu, 20 May 2021 00:37:31 +0800 Subject: [PATCH 0368/1181] =?UTF-8?q?=E6=96=B0=E5=A2=9E=204=20=E4=B8=AA?= =?UTF-8?q?=E7=94=9F=E6=80=81=E9=A1=B9=E7=9B=AE=EF=BC=9A1=20=E4=B8=AA?= =?UTF-8?q?=E7=AC=AC=E4=B8=89=E6=96=B9=20SQL=20Parser=20=E5=92=8C=203=20?= =?UTF-8?q?=E4=B8=AA=20Demo=20=E9=A1=B9=E7=9B=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 7adf5e1b7..75108017e 100644 --- a/README.md +++ b/README.md @@ -382,18 +382,26 @@ https://github.com/Tencent/APIJSON/blob/master/CONTRIBUTING.md [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,支持 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 等 [APIJSONParser](https://github.com/Zerounary/APIJSONParser) 第三方 APIJSON 解析器,将 JSON 动态解析成 SQL -[ApiJsonByJFinal](https://gitee.com/zhiyuexin/ApiJsonByJFinal) 整合 APIJSON 和 JFinal 的 Demo +[FfApiJson](https://gitee.com/own_3_0/ff-api-json) 用 JSON 格式直接生成 SQL,借鉴 APIJSON 支持多数据源 + +[apijson-sample](https://gitee.com/greyzeng/apijson-sample) APIJSON 简单使用 Demo 及教程 + +[apijson-examples](https://gitee.com/drone/apijson-examples) APIJSON 的前端、业务后端、管理后端 Demo [light4j](https://github.com/xlongwei/light4j) 整合 APIJSON 和微服务框架 light-4j 的 Demo,同时接入了 Redis [SpringServer1.2-APIJSON](https://github.com/Airforce-1/SpringServer1.2-APIJSON) 智慧党建服务器端,提供 上传 和 下载 文件的接口 +[apijson-examples](https://gitee.com/drone/apijson-examples) APIJSON 的前端、业务后端、管理后端 Demo + +[ApiJsonByJFinal](https://gitee.com/zhiyuexin/ApiJsonByJFinal) 整合 APIJSON 和 JFinal 的 Demo + [apijson-builder](https://github.com/pengxianggui/apijson-builder) 一个方便为 APIJSON 构建 RESTful 请求的 JavaScript 库 [AbsGrade](https://github.com/APIJSON/AbsGrade) 列表级联算法,支持微信朋友圈单层评论、QQ空间双层评论、百度网盘多层(无限层)文件夹等 From 6c690cd4c5b98a7de5f389134138cd06ff34cb0c Mon Sep 17 00:00:00 2001 From: Rkyzzy <982993741@qq.com> Date: Thu, 20 May 2021 19:40:17 +0800 Subject: [PATCH 0369/1181] =?UTF-8?q?fix:=20=E6=9B=B4=E6=96=B0=E4=B8=BA?= =?UTF-8?q?=E6=9C=80=E6=96=B0=E7=9A=84=E6=89=8B=E6=9C=BA=E5=8F=B7=E6=AD=A3?= =?UTF-8?q?=E5=88=99=E8=A1=A8=E8=BE=BE=E5=BC=8F=E5=8C=B9=E9=85=8D=20issue#?= =?UTF-8?q?240?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- APIJSONORM/src/main/java/apijson/StringUtil.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/APIJSONORM/src/main/java/apijson/StringUtil.java b/APIJSONORM/src/main/java/apijson/StringUtil.java index fc9552de4..671f04ae0 100755 --- a/APIJSONORM/src/main/java/apijson/StringUtil.java +++ b/APIJSONORM/src/main/java/apijson/StringUtil.java @@ -311,7 +311,8 @@ public static boolean isNotEmpty(String s, boolean trim) { PATTERN_ALPHA_BIG = Pattern.compile("^[A-Z]+$"); PATTERN_ALPHA_SMALL = Pattern.compile("^[a-z]+$"); PATTERN_NAME = Pattern.compile("^[0-9a-zA-Z_]+$");//已用55个中英字符测试通过 - PATTERN_PHONE = Pattern.compile("^((13[0-9])|(15[^4,\\D])|(18[0-2,5-9])|(17[0-9]))\\d{8}$"); + //PATTERN_PHONE = Pattern.compile("^((13[0-9])|(15[^4,\\D])|(18[0-2,5-9])|(17[0-9]))\\d{8}$"); + PATTERN_PHONE = Pattern.compile("^1(?:3\\d{3}|5[^4\\D]\\d{2}|8\\d{3}|7(?:[0-35-9]\\d{2}|4(?:0\\d|1[0-2]|9\\d))|9[0-35-9]\\d{2}|6[2567]\\d{2}|4(?:(?:10|4[01])\\d{3}|[68]\\d{4}|[579]\\d{2}))\\d{6}$"); PATTERN_EMAIL = Pattern.compile("^([a-zA-Z0-9_\\-\\.]+)@((\\[[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}\\.)|(([a-zA-Z0-9\\-]+\\.)+))([a-zA-Z]{2,4}|[0-9]{1,3})(\\]?)$"); PATTERN_ID_CARD = Pattern.compile("(^[1-9]\\d{5}(18|19|([23]\\d))\\d{2}((0[1-9])|(10|11|12))(([0-2][1-9])|10|20|30|31)\\d{3}[0-9Xx]$)|(^[1-9]\\d{5}\\d{2}((0[1-9])|(10|11|12))(([0-2][1-9])|10|20|30|31)\\d{2}$)"); PATTERN_PASSWORD = Pattern.compile("^[0-9a-zA-Z]+$"); From 89fb9bf7cd299aafa189d044d83f17651b41ba3d Mon Sep 17 00:00:00 2001 From: Rkyzzy <982993741@qq.com> Date: Thu, 20 May 2021 19:44:31 +0800 Subject: [PATCH 0370/1181] =?UTF-8?q?fix=20=E6=9B=B4=E6=96=B0=E4=BA=86?= =?UTF-8?q?=E6=9C=80=E6=96=B0=E7=9A=84=E6=89=8B=E6=9C=BA=E6=AD=A3=E5=88=99?= =?UTF-8?q?=E8=A1=A8=E8=BE=BE=E5=BC=8F=E5=8C=B9=E9=85=8D=E6=B3=95=E5=88=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- APIJSONORM/src/main/java/apijson/StringUtil.java | 1 - 1 file changed, 1 deletion(-) diff --git a/APIJSONORM/src/main/java/apijson/StringUtil.java b/APIJSONORM/src/main/java/apijson/StringUtil.java index 671f04ae0..8d3c32798 100755 --- a/APIJSONORM/src/main/java/apijson/StringUtil.java +++ b/APIJSONORM/src/main/java/apijson/StringUtil.java @@ -311,7 +311,6 @@ public static boolean isNotEmpty(String s, boolean trim) { PATTERN_ALPHA_BIG = Pattern.compile("^[A-Z]+$"); PATTERN_ALPHA_SMALL = Pattern.compile("^[a-z]+$"); PATTERN_NAME = Pattern.compile("^[0-9a-zA-Z_]+$");//已用55个中英字符测试通过 - //PATTERN_PHONE = Pattern.compile("^((13[0-9])|(15[^4,\\D])|(18[0-2,5-9])|(17[0-9]))\\d{8}$"); PATTERN_PHONE = Pattern.compile("^1(?:3\\d{3}|5[^4\\D]\\d{2}|8\\d{3}|7(?:[0-35-9]\\d{2}|4(?:0\\d|1[0-2]|9\\d))|9[0-35-9]\\d{2}|6[2567]\\d{2}|4(?:(?:10|4[01])\\d{3}|[68]\\d{4}|[579]\\d{2}))\\d{6}$"); PATTERN_EMAIL = Pattern.compile("^([a-zA-Z0-9_\\-\\.]+)@((\\[[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}\\.)|(([a-zA-Z0-9\\-]+\\.)+))([a-zA-Z]{2,4}|[0-9]{1,3})(\\]?)$"); PATTERN_ID_CARD = Pattern.compile("(^[1-9]\\d{5}(18|19|([23]\\d))\\d{2}((0[1-9])|(10|11|12))(([0-2][1-9])|10|20|30|31)\\d{3}[0-9Xx]$)|(^[1-9]\\d{5}\\d{2}((0[1-9])|(10|11|12))(([0-2][1-9])|10|20|30|31)\\d{2}$)"); From 8cffe1301fbc83173188e90ba61955cbf499ef25 Mon Sep 17 00:00:00 2001 From: Rkyzzy <982993741@qq.com> Date: Thu, 20 May 2021 19:47:44 +0800 Subject: [PATCH 0371/1181] =?UTF-8?q?fix=20=E6=9B=B4=E6=96=B0=E4=BA=86?= =?UTF-8?q?=E6=9C=80=E6=96=B0=E7=9A=84=E6=89=8B=E6=9C=BA=E5=8F=B7=E6=AD=A3?= =?UTF-8?q?=E5=88=99=E8=A1=A8=E8=BE=BE=E5=BC=8F=E5=8C=B9=E9=85=8D=E6=B3=95?= =?UTF-8?q?=E5=88=99\=20issue=20#240?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- APIJSONORM/src/main/java/apijson/StringUtil.java | 1 + 1 file changed, 1 insertion(+) diff --git a/APIJSONORM/src/main/java/apijson/StringUtil.java b/APIJSONORM/src/main/java/apijson/StringUtil.java index 8d3c32798..9fac4e34e 100755 --- a/APIJSONORM/src/main/java/apijson/StringUtil.java +++ b/APIJSONORM/src/main/java/apijson/StringUtil.java @@ -311,6 +311,7 @@ public static boolean isNotEmpty(String s, boolean trim) { PATTERN_ALPHA_BIG = Pattern.compile("^[A-Z]+$"); PATTERN_ALPHA_SMALL = Pattern.compile("^[a-z]+$"); PATTERN_NAME = Pattern.compile("^[0-9a-zA-Z_]+$");//已用55个中英字符测试通过 + //newest phone regex expression reference https://github.com/VincentSit/ChinaMobilePhoneNumberRegex PATTERN_PHONE = Pattern.compile("^1(?:3\\d{3}|5[^4\\D]\\d{2}|8\\d{3}|7(?:[0-35-9]\\d{2}|4(?:0\\d|1[0-2]|9\\d))|9[0-35-9]\\d{2}|6[2567]\\d{2}|4(?:(?:10|4[01])\\d{3}|[68]\\d{4}|[579]\\d{2}))\\d{6}$"); PATTERN_EMAIL = Pattern.compile("^([a-zA-Z0-9_\\-\\.]+)@((\\[[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}\\.)|(([a-zA-Z0-9\\-]+\\.)+))([a-zA-Z]{2,4}|[0-9]{1,3})(\\]?)$"); PATTERN_ID_CARD = Pattern.compile("(^[1-9]\\d{5}(18|19|([23]\\d))\\d{2}((0[1-9])|(10|11|12))(([0-2][1-9])|10|20|30|31)\\d{3}[0-9Xx]$)|(^[1-9]\\d{5}\\d{2}((0[1-9])|(10|11|12))(([0-2][1-9])|10|20|30|31)\\d{2}$)"); From e73841dd919112d947ce67e1ee2fc1be7a3fc375 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Thu, 27 May 2021 18:00:02 +0800 Subject: [PATCH 0372/1181] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E6=96=87=E7=AB=A0?= =?UTF-8?q?=20APIJSON=E6=95=99=E7=A8=8B=EF=BC=88=E4=B8=80=EF=BC=89?= =?UTF-8?q?=EF=BC=9A=E4=B8=8A=E6=89=8Bapijson=E9=A1=B9=E7=9B=AE=EF=BC=8C?= =?UTF-8?q?=E5=AD=A6=E4=B9=A0apijson=E8=AF=AD=E6=B3=95=EF=BC=8C=E5=B9=B6?= =?UTF-8?q?=E5=AE=9E=E7=8E=B0=E6=8C=81=E4=B9=85=E5=B1=82=E9=85=8D=E7=BD=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit https://zhuanlan.zhihu.com/p/375681893 --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 75108017e..952541e0a 100644 --- a/README.md +++ b/README.md @@ -337,6 +337,8 @@ https://github.com/Tencent/APIJSON/blob/master/CONTRIBUTING.md [APIJSON对接分布式HTAP数据库TiDB](https://asktug.com/t/htap-tidb/395) +[APIJSON教程(一):上手apijson项目,学习apijson语法,并实现持久层配置](https://zhuanlan.zhihu.com/p/375681893) + [apijson简单demo](https://blog.csdn.net/dmw412724/article/details/113558115) [apijson简单使用](https://www.cnblogs.com/greyzeng/p/14311995.html) From f93c1f314aa1764e2c12bb90d41994c01d2d0d30 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Mon, 7 Jun 2021 00:16:43 +0800 Subject: [PATCH 0373/1181] English README: optimize format of request json --- README-English.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/README-English.md b/README-English.md index 9ed377326..68e8ad53f 100644 --- a/README-English.md +++ b/README-English.md @@ -79,7 +79,7 @@ Server developers no longer need to worry about compatibility of APIs and docume #### Get a User Request: -```json +```js { "User":{  } @@ -90,7 +90,7 @@ Request: Response: -```json +```js { "User":{ "id":38710, @@ -116,7 +116,7 @@ Response: Request: -```json +```js { "[]":{   "count":3,             //just get 3 results @@ -131,7 +131,7 @@ Request: Response: -```json +```js { "[]":[ { From a9efd6ddcfd11800fea6264560b8fe021c3f2159 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Mon, 7 Jun 2021 00:17:53 +0800 Subject: [PATCH 0374/1181] English README: fix online testing url --- README-English.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README-English.md b/README-English.md index 68e8ad53f..770afb08d 100644 --- a/README-English.md +++ b/README-English.md @@ -160,7 +160,7 @@ Response:
-[Test it online](http://apijson.cn/)
+[Test it online](http://apijson.cn/api)
![](https://raw.githubusercontent.com/TommyLemon/StaticResources/master/APIJSON_Auto_get.jpg) From 3b525d218450eebc4e4945d6e425a536b383b899 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Mon, 7 Jun 2021 00:25:36 +0800 Subject: [PATCH 0375/1181] English README: add new contributors --- README-English.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/README-English.md b/README-English.md index 770afb08d..40ab91fa7 100644 --- a/README-English.md +++ b/README-English.md @@ -331,17 +331,26 @@ Here are the contributers of this project and authors of other projects for ecos height="54" width="54" > + + + + + + + + +
From 0fc409b90132527aed936083c2454f317b1bd2c4 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Wed, 9 Jun 2021 22:11:35 +0800 Subject: [PATCH 0376/1181] Update README.md --- README.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/README.md b/README.md index 952541e0a..0083f1814 100644 --- a/README.md +++ b/README.md @@ -129,9 +129,14 @@ APIJSON 是一种专为 API 而生的 JSON 网络传输协议 以及 基于这
### APIJSON 分享演讲 +APIJSON-零代码接口与文档 ORM 库(国际开源谷 Gitee Meetup)
https://www.bilibili.com/video/BV1Tv411t74v ![image](http://apijson.cn/images/comparison/APIJSON_vs_PreviousWays.jpg) +APIJSON和APIAuto-零代码开发和测试(QECon 全球软件质量&效能大会)
+https://www.bilibili.com/video/BV1yv411p7Y4 +wecom-temp-377bbd0daf5aed716baf7ebcb003d94c +
From 7e6097a1e2a63334272edd33b77789cda94e5eab Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Wed, 9 Jun 2021 22:27:11 +0800 Subject: [PATCH 0377/1181] =?UTF-8?q?=E5=88=86=E4=BA=AB=E6=BC=94=E8=AE=B2?= =?UTF-8?q?=E6=96=B0=E5=A2=9E=20QECon=20=E5=85=A8=E7=90=83=E8=BD=AF?= =?UTF-8?q?=E4=BB=B6=E8=B4=A8=E9=87=8F&=E6=95=88=E8=83=BD=E5=A4=A7?= =?UTF-8?q?=E4=BC=9A=20=E7=9A=84=E9=9B=B6=E4=BB=A3=E7=A0=81=E5=BC=80?= =?UTF-8?q?=E5=8F=91=E5=92=8C=E6=B5=8B=E8=AF=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit https://mp.weixin.qq.com/s/plcRVRcIN_1l59jCigNjhQ --- README.md | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 0083f1814..4ed8b8a29 100644 --- a/README.md +++ b/README.md @@ -129,12 +129,17 @@ APIJSON 是一种专为 API 而生的 JSON 网络传输协议 以及 基于这
### APIJSON 分享演讲 -APIJSON-零代码接口与文档 ORM 库(国际开源谷 Gitee Meetup)
+#### APIJSON-零代码接口与文档 ORM 库(国际开源谷 Gitee Meetup) + https://www.bilibili.com/video/BV1Tv411t74v + ![image](http://apijson.cn/images/comparison/APIJSON_vs_PreviousWays.jpg) -APIJSON和APIAuto-零代码开发和测试(QECon 全球软件质量&效能大会)
+ +#### APIJSON 和 APIAuto-零代码开发和测试(QECon 全球软件质量&效能大会) + https://www.bilibili.com/video/BV1yv411p7Y4 + wecom-temp-377bbd0daf5aed716baf7ebcb003d94c From 81da25d53389e14fa81c5091cb7595af592ff45d Mon Sep 17 00:00:00 2001 From: jun0315 <1072505283@qq.com> Date: Fri, 11 Jun 2021 13:53:07 +0800 Subject: [PATCH 0378/1181] feat:log print current time --- APIJSONORM/src/main/java/apijson/Log.java | 43 +++++++++++++++++++---- 1 file changed, 36 insertions(+), 7 deletions(-) diff --git a/APIJSONORM/src/main/java/apijson/Log.java b/APIJSONORM/src/main/java/apijson/Log.java index eecc7e1c2..e3f64f86d 100755 --- a/APIJSONORM/src/main/java/apijson/Log.java +++ b/APIJSONORM/src/main/java/apijson/Log.java @@ -5,20 +5,49 @@ package apijson; +import java.text.SimpleDateFormat; + /**测试用Log * @modifier Lemon */ public class Log { public static boolean DEBUG = true; - + + //默认的时间格式 + public static SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss.SSS"); + + /** + * modify date format + * @param dateFormatString + */ + public static void setDateFormat(String dateFormatString) { + dateFormat = new SimpleDateFormat(dateFormatString); + } + + /** + * log info by level tag and msg + * @param TAG + * @param msg + * @param level + */ + public static void logInfo(String TAG, String msg, String level){ + if(level.equals("DEBUG") || level .equals("ERROR") ||level.equals("WARN")){ + System.err.println(dateFormat.format(System.currentTimeMillis()) + ": " + TAG + "." + level + ": " + msg); + } + else if(level.equals("VERBOSE") || level .equals("INFO") ){ + System.out.println(dateFormat.format(System.currentTimeMillis()) + ": " + TAG + "." + level + ": " + msg); + } + } + + /** * @param TAG * @param msg */ public static void d(String TAG, String msg) { if (DEBUG) { - System.err.println(TAG + ".DEBUG: " + msg); + logInfo(TAG,msg,"DEBUG"); } } @@ -28,7 +57,7 @@ public static void d(String TAG, String msg) { * @param msg debug messages */ public static void fd(String TAG, String msg) { - System.err.println(TAG + ".DEBUG: " + msg); + logInfo(TAG,msg,"DEBUG"); } /** @@ -47,7 +76,7 @@ public static void sl(String pre,char symbol ,String post) { */ public static void v(String TAG, String msg) { if (DEBUG) { - System.out.println(TAG + ".VERBOSE: " + msg); + logInfo(TAG,msg,"VERBOSE"); } } @@ -57,7 +86,7 @@ public static void v(String TAG, String msg) { */ public static void i(String TAG, String msg) { if (DEBUG) { - System.out.println(TAG + ".INFO: " + msg); + logInfo(TAG,msg,"INFO"); } } @@ -67,7 +96,7 @@ public static void i(String TAG, String msg) { */ public static void e(String TAG, String msg) { if (DEBUG) { - System.err.println(TAG + ".ERROR: " + msg); + logInfo(TAG,msg,"ERROR"); } } @@ -77,7 +106,7 @@ public static void e(String TAG, String msg) { */ public static void w(String TAG, String msg) { if (DEBUG) { - System.err.println(TAG + ".WARN: " + msg); + logInfo(TAG,msg,"WARN"); } } From 7ade5730df83fb59216d1b7830e7df9ba2b7196d Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Fri, 11 Jun 2021 16:30:23 +0800 Subject: [PATCH 0379/1181] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E4=B8=80=E4=B8=AA?= =?UTF-8?q?=E8=B4=A1=E7=8C=AE=E8=80=85=EF=BC=8C=E6=84=9F=E8=B0=A2=E4=B8=BA?= =?UTF-8?q?=E6=97=A5=E5=BF=97=E6=89=93=E5=8D=B0=E6=96=B0=E5=A2=9E=E5=AF=B9?= =?UTF-8?q?=E5=BA=94=E6=97=B6=E9=97=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit https://github.com/Tencent/APIJSON/pull/250 --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 4ed8b8a29..49ec6ac0b 100644 --- a/README.md +++ b/README.md @@ -261,6 +261,7 @@ https://github.com/Tencent/APIJSON/issues/187 +
Date: Fri, 11 Jun 2021 16:31:48 +0800 Subject: [PATCH 0380/1181] =?UTF-8?q?=E8=B4=A1=E7=8C=AE=E8=80=85=E5=90=8D?= =?UTF-8?q?=E5=8D=95=E6=96=B0=E5=A2=9E=20jun0315=EF=BC=8C=E6=84=9F?= =?UTF-8?q?=E8=B0=A2=E4=B8=BA=E6=97=A5=E5=BF=97=E6=89=93=E5=8D=B0=E6=96=B0?= =?UTF-8?q?=E5=A2=9E=E5=AF=B9=E5=BA=94=E6=97=B6=E9=97=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit https://github.com/Tencent/APIJSON/pull/250 --- CONTRIBUTING.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 7613337a3..0c39db52c 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -29,6 +29,7 @@ - [kxlv2000](https://github.com/kxlv2000)(SUSTech) - [caohao-php](https://github.com/caohao-php)(腾讯工程师) - [Wscats](https://github.com/Wscats)(腾讯工程师) +- [jun0315](https://github.com/jun0315) #### 其中特别致谢:
From b39dda60aedc32c0d41fad81df0fa966dd59e0c9 Mon Sep 17 00:00:00 2001 From: iceewei Date: Mon, 14 Jun 2021 22:59:40 +0800 Subject: [PATCH 0381/1181] =?UTF-8?q?=E6=96=B0=E5=A2=9Edatasource=E5=85=B3?= =?UTF-8?q?=E9=94=AE=E5=AD=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/main/java/apijson/JSONObject.java | 27 +++++--- .../apijson/orm/AbstractObjectParser.java | 4 ++ .../main/java/apijson/orm/AbstractParser.java | 15 ++++- .../java/apijson/orm/AbstractSQLConfig.java | 51 ++++++++++++-- .../java/apijson/orm/AbstractVerifier.java | 67 ++++++++++++++++--- .../src/main/java/apijson/orm/Parser.java | 1 + .../src/main/java/apijson/orm/SQLConfig.java | 7 +- 7 files changed, 148 insertions(+), 24 deletions(-) diff --git a/APIJSONORM/src/main/java/apijson/JSONObject.java b/APIJSONORM/src/main/java/apijson/JSONObject.java index 026d63efb..925735bb9 100755 --- a/APIJSONORM/src/main/java/apijson/JSONObject.java +++ b/APIJSONORM/src/main/java/apijson/JSONObject.java @@ -137,9 +137,10 @@ public JSONObject setUserIdIn(List list) { public static final String KEY_ROLE = "@role"; //角色,拥有对某些数据的某些操作的权限 public static final String KEY_DATABASE = "@database"; //数据库类型,默认为MySQL + public static final String KEY_SCHEMA = "@schema"; //数据库,Table在非默认schema内时需要声明 + public static final String KEY_DATASOURCE = "@datasource"; //数据源 public static final String KEY_EXPLAIN = "@explain"; //分析 true/false public static final String KEY_CACHE = "@cache"; //缓存 RAM/ROM/ALL - public static final String KEY_SCHEMA = "@schema"; //数据库,Table在非默认schema内时需要声明 public static final String KEY_COLUMN = "@column"; //查询的Table字段或SQL函数 public static final String KEY_FROM = "@from"; //FROM语句 public static final String KEY_COMBINE = "@combine"; //条件组合,每个条件key前面可以放&,|,!逻辑关系 "id!{},&sex,!name&$" @@ -154,9 +155,10 @@ public JSONObject setUserIdIn(List list) { TABLE_KEY_LIST = new ArrayList(); TABLE_KEY_LIST.add(KEY_ROLE); TABLE_KEY_LIST.add(KEY_DATABASE); + TABLE_KEY_LIST.add(KEY_SCHEMA); + TABLE_KEY_LIST.add(KEY_DATASOURCE); TABLE_KEY_LIST.add(KEY_EXPLAIN); TABLE_KEY_LIST.add(KEY_CACHE); - TABLE_KEY_LIST.add(KEY_SCHEMA); TABLE_KEY_LIST.add(KEY_COLUMN); TABLE_KEY_LIST.add(KEY_FROM); TABLE_KEY_LIST.add(KEY_COMBINE); @@ -216,6 +218,20 @@ public JSONObject setRole(String role) { public JSONObject setDatabase(String database) { return puts(KEY_DATABASE, database); } + /**set schema where table was puts + * @param schema + * @return this + */ + public JSONObject setSchema(String schema) { + return puts(KEY_SCHEMA, schema); + } + /**set datasource where table was puts + * @param datasource + * @return this + */ + public JSONObject setDatasource(String datasource) { + return puts(KEY_DATASOURCE, datasource); + } /**set if return explain informations * @param explain * @return @@ -243,13 +259,6 @@ public JSONObject setCache(Integer cache) { public JSONObject setCache(String cache) { return puts(KEY_CACHE, cache); } - /**set schema where table was puts - * @param schema - * @return this - */ - public JSONObject setSchema(String schema) { - return puts(KEY_SCHEMA, schema); - } /**set keys need to be returned * @param keys key0, key1, key2 ... diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractObjectParser.java b/APIJSONORM/src/main/java/apijson/orm/AbstractObjectParser.java index f83c9e8bb..5244abc9a 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractObjectParser.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractObjectParser.java @@ -287,6 +287,10 @@ else if (method == PUT && value instanceof JSONArray if (parser.getGlobleSchema() != null && sqlRequest.get(JSONRequest.KEY_SCHEMA) == null) { sqlRequest.put(JSONRequest.KEY_SCHEMA, parser.getGlobleSchema()); } + if (parser.getGlobleDatasource() != null && sqlRequest.get(JSONRequest.KEY_DATASOURCE) == null) { + sqlRequest.put(JSONRequest.KEY_DATASOURCE, parser.getGlobleDatasource()); + } + if (isSubquery == false) { //解决 SQL 语法报错,子查询不能 EXPLAIN if (parser.getGlobleExplain() != null && sqlRequest.get(JSONRequest.KEY_EXPLAIN) == null) { sqlRequest.put(JSONRequest.KEY_EXPLAIN, parser.getGlobleExplain()); diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java b/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java index c46e82e4e..84bbca2b7 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java @@ -199,6 +199,16 @@ public AbstractParser setGlobleSchema(String globleSchema) { public String getGlobleSchema() { return globleSchema; } + protected String globleDatasource; + @Override + public String getGlobleDatasource() { + return globleDatasource; + } + public AbstractParser setGlobleDatasource(String globleDatasource) { + this.globleDatasource = globleDatasource; + return this; + } + protected Boolean globleExplain; public AbstractParser setGlobleExplain(Boolean globleExplain) { this.globleExplain = globleExplain; @@ -361,12 +371,14 @@ public JSONObject parseResponse(JSONObject request) { setGlobleFormat(requestObject.getBoolean(JSONRequest.KEY_FORMAT)); setGlobleDatabase(requestObject.getString(JSONRequest.KEY_DATABASE)); setGlobleSchema(requestObject.getString(JSONRequest.KEY_SCHEMA)); + setGlobleDatasource(requestObject.getString(JSONRequest.KEY_DATASOURCE)); setGlobleExplain(requestObject.getBoolean(JSONRequest.KEY_EXPLAIN)); setGlobleCache(requestObject.getString(JSONRequest.KEY_CACHE)); requestObject.remove(JSONRequest.KEY_FORMAT); requestObject.remove(JSONRequest.KEY_DATABASE); requestObject.remove(JSONRequest.KEY_SCHEMA); + requestObject.remove(JSONRequest.KEY_DATASOURCE); requestObject.remove(JSONRequest.KEY_EXPLAIN); requestObject.remove(JSONRequest.KEY_CACHE); } catch (Exception e) { @@ -1245,6 +1257,7 @@ else if (join != null){ JOIN_COPY_KEY_LIST.add(JSONRequest.KEY_ROLE); JOIN_COPY_KEY_LIST.add(JSONRequest.KEY_DATABASE); JOIN_COPY_KEY_LIST.add(JSONRequest.KEY_SCHEMA); + JOIN_COPY_KEY_LIST.add(JSONRequest.KEY_DATASOURCE); JOIN_COPY_KEY_LIST.add(JSONRequest.KEY_COLUMN); JOIN_COPY_KEY_LIST.add(JSONRequest.KEY_COMBINE); JOIN_COPY_KEY_LIST.add(JSONRequest.KEY_GROUP); @@ -1464,7 +1477,7 @@ public Object getValueByPath(String valuePath) { //取出key被valuePath包含的result,再从里面获取key对应的value JSONObject parent = null; String[] keys = null; - for (Map.Entry entry : queryResultMap.entrySet()){ + for (Entry entry : queryResultMap.entrySet()){ String path = entry.getKey(); if (valuePath.startsWith(path + "/")) { try { diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java index efedbe3e8..d963fea55 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java @@ -9,6 +9,7 @@ import static apijson.JSONObject.KEY_COLUMN; import static apijson.JSONObject.KEY_COMBINE; import static apijson.JSONObject.KEY_DATABASE; +import static apijson.JSONObject.KEY_DATASOURCE; import static apijson.JSONObject.KEY_EXPLAIN; import static apijson.JSONObject.KEY_FROM; import static apijson.JSONObject.KEY_GROUP; @@ -335,6 +336,7 @@ public String getUserIdKey() { private boolean distinct = false; private String database; //表所在的数据库类型 private String schema; //表所在的数据库名 + private String datasource; //数据源 private String table; //表名 private String alias; //表别名 private String group; //分组方式的字符串数组,','分隔 @@ -549,6 +551,17 @@ public AbstractSQLConfig setSchema(String schema) { this.schema = schema; return this; } + + @Override + public String getDatasource() { + return datasource; + } + @Override + public SQLConfig setDatasource(String datasource) { + this.datasource = datasource; + return this; + } + /**请求传进来的Table名 * @return * @see {@link #getSQLTable()} @@ -1547,7 +1560,7 @@ public Object getWhere(String key, boolean exactMatch) { synchronized (where) { if (where != null) { int index; - for (Map.Entry entry : where.entrySet()) { + for (Entry entry : where.entrySet()) { String k = entry.getKey(); index = k.indexOf(key); if (index >= 0 && StringUtil.isName(k.substring(index)) == false) { @@ -2502,7 +2515,7 @@ public String getSetString(RequestMethod method, Map content, bo Object value; String idKey = getIdKey(); - for (Map.Entry entry : content.entrySet()) { + for (Entry entry : content.entrySet()) { String key = entry.getKey(); //避免筛选到全部 value = key == null ? null : content.get(key); if (key == null || idKey.equals(key)) { @@ -2811,12 +2824,14 @@ public static SQLConfig newSQLConfig(RequestMethod method, String table, String } String schema = request.getString(KEY_SCHEMA); + String datasource = request.getString(KEY_DATASOURCE); SQLConfig config = callback.getSQLConfig(method, database, schema, table); config.setAlias(alias); config.setDatabase(database); //不删,后面表对象还要用的,必须放在 parseJoin 前 config.setSchema(schema); //不删,后面表对象还要用的 + config.setDatasource(datasource); //不删,后面表对象还要用的 if (isProcedure) { return config; @@ -3387,21 +3402,39 @@ public static interface IdCallback { */ Object newId(RequestMethod method, String database, String schema, String table); - /**获取主键名 + /**已废弃,最早 5.0.0 移除,改用 {@link #getIdKey(String, String, String, String)} * @param database * @param schema * @param table * @return */ + @Deprecated String getIdKey(String database, String schema, String table); + + /**获取主键名 + * @param database + * @param schema + * @param table + * @return + */ + String getIdKey(String database, String schema, String datasource, String table); - /**获取 User 的主键名 + /**已废弃,最早 5.0.0 移除,改用 {@link #getUserIdKey(String, String, String, String)} * @param database * @param schema * @param table * @return */ + @Deprecated String getUserIdKey(String database, String schema, String table); + + /**获取 User 的主键名 + * @param database + * @param schema + * @param table + * @return + */ + String getUserIdKey(String database, String schema, String datasource, String table); } public static interface Callback extends IdCallback { @@ -3434,11 +3467,21 @@ public Object newId(RequestMethod method, String database, String schema, String public String getIdKey(String database, String schema, String table) { return KEY_ID; } + + @Override + public String getIdKey(String database, String schema, String datasource, String table) { + return getIdKey(database, schema, table); + } @Override public String getUserIdKey(String database, String schema, String table) { return KEY_USER_ID; } + + @Override + public String getUserIdKey(String database, String schema, String datasource, String table) { + return getUserIdKey(database, schema, table); + } @Override public void onMissingKey4Combine(String name, JSONObject request, String combine, String item, String key) throws Exception { diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractVerifier.java b/APIJSONORM/src/main/java/apijson/orm/AbstractVerifier.java index 926a037f8..30aeb5f15 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractVerifier.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractVerifier.java @@ -109,8 +109,8 @@ public abstract class AbstractVerifier implements Verifier, IdCallback { OPERATION_KEY_LIST.add(REMOVE.name()); OPERATION_KEY_LIST.add(MUST.name()); OPERATION_KEY_LIST.add(REFUSE.name()); - - + + SYSTEM_ACCESS_MAP = new HashMap>(); SYSTEM_ACCESS_MAP.put(Access.class.getSimpleName(), getAccessMap(Access.class.getAnnotation(MethodAccess.class))); @@ -170,10 +170,18 @@ public String getIdKey(String database, String schema, String table) { return apijson.JSONObject.KEY_ID; } @Override + public String getIdKey(String database, String schema, String datasource, String table) { + return getIdKey(database, schema, table); + } + @Override public String getUserIdKey(String database, String schema, String table) { return apijson.JSONObject.KEY_USER_ID; } @Override + public String getUserIdKey(String database, String schema, String datasource, String table) { + return getUserIdKey(database, schema, table); + } + @Override public Object newId(RequestMethod method, String database, String schema, String table) { return System.currentTimeMillis(); } @@ -515,6 +523,23 @@ public static JSONObject verifyRequest(@NotNull final RequestMethod method, fina public static JSONObject verifyRequest(@NotNull final RequestMethod method, final String name , final JSONObject target, final JSONObject request, final int maxUpdateCount , final String database, final String schema, final IdCallback idCallback, final SQLCreator creator) throws Exception { + return verifyRequest(method, name, target, request, maxUpdateCount, database, schema, null, idCallback, creator); + } + /**从request提取target指定的内容 + * @param method + * @param name + * @param target + * @param request + * @param maxUpdateCount + * @param idKey + * @param userIdKey + * @param creator + * @return + * @throws Exception + */ + public static JSONObject verifyRequest(@NotNull final RequestMethod method, final String name + , final JSONObject target, final JSONObject request, final int maxUpdateCount + , final String database, final String schema, final String datasource, final IdCallback idCallback, final SQLCreator creator) throws Exception { Log.i(TAG, "verifyRequest method = " + method + "; name = " + name + "; target = \n" + JSON.toJSONString(target) @@ -546,14 +571,18 @@ public JSONObject onParseJSONObject(String key, JSONObject tobj, JSONObject robj } else if (apijson.JSONObject.isTableKey(key)) { String db = request.getString(apijson.JSONObject.KEY_DATABASE); String sh = request.getString(apijson.JSONObject.KEY_SCHEMA); + String ds = request.getString(apijson.JSONObject.KEY_DATASOURCE); if (StringUtil.isEmpty(db, false)) { db = database; } if (StringUtil.isEmpty(sh, false)) { sh = schema; } + if (StringUtil.isEmpty(ds, false)) { + ds = datasource; + } - String idKey = idCallback == null ? null : idCallback.getIdKey(db, sh, key); + String idKey = idCallback == null ? null : idCallback.getIdKey(db, sh, ds, key); String finalIdKey = StringUtil.isEmpty(idKey, false) ? apijson.JSONObject.KEY_ID : idKey; if (method == RequestMethod.POST) { @@ -564,7 +593,7 @@ public JSONObject onParseJSONObject(String key, JSONObject tobj, JSONObject robj if (RequestMethod.isQueryMethod(method) == false) { verifyId(method.name(), name, key, robj, finalIdKey, maxUpdateCount, true); - String userIdKey = idCallback == null ? null : idCallback.getUserIdKey(db, sh, key); + String userIdKey = idCallback == null ? null : idCallback.getUserIdKey(db, sh, ds, key); String finalUserIdKey = StringUtil.isEmpty(userIdKey, false) ? apijson.JSONObject.KEY_USER_ID : userIdKey; verifyId(method.name(), name, key, robj, finalUserIdKey, maxUpdateCount, false); } @@ -742,14 +771,14 @@ public static JSONObject parse(@NotNull final RequestMethod method, String name, , SQLCreator creator, @NotNull OnParseCallback callback) throws Exception { return parse(method, name, target, real, null, null, null, creator, callback); } - /**对request和response不同的解析用callback返回 * @param method * @param name * @param target * @param real - * @param idKey - * @param userIdKey + * @param database + * @param schema + * @param idCallback * @param creator * @param callback * @return @@ -757,6 +786,24 @@ public static JSONObject parse(@NotNull final RequestMethod method, String name, */ public static JSONObject parse(@NotNull final RequestMethod method, String name, JSONObject target, JSONObject real , final String database, final String schema, final IdCallback idCallback, SQLCreator creator, @NotNull OnParseCallback callback) throws Exception { + return parse(method, name, target, real, database, schema, null, idCallback, creator, callback); + } + /**对request和response不同的解析用callback返回 + * @param method + * @param name + * @param target + * @param real + * @param database + * @param schema + * @param datasource + * @param idCallback + * @param creator + * @param callback + * @return + * @throws Exception + */ + public static JSONObject parse(@NotNull final RequestMethod method, String name, JSONObject target, JSONObject real + , final String database, final String schema, final String datasource, final IdCallback idCallback, SQLCreator creator, @NotNull OnParseCallback callback) throws Exception { if (target == null) { return null; } @@ -913,12 +960,16 @@ public static JSONObject parse(@NotNull final RequestMethod method, String name, String db = real.getString(apijson.JSONObject.KEY_DATABASE); String sh = real.getString(apijson.JSONObject.KEY_SCHEMA); + String ds = real.getString(apijson.JSONObject.KEY_DATASOURCE); if (StringUtil.isEmpty(db, false)) { db = database; } if (StringUtil.isEmpty(sh, false)) { sh = schema; } + if (StringUtil.isEmpty(ds, false)) { + ds = datasource; + } String idKey = idCallback == null ? null : idCallback.getIdKey(db, sh, name); String finalIdKey = StringUtil.isEmpty(idKey, false) ? apijson.JSONObject.KEY_ID : idKey; @@ -977,7 +1028,7 @@ private static JSONObject operate(Operation opt, JSONObject targetChild, JSONObj if (tk == null || OPERATION_KEY_LIST.contains(tk)) { continue; } - + tv = e.getValue(); if (opt == TYPE) { diff --git a/APIJSONORM/src/main/java/apijson/orm/Parser.java b/APIJSONORM/src/main/java/apijson/orm/Parser.java index dee098b29..6e0af5368 100755 --- a/APIJSONORM/src/main/java/apijson/orm/Parser.java +++ b/APIJSONORM/src/main/java/apijson/orm/Parser.java @@ -124,6 +124,7 @@ JSONObject parseCorrectRequest(RequestMethod method, String tag, int version, St RequestRole getGlobleRole(); String getGlobleDatabase(); String getGlobleSchema(); + String getGlobleDatasource(); Boolean getGlobleExplain(); String getGlobleCache(); diff --git a/APIJSONORM/src/main/java/apijson/orm/SQLConfig.java b/APIJSONORM/src/main/java/apijson/orm/SQLConfig.java index 7e73bdd18..9abce83b9 100755 --- a/APIJSONORM/src/main/java/apijson/orm/SQLConfig.java +++ b/APIJSONORM/src/main/java/apijson/orm/SQLConfig.java @@ -120,10 +120,13 @@ public interface SQLConfig { String getDatabase(); SQLConfig setDatabase(String database); - String getQuote(); - String getSchema(); SQLConfig setSchema(String schema); + + String getDatasource(); + SQLConfig setDatasource(String datasource); + + String getQuote(); /**请求传进来的Table名 * @return From ccec4b836b25cdd646ac184775924379de0b0087 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Tue, 15 Jun 2021 00:03:02 +0800 Subject: [PATCH 0382/1181] =?UTF-8?q?=E5=88=86=E9=A1=B5=EF=BC=9A=E8=A7=A3?= =?UTF-8?q?=E5=86=B3=20query=3D2=20=E4=B8=8D=E5=85=BC=E5=AE=B9=20=E4=B8=BB?= =?UTF-8?q?=E8=A1=A8=20@column:"fun()"=20=E8=BF=99=E7=A7=8D=E5=8C=85?= =?UTF-8?q?=E5=90=AB=20SQL=20=E5=87=BD=E6=95=B0=E7=9A=84=E5=86=99=E6=B3=95?= =?UTF-8?q?=EF=BC=9BSQL=20=E5=87=BD=E6=95=B0=EF=BC=9A=E8=8E=B7=E5=8F=96?= =?UTF-8?q?=E5=8F=B3=E6=8B=AC=E5=8F=B7=20)=20=E7=9A=84=E4=BD=8D=E7=BD=AE?= =?UTF-8?q?=E4=BB=8E=20=20indexOf=20=E6=94=B9=E4=B8=BA=20lastIndexOf?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/apijson/orm/AbstractSQLConfig.java | 29 ++++++++++++++----- 1 file changed, 22 insertions(+), 7 deletions(-) diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java index d963fea55..758b49cbd 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java @@ -768,7 +768,7 @@ public String getHavingString(boolean hasPrefix) { continue; } - int end = expression.indexOf(")"); + int end = expression.lastIndexOf(")"); if (start >= end) { throw new IllegalArgumentException("字符 " + expression + " 不合法!" + "@having:value 中 value 里的 SQL函数必须为 function(arg0,arg1,...) 这种格式!"); @@ -1040,13 +1040,28 @@ public String getColumnString(boolean inSQLJoin) throws Exception { index = c.lastIndexOf(":"); //StringUtil.split返回数组中,子项不会有null origin = index < 0 ? c : c.substring(0, index); alias = index < 0 ? null : c.substring(index + 1); - if (StringUtil.isName(origin) == false || (alias != null && StringUtil.isName(alias) == false)) { - throw new IllegalArgumentException("HEAD请求: 预编译模式下 @column:value 中 value里面用 , 分割的每一项" + + if (alias != null && StringUtil.isName(alias) == false) { + throw new IllegalArgumentException("HEAD请求: 字符 " + alias + " 不合法!预编译模式下 @column:value 中 value里面用 , 分割的每一项" + " column:alias 中 column 必须是1个单词!如果有alias,则alias也必须为1个单词!并且不要有多余的空格!"); } + + if (StringUtil.isName(origin) == false) { + int start = origin.indexOf("("); + if (start < 0 || origin.lastIndexOf(")") <= start) { + throw new IllegalArgumentException("HEAD请求: 字符" + origin + " 不合法!预编译模式下 @column:value 中 value里面用 , 分割的每一项" + + " column:alias 中 column 必须是1个单词!如果有alias,则alias也必须为1个单词!并且不要有多余的空格!"); + } + + if (start > 0 && StringUtil.isName(origin.substring(0, start)) == false) { + throw new IllegalArgumentException("HEAD请求: 字符 " + origin.substring(0, start) + " 不合法!预编译模式下 @column:value 中 value里面用 , 分割的每一项" + + " column:alias 中 column 必须是1个单词!如果有alias,则alias也必须为1个单词!并且不要有多余的空格!"); + } + } } } - return SQL.count(column != null && column.size() == 1 ? getKey(Pair.parseEntry(column.get(0), true).getKey()) : "*"); + + return SQL.count(column != null && column.size() == 1 && StringUtil.isName(column.get(0)) ? getKey(column.get(0)) : "*"); case POST: if (column == null || column.isEmpty()) { throw new IllegalArgumentException("POST 请求必须在Table内设置要保存的 key:value !"); @@ -1151,7 +1166,7 @@ public String getColumnString(boolean inSQLJoin) throws Exception { int start = expression.indexOf("("); int end = 0; if (start >= 0) { - end = expression.indexOf(")"); + end = expression.lastIndexOf(")"); if (start >= end) { throw new IllegalArgumentException("字符 " + expression + " 不合法!" + "@column:value 中 value 里的 SQL函数必须为 function(arg0,arg1,...) 这种格式!"); @@ -2254,7 +2269,7 @@ else if (range instanceof String) {//非Number类型需要客户端拼接成 < ' if (rawSQL != null) { int index = rawSQL == null ? -1 : rawSQL.indexOf("("); - condition = (index >= 0 && index < rawSQL.indexOf(")") ? "" : getKey(k) + " ") + rawSQL; + condition = (index >= 0 && index < rawSQL.lastIndexOf(")") ? "" : getKey(k) + " ") + rawSQL; } // 还是只支持整段为 Raw SQL 比较好 @@ -2299,7 +2314,7 @@ else if (isPrepared() && (c.contains("--") || PATTERN_RANGE.matcher(c).matches() index = c == null ? -1 : c.indexOf("("); condition += ((i <= 0 ? "" : (logic.isAnd() ? AND : OR)) //连接方式 - + (index >= 0 && index < c.indexOf(")") ? "" : getKey(k) + " ") //函数和非函数条件 + + (index >= 0 && index < c.lastIndexOf(")") ? "" : getKey(k) + " ") //函数和非函数条件 + c); // 还是只支持整段为 Raw SQL 比较好 (appendRaw && index > 0 ? rawSQL : "") + c); //单个条件,如果有 Raw SQL 则按原来位置拼接 } } From db7916845292034950f846a4dec3a6022ae3ff40 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Tue, 15 Jun 2021 00:51:16 +0800 Subject: [PATCH 0383/1181] =?UTF-8?q?=E5=88=86=E9=A1=B5:=20=E5=9C=A8?= =?UTF-8?q?=E5=88=86=E9=A1=B5=E8=AF=A6=E6=83=85=20info=20=E5=86=85?= =?UTF-8?q?=E8=BF=94=E5=9B=9E=E6=9F=A5=E8=AF=A2=E8=AE=A1=E5=88=92=20@expla?= =?UTF-8?q?in?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../main/java/apijson/orm/AbstractParser.java | 5 + .../java/apijson/orm/AbstractSQLConfig.java | 11 +- .../java/apijson/orm/AbstractSQLExecutor.java | 173 +++++++++--------- 3 files changed, 100 insertions(+), 89 deletions(-) diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java b/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java index 84bbca2b7..7051b3e06 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java @@ -871,6 +871,10 @@ public JSONObject onObjectParse(final JSONObject request } JSONObject pagination = new JSONObject(true); + Object explain = rp.get(JSONResponse.KEY_EXPLAIN); + if (explain instanceof JSONObject) { + pagination.put(JSONResponse.KEY_EXPLAIN, explain); + } pagination.put(JSONResponse.KEY_TOTAL, total); pagination.put(JSONRequest.KEY_COUNT, count); pagination.put(JSONRequest.KEY_PAGE, page); @@ -878,6 +882,7 @@ public JSONObject onObjectParse(final JSONObject request pagination.put(JSONResponse.KEY_MORE, page < max); pagination.put(JSONResponse.KEY_FIRST, page == 0); pagination.put(JSONResponse.KEY_LAST, page == max); + putQueryResult(pathPrefix + JSONResponse.KEY_INFO, pagination); if (total <= count*page) { diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java index 758b49cbd..2eabffc62 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java @@ -2647,12 +2647,13 @@ public static String getSQL(AbstractSQLConfig config) throws Exception { config.setPreparedValueList(new ArrayList()); String column = config.getColumnString(); - if(config.isOracle()){ + if (config.isOracle()) { //When config's database is oracle,Using subquery since Oracle12 below does not support OFFSET FETCH paging syntax. - return explain + "SELECT * FROM (SELECT"+ (config.getCache() == JSONRequest.CACHE_RAM ? "SQL_NO_CACHE " : "") + column + " FROM "+getConditionString(column, tablePath, config)+ ") "+config.getLimitString(); - }else - return explain + "SELECT " + (config.getCache() == JSONRequest.CACHE_RAM ? "SQL_NO_CACHE " : "") + column + " FROM " + getConditionString(column, tablePath, config); + return explain + "SELECT * FROM (SELECT"+ (config.getCache() == JSONRequest.CACHE_RAM ? "SQL_NO_CACHE " : "") + column + " FROM " + getConditionString(column, tablePath, config) + ") " + config.getLimitString(); } + + return explain + "SELECT " + (config.getCache() == JSONRequest.CACHE_RAM ? "SQL_NO_CACHE " : "") + column + " FROM " + getConditionString(column, tablePath, config) + config.getLimitString(); + } } /**获取条件SQL字符串 @@ -2679,7 +2680,7 @@ private static String getConditionString(String column, String table, AbstractSQ //no need to optimize // if (config.getPage() <= 0 || ID.equals(column.trim())) { - return config.isOracle()? condition:condition + config.getLimitString(); + return condition; // config.isOracle() ? condition : condition + config.getLimitString(); // } // // diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java index 6008c4b13..15b178be4 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java @@ -148,21 +148,24 @@ public ResultSet execute(@NotNull Statement statement, String sql) throws Except */ @Override public JSONObject execute(@NotNull SQLConfig config, boolean unknowType) throws Exception { - boolean prepared = config.isPrepared(); + boolean isPrepared = config.isPrepared(); final String sql = config.getSQL(false); - config.setPrepared(prepared); + config.setPrepared(isPrepared); if (StringUtil.isEmpty(sql, true)) { Log.e(TAG, "execute StringUtil.isEmpty(sql, true) >> return null;"); return null; } + + boolean isExplain = config.isExplain(); + boolean isHead = RequestMethod.isHeadMethod(config.getMethod(), true); final int position = config.getPosition(); JSONObject result; - if (config.isExplain() == false) { + if (isExplain == false) { generatedSQLCount ++; } @@ -192,17 +195,6 @@ public JSONObject execute(@NotNull SQLConfig config, boolean unknowType) throws } else { switch (config.getMethod()) { - case HEAD: - case HEADS: - rs = executeQuery(config); - - executedSQLCount ++; - - result = rs.next() ? AbstractParser.newSuccessResult() - : AbstractParser.newErrorResult(new SQLException("数据库错误, rs.next() 失败!")); - result.put(JSONResponse.KEY_COUNT, rs.getLong(1)); - return result; - case POST: case PUT: case DELETE: @@ -224,10 +216,12 @@ public JSONObject execute(@NotNull SQLConfig config, boolean unknowType) throws result.put(config.getIdKey() + "[]", config.getWhere(config.getIdKey() + "{}", true)); } return result; - + case GET: case GETS: - result = getCacheItem(sql, position, config.getCache()); + case HEAD: + case HEADS: + result = isHead ? null : getCacheItem(sql, position, config.getCache()); Log.i(TAG, ">>> execute result = getCache('" + sql + "', " + position + ") = " + result); if (result != null) { cachedSQLCount ++; @@ -238,7 +232,7 @@ public JSONObject execute(@NotNull SQLConfig config, boolean unknowType) throws rs = executeQuery(config); //FIXME SQL Server 是一次返回两个结果集,包括查询结果和执行计划,需要 moreResults - if (config.isExplain() == false) { //只有 SELECT 才能 EXPLAIN + if (isExplain == false) { //只有 SELECT 才能 EXPLAIN executedSQLCount ++; } break; @@ -249,58 +243,68 @@ public JSONObject execute(@NotNull SQLConfig config, boolean unknowType) throws } } + + if (isExplain == false && isHead) { + if (rs.next() == false) { + return AbstractParser.newErrorResult(new SQLException("数据库错误, rs.next() 失败!")); + } + result = AbstractParser.newSuccessResult(); + result.put(JSONResponse.KEY_COUNT, rs.getLong(1)); + resultList = new ArrayList<>(1); + resultList.add(result); + } + else { + // final boolean cache = config.getCount() != 1; + // TODO 设置初始容量为查到的数据量,解决频繁扩容导致的延迟,貌似只有 rs.last 取 rs.getRow() ? 然后又得 rs.beforeFirst 重置位置以便下方取值 + resultList = new ArrayList<>(config.getCount() <= 0 ? Parser.MAX_QUERY_COUNT : config.getCount()); + // Log.d(TAG, "select cache = " + cache + "; resultList" + (resultList == null ? "=" : "!=") + "null"); - // final boolean cache = config.getCount() != 1; - // TODO 设置初始容量为查到的数据量,解决频繁扩容导致的延迟,貌似只有 rs.last 取 rs.getRow() ? 然后又得 rs.beforeFirst 重置位置以便下方取值 - resultList = new ArrayList<>(config.getCount() <= 0 ? Parser.MAX_QUERY_COUNT : config.getCount()); - // Log.d(TAG, "select cache = " + cache + "; resultList" + (resultList == null ? "=" : "!=") + "null"); - - int index = -1; + int index = -1; - ResultSetMetaData rsmd = rs.getMetaData(); - final int length = rsmd.getColumnCount(); + ResultSetMetaData rsmd = rs.getMetaData(); + final int length = rsmd.getColumnCount(); - // + childMap = new HashMap<>(); //要存到cacheMap + // WHERE id = ? AND ... 或 WHERE ... AND id = ? 强制排序 remove 再 put,还是重新 getSQL吧 - boolean hasJoin = config.hasJoin(); - int viceColumnStart = length + 1; //第一个副表字段的index - while (rs.next()) { - index ++; - Log.d(TAG, "\n\n<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<\n execute while (rs.next()){ index = " + index + "\n\n"); + boolean hasJoin = config.hasJoin(); + int viceColumnStart = length + 1; //第一个副表字段的index + while (rs.next()) { + index ++; + Log.d(TAG, "\n\n<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<\n execute while (rs.next()){ index = " + index + "\n\n"); - JSONObject item = new JSONObject(true); + JSONObject item = new JSONObject(true); - for (int i = 1; i <= length; i++) { + for (int i = 1; i <= length; i++) { - // if (hasJoin && viceColumnStart > length && config.getSQLTable().equalsIgnoreCase(rsmd.getTableName(i)) == false) { - // viceColumnStart = i; - // } + // if (hasJoin && viceColumnStart > length && config.getSQLTable().equalsIgnoreCase(rsmd.getTableName(i)) == false) { + // viceColumnStart = i; + // } - // bugfix-修复非常规数据库字段,获取表名失败导致输出异常 - if (config.isExplain() == false && hasJoin && viceColumnStart > length) { - List column = config.getColumn(); + // bugfix-修复非常规数据库字段,获取表名失败导致输出异常 + if (isExplain == false && hasJoin && viceColumnStart > length) { + List column = config.getColumn(); - if (column != null && column.isEmpty() == false) { - viceColumnStart = column.size() + 1; - } - else if (config.getSQLTable().equalsIgnoreCase(rsmd.getTableName(i)) == false) { - viceColumnStart = i; + if (column != null && column.isEmpty() == false) { + viceColumnStart = column.size() + 1; + } + else if (config.getSQLTable().equalsIgnoreCase(rsmd.getTableName(i)) == false) { + viceColumnStart = i; + } } - } - item = onPutColumn(config, rs, rsmd, index, item, i, config.isExplain() == false && hasJoin && i >= viceColumnStart ? childMap : null); - } + item = onPutColumn(config, rs, rsmd, index, item, i, isExplain == false && hasJoin && i >= viceColumnStart ? childMap : null); + } - resultList = onPutTable(config, rs, rsmd, resultList, index, item); + resultList = onPutTable(config, rs, rsmd, resultList, index, item); - Log.d(TAG, "\n execute while (rs.next()) { resultList.put( " + index + ", result); " - + "\n >>>>>>>>>>>>>>>>>>>>>>>>>>> \n\n"); + Log.d(TAG, "\n execute while (rs.next()) { resultList.put( " + index + ", result); " + + "\n >>>>>>>>>>>>>>>>>>>>>>>>>>> \n\n"); + } } - } finally { if (rs != null) { @@ -317,50 +321,51 @@ else if (config.getSQLTable().equalsIgnoreCase(rsmd.getTableName(i)) == false) { return null; } - if (unknowType || config.isExplain()) { - if (config.isExplain()) { + if (unknowType || isExplain) { + if (isExplain) { if (result == null) { result = new JSONObject(true); } - boolean explain = config.isExplain(); config.setExplain(false); result.put("sql", config.getSQL(false)); - config.setExplain(explain); - config.setPrepared(prepared); + config.setExplain(isExplain); + config.setPrepared(isPrepared); } result.put("list", resultList); return result; } + + if (isHead == false) { + // @ APP JOIN 查询副表并缓存到 childMap <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< - // @ APP JOIN 查询副表并缓存到 childMap <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< + executeAppJoin(config, resultList, childMap); - executeAppJoin(config, resultList, childMap); + // @ APP JOIN 查询副表并缓存到 childMap >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> - // @ APP JOIN 查询副表并缓存到 childMap >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> + //子查询 SELECT Moment.*, Comment.id 中的 Comment 内字段 + Set> set = childMap.entrySet(); - //子查询 SELECT Moment.*, Comment.id 中的 Comment 内字段 - Set> set = childMap.entrySet(); + // + for (Entry entry : set) { + List l = new ArrayList<>(); + l.add(entry.getValue()); + putCache(entry.getKey(), l, JSONRequest.CACHE_ROM); + } - // - for (Entry entry : set) { - List l = new ArrayList<>(); - l.add(entry.getValue()); - putCache(entry.getKey(), l, JSONRequest.CACHE_ROM); - } + putCache(sql, resultList, config.getCache()); + Log.i(TAG, ">>> execute putCache('" + sql + "', resultList); resultList.size() = " + resultList.size()); - putCache(sql, resultList, config.getCache()); - Log.i(TAG, ">>> execute putCache('" + sql + "', resultList); resultList.size() = " + resultList.size()); + // 数组主表对象额外一次返回全部,方便 Parser 缓存来提高性能 - // 数组主表对象额外一次返回全部,方便 Parser 缓存来提高性能 - - result = position >= resultList.size() ? new JSONObject() : resultList.get(position); - if (position == 0 && resultList.size() > 1 && result != null && result.isEmpty() == false) { - // 不是 main 不会直接执行,count=1 返回的不会超过 1 && config.isMain() && config.getCount() != 1 - Log.i(TAG, ">>> execute position == 0 && resultList.size() > 1 && result != null && result.isEmpty() == false" - + " >> result = new JSONObject(result); result.put(KEY_RAW_LIST, resultList);"); + result = position >= resultList.size() ? new JSONObject() : resultList.get(position); + if (position == 0 && resultList.size() > 1 && result != null && result.isEmpty() == false) { + // 不是 main 不会直接执行,count=1 返回的不会超过 1 && config.isMain() && config.getCount() != 1 + Log.i(TAG, ">>> execute position == 0 && resultList.size() > 1 && result != null && result.isEmpty() == false" + + " >> result = new JSONObject(result); result.put(KEY_RAW_LIST, resultList);"); - result = new JSONObject(result); - result.put(KEY_RAW_LIST, resultList); + result = new JSONObject(result); + result.put(KEY_RAW_LIST, resultList); + } } long endTime = System.currentTimeMillis(); @@ -396,7 +401,7 @@ protected void executeAppJoin(SQLConfig config, List resultList, Map } continue; } - + jc = j.getJoinConfig(); //取出 "id@": "@/User/userId" 中所有 userId 的值 @@ -550,7 +555,7 @@ protected JSONObject onPutColumn(@NotNull SQLConfig config, @NotNull ResultSet r } } } - + } Object value = getValue(config, rs, rsmd, tablePosition, table, columnIndex, lable, childMap); @@ -561,7 +566,7 @@ protected JSONObject onPutColumn(@NotNull SQLConfig config, @NotNull ResultSet r } finalTable.put(lable, value); } - + return table; } @@ -581,7 +586,7 @@ protected List onPutTable(@NotNull SQLConfig config, @NotNull Result return resultList; } - + protected String getKey(@NotNull SQLConfig config, @NotNull ResultSet rs, @NotNull ResultSetMetaData rsmd , final int tablePosition, @NotNull JSONObject table, final int columnIndex, Map childMap) throws Exception { From 550dfe9d26868102b13b1afa09e37a0e6cd6fa66 Mon Sep 17 00:00:00 2001 From: JackJay <471771548@qq.com> Date: Thu, 17 Jun 2021 18:50:31 +0800 Subject: [PATCH 0384/1181] =?UTF-8?q?fix:=20=E4=BF=AE=E6=94=B9=E5=88=86?= =?UTF-8?q?=E9=A1=B5=E6=9F=A5=E8=AF=A2query=3D2,=E4=B8=94@raw=E5=85=B3?= =?UTF-8?q?=E9=94=AE=E5=AD=97=E6=89=A7=E8=A1=8CSQL=E5=87=BD=E6=95=B0date?= =?UTF-8?q?=5Fformat=E7=9A=84Bug?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 当分页查询query=2,且@raw关键字执行SQL函数date_format时,格式化字符串(例:%Y-%m-%d %H:%i:%s)中含有冒号会导致解析出错 -在getColumnString方法中,Method为HEAD,HEADS循环拼接SQL时,排除RAW_MAP中包含的值,使SQL拼接成功,执行数量查询。 --- .../java/apijson/orm/AbstractSQLConfig.java | 6978 +++++++++-------- 1 file changed, 3549 insertions(+), 3429 deletions(-) diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java index 2eabffc62..755209951 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java @@ -71,3439 +71,3559 @@ import apijson.orm.model.Table; import apijson.orm.model.TestRecord; -/**config sql for JSON Request +/** + * config sql for JSON Request + * * @author Lemon */ public abstract class AbstractSQLConfig implements SQLConfig { - private static final String TAG = "AbstractSQLConfig"; - - public static String DEFAULT_DATABASE = DATABASE_MYSQL; - public static String DEFAULT_SCHEMA = "sys"; - public static String PREFFIX_DISTINCT = "DISTINCT "; - - // * 和 / 不能同时出现,防止 /* */ 段注释! # 和 -- 不能出现,防止行注释! ; 不能出现,防止隔断SQL语句!空格不能出现,防止 CRUD,DROP,SHOW TABLES等语句! - private static final Pattern PATTERN_RANGE; - private static final Pattern PATTERN_FUNCTION; - - /** - * 表名映射,隐藏真实表名,对安全要求很高的表可以这么做 - */ - public static final Map TABLE_KEY_MAP; - public static final List CONFIG_TABLE_LIST; - public static final List DATABASE_LIST; - // 自定义原始 SQL 片段 Map:当 substring 为 null 时忽略;当 substring 为 "" 时整个 value 是 raw SQL;其它情况则只是 substring 这段为 raw SQL - public static final Map RAW_MAP; - // 允许调用的 SQL 函数:当 substring 为 null 时忽略;当 substring 为 "" 时整个 value 是 raw SQL;其它情况则只是 substring 这段为 raw SQL - public static final Map SQL_FUNCTION_MAP; - static { // 凡是 SQL 边界符、分隔符、注释符 都不允许,例如 ' " ` ( ) ; # -- ,以免拼接 SQL 时被注入意外可执行指令 - PATTERN_RANGE = Pattern.compile("^[0-9%,!=\\<\\>/\\.\\+\\-\\*\\^]+$"); // ^[a-zA-Z0-9_*%!=<>(),"]+$ 导致 exists(select*from(Comment)) 通过! - PATTERN_FUNCTION = Pattern.compile("^[A-Za-z0-9%,:_@&~!=\\<\\>\\|\\[\\]\\{\\} /\\.\\+\\-\\*\\^\\?\\$]+$"); //TODO 改成更好的正则,校验前面为单词,中间为操作符,后面为值 - - - TABLE_KEY_MAP = new HashMap(); - TABLE_KEY_MAP.put(Table.class.getSimpleName(), Table.TABLE_NAME); - TABLE_KEY_MAP.put(Column.class.getSimpleName(), Column.TABLE_NAME); - TABLE_KEY_MAP.put(PgClass.class.getSimpleName(), PgClass.TABLE_NAME); - TABLE_KEY_MAP.put(PgAttribute.class.getSimpleName(), PgAttribute.TABLE_NAME); - TABLE_KEY_MAP.put(SysTable.class.getSimpleName(), SysTable.TABLE_NAME); - TABLE_KEY_MAP.put(SysColumn.class.getSimpleName(), SysColumn.TABLE_NAME); - TABLE_KEY_MAP.put(ExtendedProperty.class.getSimpleName(), ExtendedProperty.TABLE_NAME); - - CONFIG_TABLE_LIST = new ArrayList<>(); // Table, Column 等是系统表 AbstractVerifier.SYSTEM_ACCESS_MAP.keySet()); - CONFIG_TABLE_LIST.add(Function.class.getSimpleName()); - CONFIG_TABLE_LIST.add(Request.class.getSimpleName()); - CONFIG_TABLE_LIST.add(Response.class.getSimpleName()); - CONFIG_TABLE_LIST.add(Access.class.getSimpleName()); - CONFIG_TABLE_LIST.add(Document.class.getSimpleName()); - CONFIG_TABLE_LIST.add(TestRecord.class.getSimpleName()); - - - DATABASE_LIST = new ArrayList<>(); - DATABASE_LIST.add(DATABASE_MYSQL); - DATABASE_LIST.add(DATABASE_POSTGRESQL); - DATABASE_LIST.add(DATABASE_SQLSERVER); - DATABASE_LIST.add(DATABASE_ORACLE); - DATABASE_LIST.add(DATABASE_DB2); - - - RAW_MAP = new LinkedHashMap<>(); // 保证顺序,避免配置冲突等意外情况 - - - SQL_FUNCTION_MAP = new LinkedHashMap<>(); // 保证顺序,避免配置冲突等意外情况 - - // MySQL 字符串函数 - SQL_FUNCTION_MAP.put("ascii", ""); // ASCII(s) 返回字符串 s 的第一个字符的 ASCII 码。 - SQL_FUNCTION_MAP.put("char_length", ""); // CHAR_LENGTH(s) 返回字符串 s 的字符数 - SQL_FUNCTION_MAP.put("character_length", ""); // CHARACTER_LENGTH(s) 返回字符串 s 的字符数 - SQL_FUNCTION_MAP.put("concat", ""); // CONCAT(s1, s2...sn) 字符串 s1,s2 等多个字符串合并为一个字符串 - SQL_FUNCTION_MAP.put("concat_ws", ""); // CONCAT_WS(x, s1, s2...sn) 同 CONCAT(s1, s2 ...) 函数,但是每个字符串之间要加上 x,x 可以是分隔符 - SQL_FUNCTION_MAP.put("field", ""); // FIELD(s, s1, s2...) 返回第一个字符串 s 在字符串列表 (s1, s2...)中的位置 - SQL_FUNCTION_MAP.put("find_in_set", ""); // FIND_IN_SET(s1, s2) 返回在字符串s2中与s1匹配的字符串的位置 - SQL_FUNCTION_MAP.put("format", ""); // FORMAT(x, n) 函数可以将数字 x 进行格式化 "#,###.##", 将 x 保留到小数点后 n 位,最后一位四舍五入。 - SQL_FUNCTION_MAP.put("insert", ""); // INSERT(s1, x, len, s2) 字符串 s2 替换 s1 的 x 位置开始长度为 len 的字符串 - SQL_FUNCTION_MAP.put("locate", ""); // LOCATE(s1, s) 从字符串 s 中获取 s1 的开始位置 - SQL_FUNCTION_MAP.put("lcase", ""); // LCASE(s) 将字符串 s 的所有字母变成小写字母 - SQL_FUNCTION_MAP.put("left", ""); // LEFT(s, n) 返回字符串 s 的前 n 个字符 - SQL_FUNCTION_MAP.put("length", ""); // LENGTH(s) 返回字符串 s 的字符数 - SQL_FUNCTION_MAP.put("lower", ""); // LOWER(s) 将字符串 s 的所有字母变成小写字母 - SQL_FUNCTION_MAP.put("lpad", ""); // LPAD(s1, len, s2) 在字符串 s1 的开始处填充字符串 s2,使字符串长度达到 len - SQL_FUNCTION_MAP.put("ltrim", ""); // LTRIM(s) 去掉字符串 s 开始处的空格 - SQL_FUNCTION_MAP.put("mid", ""); // MID(s, n, len) 从字符串 s 的 n 位置截取长度为 len 的子字符串,同 SUBSTRING(s, n, len) - SQL_FUNCTION_MAP.put("position", ""); // POSITION(s, s1); 从字符串 s 中获取 s1 的开始位置 - SQL_FUNCTION_MAP.put("repeat", ""); // REPEAT(s, n) 将字符串 s 重复 n 次 - SQL_FUNCTION_MAP.put("replace", ""); // REPLACE(s, s1, s2) 将字符串 s2 替代字符串 s 中的字符串 s1 - SQL_FUNCTION_MAP.put("reverse", ""); // REVERSE(s); // ) 将字符串s的顺序反过来 - SQL_FUNCTION_MAP.put("right", ""); // RIGHT(s, n) 返回字符串 s 的后 n 个字符 - SQL_FUNCTION_MAP.put("rpad", ""); // RPAD(s1, len, s2) 在字符串 s1 的结尾处添加字符串 s2,使字符串的长度达到 len - SQL_FUNCTION_MAP.put("rtrim", ""); // RTRIM", ""); // ) 去掉字符串 s 结尾处的空格 - SQL_FUNCTION_MAP.put("space", ""); // SPACE(n) 返回 n 个空格 - SQL_FUNCTION_MAP.put("strcmp", ""); // STRCMP(s1, s2) 比较字符串 s1 和 s2,如果 s1 与 s2 相等返回 0 ,如果 s1>s2 返回 1,如果 s1d2 之间相隔的天数 - SQL_FUNCTION_MAP.put("date_add", ""); // DATE_ADD(d,INTERVAL expr type) 计算起始日期 d 加上一个时间段后的日期 - SQL_FUNCTION_MAP.put("date_format", ""); // DATE_FORMAT(d,f) 按表达式 f的要求显示日期 d - SQL_FUNCTION_MAP.put("date_sub", ""); // DATE_SUB(date,INTERVAL expr type) 函数从日期减去指定的时间间隔。 - SQL_FUNCTION_MAP.put("day", ""); // DAY(d) 返回日期值 d 的日期部分 - SQL_FUNCTION_MAP.put("dayname", ""); // DAYNAME(d) 返回日期 d 是星期几,如 Monday,Tuesday - SQL_FUNCTION_MAP.put("dayofmonth", ""); // DAYOFMONTH(d) 计算日期 d 是本月的第几天 - SQL_FUNCTION_MAP.put("dayofweek", ""); // DAYOFWEEK(d) 日期 d 今天是星期几,1 星期日,2 星期一,以此类推 - SQL_FUNCTION_MAP.put("dayofyear", ""); // DAYOFYEAR(d) 计算日期 d 是本年的第几天 - SQL_FUNCTION_MAP.put("extract", ""); // EXTRACT(type FROM d) 从日期 d 中获取指定的值,type 指定返回的值。 - SQL_FUNCTION_MAP.put("from_days", ""); // FROM_DAYS(n) 计算从 0000 年 1 月 1 日开始 n 天后的日期 - SQL_FUNCTION_MAP.put("hour", ""); // 'HOUR(t) 返回 t 中的小时值 - SQL_FUNCTION_MAP.put("last_day", ""); // LAST_DAY(d) 返回给给定日期的那一月份的最后一天 - SQL_FUNCTION_MAP.put("localtime", ""); // LOCALTIME() 返回当前日期和时间 - SQL_FUNCTION_MAP.put("localtimestamp", ""); // LOCALTIMESTAMP() 返回当前日期和时间 - SQL_FUNCTION_MAP.put("makedate", ""); // MAKEDATE(year, day-of-year) 基于给定参数年份 year 和所在年中的天数序号 day-of-year 返回一个日期 - SQL_FUNCTION_MAP.put("maketime", ""); // MAKETIME(hour, minute, second) 组合时间,参数分别为小时、分钟、秒 - SQL_FUNCTION_MAP.put("microsecond", ""); // MICROSECOND(date) 返回日期参数所对应的微秒数 - SQL_FUNCTION_MAP.put("minute", ""); // MINUTE(t) 返回 t 中的分钟值 - SQL_FUNCTION_MAP.put("monthname", ""); // MONTHNAME(d) 返回日期当中的月份名称,如 November - SQL_FUNCTION_MAP.put("month", ""); // MONTH(d) 返回日期d中的月份值,1 到 12 - SQL_FUNCTION_MAP.put("now", ""); // NOW() 返回当前日期和时间 - SQL_FUNCTION_MAP.put("period_add", ""); // PERIOD_ADD(period, number) 为 年-月 组合日期添加一个时段 - SQL_FUNCTION_MAP.put("period_diff", ""); // PERIOD_DIFF(period1, period2) 返回两个时段之间的月份差值 - SQL_FUNCTION_MAP.put("quarter", ""); // QUARTER(d) 返回日期d是第几季节,返回 1 到 4 - SQL_FUNCTION_MAP.put("second", ""); // SECOND(t) 返回 t 中的秒钟值 - SQL_FUNCTION_MAP.put("sec_to_time", ""); // SEC_TO_TIME", ""); // ) 将以秒为单位的时间 s 转换为时分秒的格式 - SQL_FUNCTION_MAP.put("str_to_date", ""); // STR_TO_DATE", ""); // tring, format_mask) 将字符串转变为日期 - SQL_FUNCTION_MAP.put("subdate", ""); // SUBDATE(d,n) 日期 d 减去 n 天后的日期 - SQL_FUNCTION_MAP.put("subtime", ""); // SUBTIME(t,n) 时间 t 减去 n 秒的时间 - SQL_FUNCTION_MAP.put("sysdate", ""); // SYSDATE() 返回当前日期和时间 - SQL_FUNCTION_MAP.put("time", ""); // TIME(expression) 提取传入表达式的时间部分 - SQL_FUNCTION_MAP.put("time_format", ""); // TIME_FORMAT(t,f) 按表达式 f 的要求显示时间 t - SQL_FUNCTION_MAP.put("time_to_sec", ""); // TIME_TO_SEC(t) 将时间 t 转换为秒 - SQL_FUNCTION_MAP.put("timediff", ""); // TIMEDIFF(time1, time2) 计算时间差值 - SQL_FUNCTION_MAP.put("timestamp", ""); // TIMESTAMP(expression, interval) 单个参数时,函数返回日期或日期时间表达式;有2个参数时,将参数加和 - SQL_FUNCTION_MAP.put("to_days", ""); // TO_DAYS(d) 计算日期 d 距离 0000 年 1 月 1 日的天数 - SQL_FUNCTION_MAP.put("week", ""); // WEEK(d) 计算日期 d 是本年的第几个星期,范围是 0 到 53 - SQL_FUNCTION_MAP.put("weekday", ""); // WEEKDAY(d) 日期 d 是星期几,0 表示星期一,1 表示星期二 - SQL_FUNCTION_MAP.put("weekofyear", ""); // WEEKOFYEAR(d) 计算日期 d 是本年的第几个星期,范围是 0 到 53 - SQL_FUNCTION_MAP.put("year", ""); // YEAR(d) 返回年份 - SQL_FUNCTION_MAP.put("yearweek", ""); // YEARWEEK(date, mode) 返回年份及第几周(0到53),mode 中 0 表示周天,1表示周一,以此类推 - SQL_FUNCTION_MAP.put("unix_timestamp", ""); // UNIX_TIMESTAMP(date) 获取UNIX时间戳函数,返回一个以 UNIX 时间戳为基础的无符号整数 - SQL_FUNCTION_MAP.put("from_unixtime", ""); // FROM_UNIXTIME(date) 将 UNIX 时间戳转换为时间格式,与UNIX_TIMESTAMP互为反函数 - - // MYSQL JSON 函数 - SQL_FUNCTION_MAP.put("json_append", ""); // JSON_APPEND(json_doc, path, val[, path, val] ...)) 插入JSON数组 - SQL_FUNCTION_MAP.put("json_array", ""); // JSON_ARRAY(val1, val2...) 创建JSON数组 - SQL_FUNCTION_MAP.put("json_array_append", ""); // JSON_ARRAY_APPEND(json_doc, val) 将数据附加到JSON文档 - SQL_FUNCTION_MAP.put("json_array_insert", ""); // JSON_ARRAY_INSERT(json_doc, val) 插入JSON数组 - SQL_FUNCTION_MAP.put("json_contains", ""); // JSON_CONTAINS(json_doc, val) JSON文档是否在路径中包含特定对象 - SQL_FUNCTION_MAP.put("json_contains_path", ""); // JSON_CONTAINS_PATH(json_doc, path) JSON文档是否在路径中包含任何数据 - SQL_FUNCTION_MAP.put("json_depth", ""); // JSON_DEPTH(json_doc) JSON文档的最大深度 - SQL_FUNCTION_MAP.put("json_extract", ""); // JSON_EXTRACT(json_doc, path) 从JSON文档返回数据 - SQL_FUNCTION_MAP.put("json_insert", ""); // JSON_INSERT(json_doc, val) 将数据插入JSON文档 - SQL_FUNCTION_MAP.put("json_keys", ""); // JSON_KEYS(json_doc[, path]) JSON文档中的键数组 - SQL_FUNCTION_MAP.put("json_length", ""); // JSON_LENGTH(json_doc) JSON文档中的元素数 - SQL_FUNCTION_MAP.put("json_merge", ""); // JSON_MERGE(json_doc1, json_doc2) (已弃用) 合并JSON文档,保留重复的键。JSON_MERGE_PRESERVE()的已弃用同义词 - SQL_FUNCTION_MAP.put("json_merge_patch", ""); // JSON_MERGE_PATCH(json_doc1, json_doc2) 合并JSON文档,替换重复键的值 - SQL_FUNCTION_MAP.put("json_merge_preserve", ""); // JSON_MERGE_PRESERVE(json_doc1, json_doc2) 合并JSON文档,保留重复的键 - SQL_FUNCTION_MAP.put("json_object", ""); // JSON_OBJECT(key1, val1, key2, val2...) 创建JSON对象 - SQL_FUNCTION_MAP.put("json_overlaps", ""); // JSON_OVERLAPS(json_doc1, json_doc2) (引入8.0.17) 比较两个JSON文档,如果它们具有相同的键值对或数组元素,则返回TRUE(1),否则返回FALSE(0) - SQL_FUNCTION_MAP.put("json_pretty", ""); // JSON_PRETTY(json_doc) 以易于阅读的格式打印JSON文档 - SQL_FUNCTION_MAP.put("json_quote", ""); // JSON_QUOTE(json_doc1) 引用JSON文档 - SQL_FUNCTION_MAP.put("json_remove", ""); // JSON_REMOVE(json_doc1, path) 从JSON文档中删除数据 - SQL_FUNCTION_MAP.put("json_replace", ""); // JSON_REPLACE(json_doc1, val1, val2) 替换JSON文档中的值 - SQL_FUNCTION_MAP.put("json_schema_valid", ""); // JSON_SCHEMA_VALID(json_doc) (引入8.0.17) 根据JSON模式验证JSON文档;如果文档针对架构进行验证,则返回TRUE / 1;否则,则返回FALSE / 0 - SQL_FUNCTION_MAP.put("json_schema_validation_report", ""); // JSON_SCHEMA_VALIDATION_REPORT(json_doc, mode) (引入8.0.17) 根据JSON模式验证JSON文档;以JSON格式返回有关验证结果的报告,包括成功或失败以及失败原因 - SQL_FUNCTION_MAP.put("json_search", ""); // JSON_SEARCH(json_doc, val) JSON文档中值的路径 - SQL_FUNCTION_MAP.put("json_set", ""); // JSON_SET(json_doc, val) 将数据插入JSON文档 - // SQL_FUNCTION_MAP.put("json_storage_free", ""); // JSON_STORAGE_FREE() 部分更新后,JSON列值的二进制表示形式中的可用空间 - // SQL_FUNCTION_MAP.put("json_storage_size", ""); // JSON_STORAGE_SIZE() 用于存储JSON文档的二进制表示的空间 - SQL_FUNCTION_MAP.put("json_table", ""); // JSON_TABLE() 从JSON表达式返回数据作为关系表 - SQL_FUNCTION_MAP.put("json_type", ""); // JSON_TYPE(json_doc) JSON值类型 - SQL_FUNCTION_MAP.put("json_unquote", ""); // JSON_UNQUOTE(json_doc) 取消引用JSON值 - SQL_FUNCTION_MAP.put("json_valid", ""); // JSON_VALID(json_doc) JSON值是否有效 - SQL_FUNCTION_MAP.put("json_arrayagg", ""); // JSON_ARRAYAGG(key) 将每个表达式转换为 JSON 值,然后返回一个包含这些 JSON 值的 JSON 数组 - SQL_FUNCTION_MAP.put("json_objectagg", ""); // JSON_OBJECTAGG(key, val)) 将每个表达式转换为 JSON 值,然后返回一个包含这些 JSON 值的 JSON 对象 - - // MySQL 高级函数 - // SQL_FUNCTION_MAP.put("bin", ""); // BIN(x) 返回 x 的二进制编码 - // SQL_FUNCTION_MAP.put("binary", ""); // BINARY(s) 将字符串 s 转换为二进制字符串 - SQL_FUNCTION_MAP.put("case", ""); // CASE 表示函数开始,END 表示函数结束。如果 condition1 成立,则返回 result1, 如果 condition2 成立,则返回 result2,当全部不成立则返回 result,而当有一个成立之后,后面的就不执行了。 - SQL_FUNCTION_MAP.put("cast", ""); // CAST(x AS type) 转换数据类型 - SQL_FUNCTION_MAP.put("coalesce", ""); // COALESCE(expr1, expr2, ...., expr_n) 返回参数中的第一个非空表达式(从左向右) - // SQL_FUNCTION_MAP.put("conv", ""); // CONV(x,f1,f2) 返回 f1 进制数变成 f2 进制数 - // SQL_FUNCTION_MAP.put("convert", ""); // CONVERT(s, cs) 函数将字符串 s 的字符集变成 cs - SQL_FUNCTION_MAP.put("if", ""); // IF(expr,v1,v2) 如果表达式 expr 成立,返回结果 v1;否则,返回结果 v2。 - SQL_FUNCTION_MAP.put("ifnull", ""); // IFNULL(v1,v2) 如果 v1 的值不为 NULL,则返回 v1,否则返回 v2。 - SQL_FUNCTION_MAP.put("isnull", ""); // ISNULL(expression) 判断表达式是否为 NULL - SQL_FUNCTION_MAP.put("nullif", ""); // NULLIF(expr1, expr2) 比较两个字符串,如果字符串 expr1 与 expr2 相等 返回 NULL,否则返回 expr1 - SQL_FUNCTION_MAP.put("group_concat", ""); // GROUP_CONCAT([DISTINCT], s1, s2...) - - } - - - @Override - public boolean limitSQLCount() { - return Log.DEBUG == false || AbstractVerifier.SYSTEM_ACCESS_MAP.containsKey(getTable()) == false; - } - - @NotNull - @Override - public String getIdKey() { - return KEY_ID; - } - @NotNull - @Override - public String getUserIdKey() { - return KEY_USER_ID; - } - - - private Object id; //Table的id - private RequestMethod method; //操作方法 - private boolean prepared = true; //预编译 - private boolean main = true; - /** - * TODO 被关联的表通过就忽略关联的表?(这个不行 User:{"sex@":"/Comment/toId"}) - */ - private RequestRole role; //发送请求的用户的角色 - private boolean distinct = false; - private String database; //表所在的数据库类型 - private String schema; //表所在的数据库名 - private String datasource; //数据源 - private String table; //表名 - private String alias; //表别名 - private String group; //分组方式的字符串数组,','分隔 - private String having; //聚合函数的字符串数组,','分隔 - private String order; //排序方式的字符串数组,','分隔 - private List raw; //需要保留原始 SQL 的字段,','分隔 - private List json; //需要转为 JSON 的字段,','分隔 - private Subquery from; //子查询临时表 - private List column; //表内字段名(或函数名,仅查询操作可用)的字符串数组,','分隔 - private List> values; //对应表内字段的值的字符串数组,','分隔 - private Map content; //Request内容,key:value形式,column = content.keySet(),values = content.values() - private Map where; //筛选条件,key:value形式 - private Map> combine; //条件组合,{ "&":[key], "|":[key], "!":[key] } - - - //array item <<<<<<<<<< - private int count; //Table数量 - private int page; //Table所在页码 - private int position; //Table在[]中的位置 - private int query; //JSONRequest.query - private int type; //ObjectParser.type - private int cache; - private boolean explain; - - private List joinList; //连表 配置列表 - //array item >>>>>>>>>> - private boolean test; //测试 - - private String procedure; - - public SQLConfig setProcedure(String procedure) { - this.procedure = procedure; - return this; - } - public String getProcedure() { - return procedure; - } - - public AbstractSQLConfig(RequestMethod method) { - setMethod(method); - } - public AbstractSQLConfig(RequestMethod method, String table) { - this(method); - setTable(table); - } - public AbstractSQLConfig(RequestMethod method, int count, int page) { - this(method); - setCount(count); - setPage(page); - } - - @NotNull - @Override - public RequestMethod getMethod() { - if (method == null) { - method = GET; - } - return method; - } - @Override - public AbstractSQLConfig setMethod(RequestMethod method) { - this.method = method; - return this; - } - @Override - public boolean isPrepared() { - return prepared; - } - @Override - public AbstractSQLConfig setPrepared(boolean prepared) { - this.prepared = prepared; - return this; - } - @Override - public boolean isMain() { - return main; - } - @Override - public AbstractSQLConfig setMain(boolean main) { - this.main = main; - return this; - } - - - @Override - public Object getId() { - return id; - } - @Override - public AbstractSQLConfig setId(Object id) { - this.id = id; - return this; - } - - @Override - public RequestRole getRole() { - //不能 @NotNull , AbstractParser#getSQLObject 内当getRole() == null时填充默认值 - return role; - } - public AbstractSQLConfig setRole(String roleName) throws Exception { - return setRole(RequestRole.get(roleName)); - } - @Override - public AbstractSQLConfig setRole(RequestRole role) { - this.role = role; - return this; - } - - @Override - public boolean isDistinct() { - return distinct; - } - @Override - public SQLConfig setDistinct(boolean distinct) { - this.distinct = distinct; - return this; - } - - @Override - public String getDatabase() { - return database; - } - @Override - public SQLConfig setDatabase(String database) { - this.database = database; - return this; - } - /** - * @return db == null ? DEFAULT_DATABASE : db - */ - @NotNull - public String getSQLDatabase() { - String db = getDatabase(); - return db == null ? DEFAULT_DATABASE : db; // "" 表示已设置,不需要用全局默认的 StringUtil.isEmpty(db, false)) { - } - - @Override - public boolean isMySQL() { - return isMySQL(getSQLDatabase()); - } - public static boolean isMySQL(String db) { - return DATABASE_MYSQL.equals(db); - } - @Override - public boolean isPostgreSQL() { - return isPostgreSQL(getSQLDatabase()); - } - public static boolean isPostgreSQL(String db) { - return DATABASE_POSTGRESQL.equals(db); - } - @Override - public boolean isSQLServer() { - return isSQLServer(getSQLDatabase()); - } - public static boolean isSQLServer(String db) { - return DATABASE_SQLSERVER.equals(db); - } - @Override - public boolean isOracle() { - return isOracle(getSQLDatabase()); - } - public static boolean isOracle(String db) { - return DATABASE_ORACLE.equals(db); - } - @Override - public boolean isDb2() { - return isDb2(getSQLDatabase()); - } - public static boolean isDb2(String db) { - return DATABASE_DB2.equals(db); - } - - @Override - public String getQuote() { - return isMySQL() ? "`" : "\""; - } - - @Override - public String getSchema() { - return schema; - } - /** - * @param sqlTable - * @return - */ - @NotNull - public String getSQLSchema() { - String table = getTable(); - //强制,避免因为全局默认的 @schema 自动填充进来,导致这几个类的 schema 为 sys 等其它值 - if (Table.TAG.equals(table) || Column.TAG.equals(table)) { - return SCHEMA_INFORMATION; //MySQL, PostgreSQL, SQL Server 都有的 - } - if (PgClass.TAG.equals(table) || PgAttribute.TAG.equals(table)) { - return ""; //PostgreSQL 的 pg_class 和 pg_attribute 表好像不属于任何 Schema - } - if (SysTable.TAG.equals(table) || SysColumn.TAG.equals(table) || ExtendedProperty.TAG.equals(table)) { - return SCHEMA_SYS; //SQL Server 在 sys 中的属性比 information_schema 中的要全,能拿到注释 - } - - String sch = getSchema(); - return sch == null ? DEFAULT_SCHEMA : sch; - } - @Override - public AbstractSQLConfig setSchema(String schema) { - if (schema != null) { - String quote = getQuote(); - String s = schema.startsWith(quote) && schema.endsWith(quote) ? schema.substring(1, schema.length() - 1) : schema; - if (StringUtil.isEmpty(s, true) == false && StringUtil.isName(s) == false) { - throw new IllegalArgumentException("@schema:value 中value必须是1个单词!"); - } - } - this.schema = schema; - return this; - } - - @Override - public String getDatasource() { - return datasource; - } - @Override - public SQLConfig setDatasource(String datasource) { - this.datasource = datasource; - return this; - } - - /**请求传进来的Table名 - * @return - * @see {@link #getSQLTable()} - */ - @Override - public String getTable() { - return table; - } - /**数据库里的真实Table名 - * 通过 {@link #TABLE_KEY_MAP} 映射 - * @return - */ - @JSONField(serialize = false) - @Override - public String getSQLTable() { - // String t = TABLE_KEY_MAP.containsKey(table) ? TABLE_KEY_MAP.get(table) : table; - //如果要强制小写,则可在子类重写这个方法再 toLowerCase return DATABASE_POSTGRESQL.equals(getDatabase()) ? t.toLowerCase() : t; - return TABLE_KEY_MAP.containsKey(table) ? TABLE_KEY_MAP.get(table) : table; - } - @JSONField(serialize = false) - @Override - public String getTablePath() { - String q = getQuote(); - - String sch = getSQLSchema(); - String sqlTable = getSQLTable(); - - return (StringUtil.isEmpty(sch, true) ? "" : q + sch + q + ".") + q + sqlTable + q + ( isKeyPrefix() ? " AS " + getAliasWithQuote() : ""); - } - @Override - public AbstractSQLConfig setTable(String table) { //Table已经在Parser中校验,所以这里不用防SQL注入 - this.table = table; - return this; - } - - @Override - public String getAlias() { - return alias; - } - @Override - public AbstractSQLConfig setAlias(String alias) { - this.alias = alias; - return this; - } - public String getAliasWithQuote() { - String a = getAlias(); - if (StringUtil.isEmpty(a, true)) { - a = getTable(); - } - String q = getQuote(); - //getTable 不能小写,因为Verifier用大小写敏感的名称判断权限 - //如果要强制小写,则可在子类重写这个方法再 toLowerCase return q + (DATABASE_POSTGRESQL.equals(getDatabase()) ? a.toLowerCase() : a) + q; - return q + a + q; - } - - @Override - public String getGroup() { - return group; - } - public AbstractSQLConfig setGroup(String... keys) { - return setGroup(StringUtil.getString(keys)); - } - @Override - public AbstractSQLConfig setGroup(String group) { - this.group = group; - return this; - } - @JSONField(serialize = false) - public String getGroupString(boolean hasPrefix) { - //加上子表的 group - String joinGroup = ""; - if (joinList != null) { - SQLConfig cfg; - String c; - boolean first = true; - for (Join j : joinList) { - if (j.isAppJoin()) { - continue; - } - - cfg = j.isLeftOrRightJoin() ? j.getOuterConfig() : j.getJoinConfig(); - if (StringUtil.isEmpty(cfg.getAlias(), true)) { - cfg.setAlias(cfg.getTable()); - } - - c = ((AbstractSQLConfig) cfg).getGroupString(false); - if (StringUtil.isEmpty(c, true) == false) { - joinGroup += (first ? "" : ", ") + c; - first = false; - } - - } - } - - - group = StringUtil.getTrimedString(group); - String[] keys = StringUtil.split(group); - if (keys == null || keys.length <= 0) { - return StringUtil.isEmpty(joinGroup, true) ? "" : (hasPrefix ? " GROUP BY " : "") + joinGroup; - } - - for (int i = 0; i < keys.length; i++) { - if (isPrepared()) { //不能通过 ? 来代替,因为SQLExecutor statement.setString后 GROUP BY 'userId' 有单引号,只能返回一条数据,必须去掉单引号才行! - if (StringUtil.isName(keys[i]) == false) { - throw new IllegalArgumentException("@group:value 中 value里面用 , 分割的每一项都必须是1个单词!并且不要有空格!"); - } - } - - keys[i] = getKey(keys[i]); - } - - return (hasPrefix ? " GROUP BY " : "") + StringUtil.concat(StringUtil.getString(keys), joinGroup, ", "); - } - - @Override - public String getHaving() { - return having; - } - public AbstractSQLConfig setHaving(String... conditions) { - return setHaving(StringUtil.getString(conditions)); - } - @Override - public AbstractSQLConfig setHaving(String having) { - this.having = having; - return this; - } - /**TODO @having 改为默认 | 或连接,且支持 @having: { "key1>": 1, "key{}": "length(key2)>0", "@combine": "key1,key2" } - * @return HAVING conditoin0 AND condition1 OR condition2 ... - */ - @JSONField(serialize = false) - public String getHavingString(boolean hasPrefix) { - //加上子表的 having - String joinHaving = ""; - if (joinList != null) { - SQLConfig cfg; - String c; - boolean first = true; - for (Join j : joinList) { - if (j.isAppJoin()) { - continue; - } - - cfg = j.isLeftOrRightJoin() ? j.getOuterConfig() : j.getJoinConfig(); - if (StringUtil.isEmpty(cfg.getAlias(), true)) { - cfg.setAlias(cfg.getTable()); - } - - c = ((AbstractSQLConfig) cfg).getHavingString(false); - if (StringUtil.isEmpty(c, true) == false) { - joinHaving += (first ? "" : ", ") + c; - first = false; - } - - } - } - - String[] keys = StringUtil.split(getHaving(), ";"); - if (keys == null || keys.length <= 0) { - return StringUtil.isEmpty(joinHaving, true) ? "" : (hasPrefix ? " HAVING " : "") + joinHaving; - } - - String quote = getQuote(); - String tableAlias = getAliasWithQuote(); - - List raw = getRaw(); - boolean containRaw = raw != null && raw.contains(KEY_HAVING); - - String expression; - String method; - //暂时不允许 String prefix; - String suffix; - - //fun0(arg0,arg1,...);fun1(arg0,arg1,...) - for (int i = 0; i < keys.length; i++) { - - //fun(arg0,arg1,...) - expression = keys[i]; - if (containRaw) { - try { - String rawSQL = getRawSQL(KEY_HAVING, expression); - if (rawSQL != null) { - keys[i] = rawSQL; - continue; - } - } catch (Exception e) { - Log.e(TAG, "newSQLConfig rawColumnSQL == null >> try { " - + " String rawSQL = ((AbstractSQLConfig) config).getRawSQL(KEY_COLUMN, fk); ... " - + "} catch (Exception e) = " + e.getMessage()); - } - } - - if (expression.length() > 50) { - throw new UnsupportedOperationException("@having:value 的 value 中字符串 " + expression + " 不合法!" - + "不允许传超过 50 个字符的函数或表达式!请用 @raw 简化传参!"); - } - - int start = expression.indexOf("("); - if (start < 0) { - if (isPrepared() && PATTERN_FUNCTION.matcher(expression).matches() == false) { - throw new UnsupportedOperationException("字符串 " + expression + " 不合法!" - + "预编译模式下 @having:\"column?value;function(arg0,arg1,...)?value...\"" - + " 中 column?value 必须符合正则表达式 " + PATTERN_FUNCTION + " 且不包含连续减号 -- !不允许空格!"); - } - continue; - } - - int end = expression.lastIndexOf(")"); - if (start >= end) { - throw new IllegalArgumentException("字符 " + expression + " 不合法!" - + "@having:value 中 value 里的 SQL函数必须为 function(arg0,arg1,...) 这种格式!"); - } - - method = expression.substring(0, start); - if (method.isEmpty() == false) { - if (SQL_FUNCTION_MAP == null || SQL_FUNCTION_MAP.isEmpty()) { - if (StringUtil.isName(method) == false) { - throw new IllegalArgumentException("字符 " + method + " 不合法!" - + "预编译模式下 @having:\"column?value;function(arg0,arg1,...)?value...\"" - + " 中 function 必须符合小写英文单词的 SQL 函数名格式!"); - } - } - else if (SQL_FUNCTION_MAP.containsKey(method) == false) { - throw new IllegalArgumentException("字符 " + method + " 不合法!" - + "预编译模式下 @column:\"column0,column1:alias;function0(arg0,arg1,...);function1(...):alias...\"" - + " 中 function 必须符合小写英文单词的 SQL 函数名格式!且必须是后端允许调用的 SQL 函数!"); - } - } - - suffix = expression.substring(end + 1, expression.length()); - - if (isPrepared() && (((String) suffix).contains("--") || ((String) suffix).contains("/*") || PATTERN_RANGE.matcher((String) suffix).matches() == false)) { - throw new UnsupportedOperationException("字符串 " + suffix + " 不合法!" - + "预编译模式下 @having:\"column?value;function(arg0,arg1,...)?value...\"" - + " 中 ?value 必须符合正则表达式 " + PATTERN_RANGE + " 且不包含连续减号 -- 或注释符 /* !不允许多余的空格!"); - } - - String[] ckeys = StringUtil.split(expression.substring(start + 1, end)); - - if (ckeys != null) { - for (int j = 0; j < ckeys.length; j++) { - String origin = ckeys[j]; - - if (isPrepared()) { - if (origin.startsWith("_") || origin.contains("--") || PATTERN_FUNCTION.matcher(origin).matches() == false) { - throw new IllegalArgumentException("字符 " + ckeys[j] + " 不合法!" - + "预编译模式下 @having:\"column?value;function(arg0,arg1,...)?value...\"" - + " 中所有 column, arg 都必须是1个不以 _ 开头的单词 或者 符合正则表达式 " + PATTERN_FUNCTION + " 且不包含连续减号 -- !不允许多余的空格!"); - } - } - - //JOIN 副表不再在外层加副表名前缀 userId AS `Commet.userId`, 而是直接 userId AS `userId` - boolean isName = false; - if (StringUtil.isNumer(origin)) { - //do nothing - } - else if (StringUtil.isName(origin)) { - origin = quote + origin + quote; - isName = true; - } - else { - origin = getValue(origin).toString(); - } - - ckeys[j] = (isName && isKeyPrefix() ? tableAlias + "." : "") + origin; - } - } - - keys[i] = method + "(" + StringUtil.getString(ckeys) + ")" + suffix; - } - - //TODO 支持 OR, NOT 参考 @combine:"&key0,|key1,!key2" - return (hasPrefix ? " HAVING " : "") + StringUtil.concat(StringUtil.getString(keys, AND), joinHaving, AND); - } - - @Override - public String getOrder() { - return order; - } - public AbstractSQLConfig setOrder(String... conditions) { - return setOrder(StringUtil.getString(conditions)); - } - @Override - public AbstractSQLConfig setOrder(String order) { - this.order = order; - return this; - } - @JSONField(serialize = false) - public String getOrderString(boolean hasPrefix) { - //加上子表的 order - String joinOrder = ""; - if (joinList != null) { - SQLConfig cfg; - String c; - boolean first = true; - for (Join j : joinList) { - if (j.isAppJoin()) { - continue; - } - - cfg = j.isLeftOrRightJoin() ? j.getOuterConfig() : j.getJoinConfig(); - if (StringUtil.isEmpty(cfg.getAlias(), true)) { - cfg.setAlias(cfg.getTable()); - } - - c = ((AbstractSQLConfig) cfg).getOrderString(false); - if (StringUtil.isEmpty(c, true) == false) { - joinOrder += (first ? "" : ", ") + c; - first = false; - } - - } - } - - - String order = StringUtil.getTrimedString(getOrder()); - // SELECT * FROM sys.Moment ORDER BY userId ASC, rand(); 前面的 userId ASC 和后面的 rand() 都有效 - // if ("rand()".equals(order)) { - // return (hasPrefix ? " ORDER BY " : "") + StringUtil.concat(order, joinOrder, ", "); - // } - - if (getCount() > 0 && (isOracle() || isSQLServer() || isDb2())) { // Oracle, SQL Server, DB2 的 OFFSET 必须加 ORDER BY - - // String[] ss = StringUtil.split(order); - if (StringUtil.isEmpty(order, true)) { //SQL Server 子查询内必须指定 OFFSET 才能用 ORDER BY - String idKey = getIdKey(); - if (StringUtil.isEmpty(idKey, true)) { - idKey = "id"; //ORDER BY NULL 不行,SQL Server 会报错,必须要有排序,才能使用 OFFSET FETCH,如果没有 idKey,请求中指定 @order 即可 - } - order = idKey; //让数据库调控默认升序还是降序 + "+"; - } - - //不用这么全面,毕竟没有语法问题还浪费性能,如果有其它问题,让前端传的 JSON 直接加上 @order 来解决 - // boolean contains = false; - // if (ss != null) { - // for (String s : ss) { - // if (s != null && s.startsWith(idKey)) { - // s = s.substring(idKey.length()); - // if ("+".equals(s) || "-".equals(s)) {// || " ASC ".equals(s) || " DESC ".equals(s)) { - // contains = true; - // break; - // } - // } - // } - // } - - // if (contains == false) { - // order = (ss == null || ss.length <= 0 ? "" : order + ",") + idKey + "+"; - // } - } - - - String[] keys = StringUtil.split(order); - if (keys == null || keys.length <= 0) { - return StringUtil.isEmpty(joinOrder, true) ? "" : (hasPrefix ? " ORDER BY " : "") + joinOrder; - } - - for (int i = 0; i < keys.length; i++) { - String item = keys[i]; - if ("rand()".equals(item)) { - continue; - } - - int index = item.endsWith("+") ? item.length() - 1 : -1; //StringUtil.split返回数组中,子项不会有null - String sort; - if (index < 0) { - index = item.endsWith("-") ? item.length() - 1 : -1; - sort = index <= 0 ? "" : " DESC "; - } - else { - sort = " ASC "; - } - - String origin = index < 0 ? item : item.substring(0, index); - - if (isPrepared()) { //不能通过 ? 来代替,SELECT 'id','name' 返回的就是 id:"id", name:"name",而不是数据库里的值! - //这里既不对origin trim,也不对 ASC/DESC ignoreCase,希望前端严格传没有任何空格的字符串过来,减少传输数据量,节约服务器性能 - if (StringUtil.isName(origin) == false) { - throw new IllegalArgumentException("预编译模式下 @order:value 中 " + item + " 不合法! value 里面用 , 分割的" - + "每一项必须是 随机函数 rand() 或 column+ / column- 且其中 column 必须是 1 个单词!并且不要有多余的空格!"); - } - } - - keys[i] = getKey(origin) + sort; - } - - return (hasPrefix ? " ORDER BY " : "") + StringUtil.concat(StringUtil.getString(keys), joinOrder, ", "); - } - - @Override - public List getRaw() { - return raw; - } - @Override - public SQLConfig setRaw(List raw) { - this.raw = raw; - return this; - } - - /**获取原始 SQL 片段 - * @param key - * @param value - * @return - * @throws Exception - */ - @Override - public String getRawSQL(String key, Object value) throws Exception { - List rawList = getRaw(); - boolean containRaw = rawList != null && rawList.contains(key); - if (containRaw && value instanceof String == false) { - throw new UnsupportedOperationException("@raw:value 的 value 中 " + key + " 不合法!" - + "对应的 " + key + ":value 中 value 类型只能为 String!"); - } - - String rawSQL = containRaw ? RAW_MAP.get(value) : null; - if (containRaw) { - if (rawSQL == null) { - throw new UnsupportedOperationException("@raw:value 的 value 中 " + key + " 不合法!" - + "对应的 " + key + ":value 中 value 值 " + value + " 未在后端 RAW_MAP 中配置 !"); - } - - if ("".equals(rawSQL)) { - return (String) value; - } - } - - return rawSQL; - } - - - @Override - public List getJson() { - return json; - } - @Override - public AbstractSQLConfig setJson(List json) { - this.json = json; - return this; - } - - - @Override - public Subquery getFrom() { - return from; - } - @Override - public AbstractSQLConfig setFrom(Subquery from) { - this.from = from; - return this; - } - - @Override - public List getColumn() { - return column; - } - @Override - public AbstractSQLConfig setColumn(List column) { - this.column = column; - return this; - } - @JSONField(serialize = false) - public String getColumnString() throws Exception { - return getColumnString(false); - } - @JSONField(serialize = false) - public String getColumnString(boolean inSQLJoin) throws Exception { - List column = getColumn(); - - switch (getMethod()) { - case HEAD: - case HEADS: //StringUtil.isEmpty(column, true) || column.contains(",") 时SQL.count(column)会return "*" - if (isPrepared() && column != null) { - String origin; - String alias; - int index; - for (String c : column) { - index = c.lastIndexOf(":"); //StringUtil.split返回数组中,子项不会有null - origin = index < 0 ? c : c.substring(0, index); - alias = index < 0 ? null : c.substring(index + 1); - - if (alias != null && StringUtil.isName(alias) == false) { - throw new IllegalArgumentException("HEAD请求: 字符 " + alias + " 不合法!预编译模式下 @column:value 中 value里面用 , 分割的每一项" - + " column:alias 中 column 必须是1个单词!如果有alias,则alias也必须为1个单词!并且不要有多余的空格!"); - } - - if (StringUtil.isName(origin) == false) { - int start = origin.indexOf("("); - if (start < 0 || origin.lastIndexOf(")") <= start) { - throw new IllegalArgumentException("HEAD请求: 字符" + origin + " 不合法!预编译模式下 @column:value 中 value里面用 , 分割的每一项" - + " column:alias 中 column 必须是1个单词!如果有alias,则alias也必须为1个单词!并且不要有多余的空格!"); - } - - if (start > 0 && StringUtil.isName(origin.substring(0, start)) == false) { - throw new IllegalArgumentException("HEAD请求: 字符 " + origin.substring(0, start) + " 不合法!预编译模式下 @column:value 中 value里面用 , 分割的每一项" - + " column:alias 中 column 必须是1个单词!如果有alias,则alias也必须为1个单词!并且不要有多余的空格!"); - } - } - } - } - - return SQL.count(column != null && column.size() == 1 && StringUtil.isName(column.get(0)) ? getKey(column.get(0)) : "*"); - case POST: - if (column == null || column.isEmpty()) { - throw new IllegalArgumentException("POST 请求必须在Table内设置要保存的 key:value !"); - } - - String s = ""; - boolean pfirst = true; - for (String c : column) { - if (isPrepared() && StringUtil.isName(c) == false) { //不能通过 ? 来代替,SELECT 'id','name' 返回的就是 id:"id", name:"name",而不是数据库里的值! - throw new IllegalArgumentException("POST请求: 每一个 key:value 中的key都必须是1个单词!"); - } - s += ((pfirst ? "" : ",") + getKey(c)); - - pfirst = false; - } - - return "(" + s + ")"; - case GET: - case GETS: - boolean isQuery = RequestMethod.isQueryMethod(method); //TODO 这个有啥用?上面应是 getMethod 的值 GET 和 GETS 了。 - String joinColumn = ""; - if (isQuery && joinList != null) { - SQLConfig ecfg; - SQLConfig cfg; - String c; - boolean first = true; - for (Join j : joinList) { - if (j.isAppJoin()) { - continue; - } - - ecfg = j.getOuterConfig(); - if (ecfg != null && ecfg.getColumn() != null) { //优先级更高 - cfg = ecfg; - } - else { - cfg = j.getJoinConfig(); - } - - if (StringUtil.isEmpty(cfg.getAlias(), true)) { - cfg.setAlias(cfg.getTable()); - } - - c = ((AbstractSQLConfig) cfg).getColumnString(true); - if (StringUtil.isEmpty(c, true) == false) { - joinColumn += (first ? "" : ", ") + c; - first = false; - } - - inSQLJoin = true; - } - } - - String tableAlias = getAliasWithQuote(); - - // String c = StringUtil.getString(column); //id,name;json_length(contactIdList):contactCount;... - - String[] keys = column == null ? null : column.toArray(new String[]{}); //StringUtil.split(c, ";"); - if (keys == null || keys.length <= 0) { - - boolean noColumn = column != null && inSQLJoin; - String mc = isKeyPrefix() == false ? (noColumn ? "" : "*") : (noColumn ? "" : tableAlias + ".*"); - - return StringUtil.concat(mc, joinColumn, ", ", true); - } - - - List raw = getRaw(); - boolean containRaw = raw != null && raw.contains(KEY_COLUMN); - - String expression; - String method = null; - - //...;fun0(arg0,arg1,...):fun0;fun1(arg0,arg1,...):fun1;... - for (int i = 0; i < keys.length; i++) { - - //fun(arg0,arg1,...) - expression = keys[i]; - - if (containRaw) { // 由于 HashMap 对 key 做了 hash 处理,所以 get 比 containsValue 更快 - if ("".equals(RAW_MAP.get(expression)) || RAW_MAP.containsValue(expression)) { // newSQLConfig 提前处理好的 - continue; - } - - // 简单点, 后台配置就带上 AS - // int index = expression.lastIndexOf(":"); - // String alias = expression.substring(index+1); - // boolean hasAlias = StringUtil.isName(alias); - // String pre = index > 0 && hasAlias ? expression.substring(0, index) : expression; - // if (RAW_MAP.containsValue(pre) || "".equals(RAW_MAP.get(pre))) { // newSQLConfig 提前处理好的 - // expression = pre + (hasAlias ? " AS " + alias : ""); - // continue; - // } - } - - if (expression.length() > 50) { - throw new UnsupportedOperationException("@column:value 的 value 中字符串 " + expression + " 不合法!" - + "不允许传超过 50 个字符的函数或表达式!请用 @raw 简化传参!"); - } - - - int start = expression.indexOf("("); - int end = 0; - if (start >= 0) { - end = expression.lastIndexOf(")"); - if (start >= end) { - throw new IllegalArgumentException("字符 " + expression + " 不合法!" - + "@column:value 中 value 里的 SQL函数必须为 function(arg0,arg1,...) 这种格式!"); - } - - method = expression.substring(0, start); - boolean distinct = i <= 0 && method.startsWith(PREFFIX_DISTINCT); - String fun = distinct ? method.substring(PREFFIX_DISTINCT.length()) : method; - - if (fun.isEmpty() == false) { - if (SQL_FUNCTION_MAP == null || SQL_FUNCTION_MAP.isEmpty()) { - if (StringUtil.isName(fun) == false) { - throw new IllegalArgumentException("字符 " + method + " 不合法!" - + "预编译模式下 @column:\"column0,column1:alias;function0(arg0,arg1,...);function1(...):alias...\"" - + " 中 function 必须符合小写英文单词的 SQL 函数名格式!"); - } - } - else if (SQL_FUNCTION_MAP.containsKey(fun) == false) { - throw new IllegalArgumentException("字符 " + method + " 不合法!" - + "预编译模式下 @column:\"column0,column1:alias;function0(arg0,arg1,...);function1(...):alias...\"" - + " 中 function 必须符合小写英文单词的 SQL 函数名格式!且必须是后端允许调用的 SQL 函数!"); - } - } - - } - - boolean isColumn = start < 0; - - String[] ckeys = StringUtil.split(isColumn ? expression : expression.substring(start + 1, end)); - String quote = getQuote(); - - // if (isPrepared()) { //不能通过 ? 来代替,SELECT 'id','name' 返回的就是 id:"id", name:"name",而不是数据库里的值! - if (ckeys != null && ckeys.length > 0) { - - boolean distinct; - String origin; - String alias; - int index; - for (int j = 0; j < ckeys.length; j++) { - index = isColumn ? ckeys[j].lastIndexOf(":") : -1; //StringUtil.split返回数组中,子项不会有null - origin = index < 0 ? ckeys[j] : ckeys[j].substring(0, index); - alias = index < 0 ? null : ckeys[j].substring(index + 1); - - distinct = j <= 0 && origin.startsWith(PREFFIX_DISTINCT); - if (distinct) { - origin = origin.substring(PREFFIX_DISTINCT.length()); - } - - if (isPrepared()) { - if (isColumn) { - if (StringUtil.isName(origin) == false || (alias != null && StringUtil.isName(alias) == false)) { - throw new IllegalArgumentException("字符 " + ckeys[j] + " 不合法!" - + "预编译模式下 @column:value 中 value里面用 , 分割的每一项" - + " column:alias 中 column 必须是1个单词!如果有alias,则alias也必须为1个单词!" - + "DISTINCT 必须全大写,且后面必须有且只有 1 个空格!其它情况不允许空格!"); - } - } - else { - // if ((StringUtil.isName(origin) == false || origin.startsWith("_"))) { - if (origin.startsWith("_") || origin.contains("--") || PATTERN_FUNCTION.matcher(origin).matches() == false) { - throw new IllegalArgumentException("字符 " + ckeys[j] + " 不合法!" - + "预编译模式下 @column:\"column0,column1:alias;function0(arg0,arg1,...);function1(...):alias...\"" - + " 中所有 arg 都必须是1个不以 _ 开头的单词 或者符合正则表达式 " + PATTERN_FUNCTION + " 且不包含连续减号 -- !DISTINCT 必须全大写,且后面必须有且只有 1 个空格!其它情况不允许空格!"); - } - } - } - - //JOIN 副表不再在外层加副表名前缀 userId AS `Commet.userId`, 而是直接 userId AS `userId` - boolean isName = false; - if (StringUtil.isNumer(origin)) { - //do nothing - } - else if (StringUtil.isName(origin)) { - origin = quote + origin + quote; - isName = true; - } - else { - origin = getValue(origin).toString(); - } - - if (isName && isKeyPrefix()) { - ckeys[j] = tableAlias + "." + origin; - // if (isColumn) { - // ckeys[j] += " AS " + quote + (isMain() ? "" : tableAlias + ".") + (StringUtil.isEmpty(alias, true) ? origin : alias) + quote; - // } - if (isColumn && StringUtil.isEmpty(alias, true) == false) { - ckeys[j] += " AS " + quote + alias + quote; - } - } else { - ckeys[j] = origin + (StringUtil.isEmpty(alias, true) ? "" : " AS " + quote + alias + quote); - } - - if (distinct) { - ckeys[j] = PREFFIX_DISTINCT + ckeys[j]; - } - } - // } - - } - - if (isColumn) { - keys[i] = StringUtil.getString(ckeys); - } - else { - String suffix = expression.substring(end + 1, expression.length()); //:contactCount - int index = suffix.lastIndexOf(":"); - String alias = index < 0 ? "" : suffix.substring(index + 1); //contactCount - suffix = index < 0 ? suffix : suffix.substring(0, index); - - if (alias.isEmpty() == false && StringUtil.isName(alias) == false) { - throw new IllegalArgumentException("字符串 " + alias + " 不合法!" - + "预编译模式下 @column:value 中 value里面用 ; 分割的每一项" - + " function(arg0,arg1,...):alias 中 alias 必须是1个单词!并且不要有多余的空格!"); - } - - if (suffix.isEmpty() == false && (((String) suffix).contains("--") || ((String) suffix).contains("/*") || PATTERN_RANGE.matcher((String) suffix).matches() == false)) { - throw new UnsupportedOperationException("字符串 " + suffix + " 不合法!" - + "预编译模式下 @column:\"column?value;function(arg0,arg1,...)?value...\"" - + " 中 ?value 必须符合正则表达式 " + PATTERN_RANGE + " 且不包含连续减号 -- 或注释符 /* !不允许多余的空格!"); - } - - String origin = method + "(" + StringUtil.getString(ckeys) + ")" + suffix; - // if (isKeyPrefix()) { - // keys[i] = origin + " AS " + quote + (isMain() ? "" : tableAlias + ".") + (StringUtil.isEmpty(alias, true) ? method : alias) + quote; - // } - // else { - keys[i] = origin + (StringUtil.isEmpty(alias, true) ? "" : " AS " + quote + alias + quote); - // } - } - - } - - String c = StringUtil.getString(keys); - c = c + (StringUtil.isEmpty(joinColumn, true) ? "" : ", " + joinColumn);//不能在这里改,后续还要用到: - return isMain() && isDistinct() ? PREFFIX_DISTINCT + c : c; - default: - throw new UnsupportedOperationException( - "服务器内部错误:getColumnString 不支持 " + RequestMethod.getName(getMethod()) - + " 等 [GET,GETS,HEAD,HEADS,POST] 外的ReuqestMethod!" - ); - } - } - - - @Override - public List> getValues() { - return values; - } - @JSONField(serialize = false) - public String getValuesString() { - String s = ""; - if (values != null && values.size() > 0) { - Object[] items = new Object[values.size()]; - List vs; - for (int i = 0; i < values.size(); i++) { - vs = values.get(i); - if (vs == null) { - continue; - } - - items[i] = "("; - for (int j = 0; j < vs.size(); j++) { - items[i] += ((j <= 0 ? "" : ",") + getValue(vs.get(j))); - } - items[i] += ")"; - } - s = StringUtil.getString(items); - } - return s; - } - @Override - public AbstractSQLConfig setValues(List> valuess) { - this.values = valuess; - return this; - } - - @Override - public Map getContent() { - return content; - } - @Override - public AbstractSQLConfig setContent(Map content) { - this.content = content; - return this; - } - - @Override - public int getCount() { - return count; - } - @Override - public AbstractSQLConfig setCount(int count) { - this.count = count; - return this; - } - @Override - public int getPage() { - return page; - } - @Override - public AbstractSQLConfig setPage(int page) { - this.page = page; - return this; - } - @Override - public int getPosition() { - return position; - } - @Override - public AbstractSQLConfig setPosition(int position) { - this.position = position; - return this; - } - - @Override - public int getQuery() { - return query; - } - @Override - public AbstractSQLConfig setQuery(int query) { - this.query = query; - return this; - } - @Override - public int getType() { - return type; - } - @Override - public AbstractSQLConfig setType(int type) { - this.type = type; - return this; - } - - @Override - public int getCache() { - return cache; - } - @Override - public AbstractSQLConfig setCache(int cache) { - this.cache = cache; - return this; - } - - public AbstractSQLConfig setCache(String cache) { - return setCache(getCache(cache)); - } - public static int getCache(String cache) { - int cache2; - if (cache == null) { - cache2 = JSONRequest.CACHE_ALL; - } - else { - // if (isSubquery) { - // throw new IllegalArgumentException("子查询内不支持传 " + JSONRequest.KEY_CACHE + "!"); - // } - - switch (cache) { - case "0": - case JSONRequest.CACHE_ALL_STRING: - cache2 = JSONRequest.CACHE_ALL; - break; - case "1": - case JSONRequest.CACHE_ROM_STRING: - cache2 = JSONRequest.CACHE_ROM; - break; - case "2": - case JSONRequest.CACHE_RAM_STRING: - cache2 = JSONRequest.CACHE_RAM; - break; - default: - throw new IllegalArgumentException(JSONRequest.KEY_CACHE + ":value 中 value 的值不合法!必须在 [0,1,2] 或 [ALL, ROM, RAM] 内 !"); - } - } - return cache2; - } - - @Override - public boolean isExplain() { - return explain; - } - @Override - public AbstractSQLConfig setExplain(boolean explain) { - this.explain = explain; - return this; - } - - @Override - public List getJoinList() { - return joinList; - } - @Override - public SQLConfig setJoinList(List joinList) { - this.joinList = joinList; - return this; - } - @Override - public boolean hasJoin() { - return joinList != null && joinList.isEmpty() == false; - } - - - @Override - public boolean isTest() { - return test; - } - @Override - public AbstractSQLConfig setTest(boolean test) { - this.test = test; - return this; - } - - /**获取初始位置offset - * @return - */ - @JSONField(serialize = false) - public int getOffset() { - return getOffset(getPage(), getCount()); - } - /**获取初始位置offset - * @param page - * @param count - * @return - */ - public static int getOffset(int page, int count) { - return page*count; - } - /**获取限制数量 - * @return - */ - @JSONField(serialize = false) - public String getLimitString() { - if (count <= 0 || RequestMethod.isHeadMethod(getMethod(), true)) { - return ""; - } - return getLimitString(getPage(), getCount(), isOracle() || isSQLServer() || isDb2(), isOracle()); - } - /**获取限制数量 - * @param limit - * @return - */ - public static String getLimitString(int page, int count, boolean isTSQL, boolean isOracle) { - int offset = getOffset(page, count); - - if (isTSQL) { // OFFSET FECTH 中所有关键词都不可省略, 另外 Oracle 数据库使用子查询加 where 分页 - return isOracle? " WHERE ROWNUM BETWEEN "+ offset +" AND "+ (offset + count): " OFFSET " + offset + " ROWS FETCH FIRST " + count + " ROWS ONLY"; - } - - return " LIMIT " + count + (offset <= 0 ? "" : " OFFSET " + offset); // DELETE, UPDATE 不支持 OFFSET - } - - //WHERE <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< - @Override - public Map getWhere() { - return where; - } - @Override - public AbstractSQLConfig setWhere(Map where) { - this.where = where; - return this; - } - @NotNull - @Override - public Map> getCombine() { - List andList = combine == null ? null : combine.get("&"); - if (andList == null) { - andList = where == null ? new ArrayList() : new ArrayList(where.keySet()); - if (combine == null) { - combine = new HashMap<>(); - } - combine.put("&", andList); - } - return combine; - } - @Override - public AbstractSQLConfig setCombine(Map> combine) { - this.combine = combine; - return this; - } - /** - * noFunctionChar = false - * @param key - * @return - */ - @JSONField(serialize = false) - @Override - public Object getWhere(String key) { - return getWhere(key, false); - } - //CS304 Issue link: https://github.com/Tencent/APIJSON/issues/48 - /** - * @param key - the key passed in - * @param exactMatch - whether it is exact match - * @return - *

use entrySet+getValue() to replace keySet+get() to enhance efficiency

- */ - @JSONField(serialize = false) - @Override - public Object getWhere(String key, boolean exactMatch) { - if (exactMatch) { - return where == null ? null : where.get(key); - } - - if (key == null || where == null){ - return null; - } - synchronized (where) { - if (where != null) { - int index; - for (Entry entry : where.entrySet()) { - String k = entry.getKey(); - index = k.indexOf(key); - if (index >= 0 && StringUtil.isName(k.substring(index)) == false) { - return entry.getValue(); - } - } - } - } - return null; - } - @Override - public AbstractSQLConfig putWhere(String key, Object value, boolean prior) { - if (key != null) { - if (where == null) { - where = new LinkedHashMap(); - } - if (value == null) { - where.remove(key); - } else { - where.put(key, value); - } - - combine = getCombine(); - List andList = combine.get("&"); - if (value == null) { - if (andList != null) { - andList.remove(key); - } - } - else if (andList == null || andList.contains(key) == false) { - int i = 0; - if (andList == null) { - andList = new ArrayList<>(); - } - else if (prior && andList.isEmpty() == false) { - - String idKey = getIdKey(); - String idInKey = idKey + "{}"; - String userIdKey = getUserIdKey(); - String userIdInKey = userIdKey + "{}"; - - if (andList.contains(idKey)) { - i ++; - } - if (andList.contains(idInKey)) { - i ++; - } - if (andList.contains(userIdKey)) { - i ++; - } - if (andList.contains(userIdInKey)) { - i ++; - } - } - - if (prior) { - andList.add(i, key); //userId的优先级不能比id高 0, key); - } else { - andList.add(key); //AbstractSQLExecutor.onPutColumn里getSQL,要保证缓存的SQL和查询的SQL里 where 的 key:value 顺序一致 - } - } - combine.put("&", andList); - } - return this; - } - - /**获取WHERE - * @return - * @throws Exception - */ - @JSONField(serialize = false) - @Override - public String getWhereString(boolean hasPrefix) throws Exception { - return getWhereString(hasPrefix, getMethod(), getWhere(), getCombine(), getJoinList(), ! isTest()); - } - /**获取WHERE - * @param method - * @param where - * @return - * @throws Exception - */ - @JSONField(serialize = false) - public String getWhereString(boolean hasPrefix, RequestMethod method, Map where, Map> combine, List joinList, boolean verifyName) throws Exception { - Set>> combineSet = combine == null ? null : combine.entrySet(); - if (combineSet == null || combineSet.isEmpty()) { - Log.w(TAG, "getWhereString combineSet == null || combineSet.isEmpty() >> return \"\";"); - return ""; - } - - List keyList; - - String whereString = ""; - - boolean isCombineFirst = true; - int logic; - - boolean isItemFirst; - String c; - String cs; - - for (Entry> ce : combineSet) { - keyList = ce == null ? null : ce.getValue(); - if (keyList == null || keyList.isEmpty()) { - continue; - } - - if ("|".equals(ce.getKey())) { - logic = Logic.TYPE_OR; - } - else if ("!".equals(ce.getKey())) { - logic = Logic.TYPE_NOT; - } - else { - logic = Logic.TYPE_AND; - } - - - isItemFirst = true; - cs = ""; - for (String key : keyList) { - c = getWhereItem(key, where.get(key), method, verifyName); - - if (StringUtil.isEmpty(c, true)) {//避免SQL条件连接错误 - continue; - } - - cs += (isItemFirst ? "" : (Logic.isAnd(logic) ? AND : OR)) + "(" + c + ")"; - - isItemFirst = false; - } - - if (StringUtil.isEmpty(cs, true)) {//避免SQL条件连接错误 - continue; - } - - whereString += (isCombineFirst ? "" : AND) + (Logic.isNot(logic) ? NOT : "") + " ( " + cs + " ) "; - isCombineFirst = false; - } - - - if (joinList != null) { - - String newWs = ""; - String ws = whereString; - - List newPvl = new ArrayList<>(); - List pvl = new ArrayList<>(preparedValueList); - - SQLConfig jc; - String js; - - boolean changed = false; - //各种 JOIN 没办法统一用 & | !连接,只能按优先级,和 @combine 一样? - for (Join j : joinList) { - String jt = j.getJoinType(); - - switch (jt) { - case "*": // CROSS JOIN - case "@": // APP JOIN - case "<": // LEFT JOIN - case ">": // RIGHT JOIN - break; - - case "&": // INNER JOIN: A & B - case "": // FULL JOIN: A | B - case "|": // FULL JOIN: A | B - case "!": // OUTER JOIN: ! (A | B) - case "^": // SIDE JOIN: ! (A & B) - case "(": // ANTI JOIN: A & ! B - case ")": // FOREIGN JOIN: B & ! A - jc = j.getJoinConfig(); - boolean isMain = jc.isMain(); - jc.setMain(false).setPrepared(isPrepared()).setPreparedValueList(new ArrayList()); - js = jc.getWhereString(false); - jc.setMain(isMain); - - boolean isOuterJoin = "!".equals(jt); - boolean isSideJoin = "^".equals(jt); - boolean isAntiJoin = "(".equals(jt); - boolean isForeignJoin = ")".equals(jt); - boolean isWsEmpty = StringUtil.isEmpty(ws, true); - - if (isWsEmpty) { - if (isOuterJoin) { // ! OUTER JOIN: ! (A | B) - throw new NotExistException("no result for ! OUTER JOIN( ! (A | B) ) when A or B is empty!"); - } - if (isForeignJoin) { // ) FOREIGN JOIN: B & ! A - throw new NotExistException("no result for ) FOREIGN JOIN( B & ! A ) when A is empty!"); - } - } - - if (StringUtil.isEmpty(js, true)) { - if (isOuterJoin) { // ! OUTER JOIN: ! (A | B) - throw new NotExistException("no result for ! OUTER JOIN( ! (A | B) ) when A or B is empty!"); - } - if (isAntiJoin) { // ( ANTI JOIN: A & ! B - throw new NotExistException("no result for ( ANTI JOIN( A & ! B ) when B is empty!"); - } - - if (isWsEmpty) { - if (isSideJoin) { - throw new NotExistException("no result for ^ SIDE JOIN( ! (A & B) ) when both A and B are empty!"); - } - } - else { - if (isSideJoin || isForeignJoin) { - newWs += " ( " + getCondition(true, ws) + " ) "; - - newPvl.addAll(pvl); - newPvl.addAll(jc.getPreparedValueList()); - changed = true; - } - } - - continue; - } - - if (StringUtil.isEmpty(newWs, true) == false) { - newWs += AND; - } - - if (isAntiJoin) { // ( ANTI JOIN: A & ! B - newWs += " ( " + ( isWsEmpty ? "" : ws + AND ) + NOT + " ( " + js + " ) " + " ) "; - } - else if (isForeignJoin) { // ) FOREIGN JOIN: (! A) & B // preparedValueList.add 不好反过来 B & ! A - newWs += " ( " + NOT + " ( " + ws + " ) ) " + AND + " ( " + js + " ) "; - } - else if (isSideJoin) { // ^ SIDE JOIN: ! (A & B) - //MySQL 因为 NULL 值处理问题,(A & ! B) | (B & ! A) 与 ! (A & B) 返回结果不一样,后者往往更多 - newWs += " ( " + getCondition( - true, - ( isWsEmpty ? "" : ws + AND ) + " ( " + js + " ) " - ) + " ) "; - } - else { // & INNER JOIN: A & B; | FULL JOIN: A | B; OUTER JOIN: ! (A | B) - logic = Logic.getType(jt); - newWs += " ( " - + getCondition( - Logic.isNot(logic), - ws - + ( isWsEmpty ? "" : (Logic.isAnd(logic) ? AND : OR) ) - + " ( " + js + " ) " - ) - + " ) "; - } - - newPvl.addAll(pvl); - newPvl.addAll(jc.getPreparedValueList()); - - changed = true; - break; - default: - throw new UnsupportedOperationException( - "join:value 中 value 里的 " + jt + "/" + j.getPath() - + "错误!不支持 " + jt + " 等 [ @ APP, < LEFT, > RIGHT, * CROSS" - + ", & INNER, | FULL, ! OUTER, ^ SIDE, ( ANTI, ) FOREIGN ] 之外的 JOIN 类型 !" - ); - } - } - - if (changed) { - whereString = newWs; - preparedValueList = newPvl; - } - } - - String s = StringUtil.isEmpty(whereString, true) ? "" : (hasPrefix ? " WHERE " : "") + whereString; - - if (s.isEmpty() && RequestMethod.isQueryMethod(method) == false) { - throw new UnsupportedOperationException("写操作请求必须带条件!!!"); - } - - return s; - } - - /** - * @param key - * @param value - * @param method - * @param verifyName - * @return - * @throws Exception - */ - protected String getWhereItem(String key, Object value, RequestMethod method, boolean verifyName) throws Exception { - Log.d(TAG, "getWhereItem key = " + key); - //避免筛选到全部 value = key == null ? null : where.get(key); - if (key == null || value == null || key.endsWith("()") || key.startsWith("@")) { //关键字||方法, +或-直接报错 - Log.d(TAG, "getWhereItem key == null || value == null" - + " || key.startsWith(@) || key.endsWith(()) >> continue;"); - return null; - } - if (key.endsWith("@")) {//引用 - // key = key.substring(0, key.lastIndexOf("@")); - throw new IllegalArgumentException(TAG + ".getWhereItem: 字符 " + key + " 不合法!"); - } - - // 原始 SQL 片段 - String rawSQL = getRawSQL(key, value); - - int keyType; - if (key.endsWith("$")) { - keyType = 1; - } - else if (key.endsWith("~")) { - keyType = key.charAt(key.length() - 2) == '*' ? -2 : 2; //FIXME StringIndexOutOfBoundsException - } - else if (key.endsWith("%")) { - keyType = 3; - } - else if (key.endsWith("{}")) { - keyType = 4; - } - else if (key.endsWith("}{")) { - keyType = 5; - } - else if (key.endsWith("<>")) { - keyType = 6; - } - else if (key.endsWith(">=")) { - keyType = 7; - } - else if (key.endsWith("<=")) { - keyType = 8; - } - else if (key.endsWith(">")) { - keyType = 9; - } - else if (key.endsWith("<")) { - keyType = 10; - } else { // else绝对不能省,避免再次踩坑! keyType = 0; 写在for循环外面都没注意! - keyType = 0; - } - - key = getRealKey(method, key, false, true, verifyName); - - switch (keyType) { - case 1: - return getSearchString(key, value, rawSQL); - case -2: - case 2: - return getRegExpString(key, value, keyType < 0, rawSQL); - case 3: - return getBetweenString(key, value, rawSQL); - case 4: - return getRangeString(key, value, rawSQL); - case 5: - return getExistsString(key, value, rawSQL); - case 6: - return getContainString(key, value, rawSQL); - case 7: - return getCompareString(key, value, ">=", rawSQL); - case 8: - return getCompareString(key, value, "<=", rawSQL); - case 9: - return getCompareString(key, value, ">", rawSQL); - case 10: - return getCompareString(key, value, "<", rawSQL); - default: // TODO MySQL JSON类型的字段对比 key='[]' 会无结果! key LIKE '[1, 2, 3]' //TODO MySQL , 后面有空格! - return getEqualString(key, value, rawSQL); - } - } - - - @JSONField(serialize = false) - public String getEqualString(String key, Object value, String rawSQL) throws Exception { - if (JSON.isBooleanOrNumberOrString(value) == false && value instanceof Subquery == false) { - throw new IllegalArgumentException(key + ":value 中value不合法!非PUT请求只支持 [Boolean, Number, String] 内的类型 !"); - } - - boolean not = key.endsWith("!"); // & | 没有任何意义,写法多了不好控制 - if (not) { - key = key.substring(0, key.length() - 1); - } - if (StringUtil.isName(key) == false) { - throw new IllegalArgumentException(key + ":value 中key不合法!不支持 ! 以外的逻辑符 !"); - } - - return getKey(key) + (not ? " != " : " = ") + (value instanceof Subquery ? getSubqueryString((Subquery) value) : (rawSQL != null ? rawSQL : getValue(value))); - } - - @JSONField(serialize = false) - public String getCompareString(String key, Object value, String type, String rawSQL) throws Exception { - if (JSON.isBooleanOrNumberOrString(value) == false && value instanceof Subquery == false) { - throw new IllegalArgumentException(key + type + ":value 中value不合法!比较运算 [>, <, >=, <=] 只支持 [Boolean, Number, String] 内的类型 !"); - } - if (StringUtil.isName(key) == false) { - throw new IllegalArgumentException(key + type + ":value 中key不合法!比较运算 [>, <, >=, <=] 不支持 [&, !, |] 中任何逻辑运算符 !"); - } - - return getKey(key) + " " + type + " " + (value instanceof Subquery ? getSubqueryString((Subquery) value) : (rawSQL != null ? rawSQL : getValue(value))); - } - - public String getKey(String key) { - if (isTest()) { - if (key.contains("'")) { // || key.contains("#") || key.contains("--")) { - throw new IllegalArgumentException("参数 " + key + " 不合法!key 中不允许有单引号 ' !"); - } - return getSQLValue(key).toString(); - } - - return getSQLKey(key); - } - public String getSQLKey(String key) { - String q = getQuote(); - return (isKeyPrefix() ? getAliasWithQuote() + "." : "") + q + key + q; - } - - /** - * 使用prepareStatement预编译,值为 ? ,后续动态set进去 - */ - private List preparedValueList = new ArrayList<>(); - private Object getValue(@NotNull Object value) { - if (isPrepared()) { - preparedValueList.add(value); - return "?"; - } - return getSQLValue(value); - } - public Object getSQLValue(@NotNull Object value) { - // return (value instanceof Number || value instanceof Boolean) && DATABASE_POSTGRESQL.equals(getDatabase()) ? value : "'" + value + "'"; - return (value instanceof Number || value instanceof Boolean) ? value : "'" + value + "'"; //MySQL 隐式转换用不了索引 - } - - @Override - public List getPreparedValueList() { - return preparedValueList; - } - @Override - public AbstractSQLConfig setPreparedValueList(List preparedValueList) { - this.preparedValueList = preparedValueList; - return this; - } - - //$ search <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< - /**search key match value - * @param in - * @return {@link #getSearchString(String, Object[], int)} - * @throws IllegalArgumentException - */ - @JSONField(serialize = false) - public String getSearchString(String key, Object value, String rawSQL) throws IllegalArgumentException { - if (rawSQL != null) { - throw new UnsupportedOperationException("@raw:value 中 " + key + " 不合法!@raw 不支持 key$ 这种功能符 !只支持 key, key!, key<, key{} 等比较运算 和 @column, @having !"); - } - if (value == null) { - return ""; - } - - Logic logic = new Logic(key); - key = logic.getKey(); - Log.i(TAG, "getSearchString key = " + key); - - JSONArray arr = newJSONArray(value); - if (arr.isEmpty()) { - return ""; - } - return getSearchString(key, arr.toArray(), logic.getType()); - } - /**search key match values - * @param in - * @return LOGIC [ key LIKE 'values[i]' ] - * @throws IllegalArgumentException - */ - @JSONField(serialize = false) - public String getSearchString(String key, Object[] values, int type) throws IllegalArgumentException { - if (values == null || values.length <= 0) { - return ""; - } - - String condition = ""; - for (int i = 0; i < values.length; i++) { - Object v = values[i]; - if (v instanceof String == false) { - throw new IllegalArgumentException(key + "$:value 中 value 的类型只能为 String 或 String[]!"); - } - if (((String) v).isEmpty()) { // 允许查空格 StringUtil.isEmpty((String) v, true) - throw new IllegalArgumentException(key + "$:value 中 value 值 " + v + "是空字符串,没有意义,不允许这样传!"); - } - // if (((String) v).contains("%%")) { // 需要通过 %\%% 来模糊搜索 % - // throw new IllegalArgumentException(key + "$:value 中 value 值 " + v + " 中包含 %% !不允许有连续的 % !"); - // } - - condition += (i <= 0 ? "" : (Logic.isAnd(type) ? AND : OR)) + getLikeString(key, v); - } - - return getCondition(Logic.isNot(type), condition); - } - - /**WHERE key LIKE 'value' - * @param key - * @param value - * @return key LIKE 'value' - */ - @JSONField(serialize = false) - public String getLikeString(String key, Object value) { - return getKey(key) + " LIKE " + getValue(value); - } - - //$ search >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> - - - - //~ regexp <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< - /**search key match RegExp values - * @param key - * @param value - * @param ignoreCase - * @return {@link #getRegExpString(String, Object[], int, boolean)} - * @throws IllegalArgumentException - */ - @JSONField(serialize = false) - public String getRegExpString(String key, Object value, boolean ignoreCase, String rawSQL) throws IllegalArgumentException { - if (rawSQL != null) { - throw new UnsupportedOperationException("@raw:value 中 " + key + " 不合法!@raw 不支持 key~ 这种功能符 !只支持 key, key!, key<, key{} 等比较运算 和 @column, @having !"); - } - if (value == null) { - return ""; - } - - Logic logic = new Logic(key); - key = logic.getKey(); - Log.i(TAG, "getRegExpString key = " + key); - - JSONArray arr = newJSONArray(value); - if (arr.isEmpty()) { - return ""; - } - return getRegExpString(key, arr.toArray(), logic.getType(), ignoreCase); - } - /**search key match RegExp values - * @param key - * @param values - * @param type - * @param ignoreCase - * @return LOGIC [ key REGEXP 'values[i]' ] - * @throws IllegalArgumentException - */ - @JSONField(serialize = false) - public String getRegExpString(String key, Object[] values, int type, boolean ignoreCase) throws IllegalArgumentException { - if (values == null || values.length <= 0) { - return ""; - } - - String condition = ""; - for (int i = 0; i < values.length; i++) { - if (values[i] instanceof String == false) { - throw new IllegalArgumentException(key + "$:value 中value的类型只能为String或String[]!"); - } - condition += (i <= 0 ? "" : (Logic.isAnd(type) ? AND : OR)) + getRegExpString(key, (String) values[i], ignoreCase); - } - - return getCondition(Logic.isNot(type), condition); - } - - /**WHERE key REGEXP 'value' - * @param key - * @param value - * @param ignoreCase - * @return key REGEXP 'value' - */ - @JSONField(serialize = false) - public String getRegExpString(String key, String value, boolean ignoreCase) { - if (isPostgreSQL()) { - return getKey(key) + " ~" + (ignoreCase ? "* " : " ") + getValue(value); - } - if (isOracle()) { - return "regexp_like(" + getKey(key) + ", " + getValue(value) + (ignoreCase ? ", 'i'" : ", 'c'") + ")"; - } - return getKey(key) + " REGEXP " + (ignoreCase ? "" : "BINARY ") + getValue(value); - } - //~ regexp >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> - - - - //% between <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< - - /**WHERE key BETWEEN 'start' AND 'end' - * @param key - * @param value 'start,end' - * @return LOGIC [ key BETWEEN 'start' AND 'end' ] - * @throws IllegalArgumentException - */ - @JSONField(serialize = false) - public String getBetweenString(String key, Object value, String rawSQL) throws IllegalArgumentException { - if (rawSQL != null) { - throw new UnsupportedOperationException("@raw:value 中 " + key + " 不合法!@raw 不支持 key% 这种功能符 !只支持 key, key!, key<, key{} 等比较运算 和 @column, @having !"); - } - if (value == null) { - return ""; - } - - Logic logic = new Logic(key); - key = logic.getKey(); - Log.i(TAG, "getBetweenString key = " + key); - - JSONArray arr = newJSONArray(value); - if (arr.isEmpty()) { - return ""; - } - return getBetweenString(key, arr.toArray(), logic.getType()); - } - - /**WHERE key BETWEEN 'start' AND 'end' - * @param key - * @param value 'start,end' TODO 在 '1,2' 和 ['1,2', '3,4'] 基础上新增支持 [1, 2] 和 [[1,2], [3,4]] ? - * @return LOGIC [ key BETWEEN 'start' AND 'end' ] - * @throws IllegalArgumentException - */ - @JSONField(serialize = false) - public String getBetweenString(String key, Object[] values, int type) throws IllegalArgumentException { - if (values == null || values.length <= 0) { - return ""; - } - - String condition = ""; - String[] vs; - for (int i = 0; i < values.length; i++) { - if (values[i] instanceof String == false) { - throw new IllegalArgumentException(key + "%:value 中 value 的类型只能为 String 或 String[] !"); - } - - vs = StringUtil.split((String) values[i]); - if (vs == null || vs.length != 2) { - throw new IllegalArgumentException(key + "%:value 中 value 不合法!类型为 String 时必须包括1个逗号 , 且左右两侧都有值!类型为 String[] 里面每个元素要符合前面类型为 String 的规则 !"); - } - - condition += (i <= 0 ? "" : (Logic.isAnd(type) ? AND : OR)) + "(" + getBetweenString(key, (Object) vs[0], (Object) vs[1]) + ")"; - } - - return getCondition(Logic.isNot(type), condition); - } - - /**WHERE key BETWEEN 'start' AND 'end' - * @param key - * @param value 'start,end' TODO 在 '1,2' 和 ['1,2', '3,4'] 基础上新增支持 [1, 2] 和 [[1,2], [3,4]] ? - * @return key BETWEEN 'start' AND 'end' - * @throws IllegalArgumentException - */ - @JSONField(serialize = false) - public String getBetweenString(String key, Object start, Object end) throws IllegalArgumentException { - if (JSON.isBooleanOrNumberOrString(start) == false || JSON.isBooleanOrNumberOrString(end) == false) { - throw new IllegalArgumentException(key + "%:value 中 value 不合法!类型为 String 时必须包括1个逗号 , 且左右两侧都有值!类型为 String[] 里面每个元素要符合前面类型为 String 的规则 !"); - } - return getKey(key) + " BETWEEN " + getValue(start) + AND + getValue(end); - } - - - //% between >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> - - - - //{} range <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< - - - /**WHERE key > 'key0' AND key <= 'key1' AND ... - * @param key - * @param range "condition0,condition1..." - * @return key condition0 AND key condition1 AND ... - * @throws Exception - */ - @JSONField(serialize = false) - public String getRangeString(String key, Object range, String rawSQL) throws Exception { - Log.i(TAG, "getRangeString key = " + key); - if (range == null) {//依赖的对象都没有给出有效值,这个存在无意义。如果是客户端传的,那就能在客户端确定了。 - throw new NotExistException(TAG + "getRangeString(" + key + ", " + range - + ") range == null"); - } - - Logic logic = new Logic(key); - String k = logic.getKey(); - Log.i(TAG, "getRangeString k = " + k); - - if (range instanceof List) { - if (rawSQL != null) { - throw new UnsupportedOperationException("@raw:value 的 value 中 " + key + "{} 不合法!" - + "Raw SQL 不支持 key{}:[] 这种键值对!"); - } - - if (logic.isOr() || logic.isNot()) { - List l = (List) range; - if (logic.isNot() && l.isEmpty()) { - return ""; // key!{}: [] 这个条件无效,加到 SQL 语句中 key IN() 会报错,getInString 里不好处理 - } - return getKey(k) + getInString(k, l.toArray(), logic.isNot()); - } - throw new IllegalArgumentException(key + "{}\":[] 中 {} 前面的逻辑运算符错误!只能用'|','!'中的一种 !"); - } - else if (range instanceof String) {//非Number类型需要客户端拼接成 < 'value0', >= 'value1'这种 - String condition = ""; - String[] cs = rawSQL != null ? null : StringUtil.split((String) range, false); - - if (rawSQL != null) { - int index = rawSQL == null ? -1 : rawSQL.indexOf("("); - condition = (index >= 0 && index < rawSQL.lastIndexOf(")") ? "" : getKey(k) + " ") + rawSQL; - } - - // 还是只支持整段为 Raw SQL 比较好 - // boolean appendRaw = false; - // if ("".equals(rawSQL)) { - // condition = rawSQL; - // cs = null; - // } - // else { - // if (rawSQL != null) { //先找出所有 rawSQL 的位置,然后去掉,再最后按原位置来拼接 - // String[] rs = StringUtil.split((String) range, rawSQL, false); - // - // if (rs != null && rs.length > 0) { - // String cond = ""; - // for (int i = 0; i < rs.length; i++) { - // cond += rs[i]; - // } - // range = cond; - // appendRaw = true; - // } - // } - // - // cs = StringUtil.split((String) range, false); - // } - - if (cs != null) { - String c; - int index; - for (int i = 0; i < cs.length; i++) {//对函数条件length(key)<=5这种不再在开头加key - c = cs[i]; - - if ("=null".equals(c)) { - c = SQL.isNull(); - } - else if ("!=null".equals(c)) { - c = SQL.isNull(false); - } - else if (isPrepared() && (c.contains("--") || PATTERN_RANGE.matcher(c).matches() == false)) { - throw new UnsupportedOperationException(key + "{}:value 的 value 中 " + c + " 不合法!" - + "预编译模式下 key{}:\"condition\" 中 condition 必须 为 =null 或 !=null 或 符合正则表达式 " + PATTERN_RANGE + " !不允许连续减号 -- !不允许空格!"); - } - - index = c == null ? -1 : c.indexOf("("); - condition += ((i <= 0 ? "" : (logic.isAnd() ? AND : OR)) //连接方式 - + (index >= 0 && index < c.lastIndexOf(")") ? "" : getKey(k) + " ") //函数和非函数条件 - + c); // 还是只支持整段为 Raw SQL 比较好 (appendRaw && index > 0 ? rawSQL : "") + c); //单个条件,如果有 Raw SQL 则按原来位置拼接 - } - } - if (condition.isEmpty()) { - return ""; - } - - return getCondition(logic.isNot(), condition); - } - else if (range instanceof Subquery) { //如果在 Parser 解析成 SQL 字符串再引用,没法保证安全性,毕竟可以再通过远程函数等方式来拼接再替代,最后引用的字符串就能注入 - return getKey(k) + (logic.isNot() ? NOT : "") + " IN " + getSubqueryString((Subquery) range); - } - - throw new IllegalArgumentException(key + "{}:range 类型为" + range.getClass().getSimpleName() - + "!range 只能是 用','分隔条件的字符串 或者 可取选项JSONArray!"); - } - /**WHERE key IN ('key0', 'key1', ... ) - * @param in - * @return IN ('key0', 'key1', ... ) - * @throws NotExistException - */ - @JSONField(serialize = false) - public String getInString(String key, Object[] in, boolean not) throws NotExistException { - String condition = ""; - if (in != null) {//返回 "" 会导致 id:[] 空值时效果和没有筛选id一样! - for (int i = 0; i < in.length; i++) { - condition += ((i > 0 ? "," : "") + getValue(in[i])); - } - } - if (condition.isEmpty()) {//条件如果存在必须执行,不能忽略。条件为空会导致出错,又很难保证条件不为空(@:条件),所以还是这样好 - throw new NotExistException(TAG + ".getInString(" + key + ", [], " + not - + ") >> condition.isEmpty() >> IN()"); - } - return (not ? NOT : "") + " IN (" + condition + ")"; - } - //{} range >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> - - - //}{ exists <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< - /**WHERE EXISTS subquery - * 如果合并到 getRangeString,一方面支持不了 [1,2,2] 和 ">1" (转成 EXISTS(SELECT IN ) 需要static newSQLConfig,但它不能传入子类实例,除非不是 static),另一方面多了子查询临时表性能会比 IN 差 - * @param key - * @param value - * @return EXISTS ALL(SELECT ...) - * @throws NotExistException - */ - @JSONField(serialize = false) - public String getExistsString(String key, Object value, String rawSQL) throws Exception { - if (rawSQL != null) { - throw new UnsupportedOperationException("@raw:value 中 " + key + " 不合法!@raw 不支持 key}{ 这种功能符 !只支持 key, key!, key<, key{} 等比较运算 和 @column, @having !"); - } - if (value == null) { - return ""; - } - if (value instanceof Subquery == false) { - throw new IllegalArgumentException(key + "}{:subquery 类型为" + value.getClass().getSimpleName() - + "!subquery 只能是 子查询JSONObejct!"); - } - - Logic logic = new Logic(key); - key = logic.getKey(); - Log.i(TAG, "getExistsString key = " + key); - - return (logic.isNot() ? NOT : "") + " EXISTS " + getSubqueryString((Subquery) value); - } - //}{ exists >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> - - //<> contain <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< - /**WHERE key contains value - * @param key - * @param value - * @return {@link #getContainString(String, Object[], int)} - * @throws NotExistException - */ - @JSONField(serialize = false) - public String getContainString(String key, Object value, String rawSQL) throws IllegalArgumentException { - if (rawSQL != null) { - throw new UnsupportedOperationException("@raw:value 中 " + key + " 不合法!@raw 不支持 key<> 这种功能符 !只支持 key, key!, key<, key{} 等比较运算 和 @column, @having !"); - } - if (value == null) { - return ""; - } - - Logic logic = new Logic(key); - key = logic.getKey(); - Log.i(TAG, "getContainString key = " + key); - - return getContainString(key, newJSONArray(value).toArray(), logic.getType()); - } - /**WHERE key contains childs - * @param key - * @param childs null ? "" : (empty ? no child : contains childs) - * @param type |, &, ! - * @return LOGIC [ ( key LIKE '[" + childs[i] + "]' OR key LIKE '[" + childs[i] + ", %' - * OR key LIKE '%, " + childs[i] + ", %' OR key LIKE '%, " + childs[i] + "]' ) ] - * @throws IllegalArgumentException - */ - @JSONField(serialize = false) - public String getContainString(String key, Object[] childs, int type) throws IllegalArgumentException { - boolean not = Logic.isNot(type); - String condition = ""; - if (childs != null) { - for (int i = 0; i < childs.length; i++) { - Object c = childs[i]; - if (c != null) { - if (c instanceof JSON) { - throw new IllegalArgumentException(key + "<>:value 中value类型不能为JSON!"); - } - - condition += (i <= 0 ? "" : (Logic.isAnd(type) ? AND : OR)); - if (isPostgreSQL()) { - condition += (getKey(key) + " @> " + getValue(newJSONArray(c))); //operator does not exist: jsonb @> character varying "[" + c + "]"); - } - else if (isOracle()) { - condition += ("json_textcontains(" + getKey(key) + ", '$', " + getValue(c.toString()) + ")"); - } - else { - boolean isNum = c instanceof Number; - String v = (isNum ? "" : "\"") + childs[i] + (isNum ? "" : "\""); - condition += ("json_contains(" + getKey(key) + ", " + getValue(v) + ")"); - } - } - } - if (condition.isEmpty()) { - condition = (getKey(key) + SQL.isNull(true) + OR + getLikeString(key, "[]")); // key = '[]' 无结果! - } else { - condition = (getKey(key) + SQL.isNull(false) + AND + "(" + condition + ")"); - } - } - if (condition.isEmpty()) { - return ""; - } - return getCondition(not, condition); - } - //<> contain >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> - - - - //key@:{} Subquery <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< - - @Override - public String getSubqueryString(Subquery subquery) throws Exception { - String range = subquery.getRange(); - SQLConfig cfg = subquery.getConfig(); - - cfg.setPreparedValueList(new ArrayList<>()); - String sql = (range == null || range.isEmpty() ? "" : range) + "(" + cfg.getSQL(isPrepared()) + ") "; - - preparedValueList.addAll(cfg.getPreparedValueList()); - - return sql; - } - - //key@:{} Subquery >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> - - - - /**拼接条件 - * @param not - * @param condition - * @return - */ - private static String getCondition(boolean not, String condition) { - return not ? NOT + "(" + condition + ")" : condition; - } - - - /**转为JSONArray - * @param tv - * @return - */ - @NotNull - public static JSONArray newJSONArray(Object obj) { - JSONArray array = new JSONArray(); - if (obj != null) { - if (obj instanceof Collection) { - array.addAll((Collection) obj); - } else { - array.add(obj); - } - } - return array; - } - - //WHERE >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> - - - //SET <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< - /**获取SET - * @return - * @throws Exception - */ - @JSONField(serialize = false) - public String getSetString() throws Exception { - return getSetString(getMethod(), getContent(), ! isTest()); - } - //CS304 Issue link: https://github.com/Tencent/APIJSON/issues/48 - /**获取SET - * @param method -the method used - * @param content -the content map - * @return - * @throws Exception - *

use entrySet+getValue() to replace keySet+get() to enhance efficiency

- */ - @JSONField(serialize = false) - public String getSetString(RequestMethod method, Map content, boolean verifyName) throws Exception { - Set set = content == null ? null : content.keySet(); - String setString = ""; - - if (set != null && set.size() > 0) { - boolean isFirst = true; - int keyType;// 0 - =; 1 - +, 2 - - - Object value; - - String idKey = getIdKey(); - for (Entry entry : content.entrySet()) { - String key = entry.getKey(); - //避免筛选到全部 value = key == null ? null : content.get(key); - if (key == null || idKey.equals(key)) { - continue; - } - - if (key.endsWith("+")) { - keyType = 1; - } else if (key.endsWith("-")) { - keyType = 2; - } else { - keyType = 0; //注意重置类型,不然不该加减的字段会跟着加减 - } - value = entry.getValue(); - key = getRealKey(method, key, false, true, verifyName); - - setString += (isFirst ? "" : ", ") + (getKey(key) + " = " + (keyType == 1 ? getAddString(key, value) : (keyType == 2 - ? getRemoveString(key, value) : getValue(value)) ) ); - - isFirst = false; - } - } - - if (setString.isEmpty()) { - throw new IllegalArgumentException("PUT 请求必须在Table内设置要修改的 key:value !"); - } - return " SET " + setString; - } - - /**SET key = concat(key, 'value') - * @param key - * @param value - * @return concat(key, 'value') - * @throws IllegalArgumentException - */ - @JSONField(serialize = false) - public String getAddString(String key, Object value) throws IllegalArgumentException { - if (value instanceof Number) { - return getKey(key) + " + " + value; - } - if (value instanceof String) { - return SQL.concat(getKey(key), (String) getValue(value)); - } - throw new IllegalArgumentException(key + "+ 对应的值 " + value + " 不是Number,String,Array中的任何一种!"); - } - /**SET key = replace(key, 'value', '') - * @param key - * @param value - * @return REPLACE (key, 'value', '') - * @throws IllegalArgumentException - */ - @JSONField(serialize = false) - public String getRemoveString(String key, Object value) throws IllegalArgumentException { - if (value instanceof Number) { - return getKey(key) + " - " + value; - } - if (value instanceof String) { - return SQL.replace(getKey(key), (String) getValue(value), "''");// " replace(" + key + ", '" + value + "', '') "; - } - throw new IllegalArgumentException(key + "- 对应的值 " + value + " 不是Number,String,Array中的任何一种!"); - } - //SET >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> - - - /** - * @return - * @throws Exception - */ - @JSONField(serialize = false) - @Override - public String getSQL(boolean prepared) throws Exception { - return getSQL(this.setPrepared(prepared)); - } - /** - * @param config - * @return - * @throws Exception - */ - public static String getSQL(AbstractSQLConfig config) throws Exception { - if (config == null) { - Log.i(TAG, "getSQL config == null >> return null;"); - return null; - } - - //TODO procedure 改为 List procedureList; behind : true; function: callFunction(); String key; ... - // for (...) { Call procedure1();\n SQL \n; Call procedure2(); ... } - // 貌似不需要,因为 ObjecParser 里就已经处理的顺序等,只是这里要解决下 Schema 问题。 - - String sch = config.getSQLSchema(); - if (StringUtil.isNotEmpty(config.getProcedure(), true)) { - String q = config.getQuote(); - return "CALL " + q + sch + q + "."+ config.getProcedure(); - } - - String tablePath = config.getTablePath(); - if (StringUtil.isNotEmpty(tablePath, true) == false) { - Log.i(TAG, "getSQL StringUtil.isNotEmpty(tablePath, true) == false >> return null;"); - return null; - } - - switch (config.getMethod()) { - case POST: - return "INSERT INTO " + tablePath + config.getColumnString() + " VALUES" + config.getValuesString(); - case PUT: - return "UPDATE " + tablePath + config.getSetString() + config.getWhereString(true) + (config.isMySQL() ? config.getLimitString() : ""); - case DELETE: - return "DELETE FROM " + tablePath + config.getWhereString(true) + (config.isMySQL() ? config.getLimitString() : ""); // PostgreSQL 不允许 LIMIT - default: - String explain = (config.isExplain() ? (config.isSQLServer() || config.isOracle() ? "SET STATISTICS PROFILE ON " : "EXPLAIN ") : ""); - if (config.isTest() && RequestMethod.isGetMethod(config.getMethod(), true)) { - String q = config.getQuote(); // 生成 SELECT ( (24 >=0 AND 24 <3) ) AS `code` LIMIT 1 OFFSET 0 - return explain + "SELECT " + config.getWhereString(false) + " AS " + q + JSONResponse.KEY_CODE + q + config.getLimitString(); - } - - config.setPreparedValueList(new ArrayList()); - String column = config.getColumnString(); - if (config.isOracle()) { - //When config's database is oracle,Using subquery since Oracle12 below does not support OFFSET FETCH paging syntax. - return explain + "SELECT * FROM (SELECT"+ (config.getCache() == JSONRequest.CACHE_RAM ? "SQL_NO_CACHE " : "") + column + " FROM " + getConditionString(column, tablePath, config) + ") " + config.getLimitString(); - } - - return explain + "SELECT " + (config.getCache() == JSONRequest.CACHE_RAM ? "SQL_NO_CACHE " : "") + column + " FROM " + getConditionString(column, tablePath, config) + config.getLimitString(); - } - } - - /**获取条件SQL字符串 - * @param page - * @param column - * @param table - * @param where - * @return - * @throws Exception - */ - private static String getConditionString(String column, String table, AbstractSQLConfig config) throws Exception { - String where = config.getWhereString(true); - - Subquery from = config.getFrom(); - if (from != null) { - table = config.getSubqueryString(from) + " AS " + config.getAliasWithQuote() + " "; - } - - String condition = table + config.getJoinString() + where + ( - RequestMethod.isGetMethod(config.getMethod(), true) == false ? - "" : config.getGroupString(true) + config.getHavingString(true) + config.getOrderString(true) - ) - ; //+ config.getLimitString(); - - //no need to optimize - // if (config.getPage() <= 0 || ID.equals(column.trim())) { - return condition; // config.isOracle() ? condition : condition + config.getLimitString(); - // } - // - // - // //order: id+ -> id >= idOfStartIndex; id- -> id <= idOfStartIndex <<<<<<<<<<<<<<<<<<< - // String order = StringUtil.getNoBlankString(config.getOrder()); - // List orderList = order.isEmpty() ? null : Arrays.asList(StringUtil.split(order)); - // - // int type = 0; - // if (BaseModel.isEmpty(orderList) || BaseModel.isContain(orderList, ID+"+")) { - // type = 1; - // } - // else if (BaseModel.isContain(orderList, ID+"-")) { - // type = 2; - // } - // - // if (type > 0) { - // return condition.replace("WHERE", - // "WHERE id " + (type == 1 ? ">=" : "<=") + " (SELECT id FROM " + table - // + where + " ORDER BY id " + (type == 1 ? "ASC" : "DESC") + " LIMIT " + config.getOffset() + ", 1) AND" - // ) - // + " LIMIT " + config.getCount(); //子查询起始id不一定准确,只能作为最小可能! ;// - // } - // //order: id+ -> id >= idOfStartIndex; id- -> id <= idOfStartIndex >>>>>>>>>>>>>>>>>> - // - // - // //结果错误!SELECT * FROM User AS t0 INNER JOIN - // (SELECT id FROM User ORDER BY date ASC LIMIT 20, 10) AS t1 ON t0.id = t1.id - // //common case, inner join - // condition += config.getLimitString(); - // return table + " AS t0 INNER JOIN (SELECT id FROM " + condition + ") AS t1 ON t0.id = t1.id"; - } - - - private boolean keyPrefix; - @Override - public boolean isKeyPrefix() { - return keyPrefix; - } - @Override - public AbstractSQLConfig setKeyPrefix(boolean keyPrefix) { - this.keyPrefix = keyPrefix; - return this; - } - - - - public String getJoinString() throws Exception { - String joinOns = ""; - - if (joinList != null) { - String quote = getQuote(); - List pvl = new ArrayList<>(); - boolean changed = false; - - String sql = null; - SQLConfig jc; - String jt; - String tt; - // 主表不用别名 String ta; - for (Join j : joinList) { - if (j.isAppJoin()) { // APP JOIN,只是作为一个标记,执行完主表的查询后自动执行副表的查询 User.id IN($commentIdList) - continue; - } - String type = j.getJoinType(); - - //LEFT JOIN sys.apijson_user AS User ON User.id = Moment.userId, 都是用 = ,通过relateType处理缓存 - // <"INNER JOIN User ON User.id = Moment.userId", UserConfig> TODO AS 放 getSQLTable 内 - jc = j.getJoinConfig(); - jc.setPrepared(isPrepared()); - - jt = StringUtil.isEmpty(jc.getAlias(), true) ? jc.getTable() : jc.getAlias(); - tt = j.getTargetTable(); - - //如果要强制小写,则可在子类重写这个方法再 toLowerCase - // if (DATABASE_POSTGRESQL.equals(getDatabase())) { - // jt = jt.toLowerCase(); - // tn = tn.toLowerCase(); - // } - - switch (type) { - //前面已跳过 case "@": // APP JOIN - // continue; - - case "*": // CROSS JOIN - onGetCrossJoinString(j); - case "<": // LEFT JOIN - case ">": // RIGHT JOIN - jc.setMain(true).setKeyPrefix(false); - sql = ( "<".equals(type) ? " LEFT" : (">".equals(type) ? " RIGHT" : " CROSS") ) - + " JOIN ( " + jc.getSQL(isPrepared()) + " ) AS " - + quote + jt + quote + " ON " + quote + jt + quote + "." + quote + j.getKey() + quote + " = " - + quote + tt + quote + "." + quote + j.getTargetKey() + quote; - jc.setMain(false).setKeyPrefix(true); - - pvl.addAll(jc.getPreparedValueList()); - changed = true; - break; - - case "&": // INNER JOIN: A & B - case "": // FULL JOIN: A | B - case "|": // FULL JOIN: A | B - case "!": // OUTER JOIN: ! (A | B) - case "^": // SIDE JOIN: ! (A & B) - case "(": // ANTI JOIN: A & ! B - case ")": // FOREIGN JOIN: B & ! A - sql = " INNER JOIN " + jc.getTablePath() - + " ON " + quote + jt + quote + "." + quote + j.getKey() + quote + " = " + quote + tt + quote + "." + quote + j.getTargetKey() + quote; - break; - default: - throw new UnsupportedOperationException( - "join:value 中 value 里的 " + jt + "/" + j.getPath() - + "错误!不支持 " + jt + " 等 [ @ APP, < LEFT, > RIGHT, * CROSS" - + ", & INNER, | FULL, ! OUTER, ^ SIDE, ( ANTI, ) FOREIGN ] 之外的 JOIN 类型 !" - ); - } - - joinOns += " \n " + sql; - } - - - if (changed) { - pvl.addAll(preparedValueList); - preparedValueList = pvl; - } - - } - - return joinOns; - } - - protected void onGetCrossJoinString(Join j) throws UnsupportedOperationException { - throw new UnsupportedOperationException("已禁用 * CROSS JOIN !性能很差、需求极少,如要取消禁用可在后端重写相关方法!"); - } - - /**新建SQL配置 - * @param table - * @param request - * @param joinList - * @param isProcedure - * @param callback - * @return - * @throws Exception - */ - public static SQLConfig newSQLConfig(RequestMethod method, String table, String alias, JSONObject request, List joinList, boolean isProcedure, Callback callback) throws Exception { - if (request == null) { // User:{} 这种空内容在查询时也有效 - throw new NullPointerException(TAG + ": newSQLConfig request == null!"); - } - - boolean explain = request.getBooleanValue(KEY_EXPLAIN); - if (explain && Log.DEBUG == false) { //不在 config.setExplain 抛异常,一方面处理更早性能更好,另一方面为了内部调用可以绕过这个限制 - throw new UnsupportedOperationException("DEBUG 模式下不允许传 " + KEY_EXPLAIN + " !"); - } - - String database = request.getString(KEY_DATABASE); - if (StringUtil.isEmpty(database, false) == false && DATABASE_LIST.contains(database) == false) { - throw new UnsupportedDataTypeException("@database:value 中 value 错误,只能是 [" + StringUtil.getString(DATABASE_LIST.toArray()) + "] 中的一种!"); - } - - String schema = request.getString(KEY_SCHEMA); - String datasource = request.getString(KEY_DATASOURCE); - - SQLConfig config = callback.getSQLConfig(method, database, schema, table); - config.setAlias(alias); - - config.setDatabase(database); //不删,后面表对象还要用的,必须放在 parseJoin 前 - config.setSchema(schema); //不删,后面表对象还要用的 - config.setDatasource(datasource); //不删,后面表对象还要用的 - - if (isProcedure) { - return config; - } - - config = parseJoin(method, config, joinList, callback); //放后面会导致主表是空对象时 joinList 未解析 - - if (request.isEmpty()) { // User:{} 这种空内容在查询时也有效 - return config; //request.remove(key); 前都可以直接return,之后必须保证 put 回去 - } - - - String idKey = callback.getIdKey(database, schema, table); - String idInKey = idKey + "{}"; - String userIdKey = callback.getUserIdKey(database, schema, table); - String userIdInKey = userIdKey + "{}"; - - //对id和id{}处理,这两个一定会作为条件 - - Object idIn = request.get(idInKey); //可能是 id{}:">0" - if (idIn instanceof List) { // 排除掉 0, 负数, 空字符串 等无效 id 值 - List ids = ((List) idIn); - List newIdIn = new ArrayList<>(); - Object d; - for (int i = 0; i < ids.size(); i++) { //不用 idIn.contains(id) 因为 idIn 里存到很可能是 Integer,id 又是 Long! - d = ids.get(i); - if ((d instanceof Number && ((Number) d).longValue() > 0) || (d instanceof String && StringUtil.isNotEmpty(d, true))) { - newIdIn.add(d); - } - } - if (newIdIn.isEmpty()) { - throw new NotExistException(TAG + ": newSQLConfig idIn instanceof List >> 去掉无效 id 后 newIdIn.isEmpty()"); - } - idIn = newIdIn; - - if (method == DELETE || method == PUT) { - config.setCount(newIdIn.size()); - } - } - - Object id = request.get(idKey); - boolean hasId = id != null; - if (method == POST && hasId == false) { - id = callback.newId(method, database, schema, table); // null 表示数据库自增 id - } - - if (id != null) { //null无效 - if (id instanceof Number) { - if (((Number) id).longValue() <= 0) { //一定没有值 - throw new NotExistException(TAG + ": newSQLConfig " + table + ".id <= 0"); - } - } - else if (id instanceof String) { - if (StringUtil.isEmpty(id, true)) { //一定没有值 - throw new NotExistException(TAG + ": newSQLConfig StringUtil.isEmpty(" + table + ".id, true)"); - } - } - else if (id instanceof Subquery) {} - else { - throw new IllegalArgumentException(idKey + ":value 中 value 的类型只能是 Long , String 或 Subquery !"); - } - - if (idIn instanceof List) { //共用idIn场景少性能差 - boolean contains = false; - List ids = ((List) idIn); - Object d; - for (int i = 0; i < ids.size(); i++) { //不用 idIn.contains(id) 因为 idIn 里存到很可能是 Integer,id 又是 Long! - d = ids.get(i); - if (d != null && id.toString().equals(d.toString())) { - contains = true; - break; - } - } - if (contains == false) {//empty有效 BaseModel.isEmpty(idIn) == false) { - throw new NotExistException(TAG + ": newSQLConfig idIn != null && (((List) idIn).contains(id) == false"); - } - } - - if (method == DELETE || method == PUT) { - config.setCount(1); - } - } - - - String role = request.getString(KEY_ROLE); - String cache = request.getString(KEY_CACHE); - String combine = request.getString(KEY_COMBINE); - Subquery from = (Subquery) request.get(KEY_FROM); - String column = request.getString(KEY_COLUMN); - String group = request.getString(KEY_GROUP); - String having = request.getString(KEY_HAVING); - String order = request.getString(KEY_ORDER); - String raw = request.getString(KEY_RAW); - String json = request.getString(KEY_JSON); - - try { - //强制作为条件且放在最前面优化性能 - request.remove(idKey); - request.remove(idInKey); - //关键词 - request.remove(KEY_ROLE); - request.remove(KEY_EXPLAIN); - request.remove(KEY_CACHE); - request.remove(KEY_DATABASE); - request.remove(KEY_SCHEMA); - request.remove(KEY_COMBINE); - request.remove(KEY_FROM); - request.remove(KEY_COLUMN); - request.remove(KEY_GROUP); - request.remove(KEY_HAVING); - request.remove(KEY_ORDER); - request.remove(KEY_RAW); - request.remove(KEY_JSON); - - String[] rawArr = StringUtil.split(raw); - config.setRaw(rawArr == null || rawArr.length <= 0 ? null : new ArrayList<>(Arrays.asList(rawArr))); - - Map tableWhere = new LinkedHashMap();//保证顺序好优化 WHERE id > 1 AND name LIKE... - - //已经remove了id和id{},以及@key - Set set = request.keySet(); //前面已经判断request是否为空 - if (method == POST) { //POST操作 - if (idIn != null) { - throw new IllegalArgumentException("POST 请求中不允许传 " + idInKey + " !"); - } - - if (set != null && set.isEmpty() == false) { //不能直接return,要走完下面的流程 - String[] columns = set.toArray(new String[]{}); - - Collection valueCollection = request.values(); - Object[] values = valueCollection == null ? null : valueCollection.toArray(); - - if (values == null || values.length != columns.length) { - throw new Exception("服务器内部错误:\n" + TAG - + " newSQLConfig values == null || values.length != columns.length !"); - } - column = (id == null ? "" : idKey + ",") + StringUtil.getString(columns); //set已经判断过不为空 - - List> valuess = new ArrayList<>(1); - List items; //(item0, item1, ...) - if (id == null) { //数据库自增 id - items = Arrays.asList(values); //FIXME 是否还需要进行 add 或 remove 操作?Arrays.ArrayList 不允许修改,会抛异常 - } - else { - int size = columns.length + (id == null ? 0 : 1); //以key数量为准 - - items = new ArrayList<>(size); - items.add(id); //idList.get(i)); //第0个就是id - - for (int j = 1; j < size; j++) { - items.add(values[j-1]); //从第1个开始,允许"null" - } - } - - valuess.add(items); - config.setValues(valuess); - } - } - else { //非POST操作 - final boolean isWhere = method != PUT;//除了POST,PUT,其它全是条件!!! - - //条件<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< - List whereList = null; - - Map> combineMap = new LinkedHashMap<>(); - List andList = new ArrayList<>(); - List orList = new ArrayList<>(); - List notList = new ArrayList<>(); - - //强制作为条件且放在最前面优化性能 - if (id != null) { - tableWhere.put(idKey, id); - andList.add(idKey); - } - if (idIn != null) { - tableWhere.put(idInKey, idIn); - andList.add(idInKey); - } - - String[] ws = StringUtil.split(combine); - if (ws != null) { - if (method == DELETE || method == GETS || method == HEADS) { - throw new IllegalArgumentException("DELETE,GETS,HEADS 请求不允许传 @combine:value !"); - } - whereList = new ArrayList<>(); - - String w; - for (int i = 0; i < ws.length; i++) { //去除 &,|,! 前缀 - w = ws[i]; - if (w != null) { - if (w.startsWith("&")) { - w = w.substring(1); - andList.add(w); - } - else if (w.startsWith("|")) { - if (method == PUT) { - throw new IllegalArgumentException(table + ":{} 里的 @combine:value 中的value里条件 " + ws[i] + " 不合法!" - + "PUT请求的 @combine:\"key0,key1,...\" 不允许传 |key 或 !key !"); - } - w = w.substring(1); - orList.add(w); - } - else if (w.startsWith("!")) { - if (method == PUT) { - throw new IllegalArgumentException(table + ":{} 里的 @combine:value 中的value里条件 " + ws[i] + " 不合法!" - + "PUT请求的 @combine:\"key0,key1,...\" 不允许传 |key 或 !key !"); - } - w = w.substring(1); - notList.add(w); - } - else { - orList.add(w); - } - - if (w.isEmpty()) { - throw new IllegalArgumentException(table + ":{} 里的 @combine:value 中的value里条件 " + ws[i] + " 不合法!不允许为空值!"); - } - else { - if (idKey.equals(w) || idInKey.equals(w) || userIdKey.equals(w) || userIdInKey.equals(w)) { - throw new UnsupportedOperationException(table + ":{} 里的 @combine:value 中的value里 " + ws[i] + " 不合法!" - + "不允许传 [" + idKey + ", " + idInKey + ", " + userIdKey + ", " + userIdInKey + "] 其中任何一个!"); - } - } - - whereList.add(w); - } - - // 可重写回调方法自定义处理 // 动态设置的场景似乎很少,而且去掉后不方便用户排错!//去掉判断,有时候不在没关系,如果是对增删改等非开放请求强制要求传对应的条件,可以用 Operation.NECESSARY - if (request.containsKey(w) == false) { //和 request.get(w) == null 没区别,前面 Parser 已经过滤了 null - // throw new IllegalArgumentException(table + ":{} 里的 @combine:value 中的value里 " + ws[i] + " 对应的 " + w + " 不在它里面!"); - callback.onMissingKey4Combine(table, request, combine, ws[i], w); - } - } - - } - - //条件>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> - - Map tableContent = new LinkedHashMap(); - Object value; - for (String key : set) { - value = request.get(key); - - if (value instanceof Map) {//只允许常规Object - throw new IllegalArgumentException("不允许 " + key + " 等任何key的value类型为 {JSONObject} !"); - } - - //解决AccessVerifier新增userId没有作为条件,而是作为内容,导致PUT,DELETE出错 - if (isWhere) { - tableWhere.put(key, value); - if (whereList == null || whereList.contains(key) == false) { - andList.add(key); - } - } - else if (whereList != null && whereList.contains(key)) { - tableWhere.put(key, value); - } - else { - tableContent.put(key, value);//一样 instanceof JSONArray ? JSON.toJSONString(value) : value); - } - } - - combineMap.put("&", andList); - combineMap.put("|", orList); - combineMap.put("!", notList); - config.setCombine(combineMap); - - config.setContent(tableContent); - } - - - List cs = new ArrayList<>(); - - List rawList = config.getRaw(); - boolean containColumnRaw = rawList != null && rawList.contains(KEY_COLUMN); - - String rawColumnSQL = null; - if (containColumnRaw) { - try { - rawColumnSQL = config.getRawSQL(KEY_COLUMN, column); - if (rawColumnSQL != null) { - cs.add(rawColumnSQL); - } - } catch (Exception e) { - Log.e(TAG, "newSQLConfig config instanceof AbstractSQLConfig >> try { " - + " rawColumnSQL = ((AbstractSQLConfig) config).getRawSQL(KEY_COLUMN, column); " - + "} catch (Exception e) = " + e.getMessage()); - } - } - - boolean distinct = column == null || rawColumnSQL != null ? false : column.startsWith(PREFFIX_DISTINCT); - if (rawColumnSQL == null) { - String[] fks = StringUtil.split(distinct ? column.substring(PREFFIX_DISTINCT.length()) : column, ";"); // key0,key1;fun0(key0,...);fun1(key0,...);key3;fun2(key0,...) - if (fks != null) { - String[] ks; - for (String fk : fks) { - if (containColumnRaw) { - try { - String rawSQL = config.getRawSQL(KEY_COLUMN, fk); - if (rawSQL != null) { - cs.add(rawSQL); - continue; - } - } catch (Exception e) { - Log.e(TAG, "newSQLConfig rawColumnSQL == null >> try { " - + " String rawSQL = ((AbstractSQLConfig) config).getRawSQL(KEY_COLUMN, fk); ... " - + "} catch (Exception e) = " + e.getMessage()); - } - } - - if (fk.contains("(")) { // fun0(key0,...) - cs.add(fk); - } - else { //key0,key1... - ks = StringUtil.split(fk); - if (ks != null && ks.length > 0) { - cs.addAll(Arrays.asList(ks)); - } - } - } - } - } - - config.setExplain(explain); - config.setCache(getCache(cache)); - config.setFrom(from); - config.setDistinct(distinct); - config.setColumn(column == null ? null : cs); //解决总是 config.column != null,总是不能得到 * - config.setWhere(tableWhere); - - config.setId(id); - //在 tableWhere 第0个 config.setIdIn(idIn); - - config.setRole(RequestRole.get(role)); - config.setGroup(group); - config.setHaving(having); - config.setOrder(order); - - String[] jsonArr = StringUtil.split(json); - config.setJson(jsonArr == null || jsonArr.length <= 0 ? null : new ArrayList<>(Arrays.asList(jsonArr))); - - //TODO 解析JOIN,包括 @column,@group 等要合并 - - } - finally {//后面还可能用到,要还原 - //id或id{}条件 - if (hasId) { - request.put(idKey, id); - } - request.put(idInKey, idIn); - //关键词 - request.put(KEY_DATABASE, database); - request.put(KEY_ROLE, role); - request.put(KEY_EXPLAIN, explain); - request.put(KEY_CACHE, cache); - request.put(KEY_SCHEMA, schema); - request.put(KEY_COMBINE, combine); - request.put(KEY_FROM, from); - request.put(KEY_COLUMN, column); - request.put(KEY_GROUP, group); - request.put(KEY_HAVING, having); - request.put(KEY_ORDER, order); - request.put(KEY_RAW, raw); - request.put(KEY_JSON, json); - } - - return config; - } - - - - /** - * @param method - * @param config - * @param joinList - * @param callback - * @return - * @throws Exception - */ - public static SQLConfig parseJoin(RequestMethod method, SQLConfig config, List joinList, Callback callback) throws Exception { - boolean isQuery = RequestMethod.isQueryMethod(method); - config.setKeyPrefix(isQuery && config.isMain() == false); - - //TODO 解析出 SQLConfig 再合并 column, order, group 等 - if (joinList == null || joinList.isEmpty() || RequestMethod.isQueryMethod(method) == false) { - return config; - } - - - String table; - String alias; - for (Join j : joinList) { - table = j.getTable(); - alias = j.getAlias(); - //JOIN子查询不能设置LIMIT,因为ON关系是在子查询后处理的,会导致结果会错误 - SQLConfig joinConfig = newSQLConfig(method, table, alias, j.getRequest(), null, false, callback); - SQLConfig cacheConfig = j.canCacheViceTable() == false ? null : newSQLConfig(method, table, alias, j.getRequest(), null, false, callback).setCount(1); - - if (j.isAppJoin() == false) { //除了 @ APP JOIN,其它都是 SQL JOIN,则副表要这样配置 - if (joinConfig.getDatabase() == null) { - joinConfig.setDatabase(config.getDatabase()); //解决主表 JOIN 副表,引号不一致 - } - else if (joinConfig.getDatabase().equals(config.getDatabase()) == false) { - throw new IllegalArgumentException("主表 " + config.getTable() + " 的 @database:" + config.getDatabase() + " 和它 SQL JOIN 的副表 " + table + " 的 @database:" + joinConfig.getDatabase() + " 不一致!"); - } - if (joinConfig.getSchema() == null) { - joinConfig.setSchema(config.getSchema()); //主表 JOIN 副表,默认 schema 一致 - } - - if (cacheConfig != null) { - cacheConfig.setDatabase(joinConfig.getDatabase()).setSchema(joinConfig.getSchema()); //解决主表 JOIN 副表,引号不一致 - } - - - if (isQuery) { - config.setKeyPrefix(true); - } - - joinConfig.setMain(false).setKeyPrefix(true); - - if (j.isLeftOrRightJoin()) { - SQLConfig outterConfig = newSQLConfig(method, table, alias, j.getOuter(), null, false, callback); - outterConfig.setMain(false).setKeyPrefix(true).setDatabase(joinConfig.getDatabase()).setSchema(joinConfig.getSchema()); //解决主表 JOIN 副表,引号不一致 - j.setOuterConfig(outterConfig); - } - } - - //解决 query: 1/2 查数量时报错 + private static final String TAG = "AbstractSQLConfig"; + + public static String DEFAULT_DATABASE = DATABASE_MYSQL; + public static String DEFAULT_SCHEMA = "sys"; + public static String PREFFIX_DISTINCT = "DISTINCT "; + + // * 和 / 不能同时出现,防止 /* */ 段注释! # 和 -- 不能出现,防止行注释! ; 不能出现,防止隔断SQL语句!空格不能出现,防止 CRUD,DROP,SHOW TABLES等语句! + private static final Pattern PATTERN_RANGE; + private static final Pattern PATTERN_FUNCTION; + + /** + * 表名映射,隐藏真实表名,对安全要求很高的表可以这么做 + */ + public static final Map TABLE_KEY_MAP; + public static final List CONFIG_TABLE_LIST; + public static final List DATABASE_LIST; + // 自定义原始 SQL 片段 Map:当 substring 为 null 时忽略;当 substring 为 "" 时整个 value 是 raw SQL;其它情况则只是 substring 这段为 raw SQL + public static final Map RAW_MAP; + // 允许调用的 SQL 函数:当 substring 为 null 时忽略;当 substring 为 "" 时整个 value 是 raw SQL;其它情况则只是 substring 这段为 raw SQL + public static final Map SQL_FUNCTION_MAP; + + static { // 凡是 SQL 边界符、分隔符、注释符 都不允许,例如 ' " ` ( ) ; # -- ,以免拼接 SQL 时被注入意外可执行指令 + PATTERN_RANGE = Pattern.compile("^[0-9%,!=\\<\\>/\\.\\+\\-\\*\\^]+$"); // ^[a-zA-Z0-9_*%!=<>(),"]+$ 导致 exists(select*from(Comment)) 通过! + PATTERN_FUNCTION = Pattern.compile("^[A-Za-z0-9%,:_@&~!=\\<\\>\\|\\[\\]\\{\\} /\\.\\+\\-\\*\\^\\?\\$]+$"); //TODO 改成更好的正则,校验前面为单词,中间为操作符,后面为值 + + + TABLE_KEY_MAP = new HashMap(); + TABLE_KEY_MAP.put(Table.class.getSimpleName(), Table.TABLE_NAME); + TABLE_KEY_MAP.put(Column.class.getSimpleName(), Column.TABLE_NAME); + TABLE_KEY_MAP.put(PgClass.class.getSimpleName(), PgClass.TABLE_NAME); + TABLE_KEY_MAP.put(PgAttribute.class.getSimpleName(), PgAttribute.TABLE_NAME); + TABLE_KEY_MAP.put(SysTable.class.getSimpleName(), SysTable.TABLE_NAME); + TABLE_KEY_MAP.put(SysColumn.class.getSimpleName(), SysColumn.TABLE_NAME); + TABLE_KEY_MAP.put(ExtendedProperty.class.getSimpleName(), ExtendedProperty.TABLE_NAME); + + CONFIG_TABLE_LIST = new ArrayList<>(); // Table, Column 等是系统表 AbstractVerifier.SYSTEM_ACCESS_MAP.keySet()); + CONFIG_TABLE_LIST.add(Function.class.getSimpleName()); + CONFIG_TABLE_LIST.add(Request.class.getSimpleName()); + CONFIG_TABLE_LIST.add(Response.class.getSimpleName()); + CONFIG_TABLE_LIST.add(Access.class.getSimpleName()); + CONFIG_TABLE_LIST.add(Document.class.getSimpleName()); + CONFIG_TABLE_LIST.add(TestRecord.class.getSimpleName()); + + + DATABASE_LIST = new ArrayList<>(); + DATABASE_LIST.add(DATABASE_MYSQL); + DATABASE_LIST.add(DATABASE_POSTGRESQL); + DATABASE_LIST.add(DATABASE_SQLSERVER); + DATABASE_LIST.add(DATABASE_ORACLE); + DATABASE_LIST.add(DATABASE_DB2); + + + RAW_MAP = new LinkedHashMap<>(); // 保证顺序,避免配置冲突等意外情况 + + + SQL_FUNCTION_MAP = new LinkedHashMap<>(); // 保证顺序,避免配置冲突等意外情况 + + // MySQL 字符串函数 + SQL_FUNCTION_MAP.put("ascii", ""); // ASCII(s) 返回字符串 s 的第一个字符的 ASCII 码。 + SQL_FUNCTION_MAP.put("char_length", ""); // CHAR_LENGTH(s) 返回字符串 s 的字符数 + SQL_FUNCTION_MAP.put("character_length", ""); // CHARACTER_LENGTH(s) 返回字符串 s 的字符数 + SQL_FUNCTION_MAP.put("concat", ""); // CONCAT(s1, s2...sn) 字符串 s1,s2 等多个字符串合并为一个字符串 + SQL_FUNCTION_MAP.put("concat_ws", ""); // CONCAT_WS(x, s1, s2...sn) 同 CONCAT(s1, s2 ...) 函数,但是每个字符串之间要加上 x,x 可以是分隔符 + SQL_FUNCTION_MAP.put("field", ""); // FIELD(s, s1, s2...) 返回第一个字符串 s 在字符串列表 (s1, s2...)中的位置 + SQL_FUNCTION_MAP.put("find_in_set", ""); // FIND_IN_SET(s1, s2) 返回在字符串s2中与s1匹配的字符串的位置 + SQL_FUNCTION_MAP.put("format", ""); // FORMAT(x, n) 函数可以将数字 x 进行格式化 "#,###.##", 将 x 保留到小数点后 n 位,最后一位四舍五入。 + SQL_FUNCTION_MAP.put("insert", ""); // INSERT(s1, x, len, s2) 字符串 s2 替换 s1 的 x 位置开始长度为 len 的字符串 + SQL_FUNCTION_MAP.put("locate", ""); // LOCATE(s1, s) 从字符串 s 中获取 s1 的开始位置 + SQL_FUNCTION_MAP.put("lcase", ""); // LCASE(s) 将字符串 s 的所有字母变成小写字母 + SQL_FUNCTION_MAP.put("left", ""); // LEFT(s, n) 返回字符串 s 的前 n 个字符 + SQL_FUNCTION_MAP.put("length", ""); // LENGTH(s) 返回字符串 s 的字符数 + SQL_FUNCTION_MAP.put("lower", ""); // LOWER(s) 将字符串 s 的所有字母变成小写字母 + SQL_FUNCTION_MAP.put("lpad", ""); // LPAD(s1, len, s2) 在字符串 s1 的开始处填充字符串 s2,使字符串长度达到 len + SQL_FUNCTION_MAP.put("ltrim", ""); // LTRIM(s) 去掉字符串 s 开始处的空格 + SQL_FUNCTION_MAP.put("mid", ""); // MID(s, n, len) 从字符串 s 的 n 位置截取长度为 len 的子字符串,同 SUBSTRING(s, n, len) + SQL_FUNCTION_MAP.put("position", ""); // POSITION(s, s1); 从字符串 s 中获取 s1 的开始位置 + SQL_FUNCTION_MAP.put("repeat", ""); // REPEAT(s, n) 将字符串 s 重复 n 次 + SQL_FUNCTION_MAP.put("replace", ""); // REPLACE(s, s1, s2) 将字符串 s2 替代字符串 s 中的字符串 s1 + SQL_FUNCTION_MAP.put("reverse", ""); // REVERSE(s); // ) 将字符串s的顺序反过来 + SQL_FUNCTION_MAP.put("right", ""); // RIGHT(s, n) 返回字符串 s 的后 n 个字符 + SQL_FUNCTION_MAP.put("rpad", ""); // RPAD(s1, len, s2) 在字符串 s1 的结尾处添加字符串 s2,使字符串的长度达到 len + SQL_FUNCTION_MAP.put("rtrim", ""); // RTRIM", ""); // ) 去掉字符串 s 结尾处的空格 + SQL_FUNCTION_MAP.put("space", ""); // SPACE(n) 返回 n 个空格 + SQL_FUNCTION_MAP.put("strcmp", ""); // STRCMP(s1, s2) 比较字符串 s1 和 s2,如果 s1 与 s2 相等返回 0 ,如果 s1>s2 返回 1,如果 s1d2 之间相隔的天数 + SQL_FUNCTION_MAP.put("date_add", ""); // DATE_ADD(d,INTERVAL expr type) 计算起始日期 d 加上一个时间段后的日期 + SQL_FUNCTION_MAP.put("date_format", ""); // DATE_FORMAT(d,f) 按表达式 f的要求显示日期 d + SQL_FUNCTION_MAP.put("date_sub", ""); // DATE_SUB(date,INTERVAL expr type) 函数从日期减去指定的时间间隔。 + SQL_FUNCTION_MAP.put("day", ""); // DAY(d) 返回日期值 d 的日期部分 + SQL_FUNCTION_MAP.put("dayname", ""); // DAYNAME(d) 返回日期 d 是星期几,如 Monday,Tuesday + SQL_FUNCTION_MAP.put("dayofmonth", ""); // DAYOFMONTH(d) 计算日期 d 是本月的第几天 + SQL_FUNCTION_MAP.put("dayofweek", ""); // DAYOFWEEK(d) 日期 d 今天是星期几,1 星期日,2 星期一,以此类推 + SQL_FUNCTION_MAP.put("dayofyear", ""); // DAYOFYEAR(d) 计算日期 d 是本年的第几天 + SQL_FUNCTION_MAP.put("extract", ""); // EXTRACT(type FROM d) 从日期 d 中获取指定的值,type 指定返回的值。 + SQL_FUNCTION_MAP.put("from_days", ""); // FROM_DAYS(n) 计算从 0000 年 1 月 1 日开始 n 天后的日期 + SQL_FUNCTION_MAP.put("hour", ""); // 'HOUR(t) 返回 t 中的小时值 + SQL_FUNCTION_MAP.put("last_day", ""); // LAST_DAY(d) 返回给给定日期的那一月份的最后一天 + SQL_FUNCTION_MAP.put("localtime", ""); // LOCALTIME() 返回当前日期和时间 + SQL_FUNCTION_MAP.put("localtimestamp", ""); // LOCALTIMESTAMP() 返回当前日期和时间 + SQL_FUNCTION_MAP.put("makedate", ""); // MAKEDATE(year, day-of-year) 基于给定参数年份 year 和所在年中的天数序号 day-of-year 返回一个日期 + SQL_FUNCTION_MAP.put("maketime", ""); // MAKETIME(hour, minute, second) 组合时间,参数分别为小时、分钟、秒 + SQL_FUNCTION_MAP.put("microsecond", ""); // MICROSECOND(date) 返回日期参数所对应的微秒数 + SQL_FUNCTION_MAP.put("minute", ""); // MINUTE(t) 返回 t 中的分钟值 + SQL_FUNCTION_MAP.put("monthname", ""); // MONTHNAME(d) 返回日期当中的月份名称,如 November + SQL_FUNCTION_MAP.put("month", ""); // MONTH(d) 返回日期d中的月份值,1 到 12 + SQL_FUNCTION_MAP.put("now", ""); // NOW() 返回当前日期和时间 + SQL_FUNCTION_MAP.put("period_add", ""); // PERIOD_ADD(period, number) 为 年-月 组合日期添加一个时段 + SQL_FUNCTION_MAP.put("period_diff", ""); // PERIOD_DIFF(period1, period2) 返回两个时段之间的月份差值 + SQL_FUNCTION_MAP.put("quarter", ""); // QUARTER(d) 返回日期d是第几季节,返回 1 到 4 + SQL_FUNCTION_MAP.put("second", ""); // SECOND(t) 返回 t 中的秒钟值 + SQL_FUNCTION_MAP.put("sec_to_time", ""); // SEC_TO_TIME", ""); // ) 将以秒为单位的时间 s 转换为时分秒的格式 + SQL_FUNCTION_MAP.put("str_to_date", ""); // STR_TO_DATE", ""); // tring, format_mask) 将字符串转变为日期 + SQL_FUNCTION_MAP.put("subdate", ""); // SUBDATE(d,n) 日期 d 减去 n 天后的日期 + SQL_FUNCTION_MAP.put("subtime", ""); // SUBTIME(t,n) 时间 t 减去 n 秒的时间 + SQL_FUNCTION_MAP.put("sysdate", ""); // SYSDATE() 返回当前日期和时间 + SQL_FUNCTION_MAP.put("time", ""); // TIME(expression) 提取传入表达式的时间部分 + SQL_FUNCTION_MAP.put("time_format", ""); // TIME_FORMAT(t,f) 按表达式 f 的要求显示时间 t + SQL_FUNCTION_MAP.put("time_to_sec", ""); // TIME_TO_SEC(t) 将时间 t 转换为秒 + SQL_FUNCTION_MAP.put("timediff", ""); // TIMEDIFF(time1, time2) 计算时间差值 + SQL_FUNCTION_MAP.put("timestamp", ""); // TIMESTAMP(expression, interval) 单个参数时,函数返回日期或日期时间表达式;有2个参数时,将参数加和 + SQL_FUNCTION_MAP.put("to_days", ""); // TO_DAYS(d) 计算日期 d 距离 0000 年 1 月 1 日的天数 + SQL_FUNCTION_MAP.put("week", ""); // WEEK(d) 计算日期 d 是本年的第几个星期,范围是 0 到 53 + SQL_FUNCTION_MAP.put("weekday", ""); // WEEKDAY(d) 日期 d 是星期几,0 表示星期一,1 表示星期二 + SQL_FUNCTION_MAP.put("weekofyear", ""); // WEEKOFYEAR(d) 计算日期 d 是本年的第几个星期,范围是 0 到 53 + SQL_FUNCTION_MAP.put("year", ""); // YEAR(d) 返回年份 + SQL_FUNCTION_MAP.put("yearweek", ""); // YEARWEEK(date, mode) 返回年份及第几周(0到53),mode 中 0 表示周天,1表示周一,以此类推 + SQL_FUNCTION_MAP.put("unix_timestamp", ""); // UNIX_TIMESTAMP(date) 获取UNIX时间戳函数,返回一个以 UNIX 时间戳为基础的无符号整数 + SQL_FUNCTION_MAP.put("from_unixtime", ""); // FROM_UNIXTIME(date) 将 UNIX 时间戳转换为时间格式,与UNIX_TIMESTAMP互为反函数 + + // MYSQL JSON 函数 + SQL_FUNCTION_MAP.put("json_append", ""); // JSON_APPEND(json_doc, path, val[, path, val] ...)) 插入JSON数组 + SQL_FUNCTION_MAP.put("json_array", ""); // JSON_ARRAY(val1, val2...) 创建JSON数组 + SQL_FUNCTION_MAP.put("json_array_append", ""); // JSON_ARRAY_APPEND(json_doc, val) 将数据附加到JSON文档 + SQL_FUNCTION_MAP.put("json_array_insert", ""); // JSON_ARRAY_INSERT(json_doc, val) 插入JSON数组 + SQL_FUNCTION_MAP.put("json_contains", ""); // JSON_CONTAINS(json_doc, val) JSON文档是否在路径中包含特定对象 + SQL_FUNCTION_MAP.put("json_contains_path", ""); // JSON_CONTAINS_PATH(json_doc, path) JSON文档是否在路径中包含任何数据 + SQL_FUNCTION_MAP.put("json_depth", ""); // JSON_DEPTH(json_doc) JSON文档的最大深度 + SQL_FUNCTION_MAP.put("json_extract", ""); // JSON_EXTRACT(json_doc, path) 从JSON文档返回数据 + SQL_FUNCTION_MAP.put("json_insert", ""); // JSON_INSERT(json_doc, val) 将数据插入JSON文档 + SQL_FUNCTION_MAP.put("json_keys", ""); // JSON_KEYS(json_doc[, path]) JSON文档中的键数组 + SQL_FUNCTION_MAP.put("json_length", ""); // JSON_LENGTH(json_doc) JSON文档中的元素数 + SQL_FUNCTION_MAP.put("json_merge", ""); // JSON_MERGE(json_doc1, json_doc2) (已弃用) 合并JSON文档,保留重复的键。JSON_MERGE_PRESERVE()的已弃用同义词 + SQL_FUNCTION_MAP.put("json_merge_patch", ""); // JSON_MERGE_PATCH(json_doc1, json_doc2) 合并JSON文档,替换重复键的值 + SQL_FUNCTION_MAP.put("json_merge_preserve", ""); // JSON_MERGE_PRESERVE(json_doc1, json_doc2) 合并JSON文档,保留重复的键 + SQL_FUNCTION_MAP.put("json_object", ""); // JSON_OBJECT(key1, val1, key2, val2...) 创建JSON对象 + SQL_FUNCTION_MAP.put("json_overlaps", ""); // JSON_OVERLAPS(json_doc1, json_doc2) (引入8.0.17) 比较两个JSON文档,如果它们具有相同的键值对或数组元素,则返回TRUE(1),否则返回FALSE(0) + SQL_FUNCTION_MAP.put("json_pretty", ""); // JSON_PRETTY(json_doc) 以易于阅读的格式打印JSON文档 + SQL_FUNCTION_MAP.put("json_quote", ""); // JSON_QUOTE(json_doc1) 引用JSON文档 + SQL_FUNCTION_MAP.put("json_remove", ""); // JSON_REMOVE(json_doc1, path) 从JSON文档中删除数据 + SQL_FUNCTION_MAP.put("json_replace", ""); // JSON_REPLACE(json_doc1, val1, val2) 替换JSON文档中的值 + SQL_FUNCTION_MAP.put("json_schema_valid", ""); // JSON_SCHEMA_VALID(json_doc) (引入8.0.17) 根据JSON模式验证JSON文档;如果文档针对架构进行验证,则返回TRUE / 1;否则,则返回FALSE / 0 + SQL_FUNCTION_MAP.put("json_schema_validation_report", ""); // JSON_SCHEMA_VALIDATION_REPORT(json_doc, mode) (引入8.0.17) 根据JSON模式验证JSON文档;以JSON格式返回有关验证结果的报告,包括成功或失败以及失败原因 + SQL_FUNCTION_MAP.put("json_search", ""); // JSON_SEARCH(json_doc, val) JSON文档中值的路径 + SQL_FUNCTION_MAP.put("json_set", ""); // JSON_SET(json_doc, val) 将数据插入JSON文档 + // SQL_FUNCTION_MAP.put("json_storage_free", ""); // JSON_STORAGE_FREE() 部分更新后,JSON列值的二进制表示形式中的可用空间 + // SQL_FUNCTION_MAP.put("json_storage_size", ""); // JSON_STORAGE_SIZE() 用于存储JSON文档的二进制表示的空间 + SQL_FUNCTION_MAP.put("json_table", ""); // JSON_TABLE() 从JSON表达式返回数据作为关系表 + SQL_FUNCTION_MAP.put("json_type", ""); // JSON_TYPE(json_doc) JSON值类型 + SQL_FUNCTION_MAP.put("json_unquote", ""); // JSON_UNQUOTE(json_doc) 取消引用JSON值 + SQL_FUNCTION_MAP.put("json_valid", ""); // JSON_VALID(json_doc) JSON值是否有效 + SQL_FUNCTION_MAP.put("json_arrayagg", ""); // JSON_ARRAYAGG(key) 将每个表达式转换为 JSON 值,然后返回一个包含这些 JSON 值的 JSON 数组 + SQL_FUNCTION_MAP.put("json_objectagg", ""); // JSON_OBJECTAGG(key, val)) 将每个表达式转换为 JSON 值,然后返回一个包含这些 JSON 值的 JSON 对象 + + // MySQL 高级函数 + // SQL_FUNCTION_MAP.put("bin", ""); // BIN(x) 返回 x 的二进制编码 + // SQL_FUNCTION_MAP.put("binary", ""); // BINARY(s) 将字符串 s 转换为二进制字符串 + SQL_FUNCTION_MAP.put("case", ""); // CASE 表示函数开始,END 表示函数结束。如果 condition1 成立,则返回 result1, 如果 condition2 成立,则返回 result2,当全部不成立则返回 result,而当有一个成立之后,后面的就不执行了。 + SQL_FUNCTION_MAP.put("cast", ""); // CAST(x AS type) 转换数据类型 + SQL_FUNCTION_MAP.put("coalesce", ""); // COALESCE(expr1, expr2, ...., expr_n) 返回参数中的第一个非空表达式(从左向右) + // SQL_FUNCTION_MAP.put("conv", ""); // CONV(x,f1,f2) 返回 f1 进制数变成 f2 进制数 + // SQL_FUNCTION_MAP.put("convert", ""); // CONVERT(s, cs) 函数将字符串 s 的字符集变成 cs + SQL_FUNCTION_MAP.put("if", ""); // IF(expr,v1,v2) 如果表达式 expr 成立,返回结果 v1;否则,返回结果 v2。 + SQL_FUNCTION_MAP.put("ifnull", ""); // IFNULL(v1,v2) 如果 v1 的值不为 NULL,则返回 v1,否则返回 v2。 + SQL_FUNCTION_MAP.put("isnull", ""); // ISNULL(expression) 判断表达式是否为 NULL + SQL_FUNCTION_MAP.put("nullif", ""); // NULLIF(expr1, expr2) 比较两个字符串,如果字符串 expr1 与 expr2 相等 返回 NULL,否则返回 expr1 + SQL_FUNCTION_MAP.put("group_concat", ""); // GROUP_CONCAT([DISTINCT], s1, s2...) + + } + + + @Override + public boolean limitSQLCount() { + return Log.DEBUG == false || AbstractVerifier.SYSTEM_ACCESS_MAP.containsKey(getTable()) == false; + } + + @NotNull + @Override + public String getIdKey() { + return KEY_ID; + } + + @NotNull + @Override + public String getUserIdKey() { + return KEY_USER_ID; + } + + + private Object id; //Table的id + private RequestMethod method; //操作方法 + private boolean prepared = true; //预编译 + private boolean main = true; + /** + * TODO 被关联的表通过就忽略关联的表?(这个不行 User:{"sex@":"/Comment/toId"}) + */ + private RequestRole role; //发送请求的用户的角色 + private boolean distinct = false; + private String database; //表所在的数据库类型 + private String schema; //表所在的数据库名 + private String datasource; //数据源 + private String table; //表名 + private String alias; //表别名 + private String group; //分组方式的字符串数组,','分隔 + private String having; //聚合函数的字符串数组,','分隔 + private String order; //排序方式的字符串数组,','分隔 + private List raw; //需要保留原始 SQL 的字段,','分隔 + private List json; //需要转为 JSON 的字段,','分隔 + private Subquery from; //子查询临时表 + private List column; //表内字段名(或函数名,仅查询操作可用)的字符串数组,','分隔 + private List> values; //对应表内字段的值的字符串数组,','分隔 + private Map content; //Request内容,key:value形式,column = content.keySet(),values = content.values() + private Map where; //筛选条件,key:value形式 + private Map> combine; //条件组合,{ "&":[key], "|":[key], "!":[key] } + + + //array item <<<<<<<<<< + private int count; //Table数量 + private int page; //Table所在页码 + private int position; //Table在[]中的位置 + private int query; //JSONRequest.query + private int type; //ObjectParser.type + private int cache; + private boolean explain; + + private List joinList; //连表 配置列表 + //array item >>>>>>>>>> + private boolean test; //测试 + + private String procedure; + + public SQLConfig setProcedure(String procedure) { + this.procedure = procedure; + return this; + } + + public String getProcedure() { + return procedure; + } + + public AbstractSQLConfig(RequestMethod method) { + setMethod(method); + } + + public AbstractSQLConfig(RequestMethod method, String table) { + this(method); + setTable(table); + } + + public AbstractSQLConfig(RequestMethod method, int count, int page) { + this(method); + setCount(count); + setPage(page); + } + + @NotNull + @Override + public RequestMethod getMethod() { + if (method == null) { + method = GET; + } + return method; + } + + @Override + public AbstractSQLConfig setMethod(RequestMethod method) { + this.method = method; + return this; + } + + @Override + public boolean isPrepared() { + return prepared; + } + + @Override + public AbstractSQLConfig setPrepared(boolean prepared) { + this.prepared = prepared; + return this; + } + + @Override + public boolean isMain() { + return main; + } + + @Override + public AbstractSQLConfig setMain(boolean main) { + this.main = main; + return this; + } + + + @Override + public Object getId() { + return id; + } + + @Override + public AbstractSQLConfig setId(Object id) { + this.id = id; + return this; + } + + @Override + public RequestRole getRole() { + //不能 @NotNull , AbstractParser#getSQLObject 内当getRole() == null时填充默认值 + return role; + } + + public AbstractSQLConfig setRole(String roleName) throws Exception { + return setRole(RequestRole.get(roleName)); + } + + @Override + public AbstractSQLConfig setRole(RequestRole role) { + this.role = role; + return this; + } + + @Override + public boolean isDistinct() { + return distinct; + } + + @Override + public SQLConfig setDistinct(boolean distinct) { + this.distinct = distinct; + return this; + } + + @Override + public String getDatabase() { + return database; + } + + @Override + public SQLConfig setDatabase(String database) { + this.database = database; + return this; + } + + /** + * @return db == null ? DEFAULT_DATABASE : db + */ + @NotNull + public String getSQLDatabase() { + String db = getDatabase(); + return db == null ? DEFAULT_DATABASE : db; // "" 表示已设置,不需要用全局默认的 StringUtil.isEmpty(db, false)) { + } + + @Override + public boolean isMySQL() { + return isMySQL(getSQLDatabase()); + } + + public static boolean isMySQL(String db) { + return DATABASE_MYSQL.equals(db); + } + + @Override + public boolean isPostgreSQL() { + return isPostgreSQL(getSQLDatabase()); + } + + public static boolean isPostgreSQL(String db) { + return DATABASE_POSTGRESQL.equals(db); + } + + @Override + public boolean isSQLServer() { + return isSQLServer(getSQLDatabase()); + } + + public static boolean isSQLServer(String db) { + return DATABASE_SQLSERVER.equals(db); + } + + @Override + public boolean isOracle() { + return isOracle(getSQLDatabase()); + } + + public static boolean isOracle(String db) { + return DATABASE_ORACLE.equals(db); + } + + @Override + public boolean isDb2() { + return isDb2(getSQLDatabase()); + } + + public static boolean isDb2(String db) { + return DATABASE_DB2.equals(db); + } + + @Override + public String getQuote() { + return isMySQL() ? "`" : "\""; + } + + @Override + public String getSchema() { + return schema; + } + + /** + * @param sqlTable + * @return + */ + @NotNull + public String getSQLSchema() { + String table = getTable(); + //强制,避免因为全局默认的 @schema 自动填充进来,导致这几个类的 schema 为 sys 等其它值 + if (Table.TAG.equals(table) || Column.TAG.equals(table)) { + return SCHEMA_INFORMATION; //MySQL, PostgreSQL, SQL Server 都有的 + } + if (PgClass.TAG.equals(table) || PgAttribute.TAG.equals(table)) { + return ""; //PostgreSQL 的 pg_class 和 pg_attribute 表好像不属于任何 Schema + } + if (SysTable.TAG.equals(table) || SysColumn.TAG.equals(table) || ExtendedProperty.TAG.equals(table)) { + return SCHEMA_SYS; //SQL Server 在 sys 中的属性比 information_schema 中的要全,能拿到注释 + } + + String sch = getSchema(); + return sch == null ? DEFAULT_SCHEMA : sch; + } + + @Override + public AbstractSQLConfig setSchema(String schema) { + if (schema != null) { + String quote = getQuote(); + String s = schema.startsWith(quote) && schema.endsWith(quote) ? schema.substring(1, schema.length() - 1) : schema; + if (StringUtil.isEmpty(s, true) == false && StringUtil.isName(s) == false) { + throw new IllegalArgumentException("@schema:value 中value必须是1个单词!"); + } + } + this.schema = schema; + return this; + } + + @Override + public String getDatasource() { + return datasource; + } + + @Override + public SQLConfig setDatasource(String datasource) { + this.datasource = datasource; + return this; + } + + /** + * 请求传进来的Table名 + * + * @return + * @see {@link #getSQLTable()} + */ + @Override + public String getTable() { + return table; + } + + /** + * 数据库里的真实Table名 + * 通过 {@link #TABLE_KEY_MAP} 映射 + * + * @return + */ + @JSONField(serialize = false) + @Override + public String getSQLTable() { + // String t = TABLE_KEY_MAP.containsKey(table) ? TABLE_KEY_MAP.get(table) : table; + //如果要强制小写,则可在子类重写这个方法再 toLowerCase return DATABASE_POSTGRESQL.equals(getDatabase()) ? t.toLowerCase() : t; + return TABLE_KEY_MAP.containsKey(table) ? TABLE_KEY_MAP.get(table) : table; + } + + @JSONField(serialize = false) + @Override + public String getTablePath() { + String q = getQuote(); + + String sch = getSQLSchema(); + String sqlTable = getSQLTable(); + + return (StringUtil.isEmpty(sch, true) ? "" : q + sch + q + ".") + q + sqlTable + q + (isKeyPrefix() ? " AS " + getAliasWithQuote() : ""); + } + + @Override + public AbstractSQLConfig setTable(String table) { //Table已经在Parser中校验,所以这里不用防SQL注入 + this.table = table; + return this; + } + + @Override + public String getAlias() { + return alias; + } + + @Override + public AbstractSQLConfig setAlias(String alias) { + this.alias = alias; + return this; + } + + public String getAliasWithQuote() { + String a = getAlias(); + if (StringUtil.isEmpty(a, true)) { + a = getTable(); + } + String q = getQuote(); + //getTable 不能小写,因为Verifier用大小写敏感的名称判断权限 + //如果要强制小写,则可在子类重写这个方法再 toLowerCase return q + (DATABASE_POSTGRESQL.equals(getDatabase()) ? a.toLowerCase() : a) + q; + return q + a + q; + } + + @Override + public String getGroup() { + return group; + } + + public AbstractSQLConfig setGroup(String... keys) { + return setGroup(StringUtil.getString(keys)); + } + + @Override + public AbstractSQLConfig setGroup(String group) { + this.group = group; + return this; + } + + @JSONField(serialize = false) + public String getGroupString(boolean hasPrefix) { + //加上子表的 group + String joinGroup = ""; + if (joinList != null) { + SQLConfig cfg; + String c; + boolean first = true; + for (Join j : joinList) { + if (j.isAppJoin()) { + continue; + } + + cfg = j.isLeftOrRightJoin() ? j.getOuterConfig() : j.getJoinConfig(); + if (StringUtil.isEmpty(cfg.getAlias(), true)) { + cfg.setAlias(cfg.getTable()); + } + + c = ((AbstractSQLConfig) cfg).getGroupString(false); + if (StringUtil.isEmpty(c, true) == false) { + joinGroup += (first ? "" : ", ") + c; + first = false; + } + + } + } + + + group = StringUtil.getTrimedString(group); + String[] keys = StringUtil.split(group); + if (keys == null || keys.length <= 0) { + return StringUtil.isEmpty(joinGroup, true) ? "" : (hasPrefix ? " GROUP BY " : "") + joinGroup; + } + + for (int i = 0; i < keys.length; i++) { + if (isPrepared()) { //不能通过 ? 来代替,因为SQLExecutor statement.setString后 GROUP BY 'userId' 有单引号,只能返回一条数据,必须去掉单引号才行! + if (StringUtil.isName(keys[i]) == false) { + throw new IllegalArgumentException("@group:value 中 value里面用 , 分割的每一项都必须是1个单词!并且不要有空格!"); + } + } + + keys[i] = getKey(keys[i]); + } + + return (hasPrefix ? " GROUP BY " : "") + StringUtil.concat(StringUtil.getString(keys), joinGroup, ", "); + } + + @Override + public String getHaving() { + return having; + } + + public AbstractSQLConfig setHaving(String... conditions) { + return setHaving(StringUtil.getString(conditions)); + } + + @Override + public AbstractSQLConfig setHaving(String having) { + this.having = having; + return this; + } + + /** + * TODO @having 改为默认 | 或连接,且支持 @having: { "key1>": 1, "key{}": "length(key2)>0", "@combine": "key1,key2" } + * + * @return HAVING conditoin0 AND condition1 OR condition2 ... + */ + @JSONField(serialize = false) + public String getHavingString(boolean hasPrefix) { + //加上子表的 having + String joinHaving = ""; + if (joinList != null) { + SQLConfig cfg; + String c; + boolean first = true; + for (Join j : joinList) { + if (j.isAppJoin()) { + continue; + } + + cfg = j.isLeftOrRightJoin() ? j.getOuterConfig() : j.getJoinConfig(); + if (StringUtil.isEmpty(cfg.getAlias(), true)) { + cfg.setAlias(cfg.getTable()); + } + + c = ((AbstractSQLConfig) cfg).getHavingString(false); + if (StringUtil.isEmpty(c, true) == false) { + joinHaving += (first ? "" : ", ") + c; + first = false; + } + + } + } + + String[] keys = StringUtil.split(getHaving(), ";"); + if (keys == null || keys.length <= 0) { + return StringUtil.isEmpty(joinHaving, true) ? "" : (hasPrefix ? " HAVING " : "") + joinHaving; + } + + String quote = getQuote(); + String tableAlias = getAliasWithQuote(); + + List raw = getRaw(); + boolean containRaw = raw != null && raw.contains(KEY_HAVING); + + String expression; + String method; + //暂时不允许 String prefix; + String suffix; + + //fun0(arg0,arg1,...);fun1(arg0,arg1,...) + for (int i = 0; i < keys.length; i++) { + + //fun(arg0,arg1,...) + expression = keys[i]; + if (containRaw) { + try { + String rawSQL = getRawSQL(KEY_HAVING, expression); + if (rawSQL != null) { + keys[i] = rawSQL; + continue; + } + } catch (Exception e) { + Log.e(TAG, "newSQLConfig rawColumnSQL == null >> try { " + + " String rawSQL = ((AbstractSQLConfig) config).getRawSQL(KEY_COLUMN, fk); ... " + + "} catch (Exception e) = " + e.getMessage()); + } + } + + if (expression.length() > 50) { + throw new UnsupportedOperationException("@having:value 的 value 中字符串 " + expression + " 不合法!" + + "不允许传超过 50 个字符的函数或表达式!请用 @raw 简化传参!"); + } + + int start = expression.indexOf("("); + if (start < 0) { + if (isPrepared() && PATTERN_FUNCTION.matcher(expression).matches() == false) { + throw new UnsupportedOperationException("字符串 " + expression + " 不合法!" + + "预编译模式下 @having:\"column?value;function(arg0,arg1,...)?value...\"" + + " 中 column?value 必须符合正则表达式 " + PATTERN_FUNCTION + " 且不包含连续减号 -- !不允许空格!"); + } + continue; + } + + int end = expression.lastIndexOf(")"); + if (start >= end) { + throw new IllegalArgumentException("字符 " + expression + " 不合法!" + + "@having:value 中 value 里的 SQL函数必须为 function(arg0,arg1,...) 这种格式!"); + } + + method = expression.substring(0, start); + if (method.isEmpty() == false) { + if (SQL_FUNCTION_MAP == null || SQL_FUNCTION_MAP.isEmpty()) { + if (StringUtil.isName(method) == false) { + throw new IllegalArgumentException("字符 " + method + " 不合法!" + + "预编译模式下 @having:\"column?value;function(arg0,arg1,...)?value...\"" + + " 中 function 必须符合小写英文单词的 SQL 函数名格式!"); + } + } else if (SQL_FUNCTION_MAP.containsKey(method) == false) { + throw new IllegalArgumentException("字符 " + method + " 不合法!" + + "预编译模式下 @column:\"column0,column1:alias;function0(arg0,arg1,...);function1(...):alias...\"" + + " 中 function 必须符合小写英文单词的 SQL 函数名格式!且必须是后端允许调用的 SQL 函数!"); + } + } + + suffix = expression.substring(end + 1, expression.length()); + + if (isPrepared() && (((String) suffix).contains("--") || ((String) suffix).contains("/*") || PATTERN_RANGE.matcher((String) suffix).matches() == false)) { + throw new UnsupportedOperationException("字符串 " + suffix + " 不合法!" + + "预编译模式下 @having:\"column?value;function(arg0,arg1,...)?value...\"" + + " 中 ?value 必须符合正则表达式 " + PATTERN_RANGE + " 且不包含连续减号 -- 或注释符 /* !不允许多余的空格!"); + } + + String[] ckeys = StringUtil.split(expression.substring(start + 1, end)); + + if (ckeys != null) { + for (int j = 0; j < ckeys.length; j++) { + String origin = ckeys[j]; + + if (isPrepared()) { + if (origin.startsWith("_") || origin.contains("--") || PATTERN_FUNCTION.matcher(origin).matches() == false) { + throw new IllegalArgumentException("字符 " + ckeys[j] + " 不合法!" + + "预编译模式下 @having:\"column?value;function(arg0,arg1,...)?value...\"" + + " 中所有 column, arg 都必须是1个不以 _ 开头的单词 或者 符合正则表达式 " + PATTERN_FUNCTION + " 且不包含连续减号 -- !不允许多余的空格!"); + } + } + + //JOIN 副表不再在外层加副表名前缀 userId AS `Commet.userId`, 而是直接 userId AS `userId` + boolean isName = false; + if (StringUtil.isNumer(origin)) { + //do nothing + } else if (StringUtil.isName(origin)) { + origin = quote + origin + quote; + isName = true; + } else { + origin = getValue(origin).toString(); + } + + ckeys[j] = (isName && isKeyPrefix() ? tableAlias + "." : "") + origin; + } + } + + keys[i] = method + "(" + StringUtil.getString(ckeys) + ")" + suffix; + } + + //TODO 支持 OR, NOT 参考 @combine:"&key0,|key1,!key2" + return (hasPrefix ? " HAVING " : "") + StringUtil.concat(StringUtil.getString(keys, AND), joinHaving, AND); + } + + @Override + public String getOrder() { + return order; + } + + public AbstractSQLConfig setOrder(String... conditions) { + return setOrder(StringUtil.getString(conditions)); + } + + @Override + public AbstractSQLConfig setOrder(String order) { + this.order = order; + return this; + } + + @JSONField(serialize = false) + public String getOrderString(boolean hasPrefix) { + //加上子表的 order + String joinOrder = ""; + if (joinList != null) { + SQLConfig cfg; + String c; + boolean first = true; + for (Join j : joinList) { + if (j.isAppJoin()) { + continue; + } + + cfg = j.isLeftOrRightJoin() ? j.getOuterConfig() : j.getJoinConfig(); + if (StringUtil.isEmpty(cfg.getAlias(), true)) { + cfg.setAlias(cfg.getTable()); + } + + c = ((AbstractSQLConfig) cfg).getOrderString(false); + if (StringUtil.isEmpty(c, true) == false) { + joinOrder += (first ? "" : ", ") + c; + first = false; + } + + } + } + + + String order = StringUtil.getTrimedString(getOrder()); + // SELECT * FROM sys.Moment ORDER BY userId ASC, rand(); 前面的 userId ASC 和后面的 rand() 都有效 + // if ("rand()".equals(order)) { + // return (hasPrefix ? " ORDER BY " : "") + StringUtil.concat(order, joinOrder, ", "); + // } + + if (getCount() > 0 && (isOracle() || isSQLServer() || isDb2())) { // Oracle, SQL Server, DB2 的 OFFSET 必须加 ORDER BY + + // String[] ss = StringUtil.split(order); + if (StringUtil.isEmpty(order, true)) { //SQL Server 子查询内必须指定 OFFSET 才能用 ORDER BY + String idKey = getIdKey(); + if (StringUtil.isEmpty(idKey, true)) { + idKey = "id"; //ORDER BY NULL 不行,SQL Server 会报错,必须要有排序,才能使用 OFFSET FETCH,如果没有 idKey,请求中指定 @order 即可 + } + order = idKey; //让数据库调控默认升序还是降序 + "+"; + } + + //不用这么全面,毕竟没有语法问题还浪费性能,如果有其它问题,让前端传的 JSON 直接加上 @order 来解决 + // boolean contains = false; + // if (ss != null) { + // for (String s : ss) { + // if (s != null && s.startsWith(idKey)) { + // s = s.substring(idKey.length()); + // if ("+".equals(s) || "-".equals(s)) {// || " ASC ".equals(s) || " DESC ".equals(s)) { + // contains = true; + // break; + // } + // } + // } + // } + + // if (contains == false) { + // order = (ss == null || ss.length <= 0 ? "" : order + ",") + idKey + "+"; + // } + } + + + String[] keys = StringUtil.split(order); + if (keys == null || keys.length <= 0) { + return StringUtil.isEmpty(joinOrder, true) ? "" : (hasPrefix ? " ORDER BY " : "") + joinOrder; + } + + for (int i = 0; i < keys.length; i++) { + String item = keys[i]; + if ("rand()".equals(item)) { + continue; + } + + int index = item.endsWith("+") ? item.length() - 1 : -1; //StringUtil.split返回数组中,子项不会有null + String sort; + if (index < 0) { + index = item.endsWith("-") ? item.length() - 1 : -1; + sort = index <= 0 ? "" : " DESC "; + } else { + sort = " ASC "; + } + + String origin = index < 0 ? item : item.substring(0, index); + + if (isPrepared()) { //不能通过 ? 来代替,SELECT 'id','name' 返回的就是 id:"id", name:"name",而不是数据库里的值! + //这里既不对origin trim,也不对 ASC/DESC ignoreCase,希望前端严格传没有任何空格的字符串过来,减少传输数据量,节约服务器性能 + if (StringUtil.isName(origin) == false) { + throw new IllegalArgumentException("预编译模式下 @order:value 中 " + item + " 不合法! value 里面用 , 分割的" + + "每一项必须是 随机函数 rand() 或 column+ / column- 且其中 column 必须是 1 个单词!并且不要有多余的空格!"); + } + } + + keys[i] = getKey(origin) + sort; + } + + return (hasPrefix ? " ORDER BY " : "") + StringUtil.concat(StringUtil.getString(keys), joinOrder, ", "); + } + + @Override + public List getRaw() { + return raw; + } + + @Override + public SQLConfig setRaw(List raw) { + this.raw = raw; + return this; + } + + /** + * 获取原始 SQL 片段 + * + * @param key + * @param value + * @return + * @throws Exception + */ + @Override + public String getRawSQL(String key, Object value) throws Exception { + List rawList = getRaw(); + boolean containRaw = rawList != null && rawList.contains(key); + if (containRaw && value instanceof String == false) { + throw new UnsupportedOperationException("@raw:value 的 value 中 " + key + " 不合法!" + + "对应的 " + key + ":value 中 value 类型只能为 String!"); + } + + String rawSQL = containRaw ? RAW_MAP.get(value) : null; + if (containRaw) { + if (rawSQL == null) { + throw new UnsupportedOperationException("@raw:value 的 value 中 " + key + " 不合法!" + + "对应的 " + key + ":value 中 value 值 " + value + " 未在后端 RAW_MAP 中配置 !"); + } + + if ("".equals(rawSQL)) { + return (String) value; + } + } + + return rawSQL; + } + + + @Override + public List getJson() { + return json; + } + + @Override + public AbstractSQLConfig setJson(List json) { + this.json = json; + return this; + } + + + @Override + public Subquery getFrom() { + return from; + } + + @Override + public AbstractSQLConfig setFrom(Subquery from) { + this.from = from; + return this; + } + + @Override + public List getColumn() { + return column; + } + + @Override + public AbstractSQLConfig setColumn(List column) { + this.column = column; + return this; + } + + @JSONField(serialize = false) + public String getColumnString() throws Exception { + return getColumnString(false); + } + + @JSONField(serialize = false) + public String getColumnString(boolean inSQLJoin) throws Exception { + List column = getColumn(); + + switch (getMethod()) { + case HEAD: + case HEADS: //StringUtil.isEmpty(column, true) || column.contains(",") 时SQL.count(column)会return "*" + if (isPrepared() && column != null) { + String origin; + String alias; + int index; + + List raw = getRaw(); + boolean containRaw = raw != null && raw.contains(KEY_COLUMN); + + for (String c : column) { + if (containRaw) { + // 由于 HashMap 对 key 做了 hash 处理,所以 get 比 containsValue 更快 + if ("".equals(RAW_MAP.get(c)) || RAW_MAP.containsValue(c)) { // newSQLConfig 提前处理好的 + //排除@raw中的值,以避免使用date_format(date,'%Y-%m-%d %H:%i:%s') 时,冒号的解析出错 + column.remove(c); + continue; + } + } + index = c.lastIndexOf(":"); //StringUtil.split返回数组中,子项不会有null + origin = index < 0 ? c : c.substring(0, index); + alias = index < 0 ? null : c.substring(index + 1); + + if (alias != null && StringUtil.isName(alias) == false) { + throw new IllegalArgumentException("HEAD请求: 字符 " + alias + " 不合法!预编译模式下 @column:value 中 value里面用 , 分割的每一项" + + " column:alias 中 column 必须是1个单词!如果有alias,则alias也必须为1个单词!并且不要有多余的空格!"); + } + + if (StringUtil.isName(origin) == false) { + int start = origin.indexOf("("); + if (start < 0 || origin.lastIndexOf(")") <= start) { + throw new IllegalArgumentException("HEAD请求: 字符" + origin + " 不合法!预编译模式下 @column:value 中 value里面用 , 分割的每一项" + + " column:alias 中 column 必须是1个单词!如果有alias,则alias也必须为1个单词!并且不要有多余的空格!"); + } + + if (start > 0 && StringUtil.isName(origin.substring(0, start)) == false) { + throw new IllegalArgumentException("HEAD请求: 字符 " + origin.substring(0, start) + " 不合法!预编译模式下 @column:value 中 value里面用 , 分割的每一项" + + " column:alias 中 column 必须是1个单词!如果有alias,则alias也必须为1个单词!并且不要有多余的空格!"); + } + } + } + } + + return SQL.count(column != null && column.size() == 1 && StringUtil.isName(column.get(0)) ? getKey(column.get(0)) : "*"); + case POST: + if (column == null || column.isEmpty()) { + throw new IllegalArgumentException("POST 请求必须在Table内设置要保存的 key:value !"); + } + + String s = ""; + boolean pfirst = true; + for (String c : column) { + if (isPrepared() && StringUtil.isName(c) == false) { //不能通过 ? 来代替,SELECT 'id','name' 返回的就是 id:"id", name:"name",而不是数据库里的值! + throw new IllegalArgumentException("POST请求: 每一个 key:value 中的key都必须是1个单词!"); + } + s += ((pfirst ? "" : ",") + getKey(c)); + + pfirst = false; + } + + return "(" + s + ")"; + case GET: + case GETS: + boolean isQuery = RequestMethod.isQueryMethod(method); //TODO 这个有啥用?上面应是 getMethod 的值 GET 和 GETS 了。 + String joinColumn = ""; + if (isQuery && joinList != null) { + SQLConfig ecfg; + SQLConfig cfg; + String c; + boolean first = true; + for (Join j : joinList) { + if (j.isAppJoin()) { + continue; + } + + ecfg = j.getOuterConfig(); + if (ecfg != null && ecfg.getColumn() != null) { //优先级更高 + cfg = ecfg; + } else { + cfg = j.getJoinConfig(); + } + + if (StringUtil.isEmpty(cfg.getAlias(), true)) { + cfg.setAlias(cfg.getTable()); + } + + c = ((AbstractSQLConfig) cfg).getColumnString(true); + if (StringUtil.isEmpty(c, true) == false) { + joinColumn += (first ? "" : ", ") + c; + first = false; + } + + inSQLJoin = true; + } + } + + String tableAlias = getAliasWithQuote(); + + // String c = StringUtil.getString(column); //id,name;json_length(contactIdList):contactCount;... + + String[] keys = column == null ? null : column.toArray(new String[]{}); //StringUtil.split(c, ";"); + if (keys == null || keys.length <= 0) { + + boolean noColumn = column != null && inSQLJoin; + String mc = isKeyPrefix() == false ? (noColumn ? "" : "*") : (noColumn ? "" : tableAlias + ".*"); + + return StringUtil.concat(mc, joinColumn, ", ", true); + } + + + List raw = getRaw(); + boolean containRaw = raw != null && raw.contains(KEY_COLUMN); + + String expression; + String method = null; + + //...;fun0(arg0,arg1,...):fun0;fun1(arg0,arg1,...):fun1;... + for (int i = 0; i < keys.length; i++) { + + //fun(arg0,arg1,...) + expression = keys[i]; + + if (containRaw) { // 由于 HashMap 对 key 做了 hash 处理,所以 get 比 containsValue 更快 + if ("".equals(RAW_MAP.get(expression)) || RAW_MAP.containsValue(expression)) { // newSQLConfig 提前处理好的 + continue; + } + + // 简单点, 后台配置就带上 AS + // int index = expression.lastIndexOf(":"); + // String alias = expression.substring(index+1); + // boolean hasAlias = StringUtil.isName(alias); + // String pre = index > 0 && hasAlias ? expression.substring(0, index) : expression; + // if (RAW_MAP.containsValue(pre) || "".equals(RAW_MAP.get(pre))) { // newSQLConfig 提前处理好的 + // expression = pre + (hasAlias ? " AS " + alias : ""); + // continue; + // } + } + + if (expression.length() > 50) { + throw new UnsupportedOperationException("@column:value 的 value 中字符串 " + expression + " 不合法!" + + "不允许传超过 50 个字符的函数或表达式!请用 @raw 简化传参!"); + } + + + int start = expression.indexOf("("); + int end = 0; + if (start >= 0) { + end = expression.lastIndexOf(")"); + if (start >= end) { + throw new IllegalArgumentException("字符 " + expression + " 不合法!" + + "@column:value 中 value 里的 SQL函数必须为 function(arg0,arg1,...) 这种格式!"); + } + + method = expression.substring(0, start); + boolean distinct = i <= 0 && method.startsWith(PREFFIX_DISTINCT); + String fun = distinct ? method.substring(PREFFIX_DISTINCT.length()) : method; + + if (fun.isEmpty() == false) { + if (SQL_FUNCTION_MAP == null || SQL_FUNCTION_MAP.isEmpty()) { + if (StringUtil.isName(fun) == false) { + throw new IllegalArgumentException("字符 " + method + " 不合法!" + + "预编译模式下 @column:\"column0,column1:alias;function0(arg0,arg1,...);function1(...):alias...\"" + + " 中 function 必须符合小写英文单词的 SQL 函数名格式!"); + } + } else if (SQL_FUNCTION_MAP.containsKey(fun) == false) { + throw new IllegalArgumentException("字符 " + method + " 不合法!" + + "预编译模式下 @column:\"column0,column1:alias;function0(arg0,arg1,...);function1(...):alias...\"" + + " 中 function 必须符合小写英文单词的 SQL 函数名格式!且必须是后端允许调用的 SQL 函数!"); + } + } + + } + + boolean isColumn = start < 0; + + String[] ckeys = StringUtil.split(isColumn ? expression : expression.substring(start + 1, end)); + String quote = getQuote(); + + // if (isPrepared()) { //不能通过 ? 来代替,SELECT 'id','name' 返回的就是 id:"id", name:"name",而不是数据库里的值! + if (ckeys != null && ckeys.length > 0) { + + boolean distinct; + String origin; + String alias; + int index; + for (int j = 0; j < ckeys.length; j++) { + index = isColumn ? ckeys[j].lastIndexOf(":") : -1; //StringUtil.split返回数组中,子项不会有null + origin = index < 0 ? ckeys[j] : ckeys[j].substring(0, index); + alias = index < 0 ? null : ckeys[j].substring(index + 1); + + distinct = j <= 0 && origin.startsWith(PREFFIX_DISTINCT); + if (distinct) { + origin = origin.substring(PREFFIX_DISTINCT.length()); + } + + if (isPrepared()) { + if (isColumn) { + if (StringUtil.isName(origin) == false || (alias != null && StringUtil.isName(alias) == false)) { + throw new IllegalArgumentException("字符 " + ckeys[j] + " 不合法!" + + "预编译模式下 @column:value 中 value里面用 , 分割的每一项" + + " column:alias 中 column 必须是1个单词!如果有alias,则alias也必须为1个单词!" + + "DISTINCT 必须全大写,且后面必须有且只有 1 个空格!其它情况不允许空格!"); + } + } else { + // if ((StringUtil.isName(origin) == false || origin.startsWith("_"))) { + if (origin.startsWith("_") || origin.contains("--") || PATTERN_FUNCTION.matcher(origin).matches() == false) { + throw new IllegalArgumentException("字符 " + ckeys[j] + " 不合法!" + + "预编译模式下 @column:\"column0,column1:alias;function0(arg0,arg1,...);function1(...):alias...\"" + + " 中所有 arg 都必须是1个不以 _ 开头的单词 或者符合正则表达式 " + PATTERN_FUNCTION + " 且不包含连续减号 -- !DISTINCT 必须全大写,且后面必须有且只有 1 个空格!其它情况不允许空格!"); + } + } + } + + //JOIN 副表不再在外层加副表名前缀 userId AS `Commet.userId`, 而是直接 userId AS `userId` + boolean isName = false; + if (StringUtil.isNumer(origin)) { + //do nothing + } else if (StringUtil.isName(origin)) { + origin = quote + origin + quote; + isName = true; + } else { + origin = getValue(origin).toString(); + } + + if (isName && isKeyPrefix()) { + ckeys[j] = tableAlias + "." + origin; + // if (isColumn) { + // ckeys[j] += " AS " + quote + (isMain() ? "" : tableAlias + ".") + (StringUtil.isEmpty(alias, true) ? origin : alias) + quote; + // } + if (isColumn && StringUtil.isEmpty(alias, true) == false) { + ckeys[j] += " AS " + quote + alias + quote; + } + } else { + ckeys[j] = origin + (StringUtil.isEmpty(alias, true) ? "" : " AS " + quote + alias + quote); + } + + if (distinct) { + ckeys[j] = PREFFIX_DISTINCT + ckeys[j]; + } + } + // } + + } + + if (isColumn) { + keys[i] = StringUtil.getString(ckeys); + } else { + String suffix = expression.substring(end + 1, expression.length()); //:contactCount + int index = suffix.lastIndexOf(":"); + String alias = index < 0 ? "" : suffix.substring(index + 1); //contactCount + suffix = index < 0 ? suffix : suffix.substring(0, index); + + if (alias.isEmpty() == false && StringUtil.isName(alias) == false) { + throw new IllegalArgumentException("字符串 " + alias + " 不合法!" + + "预编译模式下 @column:value 中 value里面用 ; 分割的每一项" + + " function(arg0,arg1,...):alias 中 alias 必须是1个单词!并且不要有多余的空格!"); + } + + if (suffix.isEmpty() == false && (((String) suffix).contains("--") || ((String) suffix).contains("/*") || PATTERN_RANGE.matcher((String) suffix).matches() == false)) { + throw new UnsupportedOperationException("字符串 " + suffix + " 不合法!" + + "预编译模式下 @column:\"column?value;function(arg0,arg1,...)?value...\"" + + " 中 ?value 必须符合正则表达式 " + PATTERN_RANGE + " 且不包含连续减号 -- 或注释符 /* !不允许多余的空格!"); + } + + String origin = method + "(" + StringUtil.getString(ckeys) + ")" + suffix; + // if (isKeyPrefix()) { + // keys[i] = origin + " AS " + quote + (isMain() ? "" : tableAlias + ".") + (StringUtil.isEmpty(alias, true) ? method : alias) + quote; + // } + // else { + keys[i] = origin + (StringUtil.isEmpty(alias, true) ? "" : " AS " + quote + alias + quote); + // } + } + + } + + String c = StringUtil.getString(keys); + c = c + (StringUtil.isEmpty(joinColumn, true) ? "" : ", " + joinColumn);//不能在这里改,后续还要用到: + return isMain() && isDistinct() ? PREFFIX_DISTINCT + c : c; + default: + throw new UnsupportedOperationException( + "服务器内部错误:getColumnString 不支持 " + RequestMethod.getName(getMethod()) + + " 等 [GET,GETS,HEAD,HEADS,POST] 外的ReuqestMethod!" + ); + } + } + + + @Override + public List> getValues() { + return values; + } + + @JSONField(serialize = false) + public String getValuesString() { + String s = ""; + if (values != null && values.size() > 0) { + Object[] items = new Object[values.size()]; + List vs; + for (int i = 0; i < values.size(); i++) { + vs = values.get(i); + if (vs == null) { + continue; + } + + items[i] = "("; + for (int j = 0; j < vs.size(); j++) { + items[i] += ((j <= 0 ? "" : ",") + getValue(vs.get(j))); + } + items[i] += ")"; + } + s = StringUtil.getString(items); + } + return s; + } + + @Override + public AbstractSQLConfig setValues(List> valuess) { + this.values = valuess; + return this; + } + + @Override + public Map getContent() { + return content; + } + + @Override + public AbstractSQLConfig setContent(Map content) { + this.content = content; + return this; + } + + @Override + public int getCount() { + return count; + } + + @Override + public AbstractSQLConfig setCount(int count) { + this.count = count; + return this; + } + + @Override + public int getPage() { + return page; + } + + @Override + public AbstractSQLConfig setPage(int page) { + this.page = page; + return this; + } + + @Override + public int getPosition() { + return position; + } + + @Override + public AbstractSQLConfig setPosition(int position) { + this.position = position; + return this; + } + + @Override + public int getQuery() { + return query; + } + + @Override + public AbstractSQLConfig setQuery(int query) { + this.query = query; + return this; + } + + @Override + public int getType() { + return type; + } + + @Override + public AbstractSQLConfig setType(int type) { + this.type = type; + return this; + } + + @Override + public int getCache() { + return cache; + } + + @Override + public AbstractSQLConfig setCache(int cache) { + this.cache = cache; + return this; + } + + public AbstractSQLConfig setCache(String cache) { + return setCache(getCache(cache)); + } + + public static int getCache(String cache) { + int cache2; + if (cache == null) { + cache2 = JSONRequest.CACHE_ALL; + } else { + // if (isSubquery) { + // throw new IllegalArgumentException("子查询内不支持传 " + JSONRequest.KEY_CACHE + "!"); + // } + + switch (cache) { + case "0": + case JSONRequest.CACHE_ALL_STRING: + cache2 = JSONRequest.CACHE_ALL; + break; + case "1": + case JSONRequest.CACHE_ROM_STRING: + cache2 = JSONRequest.CACHE_ROM; + break; + case "2": + case JSONRequest.CACHE_RAM_STRING: + cache2 = JSONRequest.CACHE_RAM; + break; + default: + throw new IllegalArgumentException(JSONRequest.KEY_CACHE + ":value 中 value 的值不合法!必须在 [0,1,2] 或 [ALL, ROM, RAM] 内 !"); + } + } + return cache2; + } + + @Override + public boolean isExplain() { + return explain; + } + + @Override + public AbstractSQLConfig setExplain(boolean explain) { + this.explain = explain; + return this; + } + + @Override + public List getJoinList() { + return joinList; + } + + @Override + public SQLConfig setJoinList(List joinList) { + this.joinList = joinList; + return this; + } + + @Override + public boolean hasJoin() { + return joinList != null && joinList.isEmpty() == false; + } + + + @Override + public boolean isTest() { + return test; + } + + @Override + public AbstractSQLConfig setTest(boolean test) { + this.test = test; + return this; + } + + /** + * 获取初始位置offset + * + * @return + */ + @JSONField(serialize = false) + public int getOffset() { + return getOffset(getPage(), getCount()); + } + + /** + * 获取初始位置offset + * + * @param page + * @param count + * @return + */ + public static int getOffset(int page, int count) { + return page * count; + } + + /** + * 获取限制数量 + * + * @return + */ + @JSONField(serialize = false) + public String getLimitString() { + if (count <= 0 || RequestMethod.isHeadMethod(getMethod(), true)) { + return ""; + } + return getLimitString(getPage(), getCount(), isOracle() || isSQLServer() || isDb2(), isOracle()); + } + + /** + * 获取限制数量 + * + * @param limit + * @return + */ + public static String getLimitString(int page, int count, boolean isTSQL, boolean isOracle) { + int offset = getOffset(page, count); + + if (isTSQL) { // OFFSET FECTH 中所有关键词都不可省略, 另外 Oracle 数据库使用子查询加 where 分页 + return isOracle ? " WHERE ROWNUM BETWEEN " + offset + " AND " + (offset + count) : " OFFSET " + offset + " ROWS FETCH FIRST " + count + " ROWS ONLY"; + } + + return " LIMIT " + count + (offset <= 0 ? "" : " OFFSET " + offset); // DELETE, UPDATE 不支持 OFFSET + } + + //WHERE <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< + @Override + public Map getWhere() { + return where; + } + + @Override + public AbstractSQLConfig setWhere(Map where) { + this.where = where; + return this; + } + + @NotNull + @Override + public Map> getCombine() { + List andList = combine == null ? null : combine.get("&"); + if (andList == null) { + andList = where == null ? new ArrayList() : new ArrayList(where.keySet()); + if (combine == null) { + combine = new HashMap<>(); + } + combine.put("&", andList); + } + return combine; + } + + @Override + public AbstractSQLConfig setCombine(Map> combine) { + this.combine = combine; + return this; + } + + /** + * noFunctionChar = false + * + * @param key + * @return + */ + @JSONField(serialize = false) + @Override + public Object getWhere(String key) { + return getWhere(key, false); + } + //CS304 Issue link: https://github.com/Tencent/APIJSON/issues/48 + + /** + * @param key - the key passed in + * @param exactMatch - whether it is exact match + * @return

use entrySet+getValue() to replace keySet+get() to enhance efficiency

+ */ + @JSONField(serialize = false) + @Override + public Object getWhere(String key, boolean exactMatch) { + if (exactMatch) { + return where == null ? null : where.get(key); + } + + if (key == null || where == null) { + return null; + } + synchronized (where) { + if (where != null) { + int index; + for (Entry entry : where.entrySet()) { + String k = entry.getKey(); + index = k.indexOf(key); + if (index >= 0 && StringUtil.isName(k.substring(index)) == false) { + return entry.getValue(); + } + } + } + } + return null; + } + + @Override + public AbstractSQLConfig putWhere(String key, Object value, boolean prior) { + if (key != null) { + if (where == null) { + where = new LinkedHashMap(); + } + if (value == null) { + where.remove(key); + } else { + where.put(key, value); + } + + combine = getCombine(); + List andList = combine.get("&"); + if (value == null) { + if (andList != null) { + andList.remove(key); + } + } else if (andList == null || andList.contains(key) == false) { + int i = 0; + if (andList == null) { + andList = new ArrayList<>(); + } else if (prior && andList.isEmpty() == false) { + + String idKey = getIdKey(); + String idInKey = idKey + "{}"; + String userIdKey = getUserIdKey(); + String userIdInKey = userIdKey + "{}"; + + if (andList.contains(idKey)) { + i++; + } + if (andList.contains(idInKey)) { + i++; + } + if (andList.contains(userIdKey)) { + i++; + } + if (andList.contains(userIdInKey)) { + i++; + } + } + + if (prior) { + andList.add(i, key); //userId的优先级不能比id高 0, key); + } else { + andList.add(key); //AbstractSQLExecutor.onPutColumn里getSQL,要保证缓存的SQL和查询的SQL里 where 的 key:value 顺序一致 + } + } + combine.put("&", andList); + } + return this; + } + + /** + * 获取WHERE + * + * @return + * @throws Exception + */ + @JSONField(serialize = false) + @Override + public String getWhereString(boolean hasPrefix) throws Exception { + return getWhereString(hasPrefix, getMethod(), getWhere(), getCombine(), getJoinList(), !isTest()); + } + + /** + * 获取WHERE + * + * @param method + * @param where + * @return + * @throws Exception + */ + @JSONField(serialize = false) + public String getWhereString(boolean hasPrefix, RequestMethod method, Map where, Map> combine, List joinList, boolean verifyName) throws Exception { + Set>> combineSet = combine == null ? null : combine.entrySet(); + if (combineSet == null || combineSet.isEmpty()) { + Log.w(TAG, "getWhereString combineSet == null || combineSet.isEmpty() >> return \"\";"); + return ""; + } + + List keyList; + + String whereString = ""; + + boolean isCombineFirst = true; + int logic; + + boolean isItemFirst; + String c; + String cs; + + for (Entry> ce : combineSet) { + keyList = ce == null ? null : ce.getValue(); + if (keyList == null || keyList.isEmpty()) { + continue; + } + + if ("|".equals(ce.getKey())) { + logic = Logic.TYPE_OR; + } else if ("!".equals(ce.getKey())) { + logic = Logic.TYPE_NOT; + } else { + logic = Logic.TYPE_AND; + } + + + isItemFirst = true; + cs = ""; + for (String key : keyList) { + c = getWhereItem(key, where.get(key), method, verifyName); + + if (StringUtil.isEmpty(c, true)) {//避免SQL条件连接错误 + continue; + } + + cs += (isItemFirst ? "" : (Logic.isAnd(logic) ? AND : OR)) + "(" + c + ")"; + + isItemFirst = false; + } + + if (StringUtil.isEmpty(cs, true)) {//避免SQL条件连接错误 + continue; + } + + whereString += (isCombineFirst ? "" : AND) + (Logic.isNot(logic) ? NOT : "") + " ( " + cs + " ) "; + isCombineFirst = false; + } + + + if (joinList != null) { + + String newWs = ""; + String ws = whereString; + + List newPvl = new ArrayList<>(); + List pvl = new ArrayList<>(preparedValueList); + + SQLConfig jc; + String js; + + boolean changed = false; + //各种 JOIN 没办法统一用 & | !连接,只能按优先级,和 @combine 一样? + for (Join j : joinList) { + String jt = j.getJoinType(); + + switch (jt) { + case "*": // CROSS JOIN + case "@": // APP JOIN + case "<": // LEFT JOIN + case ">": // RIGHT JOIN + break; + + case "&": // INNER JOIN: A & B + case "": // FULL JOIN: A | B + case "|": // FULL JOIN: A | B + case "!": // OUTER JOIN: ! (A | B) + case "^": // SIDE JOIN: ! (A & B) + case "(": // ANTI JOIN: A & ! B + case ")": // FOREIGN JOIN: B & ! A + jc = j.getJoinConfig(); + boolean isMain = jc.isMain(); + jc.setMain(false).setPrepared(isPrepared()).setPreparedValueList(new ArrayList()); + js = jc.getWhereString(false); + jc.setMain(isMain); + + boolean isOuterJoin = "!".equals(jt); + boolean isSideJoin = "^".equals(jt); + boolean isAntiJoin = "(".equals(jt); + boolean isForeignJoin = ")".equals(jt); + boolean isWsEmpty = StringUtil.isEmpty(ws, true); + + if (isWsEmpty) { + if (isOuterJoin) { // ! OUTER JOIN: ! (A | B) + throw new NotExistException("no result for ! OUTER JOIN( ! (A | B) ) when A or B is empty!"); + } + if (isForeignJoin) { // ) FOREIGN JOIN: B & ! A + throw new NotExistException("no result for ) FOREIGN JOIN( B & ! A ) when A is empty!"); + } + } + + if (StringUtil.isEmpty(js, true)) { + if (isOuterJoin) { // ! OUTER JOIN: ! (A | B) + throw new NotExistException("no result for ! OUTER JOIN( ! (A | B) ) when A or B is empty!"); + } + if (isAntiJoin) { // ( ANTI JOIN: A & ! B + throw new NotExistException("no result for ( ANTI JOIN( A & ! B ) when B is empty!"); + } + + if (isWsEmpty) { + if (isSideJoin) { + throw new NotExistException("no result for ^ SIDE JOIN( ! (A & B) ) when both A and B are empty!"); + } + } else { + if (isSideJoin || isForeignJoin) { + newWs += " ( " + getCondition(true, ws) + " ) "; + + newPvl.addAll(pvl); + newPvl.addAll(jc.getPreparedValueList()); + changed = true; + } + } + + continue; + } + + if (StringUtil.isEmpty(newWs, true) == false) { + newWs += AND; + } + + if (isAntiJoin) { // ( ANTI JOIN: A & ! B + newWs += " ( " + (isWsEmpty ? "" : ws + AND) + NOT + " ( " + js + " ) " + " ) "; + } else if (isForeignJoin) { // ) FOREIGN JOIN: (! A) & B // preparedValueList.add 不好反过来 B & ! A + newWs += " ( " + NOT + " ( " + ws + " ) ) " + AND + " ( " + js + " ) "; + } else if (isSideJoin) { // ^ SIDE JOIN: ! (A & B) + //MySQL 因为 NULL 值处理问题,(A & ! B) | (B & ! A) 与 ! (A & B) 返回结果不一样,后者往往更多 + newWs += " ( " + getCondition( + true, + (isWsEmpty ? "" : ws + AND) + " ( " + js + " ) " + ) + " ) "; + } else { // & INNER JOIN: A & B; | FULL JOIN: A | B; OUTER JOIN: ! (A | B) + logic = Logic.getType(jt); + newWs += " ( " + + getCondition( + Logic.isNot(logic), + ws + + (isWsEmpty ? "" : (Logic.isAnd(logic) ? AND : OR)) + + " ( " + js + " ) " + ) + + " ) "; + } + + newPvl.addAll(pvl); + newPvl.addAll(jc.getPreparedValueList()); + + changed = true; + break; + default: + throw new UnsupportedOperationException( + "join:value 中 value 里的 " + jt + "/" + j.getPath() + + "错误!不支持 " + jt + " 等 [ @ APP, < LEFT, > RIGHT, * CROSS" + + ", & INNER, | FULL, ! OUTER, ^ SIDE, ( ANTI, ) FOREIGN ] 之外的 JOIN 类型 !" + ); + } + } + + if (changed) { + whereString = newWs; + preparedValueList = newPvl; + } + } + + String s = StringUtil.isEmpty(whereString, true) ? "" : (hasPrefix ? " WHERE " : "") + whereString; + + if (s.isEmpty() && RequestMethod.isQueryMethod(method) == false) { + throw new UnsupportedOperationException("写操作请求必须带条件!!!"); + } + + return s; + } + + /** + * @param key + * @param value + * @param method + * @param verifyName + * @return + * @throws Exception + */ + protected String getWhereItem(String key, Object value, RequestMethod method, boolean verifyName) throws Exception { + Log.d(TAG, "getWhereItem key = " + key); + //避免筛选到全部 value = key == null ? null : where.get(key); + if (key == null || value == null || key.endsWith("()") || key.startsWith("@")) { //关键字||方法, +或-直接报错 + Log.d(TAG, "getWhereItem key == null || value == null" + + " || key.startsWith(@) || key.endsWith(()) >> continue;"); + return null; + } + if (key.endsWith("@")) {//引用 + // key = key.substring(0, key.lastIndexOf("@")); + throw new IllegalArgumentException(TAG + ".getWhereItem: 字符 " + key + " 不合法!"); + } + + // 原始 SQL 片段 + String rawSQL = getRawSQL(key, value); + + int keyType; + if (key.endsWith("$")) { + keyType = 1; + } else if (key.endsWith("~")) { + keyType = key.charAt(key.length() - 2) == '*' ? -2 : 2; //FIXME StringIndexOutOfBoundsException + } else if (key.endsWith("%")) { + keyType = 3; + } else if (key.endsWith("{}")) { + keyType = 4; + } else if (key.endsWith("}{")) { + keyType = 5; + } else if (key.endsWith("<>")) { + keyType = 6; + } else if (key.endsWith(">=")) { + keyType = 7; + } else if (key.endsWith("<=")) { + keyType = 8; + } else if (key.endsWith(">")) { + keyType = 9; + } else if (key.endsWith("<")) { + keyType = 10; + } else { // else绝对不能省,避免再次踩坑! keyType = 0; 写在for循环外面都没注意! + keyType = 0; + } + + key = getRealKey(method, key, false, true, verifyName); + + switch (keyType) { + case 1: + return getSearchString(key, value, rawSQL); + case -2: + case 2: + return getRegExpString(key, value, keyType < 0, rawSQL); + case 3: + return getBetweenString(key, value, rawSQL); + case 4: + return getRangeString(key, value, rawSQL); + case 5: + return getExistsString(key, value, rawSQL); + case 6: + return getContainString(key, value, rawSQL); + case 7: + return getCompareString(key, value, ">=", rawSQL); + case 8: + return getCompareString(key, value, "<=", rawSQL); + case 9: + return getCompareString(key, value, ">", rawSQL); + case 10: + return getCompareString(key, value, "<", rawSQL); + default: // TODO MySQL JSON类型的字段对比 key='[]' 会无结果! key LIKE '[1, 2, 3]' //TODO MySQL , 后面有空格! + return getEqualString(key, value, rawSQL); + } + } + + + @JSONField(serialize = false) + public String getEqualString(String key, Object value, String rawSQL) throws Exception { + if (JSON.isBooleanOrNumberOrString(value) == false && value instanceof Subquery == false) { + throw new IllegalArgumentException(key + ":value 中value不合法!非PUT请求只支持 [Boolean, Number, String] 内的类型 !"); + } + + boolean not = key.endsWith("!"); // & | 没有任何意义,写法多了不好控制 + if (not) { + key = key.substring(0, key.length() - 1); + } + if (StringUtil.isName(key) == false) { + throw new IllegalArgumentException(key + ":value 中key不合法!不支持 ! 以外的逻辑符 !"); + } + + return getKey(key) + (not ? " != " : " = ") + (value instanceof Subquery ? getSubqueryString((Subquery) value) : (rawSQL != null ? rawSQL : getValue(value))); + } + + @JSONField(serialize = false) + public String getCompareString(String key, Object value, String type, String rawSQL) throws Exception { + if (JSON.isBooleanOrNumberOrString(value) == false && value instanceof Subquery == false) { + throw new IllegalArgumentException(key + type + ":value 中value不合法!比较运算 [>, <, >=, <=] 只支持 [Boolean, Number, String] 内的类型 !"); + } + if (StringUtil.isName(key) == false) { + throw new IllegalArgumentException(key + type + ":value 中key不合法!比较运算 [>, <, >=, <=] 不支持 [&, !, |] 中任何逻辑运算符 !"); + } + + return getKey(key) + " " + type + " " + (value instanceof Subquery ? getSubqueryString((Subquery) value) : (rawSQL != null ? rawSQL : getValue(value))); + } + + public String getKey(String key) { + if (isTest()) { + if (key.contains("'")) { // || key.contains("#") || key.contains("--")) { + throw new IllegalArgumentException("参数 " + key + " 不合法!key 中不允许有单引号 ' !"); + } + return getSQLValue(key).toString(); + } + + return getSQLKey(key); + } + + public String getSQLKey(String key) { + String q = getQuote(); + return (isKeyPrefix() ? getAliasWithQuote() + "." : "") + q + key + q; + } + + /** + * 使用prepareStatement预编译,值为 ? ,后续动态set进去 + */ + private List preparedValueList = new ArrayList<>(); + + private Object getValue(@NotNull Object value) { + if (isPrepared()) { + preparedValueList.add(value); + return "?"; + } + return getSQLValue(value); + } + + public Object getSQLValue(@NotNull Object value) { + // return (value instanceof Number || value instanceof Boolean) && DATABASE_POSTGRESQL.equals(getDatabase()) ? value : "'" + value + "'"; + return (value instanceof Number || value instanceof Boolean) ? value : "'" + value + "'"; //MySQL 隐式转换用不了索引 + } + + @Override + public List getPreparedValueList() { + return preparedValueList; + } + + @Override + public AbstractSQLConfig setPreparedValueList(List preparedValueList) { + this.preparedValueList = preparedValueList; + return this; + } + + //$ search <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< + + /** + * search key match value + * + * @param in + * @return {@link #getSearchString(String, Object[], int)} + * @throws IllegalArgumentException + */ + @JSONField(serialize = false) + public String getSearchString(String key, Object value, String rawSQL) throws IllegalArgumentException { + if (rawSQL != null) { + throw new UnsupportedOperationException("@raw:value 中 " + key + " 不合法!@raw 不支持 key$ 这种功能符 !只支持 key, key!, key<, key{} 等比较运算 和 @column, @having !"); + } + if (value == null) { + return ""; + } + + Logic logic = new Logic(key); + key = logic.getKey(); + Log.i(TAG, "getSearchString key = " + key); + + JSONArray arr = newJSONArray(value); + if (arr.isEmpty()) { + return ""; + } + return getSearchString(key, arr.toArray(), logic.getType()); + } + + /** + * search key match values + * + * @param in + * @return LOGIC [ key LIKE 'values[i]' ] + * @throws IllegalArgumentException + */ + @JSONField(serialize = false) + public String getSearchString(String key, Object[] values, int type) throws IllegalArgumentException { + if (values == null || values.length <= 0) { + return ""; + } + + String condition = ""; + for (int i = 0; i < values.length; i++) { + Object v = values[i]; + if (v instanceof String == false) { + throw new IllegalArgumentException(key + "$:value 中 value 的类型只能为 String 或 String[]!"); + } + if (((String) v).isEmpty()) { // 允许查空格 StringUtil.isEmpty((String) v, true) + throw new IllegalArgumentException(key + "$:value 中 value 值 " + v + "是空字符串,没有意义,不允许这样传!"); + } + // if (((String) v).contains("%%")) { // 需要通过 %\%% 来模糊搜索 % + // throw new IllegalArgumentException(key + "$:value 中 value 值 " + v + " 中包含 %% !不允许有连续的 % !"); + // } + + condition += (i <= 0 ? "" : (Logic.isAnd(type) ? AND : OR)) + getLikeString(key, v); + } + + return getCondition(Logic.isNot(type), condition); + } + + /** + * WHERE key LIKE 'value' + * + * @param key + * @param value + * @return key LIKE 'value' + */ + @JSONField(serialize = false) + public String getLikeString(String key, Object value) { + return getKey(key) + " LIKE " + getValue(value); + } + + //$ search >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> + + + //~ regexp <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< + + /** + * search key match RegExp values + * + * @param key + * @param value + * @param ignoreCase + * @return {@link #getRegExpString(String, Object[], int, boolean)} + * @throws IllegalArgumentException + */ + @JSONField(serialize = false) + public String getRegExpString(String key, Object value, boolean ignoreCase, String rawSQL) throws IllegalArgumentException { + if (rawSQL != null) { + throw new UnsupportedOperationException("@raw:value 中 " + key + " 不合法!@raw 不支持 key~ 这种功能符 !只支持 key, key!, key<, key{} 等比较运算 和 @column, @having !"); + } + if (value == null) { + return ""; + } + + Logic logic = new Logic(key); + key = logic.getKey(); + Log.i(TAG, "getRegExpString key = " + key); + + JSONArray arr = newJSONArray(value); + if (arr.isEmpty()) { + return ""; + } + return getRegExpString(key, arr.toArray(), logic.getType(), ignoreCase); + } + + /** + * search key match RegExp values + * + * @param key + * @param values + * @param type + * @param ignoreCase + * @return LOGIC [ key REGEXP 'values[i]' ] + * @throws IllegalArgumentException + */ + @JSONField(serialize = false) + public String getRegExpString(String key, Object[] values, int type, boolean ignoreCase) throws IllegalArgumentException { + if (values == null || values.length <= 0) { + return ""; + } + + String condition = ""; + for (int i = 0; i < values.length; i++) { + if (values[i] instanceof String == false) { + throw new IllegalArgumentException(key + "$:value 中value的类型只能为String或String[]!"); + } + condition += (i <= 0 ? "" : (Logic.isAnd(type) ? AND : OR)) + getRegExpString(key, (String) values[i], ignoreCase); + } + + return getCondition(Logic.isNot(type), condition); + } + + /** + * WHERE key REGEXP 'value' + * + * @param key + * @param value + * @param ignoreCase + * @return key REGEXP 'value' + */ + @JSONField(serialize = false) + public String getRegExpString(String key, String value, boolean ignoreCase) { + if (isPostgreSQL()) { + return getKey(key) + " ~" + (ignoreCase ? "* " : " ") + getValue(value); + } + if (isOracle()) { + return "regexp_like(" + getKey(key) + ", " + getValue(value) + (ignoreCase ? ", 'i'" : ", 'c'") + ")"; + } + return getKey(key) + " REGEXP " + (ignoreCase ? "" : "BINARY ") + getValue(value); + } + //~ regexp >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> + + + //% between <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< + + /** + * WHERE key BETWEEN 'start' AND 'end' + * + * @param key + * @param value 'start,end' + * @return LOGIC [ key BETWEEN 'start' AND 'end' ] + * @throws IllegalArgumentException + */ + @JSONField(serialize = false) + public String getBetweenString(String key, Object value, String rawSQL) throws IllegalArgumentException { + if (rawSQL != null) { + throw new UnsupportedOperationException("@raw:value 中 " + key + " 不合法!@raw 不支持 key% 这种功能符 !只支持 key, key!, key<, key{} 等比较运算 和 @column, @having !"); + } + if (value == null) { + return ""; + } + + Logic logic = new Logic(key); + key = logic.getKey(); + Log.i(TAG, "getBetweenString key = " + key); + + JSONArray arr = newJSONArray(value); + if (arr.isEmpty()) { + return ""; + } + return getBetweenString(key, arr.toArray(), logic.getType()); + } + + /** + * WHERE key BETWEEN 'start' AND 'end' + * + * @param key + * @param value 'start,end' TODO 在 '1,2' 和 ['1,2', '3,4'] 基础上新增支持 [1, 2] 和 [[1,2], [3,4]] ? + * @return LOGIC [ key BETWEEN 'start' AND 'end' ] + * @throws IllegalArgumentException + */ + @JSONField(serialize = false) + public String getBetweenString(String key, Object[] values, int type) throws IllegalArgumentException { + if (values == null || values.length <= 0) { + return ""; + } + + String condition = ""; + String[] vs; + for (int i = 0; i < values.length; i++) { + if (values[i] instanceof String == false) { + throw new IllegalArgumentException(key + "%:value 中 value 的类型只能为 String 或 String[] !"); + } + + vs = StringUtil.split((String) values[i]); + if (vs == null || vs.length != 2) { + throw new IllegalArgumentException(key + "%:value 中 value 不合法!类型为 String 时必须包括1个逗号 , 且左右两侧都有值!类型为 String[] 里面每个元素要符合前面类型为 String 的规则 !"); + } + + condition += (i <= 0 ? "" : (Logic.isAnd(type) ? AND : OR)) + "(" + getBetweenString(key, (Object) vs[0], (Object) vs[1]) + ")"; + } + + return getCondition(Logic.isNot(type), condition); + } + + /** + * WHERE key BETWEEN 'start' AND 'end' + * + * @param key + * @param value 'start,end' TODO 在 '1,2' 和 ['1,2', '3,4'] 基础上新增支持 [1, 2] 和 [[1,2], [3,4]] ? + * @return key BETWEEN 'start' AND 'end' + * @throws IllegalArgumentException + */ + @JSONField(serialize = false) + public String getBetweenString(String key, Object start, Object end) throws IllegalArgumentException { + if (JSON.isBooleanOrNumberOrString(start) == false || JSON.isBooleanOrNumberOrString(end) == false) { + throw new IllegalArgumentException(key + "%:value 中 value 不合法!类型为 String 时必须包括1个逗号 , 且左右两侧都有值!类型为 String[] 里面每个元素要符合前面类型为 String 的规则 !"); + } + return getKey(key) + " BETWEEN " + getValue(start) + AND + getValue(end); + } + + + //% between >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> + + + //{} range <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< + + + /** + * WHERE key > 'key0' AND key <= 'key1' AND ... + * + * @param key + * @param range "condition0,condition1..." + * @return key condition0 AND key condition1 AND ... + * @throws Exception + */ + @JSONField(serialize = false) + public String getRangeString(String key, Object range, String rawSQL) throws Exception { + Log.i(TAG, "getRangeString key = " + key); + if (range == null) {//依赖的对象都没有给出有效值,这个存在无意义。如果是客户端传的,那就能在客户端确定了。 + throw new NotExistException(TAG + "getRangeString(" + key + ", " + range + + ") range == null"); + } + + Logic logic = new Logic(key); + String k = logic.getKey(); + Log.i(TAG, "getRangeString k = " + k); + + if (range instanceof List) { + if (rawSQL != null) { + throw new UnsupportedOperationException("@raw:value 的 value 中 " + key + "{} 不合法!" + + "Raw SQL 不支持 key{}:[] 这种键值对!"); + } + + if (logic.isOr() || logic.isNot()) { + List l = (List) range; + if (logic.isNot() && l.isEmpty()) { + return ""; // key!{}: [] 这个条件无效,加到 SQL 语句中 key IN() 会报错,getInString 里不好处理 + } + return getKey(k) + getInString(k, l.toArray(), logic.isNot()); + } + throw new IllegalArgumentException(key + "{}\":[] 中 {} 前面的逻辑运算符错误!只能用'|','!'中的一种 !"); + } else if (range instanceof String) {//非Number类型需要客户端拼接成 < 'value0', >= 'value1'这种 + String condition = ""; + String[] cs = rawSQL != null ? null : StringUtil.split((String) range, false); + + if (rawSQL != null) { + int index = rawSQL == null ? -1 : rawSQL.indexOf("("); + condition = (index >= 0 && index < rawSQL.lastIndexOf(")") ? "" : getKey(k) + " ") + rawSQL; + } + + // 还是只支持整段为 Raw SQL 比较好 + // boolean appendRaw = false; + // if ("".equals(rawSQL)) { + // condition = rawSQL; + // cs = null; + // } + // else { + // if (rawSQL != null) { //先找出所有 rawSQL 的位置,然后去掉,再最后按原位置来拼接 + // String[] rs = StringUtil.split((String) range, rawSQL, false); + // + // if (rs != null && rs.length > 0) { + // String cond = ""; + // for (int i = 0; i < rs.length; i++) { + // cond += rs[i]; + // } + // range = cond; + // appendRaw = true; + // } + // } + // + // cs = StringUtil.split((String) range, false); + // } + + if (cs != null) { + String c; + int index; + for (int i = 0; i < cs.length; i++) {//对函数条件length(key)<=5这种不再在开头加key + c = cs[i]; + + if ("=null".equals(c)) { + c = SQL.isNull(); + } else if ("!=null".equals(c)) { + c = SQL.isNull(false); + } else if (isPrepared() && (c.contains("--") || PATTERN_RANGE.matcher(c).matches() == false)) { + throw new UnsupportedOperationException(key + "{}:value 的 value 中 " + c + " 不合法!" + + "预编译模式下 key{}:\"condition\" 中 condition 必须 为 =null 或 !=null 或 符合正则表达式 " + PATTERN_RANGE + " !不允许连续减号 -- !不允许空格!"); + } + + index = c == null ? -1 : c.indexOf("("); + condition += ((i <= 0 ? "" : (logic.isAnd() ? AND : OR)) //连接方式 + + (index >= 0 && index < c.lastIndexOf(")") ? "" : getKey(k) + " ") //函数和非函数条件 + + c); // 还是只支持整段为 Raw SQL 比较好 (appendRaw && index > 0 ? rawSQL : "") + c); //单个条件,如果有 Raw SQL 则按原来位置拼接 + } + } + if (condition.isEmpty()) { + return ""; + } + + return getCondition(logic.isNot(), condition); + } else if (range instanceof Subquery) { //如果在 Parser 解析成 SQL 字符串再引用,没法保证安全性,毕竟可以再通过远程函数等方式来拼接再替代,最后引用的字符串就能注入 + return getKey(k) + (logic.isNot() ? NOT : "") + " IN " + getSubqueryString((Subquery) range); + } + + throw new IllegalArgumentException(key + "{}:range 类型为" + range.getClass().getSimpleName() + + "!range 只能是 用','分隔条件的字符串 或者 可取选项JSONArray!"); + } + + /** + * WHERE key IN ('key0', 'key1', ... ) + * + * @param in + * @return IN ('key0', 'key1', ... ) + * @throws NotExistException + */ + @JSONField(serialize = false) + public String getInString(String key, Object[] in, boolean not) throws NotExistException { + String condition = ""; + if (in != null) {//返回 "" 会导致 id:[] 空值时效果和没有筛选id一样! + for (int i = 0; i < in.length; i++) { + condition += ((i > 0 ? "," : "") + getValue(in[i])); + } + } + if (condition.isEmpty()) {//条件如果存在必须执行,不能忽略。条件为空会导致出错,又很难保证条件不为空(@:条件),所以还是这样好 + throw new NotExistException(TAG + ".getInString(" + key + ", [], " + not + + ") >> condition.isEmpty() >> IN()"); + } + return (not ? NOT : "") + " IN (" + condition + ")"; + } + //{} range >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> + + + //}{ exists <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< + + /** + * WHERE EXISTS subquery + * 如果合并到 getRangeString,一方面支持不了 [1,2,2] 和 ">1" (转成 EXISTS(SELECT IN ) 需要static newSQLConfig,但它不能传入子类实例,除非不是 static),另一方面多了子查询临时表性能会比 IN 差 + * + * @param key + * @param value + * @return EXISTS ALL(SELECT ...) + * @throws NotExistException + */ + @JSONField(serialize = false) + public String getExistsString(String key, Object value, String rawSQL) throws Exception { + if (rawSQL != null) { + throw new UnsupportedOperationException("@raw:value 中 " + key + " 不合法!@raw 不支持 key}{ 这种功能符 !只支持 key, key!, key<, key{} 等比较运算 和 @column, @having !"); + } + if (value == null) { + return ""; + } + if (value instanceof Subquery == false) { + throw new IllegalArgumentException(key + "}{:subquery 类型为" + value.getClass().getSimpleName() + + "!subquery 只能是 子查询JSONObejct!"); + } + + Logic logic = new Logic(key); + key = logic.getKey(); + Log.i(TAG, "getExistsString key = " + key); + + return (logic.isNot() ? NOT : "") + " EXISTS " + getSubqueryString((Subquery) value); + } + //}{ exists >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> + + //<> contain <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< + + /** + * WHERE key contains value + * + * @param key + * @param value + * @return {@link #getContainString(String, Object[], int)} + * @throws NotExistException + */ + @JSONField(serialize = false) + public String getContainString(String key, Object value, String rawSQL) throws IllegalArgumentException { + if (rawSQL != null) { + throw new UnsupportedOperationException("@raw:value 中 " + key + " 不合法!@raw 不支持 key<> 这种功能符 !只支持 key, key!, key<, key{} 等比较运算 和 @column, @having !"); + } + if (value == null) { + return ""; + } + + Logic logic = new Logic(key); + key = logic.getKey(); + Log.i(TAG, "getContainString key = " + key); + + return getContainString(key, newJSONArray(value).toArray(), logic.getType()); + } + + /** + * WHERE key contains childs + * + * @param key + * @param childs null ? "" : (empty ? no child : contains childs) + * @param type |, &, ! + * @return LOGIC [ ( key LIKE '[" + childs[i] + "]' OR key LIKE '[" + childs[i] + ", %' + * OR key LIKE '%, " + childs[i] + ", %' OR key LIKE '%, " + childs[i] + "]' ) ] + * @throws IllegalArgumentException + */ + @JSONField(serialize = false) + public String getContainString(String key, Object[] childs, int type) throws IllegalArgumentException { + boolean not = Logic.isNot(type); + String condition = ""; + if (childs != null) { + for (int i = 0; i < childs.length; i++) { + Object c = childs[i]; + if (c != null) { + if (c instanceof JSON) { + throw new IllegalArgumentException(key + "<>:value 中value类型不能为JSON!"); + } + + condition += (i <= 0 ? "" : (Logic.isAnd(type) ? AND : OR)); + if (isPostgreSQL()) { + condition += (getKey(key) + " @> " + getValue(newJSONArray(c))); //operator does not exist: jsonb @> character varying "[" + c + "]"); + } else if (isOracle()) { + condition += ("json_textcontains(" + getKey(key) + ", '$', " + getValue(c.toString()) + ")"); + } else { + boolean isNum = c instanceof Number; + String v = (isNum ? "" : "\"") + childs[i] + (isNum ? "" : "\""); + condition += ("json_contains(" + getKey(key) + ", " + getValue(v) + ")"); + } + } + } + if (condition.isEmpty()) { + condition = (getKey(key) + SQL.isNull(true) + OR + getLikeString(key, "[]")); // key = '[]' 无结果! + } else { + condition = (getKey(key) + SQL.isNull(false) + AND + "(" + condition + ")"); + } + } + if (condition.isEmpty()) { + return ""; + } + return getCondition(not, condition); + } + //<> contain >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> + + + //key@:{} Subquery <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< + + @Override + public String getSubqueryString(Subquery subquery) throws Exception { + String range = subquery.getRange(); + SQLConfig cfg = subquery.getConfig(); + + cfg.setPreparedValueList(new ArrayList<>()); + String sql = (range == null || range.isEmpty() ? "" : range) + "(" + cfg.getSQL(isPrepared()) + ") "; + + preparedValueList.addAll(cfg.getPreparedValueList()); + + return sql; + } + + //key@:{} Subquery >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> + + + /** + * 拼接条件 + * + * @param not + * @param condition + * @return + */ + private static String getCondition(boolean not, String condition) { + return not ? NOT + "(" + condition + ")" : condition; + } + + + /** + * 转为JSONArray + * + * @param tv + * @return + */ + @NotNull + public static JSONArray newJSONArray(Object obj) { + JSONArray array = new JSONArray(); + if (obj != null) { + if (obj instanceof Collection) { + array.addAll((Collection) obj); + } else { + array.add(obj); + } + } + return array; + } + + //WHERE >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> + + + //SET <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< + + /** + * 获取SET + * + * @return + * @throws Exception + */ + @JSONField(serialize = false) + public String getSetString() throws Exception { + return getSetString(getMethod(), getContent(), !isTest()); + } + //CS304 Issue link: https://github.com/Tencent/APIJSON/issues/48 + + /** + * 获取SET + * + * @param method -the method used + * @param content -the content map + * @return + * @throws Exception

use entrySet+getValue() to replace keySet+get() to enhance efficiency

+ */ + @JSONField(serialize = false) + public String getSetString(RequestMethod method, Map content, boolean verifyName) throws Exception { + Set set = content == null ? null : content.keySet(); + String setString = ""; + + if (set != null && set.size() > 0) { + boolean isFirst = true; + int keyType;// 0 - =; 1 - +, 2 - - + Object value; + + String idKey = getIdKey(); + for (Entry entry : content.entrySet()) { + String key = entry.getKey(); + //避免筛选到全部 value = key == null ? null : content.get(key); + if (key == null || idKey.equals(key)) { + continue; + } + + if (key.endsWith("+")) { + keyType = 1; + } else if (key.endsWith("-")) { + keyType = 2; + } else { + keyType = 0; //注意重置类型,不然不该加减的字段会跟着加减 + } + value = entry.getValue(); + key = getRealKey(method, key, false, true, verifyName); + + setString += (isFirst ? "" : ", ") + (getKey(key) + " = " + (keyType == 1 ? getAddString(key, value) : (keyType == 2 + ? getRemoveString(key, value) : getValue(value)))); + + isFirst = false; + } + } + + if (setString.isEmpty()) { + throw new IllegalArgumentException("PUT 请求必须在Table内设置要修改的 key:value !"); + } + return " SET " + setString; + } + + /** + * SET key = concat(key, 'value') + * + * @param key + * @param value + * @return concat(key, ' value ') + * @throws IllegalArgumentException + */ + @JSONField(serialize = false) + public String getAddString(String key, Object value) throws IllegalArgumentException { + if (value instanceof Number) { + return getKey(key) + " + " + value; + } + if (value instanceof String) { + return SQL.concat(getKey(key), (String) getValue(value)); + } + throw new IllegalArgumentException(key + "+ 对应的值 " + value + " 不是Number,String,Array中的任何一种!"); + } + + /** + * SET key = replace(key, 'value', '') + * + * @param key + * @param value + * @return REPLACE (key, 'value', '') + * @throws IllegalArgumentException + */ + @JSONField(serialize = false) + public String getRemoveString(String key, Object value) throws IllegalArgumentException { + if (value instanceof Number) { + return getKey(key) + " - " + value; + } + if (value instanceof String) { + return SQL.replace(getKey(key), (String) getValue(value), "''");// " replace(" + key + ", '" + value + "', '') "; + } + throw new IllegalArgumentException(key + "- 对应的值 " + value + " 不是Number,String,Array中的任何一种!"); + } + //SET >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> + + + /** + * @return + * @throws Exception + */ + @JSONField(serialize = false) + @Override + public String getSQL(boolean prepared) throws Exception { + return getSQL(this.setPrepared(prepared)); + } + + /** + * @param config + * @return + * @throws Exception + */ + public static String getSQL(AbstractSQLConfig config) throws Exception { + if (config == null) { + Log.i(TAG, "getSQL config == null >> return null;"); + return null; + } + + //TODO procedure 改为 List procedureList; behind : true; function: callFunction(); String key; ... + // for (...) { Call procedure1();\n SQL \n; Call procedure2(); ... } + // 貌似不需要,因为 ObjecParser 里就已经处理的顺序等,只是这里要解决下 Schema 问题。 + + String sch = config.getSQLSchema(); + if (StringUtil.isNotEmpty(config.getProcedure(), true)) { + String q = config.getQuote(); + return "CALL " + q + sch + q + "." + config.getProcedure(); + } + + String tablePath = config.getTablePath(); + if (StringUtil.isNotEmpty(tablePath, true) == false) { + Log.i(TAG, "getSQL StringUtil.isNotEmpty(tablePath, true) == false >> return null;"); + return null; + } + + switch (config.getMethod()) { + case POST: + return "INSERT INTO " + tablePath + config.getColumnString() + " VALUES" + config.getValuesString(); + case PUT: + return "UPDATE " + tablePath + config.getSetString() + config.getWhereString(true) + (config.isMySQL() ? config.getLimitString() : ""); + case DELETE: + return "DELETE FROM " + tablePath + config.getWhereString(true) + (config.isMySQL() ? config.getLimitString() : ""); // PostgreSQL 不允许 LIMIT + default: + String explain = (config.isExplain() ? (config.isSQLServer() || config.isOracle() ? "SET STATISTICS PROFILE ON " : "EXPLAIN ") : ""); + if (config.isTest() && RequestMethod.isGetMethod(config.getMethod(), true)) { + String q = config.getQuote(); // 生成 SELECT ( (24 >=0 AND 24 <3) ) AS `code` LIMIT 1 OFFSET 0 + return explain + "SELECT " + config.getWhereString(false) + " AS " + q + JSONResponse.KEY_CODE + q + config.getLimitString(); + } + + config.setPreparedValueList(new ArrayList()); + String column = config.getColumnString(); + if (config.isOracle()) { + //When config's database is oracle,Using subquery since Oracle12 below does not support OFFSET FETCH paging syntax. + return explain + "SELECT * FROM (SELECT" + (config.getCache() == JSONRequest.CACHE_RAM ? "SQL_NO_CACHE " : "") + column + " FROM " + getConditionString(column, tablePath, config) + ") " + config.getLimitString(); + } + + return explain + "SELECT " + (config.getCache() == JSONRequest.CACHE_RAM ? "SQL_NO_CACHE " : "") + column + " FROM " + getConditionString(column, tablePath, config) + config.getLimitString(); + } + } + + /** + * 获取条件SQL字符串 + * + * @param page + * @param column + * @param table + * @param where + * @return + * @throws Exception + */ + private static String getConditionString(String column, String table, AbstractSQLConfig config) throws Exception { + String where = config.getWhereString(true); + + Subquery from = config.getFrom(); + if (from != null) { + table = config.getSubqueryString(from) + " AS " + config.getAliasWithQuote() + " "; + } + + String condition = table + config.getJoinString() + where + ( + RequestMethod.isGetMethod(config.getMethod(), true) == false ? + "" : config.getGroupString(true) + config.getHavingString(true) + config.getOrderString(true) + ); //+ config.getLimitString(); + + //no need to optimize + // if (config.getPage() <= 0 || ID.equals(column.trim())) { + return condition; // config.isOracle() ? condition : condition + config.getLimitString(); + // } + // + // + // //order: id+ -> id >= idOfStartIndex; id- -> id <= idOfStartIndex <<<<<<<<<<<<<<<<<<< + // String order = StringUtil.getNoBlankString(config.getOrder()); + // List orderList = order.isEmpty() ? null : Arrays.asList(StringUtil.split(order)); + // + // int type = 0; + // if (BaseModel.isEmpty(orderList) || BaseModel.isContain(orderList, ID+"+")) { + // type = 1; + // } + // else if (BaseModel.isContain(orderList, ID+"-")) { + // type = 2; + // } + // + // if (type > 0) { + // return condition.replace("WHERE", + // "WHERE id " + (type == 1 ? ">=" : "<=") + " (SELECT id FROM " + table + // + where + " ORDER BY id " + (type == 1 ? "ASC" : "DESC") + " LIMIT " + config.getOffset() + ", 1) AND" + // ) + // + " LIMIT " + config.getCount(); //子查询起始id不一定准确,只能作为最小可能! ;// + // } + // //order: id+ -> id >= idOfStartIndex; id- -> id <= idOfStartIndex >>>>>>>>>>>>>>>>>> + // + // + // //结果错误!SELECT * FROM User AS t0 INNER JOIN + // (SELECT id FROM User ORDER BY date ASC LIMIT 20, 10) AS t1 ON t0.id = t1.id + // //common case, inner join + // condition += config.getLimitString(); + // return table + " AS t0 INNER JOIN (SELECT id FROM " + condition + ") AS t1 ON t0.id = t1.id"; + } + + + private boolean keyPrefix; + + @Override + public boolean isKeyPrefix() { + return keyPrefix; + } + + @Override + public AbstractSQLConfig setKeyPrefix(boolean keyPrefix) { + this.keyPrefix = keyPrefix; + return this; + } + + + public String getJoinString() throws Exception { + String joinOns = ""; + + if (joinList != null) { + String quote = getQuote(); + List pvl = new ArrayList<>(); + boolean changed = false; + + String sql = null; + SQLConfig jc; + String jt; + String tt; + // 主表不用别名 String ta; + for (Join j : joinList) { + if (j.isAppJoin()) { // APP JOIN,只是作为一个标记,执行完主表的查询后自动执行副表的查询 User.id IN($commentIdList) + continue; + } + String type = j.getJoinType(); + + //LEFT JOIN sys.apijson_user AS User ON User.id = Moment.userId, 都是用 = ,通过relateType处理缓存 + // <"INNER JOIN User ON User.id = Moment.userId", UserConfig> TODO AS 放 getSQLTable 内 + jc = j.getJoinConfig(); + jc.setPrepared(isPrepared()); + + jt = StringUtil.isEmpty(jc.getAlias(), true) ? jc.getTable() : jc.getAlias(); + tt = j.getTargetTable(); + + //如果要强制小写,则可在子类重写这个方法再 toLowerCase + // if (DATABASE_POSTGRESQL.equals(getDatabase())) { + // jt = jt.toLowerCase(); + // tn = tn.toLowerCase(); + // } + + switch (type) { + //前面已跳过 case "@": // APP JOIN + // continue; + + case "*": // CROSS JOIN + onGetCrossJoinString(j); + case "<": // LEFT JOIN + case ">": // RIGHT JOIN + jc.setMain(true).setKeyPrefix(false); + sql = ("<".equals(type) ? " LEFT" : (">".equals(type) ? " RIGHT" : " CROSS")) + + " JOIN ( " + jc.getSQL(isPrepared()) + " ) AS " + + quote + jt + quote + " ON " + quote + jt + quote + "." + quote + j.getKey() + quote + " = " + + quote + tt + quote + "." + quote + j.getTargetKey() + quote; + jc.setMain(false).setKeyPrefix(true); + + pvl.addAll(jc.getPreparedValueList()); + changed = true; + break; + + case "&": // INNER JOIN: A & B + case "": // FULL JOIN: A | B + case "|": // FULL JOIN: A | B + case "!": // OUTER JOIN: ! (A | B) + case "^": // SIDE JOIN: ! (A & B) + case "(": // ANTI JOIN: A & ! B + case ")": // FOREIGN JOIN: B & ! A + sql = " INNER JOIN " + jc.getTablePath() + + " ON " + quote + jt + quote + "." + quote + j.getKey() + quote + " = " + quote + tt + quote + "." + quote + j.getTargetKey() + quote; + break; + default: + throw new UnsupportedOperationException( + "join:value 中 value 里的 " + jt + "/" + j.getPath() + + "错误!不支持 " + jt + " 等 [ @ APP, < LEFT, > RIGHT, * CROSS" + + ", & INNER, | FULL, ! OUTER, ^ SIDE, ( ANTI, ) FOREIGN ] 之外的 JOIN 类型 !" + ); + } + + joinOns += " \n " + sql; + } + + + if (changed) { + pvl.addAll(preparedValueList); + preparedValueList = pvl; + } + + } + + return joinOns; + } + + protected void onGetCrossJoinString(Join j) throws UnsupportedOperationException { + throw new UnsupportedOperationException("已禁用 * CROSS JOIN !性能很差、需求极少,如要取消禁用可在后端重写相关方法!"); + } + + /** + * 新建SQL配置 + * + * @param table + * @param request + * @param joinList + * @param isProcedure + * @param callback + * @return + * @throws Exception + */ + public static SQLConfig newSQLConfig(RequestMethod method, String table, String alias, JSONObject request, List joinList, boolean isProcedure, Callback callback) throws Exception { + if (request == null) { // User:{} 这种空内容在查询时也有效 + throw new NullPointerException(TAG + ": newSQLConfig request == null!"); + } + + boolean explain = request.getBooleanValue(KEY_EXPLAIN); + if (explain && Log.DEBUG == false) { //不在 config.setExplain 抛异常,一方面处理更早性能更好,另一方面为了内部调用可以绕过这个限制 + throw new UnsupportedOperationException("DEBUG 模式下不允许传 " + KEY_EXPLAIN + " !"); + } + + String database = request.getString(KEY_DATABASE); + if (StringUtil.isEmpty(database, false) == false && DATABASE_LIST.contains(database) == false) { + throw new UnsupportedDataTypeException("@database:value 中 value 错误,只能是 [" + StringUtil.getString(DATABASE_LIST.toArray()) + "] 中的一种!"); + } + + String schema = request.getString(KEY_SCHEMA); + String datasource = request.getString(KEY_DATASOURCE); + + SQLConfig config = callback.getSQLConfig(method, database, schema, table); + config.setAlias(alias); + + config.setDatabase(database); //不删,后面表对象还要用的,必须放在 parseJoin 前 + config.setSchema(schema); //不删,后面表对象还要用的 + config.setDatasource(datasource); //不删,后面表对象还要用的 + + if (isProcedure) { + return config; + } + + config = parseJoin(method, config, joinList, callback); //放后面会导致主表是空对象时 joinList 未解析 + + if (request.isEmpty()) { // User:{} 这种空内容在查询时也有效 + return config; //request.remove(key); 前都可以直接return,之后必须保证 put 回去 + } + + + String idKey = callback.getIdKey(database, schema, table); + String idInKey = idKey + "{}"; + String userIdKey = callback.getUserIdKey(database, schema, table); + String userIdInKey = userIdKey + "{}"; + + //对id和id{}处理,这两个一定会作为条件 + + Object idIn = request.get(idInKey); //可能是 id{}:">0" + if (idIn instanceof List) { // 排除掉 0, 负数, 空字符串 等无效 id 值 + List ids = ((List) idIn); + List newIdIn = new ArrayList<>(); + Object d; + for (int i = 0; i < ids.size(); i++) { //不用 idIn.contains(id) 因为 idIn 里存到很可能是 Integer,id 又是 Long! + d = ids.get(i); + if ((d instanceof Number && ((Number) d).longValue() > 0) || (d instanceof String && StringUtil.isNotEmpty(d, true))) { + newIdIn.add(d); + } + } + if (newIdIn.isEmpty()) { + throw new NotExistException(TAG + ": newSQLConfig idIn instanceof List >> 去掉无效 id 后 newIdIn.isEmpty()"); + } + idIn = newIdIn; + + if (method == DELETE || method == PUT) { + config.setCount(newIdIn.size()); + } + } + + Object id = request.get(idKey); + boolean hasId = id != null; + if (method == POST && hasId == false) { + id = callback.newId(method, database, schema, table); // null 表示数据库自增 id + } + + if (id != null) { //null无效 + if (id instanceof Number) { + if (((Number) id).longValue() <= 0) { //一定没有值 + throw new NotExistException(TAG + ": newSQLConfig " + table + ".id <= 0"); + } + } else if (id instanceof String) { + if (StringUtil.isEmpty(id, true)) { //一定没有值 + throw new NotExistException(TAG + ": newSQLConfig StringUtil.isEmpty(" + table + ".id, true)"); + } + } else if (id instanceof Subquery) { + } else { + throw new IllegalArgumentException(idKey + ":value 中 value 的类型只能是 Long , String 或 Subquery !"); + } + + if (idIn instanceof List) { //共用idIn场景少性能差 + boolean contains = false; + List ids = ((List) idIn); + Object d; + for (int i = 0; i < ids.size(); i++) { //不用 idIn.contains(id) 因为 idIn 里存到很可能是 Integer,id 又是 Long! + d = ids.get(i); + if (d != null && id.toString().equals(d.toString())) { + contains = true; + break; + } + } + if (contains == false) {//empty有效 BaseModel.isEmpty(idIn) == false) { + throw new NotExistException(TAG + ": newSQLConfig idIn != null && (((List) idIn).contains(id) == false"); + } + } + + if (method == DELETE || method == PUT) { + config.setCount(1); + } + } + + + String role = request.getString(KEY_ROLE); + String cache = request.getString(KEY_CACHE); + String combine = request.getString(KEY_COMBINE); + Subquery from = (Subquery) request.get(KEY_FROM); + String column = request.getString(KEY_COLUMN); + String group = request.getString(KEY_GROUP); + String having = request.getString(KEY_HAVING); + String order = request.getString(KEY_ORDER); + String raw = request.getString(KEY_RAW); + String json = request.getString(KEY_JSON); + + try { + //强制作为条件且放在最前面优化性能 + request.remove(idKey); + request.remove(idInKey); + //关键词 + request.remove(KEY_ROLE); + request.remove(KEY_EXPLAIN); + request.remove(KEY_CACHE); + request.remove(KEY_DATABASE); + request.remove(KEY_SCHEMA); + request.remove(KEY_COMBINE); + request.remove(KEY_FROM); + request.remove(KEY_COLUMN); + request.remove(KEY_GROUP); + request.remove(KEY_HAVING); + request.remove(KEY_ORDER); + request.remove(KEY_RAW); + request.remove(KEY_JSON); + + String[] rawArr = StringUtil.split(raw); + config.setRaw(rawArr == null || rawArr.length <= 0 ? null : new ArrayList<>(Arrays.asList(rawArr))); + + Map tableWhere = new LinkedHashMap();//保证顺序好优化 WHERE id > 1 AND name LIKE... + + //已经remove了id和id{},以及@key + Set set = request.keySet(); //前面已经判断request是否为空 + if (method == POST) { //POST操作 + if (idIn != null) { + throw new IllegalArgumentException("POST 请求中不允许传 " + idInKey + " !"); + } + + if (set != null && set.isEmpty() == false) { //不能直接return,要走完下面的流程 + String[] columns = set.toArray(new String[]{}); + + Collection valueCollection = request.values(); + Object[] values = valueCollection == null ? null : valueCollection.toArray(); + + if (values == null || values.length != columns.length) { + throw new Exception("服务器内部错误:\n" + TAG + + " newSQLConfig values == null || values.length != columns.length !"); + } + column = (id == null ? "" : idKey + ",") + StringUtil.getString(columns); //set已经判断过不为空 + + List> valuess = new ArrayList<>(1); + List items; //(item0, item1, ...) + if (id == null) { //数据库自增 id + items = Arrays.asList(values); //FIXME 是否还需要进行 add 或 remove 操作?Arrays.ArrayList 不允许修改,会抛异常 + } else { + int size = columns.length + (id == null ? 0 : 1); //以key数量为准 + + items = new ArrayList<>(size); + items.add(id); //idList.get(i)); //第0个就是id + + for (int j = 1; j < size; j++) { + items.add(values[j - 1]); //从第1个开始,允许"null" + } + } + + valuess.add(items); + config.setValues(valuess); + } + } else { //非POST操作 + final boolean isWhere = method != PUT;//除了POST,PUT,其它全是条件!!! + + //条件<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< + List whereList = null; + + Map> combineMap = new LinkedHashMap<>(); + List andList = new ArrayList<>(); + List orList = new ArrayList<>(); + List notList = new ArrayList<>(); + + //强制作为条件且放在最前面优化性能 + if (id != null) { + tableWhere.put(idKey, id); + andList.add(idKey); + } + if (idIn != null) { + tableWhere.put(idInKey, idIn); + andList.add(idInKey); + } + + String[] ws = StringUtil.split(combine); + if (ws != null) { + if (method == DELETE || method == GETS || method == HEADS) { + throw new IllegalArgumentException("DELETE,GETS,HEADS 请求不允许传 @combine:value !"); + } + whereList = new ArrayList<>(); + + String w; + for (int i = 0; i < ws.length; i++) { //去除 &,|,! 前缀 + w = ws[i]; + if (w != null) { + if (w.startsWith("&")) { + w = w.substring(1); + andList.add(w); + } else if (w.startsWith("|")) { + if (method == PUT) { + throw new IllegalArgumentException(table + ":{} 里的 @combine:value 中的value里条件 " + ws[i] + " 不合法!" + + "PUT请求的 @combine:\"key0,key1,...\" 不允许传 |key 或 !key !"); + } + w = w.substring(1); + orList.add(w); + } else if (w.startsWith("!")) { + if (method == PUT) { + throw new IllegalArgumentException(table + ":{} 里的 @combine:value 中的value里条件 " + ws[i] + " 不合法!" + + "PUT请求的 @combine:\"key0,key1,...\" 不允许传 |key 或 !key !"); + } + w = w.substring(1); + notList.add(w); + } else { + orList.add(w); + } + + if (w.isEmpty()) { + throw new IllegalArgumentException(table + ":{} 里的 @combine:value 中的value里条件 " + ws[i] + " 不合法!不允许为空值!"); + } else { + if (idKey.equals(w) || idInKey.equals(w) || userIdKey.equals(w) || userIdInKey.equals(w)) { + throw new UnsupportedOperationException(table + ":{} 里的 @combine:value 中的value里 " + ws[i] + " 不合法!" + + "不允许传 [" + idKey + ", " + idInKey + ", " + userIdKey + ", " + userIdInKey + "] 其中任何一个!"); + } + } + + whereList.add(w); + } + + // 可重写回调方法自定义处理 // 动态设置的场景似乎很少,而且去掉后不方便用户排错!//去掉判断,有时候不在没关系,如果是对增删改等非开放请求强制要求传对应的条件,可以用 Operation.NECESSARY + if (request.containsKey(w) == false) { //和 request.get(w) == null 没区别,前面 Parser 已经过滤了 null + // throw new IllegalArgumentException(table + ":{} 里的 @combine:value 中的value里 " + ws[i] + " 对应的 " + w + " 不在它里面!"); + callback.onMissingKey4Combine(table, request, combine, ws[i], w); + } + } + + } + + //条件>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> + + Map tableContent = new LinkedHashMap(); + Object value; + for (String key : set) { + value = request.get(key); + + if (value instanceof Map) {//只允许常规Object + throw new IllegalArgumentException("不允许 " + key + " 等任何key的value类型为 {JSONObject} !"); + } + + //解决AccessVerifier新增userId没有作为条件,而是作为内容,导致PUT,DELETE出错 + if (isWhere) { + tableWhere.put(key, value); + if (whereList == null || whereList.contains(key) == false) { + andList.add(key); + } + } else if (whereList != null && whereList.contains(key)) { + tableWhere.put(key, value); + } else { + tableContent.put(key, value);//一样 instanceof JSONArray ? JSON.toJSONString(value) : value); + } + } + + combineMap.put("&", andList); + combineMap.put("|", orList); + combineMap.put("!", notList); + config.setCombine(combineMap); + + config.setContent(tableContent); + } + + + List cs = new ArrayList<>(); + + List rawList = config.getRaw(); + boolean containColumnRaw = rawList != null && rawList.contains(KEY_COLUMN); + + String rawColumnSQL = null; + if (containColumnRaw) { + try { + rawColumnSQL = config.getRawSQL(KEY_COLUMN, column); + if (rawColumnSQL != null) { + cs.add(rawColumnSQL); + } + } catch (Exception e) { + Log.e(TAG, "newSQLConfig config instanceof AbstractSQLConfig >> try { " + + " rawColumnSQL = ((AbstractSQLConfig) config).getRawSQL(KEY_COLUMN, column); " + + "} catch (Exception e) = " + e.getMessage()); + } + } + + boolean distinct = column == null || rawColumnSQL != null ? false : column.startsWith(PREFFIX_DISTINCT); + if (rawColumnSQL == null) { + String[] fks = StringUtil.split(distinct ? column.substring(PREFFIX_DISTINCT.length()) : column, ";"); // key0,key1;fun0(key0,...);fun1(key0,...);key3;fun2(key0,...) + if (fks != null) { + String[] ks; + for (String fk : fks) { + if (containColumnRaw) { + try { + String rawSQL = config.getRawSQL(KEY_COLUMN, fk); + if (rawSQL != null) { + cs.add(rawSQL); + continue; + } + } catch (Exception e) { + Log.e(TAG, "newSQLConfig rawColumnSQL == null >> try { " + + " String rawSQL = ((AbstractSQLConfig) config).getRawSQL(KEY_COLUMN, fk); ... " + + "} catch (Exception e) = " + e.getMessage()); + } + } + + if (fk.contains("(")) { // fun0(key0,...) + cs.add(fk); + } else { //key0,key1... + ks = StringUtil.split(fk); + if (ks != null && ks.length > 0) { + cs.addAll(Arrays.asList(ks)); + } + } + } + } + } + + config.setExplain(explain); + config.setCache(getCache(cache)); + config.setFrom(from); + config.setDistinct(distinct); + config.setColumn(column == null ? null : cs); //解决总是 config.column != null,总是不能得到 * + config.setWhere(tableWhere); + + config.setId(id); + //在 tableWhere 第0个 config.setIdIn(idIn); + + config.setRole(RequestRole.get(role)); + config.setGroup(group); + config.setHaving(having); + config.setOrder(order); + + String[] jsonArr = StringUtil.split(json); + config.setJson(jsonArr == null || jsonArr.length <= 0 ? null : new ArrayList<>(Arrays.asList(jsonArr))); + + //TODO 解析JOIN,包括 @column,@group 等要合并 + + } finally {//后面还可能用到,要还原 + //id或id{}条件 + if (hasId) { + request.put(idKey, id); + } + request.put(idInKey, idIn); + //关键词 + request.put(KEY_DATABASE, database); + request.put(KEY_ROLE, role); + request.put(KEY_EXPLAIN, explain); + request.put(KEY_CACHE, cache); + request.put(KEY_SCHEMA, schema); + request.put(KEY_COMBINE, combine); + request.put(KEY_FROM, from); + request.put(KEY_COLUMN, column); + request.put(KEY_GROUP, group); + request.put(KEY_HAVING, having); + request.put(KEY_ORDER, order); + request.put(KEY_RAW, raw); + request.put(KEY_JSON, json); + } + + return config; + } + + + /** + * @param method + * @param config + * @param joinList + * @param callback + * @return + * @throws Exception + */ + public static SQLConfig parseJoin(RequestMethod method, SQLConfig config, List joinList, Callback callback) throws Exception { + boolean isQuery = RequestMethod.isQueryMethod(method); + config.setKeyPrefix(isQuery && config.isMain() == false); + + //TODO 解析出 SQLConfig 再合并 column, order, group 等 + if (joinList == null || joinList.isEmpty() || RequestMethod.isQueryMethod(method) == false) { + return config; + } + + + String table; + String alias; + for (Join j : joinList) { + table = j.getTable(); + alias = j.getAlias(); + //JOIN子查询不能设置LIMIT,因为ON关系是在子查询后处理的,会导致结果会错误 + SQLConfig joinConfig = newSQLConfig(method, table, alias, j.getRequest(), null, false, callback); + SQLConfig cacheConfig = j.canCacheViceTable() == false ? null : newSQLConfig(method, table, alias, j.getRequest(), null, false, callback).setCount(1); + + if (j.isAppJoin() == false) { //除了 @ APP JOIN,其它都是 SQL JOIN,则副表要这样配置 + if (joinConfig.getDatabase() == null) { + joinConfig.setDatabase(config.getDatabase()); //解决主表 JOIN 副表,引号不一致 + } else if (joinConfig.getDatabase().equals(config.getDatabase()) == false) { + throw new IllegalArgumentException("主表 " + config.getTable() + " 的 @database:" + config.getDatabase() + " 和它 SQL JOIN 的副表 " + table + " 的 @database:" + joinConfig.getDatabase() + " 不一致!"); + } + if (joinConfig.getSchema() == null) { + joinConfig.setSchema(config.getSchema()); //主表 JOIN 副表,默认 schema 一致 + } + + if (cacheConfig != null) { + cacheConfig.setDatabase(joinConfig.getDatabase()).setSchema(joinConfig.getSchema()); //解决主表 JOIN 副表,引号不一致 + } + + + if (isQuery) { + config.setKeyPrefix(true); + } + + joinConfig.setMain(false).setKeyPrefix(true); + + if (j.isLeftOrRightJoin()) { + SQLConfig outterConfig = newSQLConfig(method, table, alias, j.getOuter(), null, false, callback); + outterConfig.setMain(false).setKeyPrefix(true).setDatabase(joinConfig.getDatabase()).setSchema(joinConfig.getSchema()); //解决主表 JOIN 副表,引号不一致 + j.setOuterConfig(outterConfig); + } + } + + //解决 query: 1/2 查数量时报错 /* SELECT count(*) AS count FROM sys.Moment AS Moment LEFT JOIN ( SELECT count(*) AS count FROM sys.Comment ) AS Comment ON Comment.momentId = Moment.id LIMIT 1 OFFSET 0 */ - if (RequestMethod.isHeadMethod(method, true)) { - joinConfig.setMethod(GET); //子查询不能为 SELECT count(*) ,而应该是 SELECT momentId - joinConfig.setColumn(Arrays.asList(j.getKey())); //优化性能,不取非必要的字段 - - if (cacheConfig != null) { - cacheConfig.setMethod(GET); //子查询不能为 SELECT count(*) ,而应该是 SELECT momentId - cacheConfig.setColumn(Arrays.asList(j.getKey())); //优化性能,不取非必要的字段 - } - } - - j.setJoinConfig(joinConfig); - j.setCacheConfig(cacheConfig); - } - - config.setJoinList(joinList); - - return config; - } - - - - /**获取客户端实际需要的key - * verifyName = true - * @param method - * @param originKey - * @param isTableKey - * @param saveLogic 保留逻辑运算符 & | ! - * @return - */ - public static String getRealKey(RequestMethod method, String originKey - , boolean isTableKey, boolean saveLogic) throws Exception { - return getRealKey(method, originKey, isTableKey, saveLogic, true); - } - /**获取客户端实际需要的key - * @param method - * @param originKey - * @param isTableKey - * @param saveLogic 保留逻辑运算符 & | ! - * @param verifyName 验证key名是否符合代码变量/常量名 - * @return - */ - public static String getRealKey(RequestMethod method, String originKey - , boolean isTableKey, boolean saveLogic, boolean verifyName) throws Exception { - Log.i(TAG, "getRealKey saveLogic = " + saveLogic + "; originKey = " + originKey); - if (originKey == null || apijson.JSONObject.isArrayKey(originKey)) { - Log.w(TAG, "getRealKey originKey == null || apijson.JSONObject.isArrayKey(originKey) >> return originKey;"); - return originKey; - } - - String key = new String(originKey); - if (key.endsWith("$")) {//搜索 LIKE,查询时处理 - key = key.substring(0, key.length() - 1); - } - else if (key.endsWith("~")) {//匹配正则表达式 REGEXP,查询时处理 - key = key.substring(0, key.length() - 1); - if (key.endsWith("*")) {//忽略大小写 - key = key.substring(0, key.length() - 1); - } - } - else if (key.endsWith("%")) {//数字、文本、日期范围 BETWEEN AND - key = key.substring(0, key.length() - 1); - } - else if (key.endsWith("{}")) {//被包含 IN,或者说key对应值处于value的范围内。查询时处理 - key = key.substring(0, key.length() - 2); - } - else if (key.endsWith("}{")) {//被包含 EXISTS,或者说key对应值处于value的范围内。查询时处理 - key = key.substring(0, key.length() - 2); - } - else if (key.endsWith("<>")) {//包含 json_contains,或者说value处于key对应值的范围内。查询时处理 - key = key.substring(0, key.length() - 2); - } - else if (key.endsWith("()")) {//方法,查询完后处理,先用一个Map保存 - key = key.substring(0, key.length() - 2); - } - else if (key.endsWith("@")) {//引用,引用对象查询完后处理。fillTarget中暂时不用处理,因为非GET请求都是由给定的id确定,不需要引用 - key = key.substring(0, key.length() - 1); - } - else if (key.endsWith(">=")) {//比较。查询时处理 - key = key.substring(0, key.length() - 2); - } - else if (key.endsWith("<=")) {//比较。查询时处理 - key = key.substring(0, key.length() - 2); - } - else if (key.endsWith(">")) {//比较。查询时处理 - key = key.substring(0, key.length() - 1); - } - else if (key.endsWith("<")) {//比较。查询时处理 - key = key.substring(0, key.length() - 1); - } - else if (key.endsWith("+")) {//延长,PUT查询时处理 - if (method == PUT) {//不为PUT就抛异常 - key = key.substring(0, key.length() - 1); - } - } - else if (key.endsWith("-")) {//缩减,PUT查询时处理 - if (method == PUT) {//不为PUT就抛异常 - key = key.substring(0, key.length() - 1); - } - } - - String last = null;//不用Logic优化代码,否则 key 可能变为 key| 导致 key=value 变成 key|=value 而出错 - if (RequestMethod.isQueryMethod(method)) {//逻辑运算符仅供GET,HEAD方法使用 - last = key.isEmpty() ? "" : key.substring(key.length() - 1); - if ("&".equals(last) || "|".equals(last) || "!".equals(last)) { - key = key.substring(0, key.length() - 1); - } else { - last = null;//避免key + StringUtil.getString(last)错误延长 - } - } - - //"User:toUser":User转换"toUser":User, User为查询同名Table得到的JSONObject。交给客户端处理更好 - if (isTableKey) {//不允许在column key中使用Type:key形式 - key = Pair.parseEntry(key, true).getKey();//table以左边为准 - } else { - key = Pair.parseEntry(key).getValue();//column以右边为准 - } - - if (verifyName && StringUtil.isName(key.startsWith("@") ? key.substring(1) : key) == false) { - throw new IllegalArgumentException(method + "请求,字符 " + originKey + " 不合法!" - + " key:value 中的key只能关键词 '@key' 或 'key[逻辑符][条件符]' 或 PUT请求下的 'key+' / 'key-' !"); - } - - if (saveLogic && last != null) { - key = key + last; - } - Log.i(TAG, "getRealKey return key = " + key); - return key; - } - - - public static interface IdCallback { - /**为 post 请求新建 id, 只能是 Long 或 String - * @param method - * @param database - * @param schema - * @param table - * @return - */ - Object newId(RequestMethod method, String database, String schema, String table); - - /**已废弃,最早 5.0.0 移除,改用 {@link #getIdKey(String, String, String, String)} - * @param database - * @param schema - * @param table - * @return - */ - @Deprecated - String getIdKey(String database, String schema, String table); - - /**获取主键名 - * @param database - * @param schema - * @param table - * @return - */ - String getIdKey(String database, String schema, String datasource, String table); - - /**已废弃,最早 5.0.0 移除,改用 {@link #getUserIdKey(String, String, String, String)} - * @param database - * @param schema - * @param table - * @return - */ - @Deprecated - String getUserIdKey(String database, String schema, String table); - - /**获取 User 的主键名 - * @param database - * @param schema - * @param table - * @return - */ - String getUserIdKey(String database, String schema, String datasource, String table); - } - - public static interface Callback extends IdCallback { - /**获取 SQLConfig 的实例 - * @param method - * @param database - * @param schema - * @param table - * @return - */ - SQLConfig getSQLConfig(RequestMethod method, String database, String schema, String table); - - /**combine 里的 key 在 request 中 value 为 null 或不存在,即 request 中缺少用来作为 combine 条件的 key: value - * @param combine - * @param key - * @param request - */ - public void onMissingKey4Combine(String name, JSONObject request, String combine, String item, String key) throws Exception; - } - - public static abstract class SimpleCallback implements Callback { - - - @Override - public Object newId(RequestMethod method, String database, String schema, String table) { - return System.currentTimeMillis(); - } - - @Override - public String getIdKey(String database, String schema, String table) { - return KEY_ID; - } - - @Override - public String getIdKey(String database, String schema, String datasource, String table) { - return getIdKey(database, schema, table); - } - - @Override - public String getUserIdKey(String database, String schema, String table) { - return KEY_USER_ID; - } - - @Override - public String getUserIdKey(String database, String schema, String datasource, String table) { - return getUserIdKey(database, schema, table); - } - - @Override - public void onMissingKey4Combine(String name, JSONObject request, String combine, String item, String key) throws Exception { - throw new IllegalArgumentException(name + ":{} 里的 @combine:value 中的value里 " + item + " 对应的条件 " + key + ":value 中 value 不能为 null!"); - } - - } + if (RequestMethod.isHeadMethod(method, true)) { + joinConfig.setMethod(GET); //子查询不能为 SELECT count(*) ,而应该是 SELECT momentId + joinConfig.setColumn(Arrays.asList(j.getKey())); //优化性能,不取非必要的字段 + + if (cacheConfig != null) { + cacheConfig.setMethod(GET); //子查询不能为 SELECT count(*) ,而应该是 SELECT momentId + cacheConfig.setColumn(Arrays.asList(j.getKey())); //优化性能,不取非必要的字段 + } + } + + j.setJoinConfig(joinConfig); + j.setCacheConfig(cacheConfig); + } + + config.setJoinList(joinList); + + return config; + } + + + /** + * 获取客户端实际需要的key + * verifyName = true + * + * @param method + * @param originKey + * @param isTableKey + * @param saveLogic 保留逻辑运算符 & | ! + * @return + */ + public static String getRealKey(RequestMethod method, String originKey + , boolean isTableKey, boolean saveLogic) throws Exception { + return getRealKey(method, originKey, isTableKey, saveLogic, true); + } + + /** + * 获取客户端实际需要的key + * + * @param method + * @param originKey + * @param isTableKey + * @param saveLogic 保留逻辑运算符 & | ! + * @param verifyName 验证key名是否符合代码变量/常量名 + * @return + */ + public static String getRealKey(RequestMethod method, String originKey + , boolean isTableKey, boolean saveLogic, boolean verifyName) throws Exception { + Log.i(TAG, "getRealKey saveLogic = " + saveLogic + "; originKey = " + originKey); + if (originKey == null || apijson.JSONObject.isArrayKey(originKey)) { + Log.w(TAG, "getRealKey originKey == null || apijson.JSONObject.isArrayKey(originKey) >> return originKey;"); + return originKey; + } + + String key = new String(originKey); + if (key.endsWith("$")) {//搜索 LIKE,查询时处理 + key = key.substring(0, key.length() - 1); + } else if (key.endsWith("~")) {//匹配正则表达式 REGEXP,查询时处理 + key = key.substring(0, key.length() - 1); + if (key.endsWith("*")) {//忽略大小写 + key = key.substring(0, key.length() - 1); + } + } else if (key.endsWith("%")) {//数字、文本、日期范围 BETWEEN AND + key = key.substring(0, key.length() - 1); + } else if (key.endsWith("{}")) {//被包含 IN,或者说key对应值处于value的范围内。查询时处理 + key = key.substring(0, key.length() - 2); + } else if (key.endsWith("}{")) {//被包含 EXISTS,或者说key对应值处于value的范围内。查询时处理 + key = key.substring(0, key.length() - 2); + } else if (key.endsWith("<>")) {//包含 json_contains,或者说value处于key对应值的范围内。查询时处理 + key = key.substring(0, key.length() - 2); + } else if (key.endsWith("()")) {//方法,查询完后处理,先用一个Map保存 + key = key.substring(0, key.length() - 2); + } else if (key.endsWith("@")) {//引用,引用对象查询完后处理。fillTarget中暂时不用处理,因为非GET请求都是由给定的id确定,不需要引用 + key = key.substring(0, key.length() - 1); + } else if (key.endsWith(">=")) {//比较。查询时处理 + key = key.substring(0, key.length() - 2); + } else if (key.endsWith("<=")) {//比较。查询时处理 + key = key.substring(0, key.length() - 2); + } else if (key.endsWith(">")) {//比较。查询时处理 + key = key.substring(0, key.length() - 1); + } else if (key.endsWith("<")) {//比较。查询时处理 + key = key.substring(0, key.length() - 1); + } else if (key.endsWith("+")) {//延长,PUT查询时处理 + if (method == PUT) {//不为PUT就抛异常 + key = key.substring(0, key.length() - 1); + } + } else if (key.endsWith("-")) {//缩减,PUT查询时处理 + if (method == PUT) {//不为PUT就抛异常 + key = key.substring(0, key.length() - 1); + } + } + + String last = null;//不用Logic优化代码,否则 key 可能变为 key| 导致 key=value 变成 key|=value 而出错 + if (RequestMethod.isQueryMethod(method)) {//逻辑运算符仅供GET,HEAD方法使用 + last = key.isEmpty() ? "" : key.substring(key.length() - 1); + if ("&".equals(last) || "|".equals(last) || "!".equals(last)) { + key = key.substring(0, key.length() - 1); + } else { + last = null;//避免key + StringUtil.getString(last)错误延长 + } + } + + //"User:toUser":User转换"toUser":User, User为查询同名Table得到的JSONObject。交给客户端处理更好 + if (isTableKey) {//不允许在column key中使用Type:key形式 + key = Pair.parseEntry(key, true).getKey();//table以左边为准 + } else { + key = Pair.parseEntry(key).getValue();//column以右边为准 + } + + if (verifyName && StringUtil.isName(key.startsWith("@") ? key.substring(1) : key) == false) { + throw new IllegalArgumentException(method + "请求,字符 " + originKey + " 不合法!" + + " key:value 中的key只能关键词 '@key' 或 'key[逻辑符][条件符]' 或 PUT请求下的 'key+' / 'key-' !"); + } + + if (saveLogic && last != null) { + key = key + last; + } + Log.i(TAG, "getRealKey return key = " + key); + return key; + } + + + public static interface IdCallback { + /** + * 为 post 请求新建 id, 只能是 Long 或 String + * + * @param method + * @param database + * @param schema + * @param table + * @return + */ + Object newId(RequestMethod method, String database, String schema, String table); + + /** + * 已废弃,最早 5.0.0 移除,改用 {@link #getIdKey(String, String, String, String)} + * + * @param database + * @param schema + * @param table + * @return + */ + @Deprecated + String getIdKey(String database, String schema, String table); + + /** + * 获取主键名 + * + * @param database + * @param schema + * @param table + * @return + */ + String getIdKey(String database, String schema, String datasource, String table); + + /** + * 已废弃,最早 5.0.0 移除,改用 {@link #getUserIdKey(String, String, String, String)} + * + * @param database + * @param schema + * @param table + * @return + */ + @Deprecated + String getUserIdKey(String database, String schema, String table); + + /** + * 获取 User 的主键名 + * + * @param database + * @param schema + * @param table + * @return + */ + String getUserIdKey(String database, String schema, String datasource, String table); + } + + public static interface Callback extends IdCallback { + /** + * 获取 SQLConfig 的实例 + * + * @param method + * @param database + * @param schema + * @param table + * @return + */ + SQLConfig getSQLConfig(RequestMethod method, String database, String schema, String table); + + /** + * combine 里的 key 在 request 中 value 为 null 或不存在,即 request 中缺少用来作为 combine 条件的 key: value + * + * @param combine + * @param key + * @param request + */ + public void onMissingKey4Combine(String name, JSONObject request, String combine, String item, String key) throws Exception; + } + + public static abstract class SimpleCallback implements Callback { + + + @Override + public Object newId(RequestMethod method, String database, String schema, String table) { + return System.currentTimeMillis(); + } + + @Override + public String getIdKey(String database, String schema, String table) { + return KEY_ID; + } + + @Override + public String getIdKey(String database, String schema, String datasource, String table) { + return getIdKey(database, schema, table); + } + + @Override + public String getUserIdKey(String database, String schema, String table) { + return KEY_USER_ID; + } + + @Override + public String getUserIdKey(String database, String schema, String datasource, String table) { + return getUserIdKey(database, schema, table); + } + + @Override + public void onMissingKey4Combine(String name, JSONObject request, String combine, String item, String key) throws Exception { + throw new IllegalArgumentException(name + ":{} 里的 @combine:value 中的value里 " + item + " 对应的条件 " + key + ":value 中 value 不能为 null!"); + } + + } } From f58002f8d29241578344df6debd4953cebed5832 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Mon, 21 Jun 2021 15:38:46 +0800 Subject: [PATCH 0385/1181] Update CONTRIBUTING.md --- CONTRIBUTING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 0c39db52c..eb3ea87f3 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -29,7 +29,7 @@ - [kxlv2000](https://github.com/kxlv2000)(SUSTech) - [caohao-php](https://github.com/caohao-php)(腾讯工程师) - [Wscats](https://github.com/Wscats)(腾讯工程师) -- [jun0315](https://github.com/jun0315) +- [jun0315](https://github.com/jun0315)(腾讯工程师) #### 其中特别致谢:
From 2dfe8eda0aafd0dece15c179a05f4180a4242497 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Mon, 21 Jun 2021 15:51:02 +0800 Subject: [PATCH 0386/1181] =?UTF-8?q?=E6=AC=A2=E8=BF=8E=20AbstractSQLConfi?= =?UTF-8?q?g=20=E5=B9=B6=E5=BA=94=E7=94=A8=E5=85=B3=E9=94=AE=E6=9B=B4?= =?UTF-8?q?=E6=94=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit https://github.com/Tencent/APIJSON/pull/253 --- .../java/apijson/orm/AbstractSQLConfig.java | 6992 ++++++++--------- 1 file changed, 3443 insertions(+), 3549 deletions(-) diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java index 755209951..7b14facfa 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java @@ -71,3559 +71,3453 @@ import apijson.orm.model.Table; import apijson.orm.model.TestRecord; -/** - * config sql for JSON Request - * +/**config sql for JSON Request * @author Lemon */ public abstract class AbstractSQLConfig implements SQLConfig { - private static final String TAG = "AbstractSQLConfig"; - - public static String DEFAULT_DATABASE = DATABASE_MYSQL; - public static String DEFAULT_SCHEMA = "sys"; - public static String PREFFIX_DISTINCT = "DISTINCT "; - - // * 和 / 不能同时出现,防止 /* */ 段注释! # 和 -- 不能出现,防止行注释! ; 不能出现,防止隔断SQL语句!空格不能出现,防止 CRUD,DROP,SHOW TABLES等语句! - private static final Pattern PATTERN_RANGE; - private static final Pattern PATTERN_FUNCTION; - - /** - * 表名映射,隐藏真实表名,对安全要求很高的表可以这么做 - */ - public static final Map TABLE_KEY_MAP; - public static final List CONFIG_TABLE_LIST; - public static final List DATABASE_LIST; - // 自定义原始 SQL 片段 Map:当 substring 为 null 时忽略;当 substring 为 "" 时整个 value 是 raw SQL;其它情况则只是 substring 这段为 raw SQL - public static final Map RAW_MAP; - // 允许调用的 SQL 函数:当 substring 为 null 时忽略;当 substring 为 "" 时整个 value 是 raw SQL;其它情况则只是 substring 这段为 raw SQL - public static final Map SQL_FUNCTION_MAP; - - static { // 凡是 SQL 边界符、分隔符、注释符 都不允许,例如 ' " ` ( ) ; # -- ,以免拼接 SQL 时被注入意外可执行指令 - PATTERN_RANGE = Pattern.compile("^[0-9%,!=\\<\\>/\\.\\+\\-\\*\\^]+$"); // ^[a-zA-Z0-9_*%!=<>(),"]+$ 导致 exists(select*from(Comment)) 通过! - PATTERN_FUNCTION = Pattern.compile("^[A-Za-z0-9%,:_@&~!=\\<\\>\\|\\[\\]\\{\\} /\\.\\+\\-\\*\\^\\?\\$]+$"); //TODO 改成更好的正则,校验前面为单词,中间为操作符,后面为值 - - - TABLE_KEY_MAP = new HashMap(); - TABLE_KEY_MAP.put(Table.class.getSimpleName(), Table.TABLE_NAME); - TABLE_KEY_MAP.put(Column.class.getSimpleName(), Column.TABLE_NAME); - TABLE_KEY_MAP.put(PgClass.class.getSimpleName(), PgClass.TABLE_NAME); - TABLE_KEY_MAP.put(PgAttribute.class.getSimpleName(), PgAttribute.TABLE_NAME); - TABLE_KEY_MAP.put(SysTable.class.getSimpleName(), SysTable.TABLE_NAME); - TABLE_KEY_MAP.put(SysColumn.class.getSimpleName(), SysColumn.TABLE_NAME); - TABLE_KEY_MAP.put(ExtendedProperty.class.getSimpleName(), ExtendedProperty.TABLE_NAME); - - CONFIG_TABLE_LIST = new ArrayList<>(); // Table, Column 等是系统表 AbstractVerifier.SYSTEM_ACCESS_MAP.keySet()); - CONFIG_TABLE_LIST.add(Function.class.getSimpleName()); - CONFIG_TABLE_LIST.add(Request.class.getSimpleName()); - CONFIG_TABLE_LIST.add(Response.class.getSimpleName()); - CONFIG_TABLE_LIST.add(Access.class.getSimpleName()); - CONFIG_TABLE_LIST.add(Document.class.getSimpleName()); - CONFIG_TABLE_LIST.add(TestRecord.class.getSimpleName()); - - - DATABASE_LIST = new ArrayList<>(); - DATABASE_LIST.add(DATABASE_MYSQL); - DATABASE_LIST.add(DATABASE_POSTGRESQL); - DATABASE_LIST.add(DATABASE_SQLSERVER); - DATABASE_LIST.add(DATABASE_ORACLE); - DATABASE_LIST.add(DATABASE_DB2); - - - RAW_MAP = new LinkedHashMap<>(); // 保证顺序,避免配置冲突等意外情况 - - - SQL_FUNCTION_MAP = new LinkedHashMap<>(); // 保证顺序,避免配置冲突等意外情况 - - // MySQL 字符串函数 - SQL_FUNCTION_MAP.put("ascii", ""); // ASCII(s) 返回字符串 s 的第一个字符的 ASCII 码。 - SQL_FUNCTION_MAP.put("char_length", ""); // CHAR_LENGTH(s) 返回字符串 s 的字符数 - SQL_FUNCTION_MAP.put("character_length", ""); // CHARACTER_LENGTH(s) 返回字符串 s 的字符数 - SQL_FUNCTION_MAP.put("concat", ""); // CONCAT(s1, s2...sn) 字符串 s1,s2 等多个字符串合并为一个字符串 - SQL_FUNCTION_MAP.put("concat_ws", ""); // CONCAT_WS(x, s1, s2...sn) 同 CONCAT(s1, s2 ...) 函数,但是每个字符串之间要加上 x,x 可以是分隔符 - SQL_FUNCTION_MAP.put("field", ""); // FIELD(s, s1, s2...) 返回第一个字符串 s 在字符串列表 (s1, s2...)中的位置 - SQL_FUNCTION_MAP.put("find_in_set", ""); // FIND_IN_SET(s1, s2) 返回在字符串s2中与s1匹配的字符串的位置 - SQL_FUNCTION_MAP.put("format", ""); // FORMAT(x, n) 函数可以将数字 x 进行格式化 "#,###.##", 将 x 保留到小数点后 n 位,最后一位四舍五入。 - SQL_FUNCTION_MAP.put("insert", ""); // INSERT(s1, x, len, s2) 字符串 s2 替换 s1 的 x 位置开始长度为 len 的字符串 - SQL_FUNCTION_MAP.put("locate", ""); // LOCATE(s1, s) 从字符串 s 中获取 s1 的开始位置 - SQL_FUNCTION_MAP.put("lcase", ""); // LCASE(s) 将字符串 s 的所有字母变成小写字母 - SQL_FUNCTION_MAP.put("left", ""); // LEFT(s, n) 返回字符串 s 的前 n 个字符 - SQL_FUNCTION_MAP.put("length", ""); // LENGTH(s) 返回字符串 s 的字符数 - SQL_FUNCTION_MAP.put("lower", ""); // LOWER(s) 将字符串 s 的所有字母变成小写字母 - SQL_FUNCTION_MAP.put("lpad", ""); // LPAD(s1, len, s2) 在字符串 s1 的开始处填充字符串 s2,使字符串长度达到 len - SQL_FUNCTION_MAP.put("ltrim", ""); // LTRIM(s) 去掉字符串 s 开始处的空格 - SQL_FUNCTION_MAP.put("mid", ""); // MID(s, n, len) 从字符串 s 的 n 位置截取长度为 len 的子字符串,同 SUBSTRING(s, n, len) - SQL_FUNCTION_MAP.put("position", ""); // POSITION(s, s1); 从字符串 s 中获取 s1 的开始位置 - SQL_FUNCTION_MAP.put("repeat", ""); // REPEAT(s, n) 将字符串 s 重复 n 次 - SQL_FUNCTION_MAP.put("replace", ""); // REPLACE(s, s1, s2) 将字符串 s2 替代字符串 s 中的字符串 s1 - SQL_FUNCTION_MAP.put("reverse", ""); // REVERSE(s); // ) 将字符串s的顺序反过来 - SQL_FUNCTION_MAP.put("right", ""); // RIGHT(s, n) 返回字符串 s 的后 n 个字符 - SQL_FUNCTION_MAP.put("rpad", ""); // RPAD(s1, len, s2) 在字符串 s1 的结尾处添加字符串 s2,使字符串的长度达到 len - SQL_FUNCTION_MAP.put("rtrim", ""); // RTRIM", ""); // ) 去掉字符串 s 结尾处的空格 - SQL_FUNCTION_MAP.put("space", ""); // SPACE(n) 返回 n 个空格 - SQL_FUNCTION_MAP.put("strcmp", ""); // STRCMP(s1, s2) 比较字符串 s1 和 s2,如果 s1 与 s2 相等返回 0 ,如果 s1>s2 返回 1,如果 s1d2 之间相隔的天数 - SQL_FUNCTION_MAP.put("date_add", ""); // DATE_ADD(d,INTERVAL expr type) 计算起始日期 d 加上一个时间段后的日期 - SQL_FUNCTION_MAP.put("date_format", ""); // DATE_FORMAT(d,f) 按表达式 f的要求显示日期 d - SQL_FUNCTION_MAP.put("date_sub", ""); // DATE_SUB(date,INTERVAL expr type) 函数从日期减去指定的时间间隔。 - SQL_FUNCTION_MAP.put("day", ""); // DAY(d) 返回日期值 d 的日期部分 - SQL_FUNCTION_MAP.put("dayname", ""); // DAYNAME(d) 返回日期 d 是星期几,如 Monday,Tuesday - SQL_FUNCTION_MAP.put("dayofmonth", ""); // DAYOFMONTH(d) 计算日期 d 是本月的第几天 - SQL_FUNCTION_MAP.put("dayofweek", ""); // DAYOFWEEK(d) 日期 d 今天是星期几,1 星期日,2 星期一,以此类推 - SQL_FUNCTION_MAP.put("dayofyear", ""); // DAYOFYEAR(d) 计算日期 d 是本年的第几天 - SQL_FUNCTION_MAP.put("extract", ""); // EXTRACT(type FROM d) 从日期 d 中获取指定的值,type 指定返回的值。 - SQL_FUNCTION_MAP.put("from_days", ""); // FROM_DAYS(n) 计算从 0000 年 1 月 1 日开始 n 天后的日期 - SQL_FUNCTION_MAP.put("hour", ""); // 'HOUR(t) 返回 t 中的小时值 - SQL_FUNCTION_MAP.put("last_day", ""); // LAST_DAY(d) 返回给给定日期的那一月份的最后一天 - SQL_FUNCTION_MAP.put("localtime", ""); // LOCALTIME() 返回当前日期和时间 - SQL_FUNCTION_MAP.put("localtimestamp", ""); // LOCALTIMESTAMP() 返回当前日期和时间 - SQL_FUNCTION_MAP.put("makedate", ""); // MAKEDATE(year, day-of-year) 基于给定参数年份 year 和所在年中的天数序号 day-of-year 返回一个日期 - SQL_FUNCTION_MAP.put("maketime", ""); // MAKETIME(hour, minute, second) 组合时间,参数分别为小时、分钟、秒 - SQL_FUNCTION_MAP.put("microsecond", ""); // MICROSECOND(date) 返回日期参数所对应的微秒数 - SQL_FUNCTION_MAP.put("minute", ""); // MINUTE(t) 返回 t 中的分钟值 - SQL_FUNCTION_MAP.put("monthname", ""); // MONTHNAME(d) 返回日期当中的月份名称,如 November - SQL_FUNCTION_MAP.put("month", ""); // MONTH(d) 返回日期d中的月份值,1 到 12 - SQL_FUNCTION_MAP.put("now", ""); // NOW() 返回当前日期和时间 - SQL_FUNCTION_MAP.put("period_add", ""); // PERIOD_ADD(period, number) 为 年-月 组合日期添加一个时段 - SQL_FUNCTION_MAP.put("period_diff", ""); // PERIOD_DIFF(period1, period2) 返回两个时段之间的月份差值 - SQL_FUNCTION_MAP.put("quarter", ""); // QUARTER(d) 返回日期d是第几季节,返回 1 到 4 - SQL_FUNCTION_MAP.put("second", ""); // SECOND(t) 返回 t 中的秒钟值 - SQL_FUNCTION_MAP.put("sec_to_time", ""); // SEC_TO_TIME", ""); // ) 将以秒为单位的时间 s 转换为时分秒的格式 - SQL_FUNCTION_MAP.put("str_to_date", ""); // STR_TO_DATE", ""); // tring, format_mask) 将字符串转变为日期 - SQL_FUNCTION_MAP.put("subdate", ""); // SUBDATE(d,n) 日期 d 减去 n 天后的日期 - SQL_FUNCTION_MAP.put("subtime", ""); // SUBTIME(t,n) 时间 t 减去 n 秒的时间 - SQL_FUNCTION_MAP.put("sysdate", ""); // SYSDATE() 返回当前日期和时间 - SQL_FUNCTION_MAP.put("time", ""); // TIME(expression) 提取传入表达式的时间部分 - SQL_FUNCTION_MAP.put("time_format", ""); // TIME_FORMAT(t,f) 按表达式 f 的要求显示时间 t - SQL_FUNCTION_MAP.put("time_to_sec", ""); // TIME_TO_SEC(t) 将时间 t 转换为秒 - SQL_FUNCTION_MAP.put("timediff", ""); // TIMEDIFF(time1, time2) 计算时间差值 - SQL_FUNCTION_MAP.put("timestamp", ""); // TIMESTAMP(expression, interval) 单个参数时,函数返回日期或日期时间表达式;有2个参数时,将参数加和 - SQL_FUNCTION_MAP.put("to_days", ""); // TO_DAYS(d) 计算日期 d 距离 0000 年 1 月 1 日的天数 - SQL_FUNCTION_MAP.put("week", ""); // WEEK(d) 计算日期 d 是本年的第几个星期,范围是 0 到 53 - SQL_FUNCTION_MAP.put("weekday", ""); // WEEKDAY(d) 日期 d 是星期几,0 表示星期一,1 表示星期二 - SQL_FUNCTION_MAP.put("weekofyear", ""); // WEEKOFYEAR(d) 计算日期 d 是本年的第几个星期,范围是 0 到 53 - SQL_FUNCTION_MAP.put("year", ""); // YEAR(d) 返回年份 - SQL_FUNCTION_MAP.put("yearweek", ""); // YEARWEEK(date, mode) 返回年份及第几周(0到53),mode 中 0 表示周天,1表示周一,以此类推 - SQL_FUNCTION_MAP.put("unix_timestamp", ""); // UNIX_TIMESTAMP(date) 获取UNIX时间戳函数,返回一个以 UNIX 时间戳为基础的无符号整数 - SQL_FUNCTION_MAP.put("from_unixtime", ""); // FROM_UNIXTIME(date) 将 UNIX 时间戳转换为时间格式,与UNIX_TIMESTAMP互为反函数 - - // MYSQL JSON 函数 - SQL_FUNCTION_MAP.put("json_append", ""); // JSON_APPEND(json_doc, path, val[, path, val] ...)) 插入JSON数组 - SQL_FUNCTION_MAP.put("json_array", ""); // JSON_ARRAY(val1, val2...) 创建JSON数组 - SQL_FUNCTION_MAP.put("json_array_append", ""); // JSON_ARRAY_APPEND(json_doc, val) 将数据附加到JSON文档 - SQL_FUNCTION_MAP.put("json_array_insert", ""); // JSON_ARRAY_INSERT(json_doc, val) 插入JSON数组 - SQL_FUNCTION_MAP.put("json_contains", ""); // JSON_CONTAINS(json_doc, val) JSON文档是否在路径中包含特定对象 - SQL_FUNCTION_MAP.put("json_contains_path", ""); // JSON_CONTAINS_PATH(json_doc, path) JSON文档是否在路径中包含任何数据 - SQL_FUNCTION_MAP.put("json_depth", ""); // JSON_DEPTH(json_doc) JSON文档的最大深度 - SQL_FUNCTION_MAP.put("json_extract", ""); // JSON_EXTRACT(json_doc, path) 从JSON文档返回数据 - SQL_FUNCTION_MAP.put("json_insert", ""); // JSON_INSERT(json_doc, val) 将数据插入JSON文档 - SQL_FUNCTION_MAP.put("json_keys", ""); // JSON_KEYS(json_doc[, path]) JSON文档中的键数组 - SQL_FUNCTION_MAP.put("json_length", ""); // JSON_LENGTH(json_doc) JSON文档中的元素数 - SQL_FUNCTION_MAP.put("json_merge", ""); // JSON_MERGE(json_doc1, json_doc2) (已弃用) 合并JSON文档,保留重复的键。JSON_MERGE_PRESERVE()的已弃用同义词 - SQL_FUNCTION_MAP.put("json_merge_patch", ""); // JSON_MERGE_PATCH(json_doc1, json_doc2) 合并JSON文档,替换重复键的值 - SQL_FUNCTION_MAP.put("json_merge_preserve", ""); // JSON_MERGE_PRESERVE(json_doc1, json_doc2) 合并JSON文档,保留重复的键 - SQL_FUNCTION_MAP.put("json_object", ""); // JSON_OBJECT(key1, val1, key2, val2...) 创建JSON对象 - SQL_FUNCTION_MAP.put("json_overlaps", ""); // JSON_OVERLAPS(json_doc1, json_doc2) (引入8.0.17) 比较两个JSON文档,如果它们具有相同的键值对或数组元素,则返回TRUE(1),否则返回FALSE(0) - SQL_FUNCTION_MAP.put("json_pretty", ""); // JSON_PRETTY(json_doc) 以易于阅读的格式打印JSON文档 - SQL_FUNCTION_MAP.put("json_quote", ""); // JSON_QUOTE(json_doc1) 引用JSON文档 - SQL_FUNCTION_MAP.put("json_remove", ""); // JSON_REMOVE(json_doc1, path) 从JSON文档中删除数据 - SQL_FUNCTION_MAP.put("json_replace", ""); // JSON_REPLACE(json_doc1, val1, val2) 替换JSON文档中的值 - SQL_FUNCTION_MAP.put("json_schema_valid", ""); // JSON_SCHEMA_VALID(json_doc) (引入8.0.17) 根据JSON模式验证JSON文档;如果文档针对架构进行验证,则返回TRUE / 1;否则,则返回FALSE / 0 - SQL_FUNCTION_MAP.put("json_schema_validation_report", ""); // JSON_SCHEMA_VALIDATION_REPORT(json_doc, mode) (引入8.0.17) 根据JSON模式验证JSON文档;以JSON格式返回有关验证结果的报告,包括成功或失败以及失败原因 - SQL_FUNCTION_MAP.put("json_search", ""); // JSON_SEARCH(json_doc, val) JSON文档中值的路径 - SQL_FUNCTION_MAP.put("json_set", ""); // JSON_SET(json_doc, val) 将数据插入JSON文档 - // SQL_FUNCTION_MAP.put("json_storage_free", ""); // JSON_STORAGE_FREE() 部分更新后,JSON列值的二进制表示形式中的可用空间 - // SQL_FUNCTION_MAP.put("json_storage_size", ""); // JSON_STORAGE_SIZE() 用于存储JSON文档的二进制表示的空间 - SQL_FUNCTION_MAP.put("json_table", ""); // JSON_TABLE() 从JSON表达式返回数据作为关系表 - SQL_FUNCTION_MAP.put("json_type", ""); // JSON_TYPE(json_doc) JSON值类型 - SQL_FUNCTION_MAP.put("json_unquote", ""); // JSON_UNQUOTE(json_doc) 取消引用JSON值 - SQL_FUNCTION_MAP.put("json_valid", ""); // JSON_VALID(json_doc) JSON值是否有效 - SQL_FUNCTION_MAP.put("json_arrayagg", ""); // JSON_ARRAYAGG(key) 将每个表达式转换为 JSON 值,然后返回一个包含这些 JSON 值的 JSON 数组 - SQL_FUNCTION_MAP.put("json_objectagg", ""); // JSON_OBJECTAGG(key, val)) 将每个表达式转换为 JSON 值,然后返回一个包含这些 JSON 值的 JSON 对象 - - // MySQL 高级函数 - // SQL_FUNCTION_MAP.put("bin", ""); // BIN(x) 返回 x 的二进制编码 - // SQL_FUNCTION_MAP.put("binary", ""); // BINARY(s) 将字符串 s 转换为二进制字符串 - SQL_FUNCTION_MAP.put("case", ""); // CASE 表示函数开始,END 表示函数结束。如果 condition1 成立,则返回 result1, 如果 condition2 成立,则返回 result2,当全部不成立则返回 result,而当有一个成立之后,后面的就不执行了。 - SQL_FUNCTION_MAP.put("cast", ""); // CAST(x AS type) 转换数据类型 - SQL_FUNCTION_MAP.put("coalesce", ""); // COALESCE(expr1, expr2, ...., expr_n) 返回参数中的第一个非空表达式(从左向右) - // SQL_FUNCTION_MAP.put("conv", ""); // CONV(x,f1,f2) 返回 f1 进制数变成 f2 进制数 - // SQL_FUNCTION_MAP.put("convert", ""); // CONVERT(s, cs) 函数将字符串 s 的字符集变成 cs - SQL_FUNCTION_MAP.put("if", ""); // IF(expr,v1,v2) 如果表达式 expr 成立,返回结果 v1;否则,返回结果 v2。 - SQL_FUNCTION_MAP.put("ifnull", ""); // IFNULL(v1,v2) 如果 v1 的值不为 NULL,则返回 v1,否则返回 v2。 - SQL_FUNCTION_MAP.put("isnull", ""); // ISNULL(expression) 判断表达式是否为 NULL - SQL_FUNCTION_MAP.put("nullif", ""); // NULLIF(expr1, expr2) 比较两个字符串,如果字符串 expr1 与 expr2 相等 返回 NULL,否则返回 expr1 - SQL_FUNCTION_MAP.put("group_concat", ""); // GROUP_CONCAT([DISTINCT], s1, s2...) - - } - - - @Override - public boolean limitSQLCount() { - return Log.DEBUG == false || AbstractVerifier.SYSTEM_ACCESS_MAP.containsKey(getTable()) == false; - } - - @NotNull - @Override - public String getIdKey() { - return KEY_ID; - } - - @NotNull - @Override - public String getUserIdKey() { - return KEY_USER_ID; - } - - - private Object id; //Table的id - private RequestMethod method; //操作方法 - private boolean prepared = true; //预编译 - private boolean main = true; - /** - * TODO 被关联的表通过就忽略关联的表?(这个不行 User:{"sex@":"/Comment/toId"}) - */ - private RequestRole role; //发送请求的用户的角色 - private boolean distinct = false; - private String database; //表所在的数据库类型 - private String schema; //表所在的数据库名 - private String datasource; //数据源 - private String table; //表名 - private String alias; //表别名 - private String group; //分组方式的字符串数组,','分隔 - private String having; //聚合函数的字符串数组,','分隔 - private String order; //排序方式的字符串数组,','分隔 - private List raw; //需要保留原始 SQL 的字段,','分隔 - private List json; //需要转为 JSON 的字段,','分隔 - private Subquery from; //子查询临时表 - private List column; //表内字段名(或函数名,仅查询操作可用)的字符串数组,','分隔 - private List> values; //对应表内字段的值的字符串数组,','分隔 - private Map content; //Request内容,key:value形式,column = content.keySet(),values = content.values() - private Map where; //筛选条件,key:value形式 - private Map> combine; //条件组合,{ "&":[key], "|":[key], "!":[key] } - - - //array item <<<<<<<<<< - private int count; //Table数量 - private int page; //Table所在页码 - private int position; //Table在[]中的位置 - private int query; //JSONRequest.query - private int type; //ObjectParser.type - private int cache; - private boolean explain; - - private List joinList; //连表 配置列表 - //array item >>>>>>>>>> - private boolean test; //测试 - - private String procedure; - - public SQLConfig setProcedure(String procedure) { - this.procedure = procedure; - return this; - } - - public String getProcedure() { - return procedure; - } - - public AbstractSQLConfig(RequestMethod method) { - setMethod(method); - } - - public AbstractSQLConfig(RequestMethod method, String table) { - this(method); - setTable(table); - } - - public AbstractSQLConfig(RequestMethod method, int count, int page) { - this(method); - setCount(count); - setPage(page); - } - - @NotNull - @Override - public RequestMethod getMethod() { - if (method == null) { - method = GET; - } - return method; - } - - @Override - public AbstractSQLConfig setMethod(RequestMethod method) { - this.method = method; - return this; - } - - @Override - public boolean isPrepared() { - return prepared; - } - - @Override - public AbstractSQLConfig setPrepared(boolean prepared) { - this.prepared = prepared; - return this; - } - - @Override - public boolean isMain() { - return main; - } - - @Override - public AbstractSQLConfig setMain(boolean main) { - this.main = main; - return this; - } - - - @Override - public Object getId() { - return id; - } - - @Override - public AbstractSQLConfig setId(Object id) { - this.id = id; - return this; - } - - @Override - public RequestRole getRole() { - //不能 @NotNull , AbstractParser#getSQLObject 内当getRole() == null时填充默认值 - return role; - } - - public AbstractSQLConfig setRole(String roleName) throws Exception { - return setRole(RequestRole.get(roleName)); - } - - @Override - public AbstractSQLConfig setRole(RequestRole role) { - this.role = role; - return this; - } - - @Override - public boolean isDistinct() { - return distinct; - } - - @Override - public SQLConfig setDistinct(boolean distinct) { - this.distinct = distinct; - return this; - } - - @Override - public String getDatabase() { - return database; - } - - @Override - public SQLConfig setDatabase(String database) { - this.database = database; - return this; - } - - /** - * @return db == null ? DEFAULT_DATABASE : db - */ - @NotNull - public String getSQLDatabase() { - String db = getDatabase(); - return db == null ? DEFAULT_DATABASE : db; // "" 表示已设置,不需要用全局默认的 StringUtil.isEmpty(db, false)) { - } - - @Override - public boolean isMySQL() { - return isMySQL(getSQLDatabase()); - } - - public static boolean isMySQL(String db) { - return DATABASE_MYSQL.equals(db); - } - - @Override - public boolean isPostgreSQL() { - return isPostgreSQL(getSQLDatabase()); - } - - public static boolean isPostgreSQL(String db) { - return DATABASE_POSTGRESQL.equals(db); - } - - @Override - public boolean isSQLServer() { - return isSQLServer(getSQLDatabase()); - } - - public static boolean isSQLServer(String db) { - return DATABASE_SQLSERVER.equals(db); - } - - @Override - public boolean isOracle() { - return isOracle(getSQLDatabase()); - } - - public static boolean isOracle(String db) { - return DATABASE_ORACLE.equals(db); - } - - @Override - public boolean isDb2() { - return isDb2(getSQLDatabase()); - } - - public static boolean isDb2(String db) { - return DATABASE_DB2.equals(db); - } - - @Override - public String getQuote() { - return isMySQL() ? "`" : "\""; - } - - @Override - public String getSchema() { - return schema; - } - - /** - * @param sqlTable - * @return - */ - @NotNull - public String getSQLSchema() { - String table = getTable(); - //强制,避免因为全局默认的 @schema 自动填充进来,导致这几个类的 schema 为 sys 等其它值 - if (Table.TAG.equals(table) || Column.TAG.equals(table)) { - return SCHEMA_INFORMATION; //MySQL, PostgreSQL, SQL Server 都有的 - } - if (PgClass.TAG.equals(table) || PgAttribute.TAG.equals(table)) { - return ""; //PostgreSQL 的 pg_class 和 pg_attribute 表好像不属于任何 Schema - } - if (SysTable.TAG.equals(table) || SysColumn.TAG.equals(table) || ExtendedProperty.TAG.equals(table)) { - return SCHEMA_SYS; //SQL Server 在 sys 中的属性比 information_schema 中的要全,能拿到注释 - } - - String sch = getSchema(); - return sch == null ? DEFAULT_SCHEMA : sch; - } - - @Override - public AbstractSQLConfig setSchema(String schema) { - if (schema != null) { - String quote = getQuote(); - String s = schema.startsWith(quote) && schema.endsWith(quote) ? schema.substring(1, schema.length() - 1) : schema; - if (StringUtil.isEmpty(s, true) == false && StringUtil.isName(s) == false) { - throw new IllegalArgumentException("@schema:value 中value必须是1个单词!"); - } - } - this.schema = schema; - return this; - } - - @Override - public String getDatasource() { - return datasource; - } - - @Override - public SQLConfig setDatasource(String datasource) { - this.datasource = datasource; - return this; - } - - /** - * 请求传进来的Table名 - * - * @return - * @see {@link #getSQLTable()} - */ - @Override - public String getTable() { - return table; - } - - /** - * 数据库里的真实Table名 - * 通过 {@link #TABLE_KEY_MAP} 映射 - * - * @return - */ - @JSONField(serialize = false) - @Override - public String getSQLTable() { - // String t = TABLE_KEY_MAP.containsKey(table) ? TABLE_KEY_MAP.get(table) : table; - //如果要强制小写,则可在子类重写这个方法再 toLowerCase return DATABASE_POSTGRESQL.equals(getDatabase()) ? t.toLowerCase() : t; - return TABLE_KEY_MAP.containsKey(table) ? TABLE_KEY_MAP.get(table) : table; - } - - @JSONField(serialize = false) - @Override - public String getTablePath() { - String q = getQuote(); - - String sch = getSQLSchema(); - String sqlTable = getSQLTable(); - - return (StringUtil.isEmpty(sch, true) ? "" : q + sch + q + ".") + q + sqlTable + q + (isKeyPrefix() ? " AS " + getAliasWithQuote() : ""); - } - - @Override - public AbstractSQLConfig setTable(String table) { //Table已经在Parser中校验,所以这里不用防SQL注入 - this.table = table; - return this; - } - - @Override - public String getAlias() { - return alias; - } - - @Override - public AbstractSQLConfig setAlias(String alias) { - this.alias = alias; - return this; - } - - public String getAliasWithQuote() { - String a = getAlias(); - if (StringUtil.isEmpty(a, true)) { - a = getTable(); - } - String q = getQuote(); - //getTable 不能小写,因为Verifier用大小写敏感的名称判断权限 - //如果要强制小写,则可在子类重写这个方法再 toLowerCase return q + (DATABASE_POSTGRESQL.equals(getDatabase()) ? a.toLowerCase() : a) + q; - return q + a + q; - } - - @Override - public String getGroup() { - return group; - } - - public AbstractSQLConfig setGroup(String... keys) { - return setGroup(StringUtil.getString(keys)); - } - - @Override - public AbstractSQLConfig setGroup(String group) { - this.group = group; - return this; - } - - @JSONField(serialize = false) - public String getGroupString(boolean hasPrefix) { - //加上子表的 group - String joinGroup = ""; - if (joinList != null) { - SQLConfig cfg; - String c; - boolean first = true; - for (Join j : joinList) { - if (j.isAppJoin()) { - continue; - } - - cfg = j.isLeftOrRightJoin() ? j.getOuterConfig() : j.getJoinConfig(); - if (StringUtil.isEmpty(cfg.getAlias(), true)) { - cfg.setAlias(cfg.getTable()); - } - - c = ((AbstractSQLConfig) cfg).getGroupString(false); - if (StringUtil.isEmpty(c, true) == false) { - joinGroup += (first ? "" : ", ") + c; - first = false; - } - - } - } - - - group = StringUtil.getTrimedString(group); - String[] keys = StringUtil.split(group); - if (keys == null || keys.length <= 0) { - return StringUtil.isEmpty(joinGroup, true) ? "" : (hasPrefix ? " GROUP BY " : "") + joinGroup; - } - - for (int i = 0; i < keys.length; i++) { - if (isPrepared()) { //不能通过 ? 来代替,因为SQLExecutor statement.setString后 GROUP BY 'userId' 有单引号,只能返回一条数据,必须去掉单引号才行! - if (StringUtil.isName(keys[i]) == false) { - throw new IllegalArgumentException("@group:value 中 value里面用 , 分割的每一项都必须是1个单词!并且不要有空格!"); - } - } - - keys[i] = getKey(keys[i]); - } - - return (hasPrefix ? " GROUP BY " : "") + StringUtil.concat(StringUtil.getString(keys), joinGroup, ", "); - } - - @Override - public String getHaving() { - return having; - } - - public AbstractSQLConfig setHaving(String... conditions) { - return setHaving(StringUtil.getString(conditions)); - } - - @Override - public AbstractSQLConfig setHaving(String having) { - this.having = having; - return this; - } - - /** - * TODO @having 改为默认 | 或连接,且支持 @having: { "key1>": 1, "key{}": "length(key2)>0", "@combine": "key1,key2" } - * - * @return HAVING conditoin0 AND condition1 OR condition2 ... - */ - @JSONField(serialize = false) - public String getHavingString(boolean hasPrefix) { - //加上子表的 having - String joinHaving = ""; - if (joinList != null) { - SQLConfig cfg; - String c; - boolean first = true; - for (Join j : joinList) { - if (j.isAppJoin()) { - continue; - } - - cfg = j.isLeftOrRightJoin() ? j.getOuterConfig() : j.getJoinConfig(); - if (StringUtil.isEmpty(cfg.getAlias(), true)) { - cfg.setAlias(cfg.getTable()); - } - - c = ((AbstractSQLConfig) cfg).getHavingString(false); - if (StringUtil.isEmpty(c, true) == false) { - joinHaving += (first ? "" : ", ") + c; - first = false; - } - - } - } - - String[] keys = StringUtil.split(getHaving(), ";"); - if (keys == null || keys.length <= 0) { - return StringUtil.isEmpty(joinHaving, true) ? "" : (hasPrefix ? " HAVING " : "") + joinHaving; - } - - String quote = getQuote(); - String tableAlias = getAliasWithQuote(); - - List raw = getRaw(); - boolean containRaw = raw != null && raw.contains(KEY_HAVING); - - String expression; - String method; - //暂时不允许 String prefix; - String suffix; - - //fun0(arg0,arg1,...);fun1(arg0,arg1,...) - for (int i = 0; i < keys.length; i++) { - - //fun(arg0,arg1,...) - expression = keys[i]; - if (containRaw) { - try { - String rawSQL = getRawSQL(KEY_HAVING, expression); - if (rawSQL != null) { - keys[i] = rawSQL; - continue; - } - } catch (Exception e) { - Log.e(TAG, "newSQLConfig rawColumnSQL == null >> try { " - + " String rawSQL = ((AbstractSQLConfig) config).getRawSQL(KEY_COLUMN, fk); ... " - + "} catch (Exception e) = " + e.getMessage()); - } - } - - if (expression.length() > 50) { - throw new UnsupportedOperationException("@having:value 的 value 中字符串 " + expression + " 不合法!" - + "不允许传超过 50 个字符的函数或表达式!请用 @raw 简化传参!"); - } - - int start = expression.indexOf("("); - if (start < 0) { - if (isPrepared() && PATTERN_FUNCTION.matcher(expression).matches() == false) { - throw new UnsupportedOperationException("字符串 " + expression + " 不合法!" - + "预编译模式下 @having:\"column?value;function(arg0,arg1,...)?value...\"" - + " 中 column?value 必须符合正则表达式 " + PATTERN_FUNCTION + " 且不包含连续减号 -- !不允许空格!"); - } - continue; - } - - int end = expression.lastIndexOf(")"); - if (start >= end) { - throw new IllegalArgumentException("字符 " + expression + " 不合法!" - + "@having:value 中 value 里的 SQL函数必须为 function(arg0,arg1,...) 这种格式!"); - } - - method = expression.substring(0, start); - if (method.isEmpty() == false) { - if (SQL_FUNCTION_MAP == null || SQL_FUNCTION_MAP.isEmpty()) { - if (StringUtil.isName(method) == false) { - throw new IllegalArgumentException("字符 " + method + " 不合法!" - + "预编译模式下 @having:\"column?value;function(arg0,arg1,...)?value...\"" - + " 中 function 必须符合小写英文单词的 SQL 函数名格式!"); - } - } else if (SQL_FUNCTION_MAP.containsKey(method) == false) { - throw new IllegalArgumentException("字符 " + method + " 不合法!" - + "预编译模式下 @column:\"column0,column1:alias;function0(arg0,arg1,...);function1(...):alias...\"" - + " 中 function 必须符合小写英文单词的 SQL 函数名格式!且必须是后端允许调用的 SQL 函数!"); - } - } - - suffix = expression.substring(end + 1, expression.length()); - - if (isPrepared() && (((String) suffix).contains("--") || ((String) suffix).contains("/*") || PATTERN_RANGE.matcher((String) suffix).matches() == false)) { - throw new UnsupportedOperationException("字符串 " + suffix + " 不合法!" - + "预编译模式下 @having:\"column?value;function(arg0,arg1,...)?value...\"" - + " 中 ?value 必须符合正则表达式 " + PATTERN_RANGE + " 且不包含连续减号 -- 或注释符 /* !不允许多余的空格!"); - } - - String[] ckeys = StringUtil.split(expression.substring(start + 1, end)); - - if (ckeys != null) { - for (int j = 0; j < ckeys.length; j++) { - String origin = ckeys[j]; - - if (isPrepared()) { - if (origin.startsWith("_") || origin.contains("--") || PATTERN_FUNCTION.matcher(origin).matches() == false) { - throw new IllegalArgumentException("字符 " + ckeys[j] + " 不合法!" - + "预编译模式下 @having:\"column?value;function(arg0,arg1,...)?value...\"" - + " 中所有 column, arg 都必须是1个不以 _ 开头的单词 或者 符合正则表达式 " + PATTERN_FUNCTION + " 且不包含连续减号 -- !不允许多余的空格!"); - } - } - - //JOIN 副表不再在外层加副表名前缀 userId AS `Commet.userId`, 而是直接 userId AS `userId` - boolean isName = false; - if (StringUtil.isNumer(origin)) { - //do nothing - } else if (StringUtil.isName(origin)) { - origin = quote + origin + quote; - isName = true; - } else { - origin = getValue(origin).toString(); - } - - ckeys[j] = (isName && isKeyPrefix() ? tableAlias + "." : "") + origin; - } - } - - keys[i] = method + "(" + StringUtil.getString(ckeys) + ")" + suffix; - } - - //TODO 支持 OR, NOT 参考 @combine:"&key0,|key1,!key2" - return (hasPrefix ? " HAVING " : "") + StringUtil.concat(StringUtil.getString(keys, AND), joinHaving, AND); - } - - @Override - public String getOrder() { - return order; - } - - public AbstractSQLConfig setOrder(String... conditions) { - return setOrder(StringUtil.getString(conditions)); - } - - @Override - public AbstractSQLConfig setOrder(String order) { - this.order = order; - return this; - } - - @JSONField(serialize = false) - public String getOrderString(boolean hasPrefix) { - //加上子表的 order - String joinOrder = ""; - if (joinList != null) { - SQLConfig cfg; - String c; - boolean first = true; - for (Join j : joinList) { - if (j.isAppJoin()) { - continue; - } - - cfg = j.isLeftOrRightJoin() ? j.getOuterConfig() : j.getJoinConfig(); - if (StringUtil.isEmpty(cfg.getAlias(), true)) { - cfg.setAlias(cfg.getTable()); - } - - c = ((AbstractSQLConfig) cfg).getOrderString(false); - if (StringUtil.isEmpty(c, true) == false) { - joinOrder += (first ? "" : ", ") + c; - first = false; - } - - } - } - - - String order = StringUtil.getTrimedString(getOrder()); - // SELECT * FROM sys.Moment ORDER BY userId ASC, rand(); 前面的 userId ASC 和后面的 rand() 都有效 - // if ("rand()".equals(order)) { - // return (hasPrefix ? " ORDER BY " : "") + StringUtil.concat(order, joinOrder, ", "); - // } - - if (getCount() > 0 && (isOracle() || isSQLServer() || isDb2())) { // Oracle, SQL Server, DB2 的 OFFSET 必须加 ORDER BY - - // String[] ss = StringUtil.split(order); - if (StringUtil.isEmpty(order, true)) { //SQL Server 子查询内必须指定 OFFSET 才能用 ORDER BY - String idKey = getIdKey(); - if (StringUtil.isEmpty(idKey, true)) { - idKey = "id"; //ORDER BY NULL 不行,SQL Server 会报错,必须要有排序,才能使用 OFFSET FETCH,如果没有 idKey,请求中指定 @order 即可 - } - order = idKey; //让数据库调控默认升序还是降序 + "+"; - } - - //不用这么全面,毕竟没有语法问题还浪费性能,如果有其它问题,让前端传的 JSON 直接加上 @order 来解决 - // boolean contains = false; - // if (ss != null) { - // for (String s : ss) { - // if (s != null && s.startsWith(idKey)) { - // s = s.substring(idKey.length()); - // if ("+".equals(s) || "-".equals(s)) {// || " ASC ".equals(s) || " DESC ".equals(s)) { - // contains = true; - // break; - // } - // } - // } - // } - - // if (contains == false) { - // order = (ss == null || ss.length <= 0 ? "" : order + ",") + idKey + "+"; - // } - } - - - String[] keys = StringUtil.split(order); - if (keys == null || keys.length <= 0) { - return StringUtil.isEmpty(joinOrder, true) ? "" : (hasPrefix ? " ORDER BY " : "") + joinOrder; - } - - for (int i = 0; i < keys.length; i++) { - String item = keys[i]; - if ("rand()".equals(item)) { - continue; - } - - int index = item.endsWith("+") ? item.length() - 1 : -1; //StringUtil.split返回数组中,子项不会有null - String sort; - if (index < 0) { - index = item.endsWith("-") ? item.length() - 1 : -1; - sort = index <= 0 ? "" : " DESC "; - } else { - sort = " ASC "; - } - - String origin = index < 0 ? item : item.substring(0, index); - - if (isPrepared()) { //不能通过 ? 来代替,SELECT 'id','name' 返回的就是 id:"id", name:"name",而不是数据库里的值! - //这里既不对origin trim,也不对 ASC/DESC ignoreCase,希望前端严格传没有任何空格的字符串过来,减少传输数据量,节约服务器性能 - if (StringUtil.isName(origin) == false) { - throw new IllegalArgumentException("预编译模式下 @order:value 中 " + item + " 不合法! value 里面用 , 分割的" - + "每一项必须是 随机函数 rand() 或 column+ / column- 且其中 column 必须是 1 个单词!并且不要有多余的空格!"); - } - } - - keys[i] = getKey(origin) + sort; - } - - return (hasPrefix ? " ORDER BY " : "") + StringUtil.concat(StringUtil.getString(keys), joinOrder, ", "); - } - - @Override - public List getRaw() { - return raw; - } - - @Override - public SQLConfig setRaw(List raw) { - this.raw = raw; - return this; - } - - /** - * 获取原始 SQL 片段 - * - * @param key - * @param value - * @return - * @throws Exception - */ - @Override - public String getRawSQL(String key, Object value) throws Exception { - List rawList = getRaw(); - boolean containRaw = rawList != null && rawList.contains(key); - if (containRaw && value instanceof String == false) { - throw new UnsupportedOperationException("@raw:value 的 value 中 " + key + " 不合法!" - + "对应的 " + key + ":value 中 value 类型只能为 String!"); - } - - String rawSQL = containRaw ? RAW_MAP.get(value) : null; - if (containRaw) { - if (rawSQL == null) { - throw new UnsupportedOperationException("@raw:value 的 value 中 " + key + " 不合法!" - + "对应的 " + key + ":value 中 value 值 " + value + " 未在后端 RAW_MAP 中配置 !"); - } - - if ("".equals(rawSQL)) { - return (String) value; - } - } - - return rawSQL; - } - - - @Override - public List getJson() { - return json; - } - - @Override - public AbstractSQLConfig setJson(List json) { - this.json = json; - return this; - } - - - @Override - public Subquery getFrom() { - return from; - } - - @Override - public AbstractSQLConfig setFrom(Subquery from) { - this.from = from; - return this; - } - - @Override - public List getColumn() { - return column; - } - - @Override - public AbstractSQLConfig setColumn(List column) { - this.column = column; - return this; - } - - @JSONField(serialize = false) - public String getColumnString() throws Exception { - return getColumnString(false); - } - - @JSONField(serialize = false) - public String getColumnString(boolean inSQLJoin) throws Exception { - List column = getColumn(); - - switch (getMethod()) { - case HEAD: - case HEADS: //StringUtil.isEmpty(column, true) || column.contains(",") 时SQL.count(column)会return "*" - if (isPrepared() && column != null) { - String origin; - String alias; - int index; - - List raw = getRaw(); - boolean containRaw = raw != null && raw.contains(KEY_COLUMN); - - for (String c : column) { - if (containRaw) { - // 由于 HashMap 对 key 做了 hash 处理,所以 get 比 containsValue 更快 - if ("".equals(RAW_MAP.get(c)) || RAW_MAP.containsValue(c)) { // newSQLConfig 提前处理好的 - //排除@raw中的值,以避免使用date_format(date,'%Y-%m-%d %H:%i:%s') 时,冒号的解析出错 - column.remove(c); - continue; - } - } - index = c.lastIndexOf(":"); //StringUtil.split返回数组中,子项不会有null - origin = index < 0 ? c : c.substring(0, index); - alias = index < 0 ? null : c.substring(index + 1); - - if (alias != null && StringUtil.isName(alias) == false) { - throw new IllegalArgumentException("HEAD请求: 字符 " + alias + " 不合法!预编译模式下 @column:value 中 value里面用 , 分割的每一项" - + " column:alias 中 column 必须是1个单词!如果有alias,则alias也必须为1个单词!并且不要有多余的空格!"); - } - - if (StringUtil.isName(origin) == false) { - int start = origin.indexOf("("); - if (start < 0 || origin.lastIndexOf(")") <= start) { - throw new IllegalArgumentException("HEAD请求: 字符" + origin + " 不合法!预编译模式下 @column:value 中 value里面用 , 分割的每一项" - + " column:alias 中 column 必须是1个单词!如果有alias,则alias也必须为1个单词!并且不要有多余的空格!"); - } - - if (start > 0 && StringUtil.isName(origin.substring(0, start)) == false) { - throw new IllegalArgumentException("HEAD请求: 字符 " + origin.substring(0, start) + " 不合法!预编译模式下 @column:value 中 value里面用 , 分割的每一项" - + " column:alias 中 column 必须是1个单词!如果有alias,则alias也必须为1个单词!并且不要有多余的空格!"); - } - } - } - } - - return SQL.count(column != null && column.size() == 1 && StringUtil.isName(column.get(0)) ? getKey(column.get(0)) : "*"); - case POST: - if (column == null || column.isEmpty()) { - throw new IllegalArgumentException("POST 请求必须在Table内设置要保存的 key:value !"); - } - - String s = ""; - boolean pfirst = true; - for (String c : column) { - if (isPrepared() && StringUtil.isName(c) == false) { //不能通过 ? 来代替,SELECT 'id','name' 返回的就是 id:"id", name:"name",而不是数据库里的值! - throw new IllegalArgumentException("POST请求: 每一个 key:value 中的key都必须是1个单词!"); - } - s += ((pfirst ? "" : ",") + getKey(c)); - - pfirst = false; - } - - return "(" + s + ")"; - case GET: - case GETS: - boolean isQuery = RequestMethod.isQueryMethod(method); //TODO 这个有啥用?上面应是 getMethod 的值 GET 和 GETS 了。 - String joinColumn = ""; - if (isQuery && joinList != null) { - SQLConfig ecfg; - SQLConfig cfg; - String c; - boolean first = true; - for (Join j : joinList) { - if (j.isAppJoin()) { - continue; - } - - ecfg = j.getOuterConfig(); - if (ecfg != null && ecfg.getColumn() != null) { //优先级更高 - cfg = ecfg; - } else { - cfg = j.getJoinConfig(); - } - - if (StringUtil.isEmpty(cfg.getAlias(), true)) { - cfg.setAlias(cfg.getTable()); - } - - c = ((AbstractSQLConfig) cfg).getColumnString(true); - if (StringUtil.isEmpty(c, true) == false) { - joinColumn += (first ? "" : ", ") + c; - first = false; - } - - inSQLJoin = true; - } - } - - String tableAlias = getAliasWithQuote(); - - // String c = StringUtil.getString(column); //id,name;json_length(contactIdList):contactCount;... - - String[] keys = column == null ? null : column.toArray(new String[]{}); //StringUtil.split(c, ";"); - if (keys == null || keys.length <= 0) { - - boolean noColumn = column != null && inSQLJoin; - String mc = isKeyPrefix() == false ? (noColumn ? "" : "*") : (noColumn ? "" : tableAlias + ".*"); - - return StringUtil.concat(mc, joinColumn, ", ", true); - } - - - List raw = getRaw(); - boolean containRaw = raw != null && raw.contains(KEY_COLUMN); - - String expression; - String method = null; - - //...;fun0(arg0,arg1,...):fun0;fun1(arg0,arg1,...):fun1;... - for (int i = 0; i < keys.length; i++) { - - //fun(arg0,arg1,...) - expression = keys[i]; - - if (containRaw) { // 由于 HashMap 对 key 做了 hash 处理,所以 get 比 containsValue 更快 - if ("".equals(RAW_MAP.get(expression)) || RAW_MAP.containsValue(expression)) { // newSQLConfig 提前处理好的 - continue; - } - - // 简单点, 后台配置就带上 AS - // int index = expression.lastIndexOf(":"); - // String alias = expression.substring(index+1); - // boolean hasAlias = StringUtil.isName(alias); - // String pre = index > 0 && hasAlias ? expression.substring(0, index) : expression; - // if (RAW_MAP.containsValue(pre) || "".equals(RAW_MAP.get(pre))) { // newSQLConfig 提前处理好的 - // expression = pre + (hasAlias ? " AS " + alias : ""); - // continue; - // } - } - - if (expression.length() > 50) { - throw new UnsupportedOperationException("@column:value 的 value 中字符串 " + expression + " 不合法!" - + "不允许传超过 50 个字符的函数或表达式!请用 @raw 简化传参!"); - } - - - int start = expression.indexOf("("); - int end = 0; - if (start >= 0) { - end = expression.lastIndexOf(")"); - if (start >= end) { - throw new IllegalArgumentException("字符 " + expression + " 不合法!" - + "@column:value 中 value 里的 SQL函数必须为 function(arg0,arg1,...) 这种格式!"); - } - - method = expression.substring(0, start); - boolean distinct = i <= 0 && method.startsWith(PREFFIX_DISTINCT); - String fun = distinct ? method.substring(PREFFIX_DISTINCT.length()) : method; - - if (fun.isEmpty() == false) { - if (SQL_FUNCTION_MAP == null || SQL_FUNCTION_MAP.isEmpty()) { - if (StringUtil.isName(fun) == false) { - throw new IllegalArgumentException("字符 " + method + " 不合法!" - + "预编译模式下 @column:\"column0,column1:alias;function0(arg0,arg1,...);function1(...):alias...\"" - + " 中 function 必须符合小写英文单词的 SQL 函数名格式!"); - } - } else if (SQL_FUNCTION_MAP.containsKey(fun) == false) { - throw new IllegalArgumentException("字符 " + method + " 不合法!" - + "预编译模式下 @column:\"column0,column1:alias;function0(arg0,arg1,...);function1(...):alias...\"" - + " 中 function 必须符合小写英文单词的 SQL 函数名格式!且必须是后端允许调用的 SQL 函数!"); - } - } - - } - - boolean isColumn = start < 0; - - String[] ckeys = StringUtil.split(isColumn ? expression : expression.substring(start + 1, end)); - String quote = getQuote(); - - // if (isPrepared()) { //不能通过 ? 来代替,SELECT 'id','name' 返回的就是 id:"id", name:"name",而不是数据库里的值! - if (ckeys != null && ckeys.length > 0) { - - boolean distinct; - String origin; - String alias; - int index; - for (int j = 0; j < ckeys.length; j++) { - index = isColumn ? ckeys[j].lastIndexOf(":") : -1; //StringUtil.split返回数组中,子项不会有null - origin = index < 0 ? ckeys[j] : ckeys[j].substring(0, index); - alias = index < 0 ? null : ckeys[j].substring(index + 1); - - distinct = j <= 0 && origin.startsWith(PREFFIX_DISTINCT); - if (distinct) { - origin = origin.substring(PREFFIX_DISTINCT.length()); - } - - if (isPrepared()) { - if (isColumn) { - if (StringUtil.isName(origin) == false || (alias != null && StringUtil.isName(alias) == false)) { - throw new IllegalArgumentException("字符 " + ckeys[j] + " 不合法!" - + "预编译模式下 @column:value 中 value里面用 , 分割的每一项" - + " column:alias 中 column 必须是1个单词!如果有alias,则alias也必须为1个单词!" - + "DISTINCT 必须全大写,且后面必须有且只有 1 个空格!其它情况不允许空格!"); - } - } else { - // if ((StringUtil.isName(origin) == false || origin.startsWith("_"))) { - if (origin.startsWith("_") || origin.contains("--") || PATTERN_FUNCTION.matcher(origin).matches() == false) { - throw new IllegalArgumentException("字符 " + ckeys[j] + " 不合法!" - + "预编译模式下 @column:\"column0,column1:alias;function0(arg0,arg1,...);function1(...):alias...\"" - + " 中所有 arg 都必须是1个不以 _ 开头的单词 或者符合正则表达式 " + PATTERN_FUNCTION + " 且不包含连续减号 -- !DISTINCT 必须全大写,且后面必须有且只有 1 个空格!其它情况不允许空格!"); - } - } - } - - //JOIN 副表不再在外层加副表名前缀 userId AS `Commet.userId`, 而是直接 userId AS `userId` - boolean isName = false; - if (StringUtil.isNumer(origin)) { - //do nothing - } else if (StringUtil.isName(origin)) { - origin = quote + origin + quote; - isName = true; - } else { - origin = getValue(origin).toString(); - } - - if (isName && isKeyPrefix()) { - ckeys[j] = tableAlias + "." + origin; - // if (isColumn) { - // ckeys[j] += " AS " + quote + (isMain() ? "" : tableAlias + ".") + (StringUtil.isEmpty(alias, true) ? origin : alias) + quote; - // } - if (isColumn && StringUtil.isEmpty(alias, true) == false) { - ckeys[j] += " AS " + quote + alias + quote; - } - } else { - ckeys[j] = origin + (StringUtil.isEmpty(alias, true) ? "" : " AS " + quote + alias + quote); - } - - if (distinct) { - ckeys[j] = PREFFIX_DISTINCT + ckeys[j]; - } - } - // } - - } - - if (isColumn) { - keys[i] = StringUtil.getString(ckeys); - } else { - String suffix = expression.substring(end + 1, expression.length()); //:contactCount - int index = suffix.lastIndexOf(":"); - String alias = index < 0 ? "" : suffix.substring(index + 1); //contactCount - suffix = index < 0 ? suffix : suffix.substring(0, index); - - if (alias.isEmpty() == false && StringUtil.isName(alias) == false) { - throw new IllegalArgumentException("字符串 " + alias + " 不合法!" - + "预编译模式下 @column:value 中 value里面用 ; 分割的每一项" - + " function(arg0,arg1,...):alias 中 alias 必须是1个单词!并且不要有多余的空格!"); - } - - if (suffix.isEmpty() == false && (((String) suffix).contains("--") || ((String) suffix).contains("/*") || PATTERN_RANGE.matcher((String) suffix).matches() == false)) { - throw new UnsupportedOperationException("字符串 " + suffix + " 不合法!" - + "预编译模式下 @column:\"column?value;function(arg0,arg1,...)?value...\"" - + " 中 ?value 必须符合正则表达式 " + PATTERN_RANGE + " 且不包含连续减号 -- 或注释符 /* !不允许多余的空格!"); - } - - String origin = method + "(" + StringUtil.getString(ckeys) + ")" + suffix; - // if (isKeyPrefix()) { - // keys[i] = origin + " AS " + quote + (isMain() ? "" : tableAlias + ".") + (StringUtil.isEmpty(alias, true) ? method : alias) + quote; - // } - // else { - keys[i] = origin + (StringUtil.isEmpty(alias, true) ? "" : " AS " + quote + alias + quote); - // } - } - - } - - String c = StringUtil.getString(keys); - c = c + (StringUtil.isEmpty(joinColumn, true) ? "" : ", " + joinColumn);//不能在这里改,后续还要用到: - return isMain() && isDistinct() ? PREFFIX_DISTINCT + c : c; - default: - throw new UnsupportedOperationException( - "服务器内部错误:getColumnString 不支持 " + RequestMethod.getName(getMethod()) - + " 等 [GET,GETS,HEAD,HEADS,POST] 外的ReuqestMethod!" - ); - } - } - - - @Override - public List> getValues() { - return values; - } - - @JSONField(serialize = false) - public String getValuesString() { - String s = ""; - if (values != null && values.size() > 0) { - Object[] items = new Object[values.size()]; - List vs; - for (int i = 0; i < values.size(); i++) { - vs = values.get(i); - if (vs == null) { - continue; - } - - items[i] = "("; - for (int j = 0; j < vs.size(); j++) { - items[i] += ((j <= 0 ? "" : ",") + getValue(vs.get(j))); - } - items[i] += ")"; - } - s = StringUtil.getString(items); - } - return s; - } - - @Override - public AbstractSQLConfig setValues(List> valuess) { - this.values = valuess; - return this; - } - - @Override - public Map getContent() { - return content; - } - - @Override - public AbstractSQLConfig setContent(Map content) { - this.content = content; - return this; - } - - @Override - public int getCount() { - return count; - } - - @Override - public AbstractSQLConfig setCount(int count) { - this.count = count; - return this; - } - - @Override - public int getPage() { - return page; - } - - @Override - public AbstractSQLConfig setPage(int page) { - this.page = page; - return this; - } - - @Override - public int getPosition() { - return position; - } - - @Override - public AbstractSQLConfig setPosition(int position) { - this.position = position; - return this; - } - - @Override - public int getQuery() { - return query; - } - - @Override - public AbstractSQLConfig setQuery(int query) { - this.query = query; - return this; - } - - @Override - public int getType() { - return type; - } - - @Override - public AbstractSQLConfig setType(int type) { - this.type = type; - return this; - } - - @Override - public int getCache() { - return cache; - } - - @Override - public AbstractSQLConfig setCache(int cache) { - this.cache = cache; - return this; - } - - public AbstractSQLConfig setCache(String cache) { - return setCache(getCache(cache)); - } - - public static int getCache(String cache) { - int cache2; - if (cache == null) { - cache2 = JSONRequest.CACHE_ALL; - } else { - // if (isSubquery) { - // throw new IllegalArgumentException("子查询内不支持传 " + JSONRequest.KEY_CACHE + "!"); - // } - - switch (cache) { - case "0": - case JSONRequest.CACHE_ALL_STRING: - cache2 = JSONRequest.CACHE_ALL; - break; - case "1": - case JSONRequest.CACHE_ROM_STRING: - cache2 = JSONRequest.CACHE_ROM; - break; - case "2": - case JSONRequest.CACHE_RAM_STRING: - cache2 = JSONRequest.CACHE_RAM; - break; - default: - throw new IllegalArgumentException(JSONRequest.KEY_CACHE + ":value 中 value 的值不合法!必须在 [0,1,2] 或 [ALL, ROM, RAM] 内 !"); - } - } - return cache2; - } - - @Override - public boolean isExplain() { - return explain; - } - - @Override - public AbstractSQLConfig setExplain(boolean explain) { - this.explain = explain; - return this; - } - - @Override - public List getJoinList() { - return joinList; - } - - @Override - public SQLConfig setJoinList(List joinList) { - this.joinList = joinList; - return this; - } - - @Override - public boolean hasJoin() { - return joinList != null && joinList.isEmpty() == false; - } - - - @Override - public boolean isTest() { - return test; - } - - @Override - public AbstractSQLConfig setTest(boolean test) { - this.test = test; - return this; - } - - /** - * 获取初始位置offset - * - * @return - */ - @JSONField(serialize = false) - public int getOffset() { - return getOffset(getPage(), getCount()); - } - - /** - * 获取初始位置offset - * - * @param page - * @param count - * @return - */ - public static int getOffset(int page, int count) { - return page * count; - } - - /** - * 获取限制数量 - * - * @return - */ - @JSONField(serialize = false) - public String getLimitString() { - if (count <= 0 || RequestMethod.isHeadMethod(getMethod(), true)) { - return ""; - } - return getLimitString(getPage(), getCount(), isOracle() || isSQLServer() || isDb2(), isOracle()); - } - - /** - * 获取限制数量 - * - * @param limit - * @return - */ - public static String getLimitString(int page, int count, boolean isTSQL, boolean isOracle) { - int offset = getOffset(page, count); - - if (isTSQL) { // OFFSET FECTH 中所有关键词都不可省略, 另外 Oracle 数据库使用子查询加 where 分页 - return isOracle ? " WHERE ROWNUM BETWEEN " + offset + " AND " + (offset + count) : " OFFSET " + offset + " ROWS FETCH FIRST " + count + " ROWS ONLY"; - } - - return " LIMIT " + count + (offset <= 0 ? "" : " OFFSET " + offset); // DELETE, UPDATE 不支持 OFFSET - } - - //WHERE <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< - @Override - public Map getWhere() { - return where; - } - - @Override - public AbstractSQLConfig setWhere(Map where) { - this.where = where; - return this; - } - - @NotNull - @Override - public Map> getCombine() { - List andList = combine == null ? null : combine.get("&"); - if (andList == null) { - andList = where == null ? new ArrayList() : new ArrayList(where.keySet()); - if (combine == null) { - combine = new HashMap<>(); - } - combine.put("&", andList); - } - return combine; - } - - @Override - public AbstractSQLConfig setCombine(Map> combine) { - this.combine = combine; - return this; - } - - /** - * noFunctionChar = false - * - * @param key - * @return - */ - @JSONField(serialize = false) - @Override - public Object getWhere(String key) { - return getWhere(key, false); - } - //CS304 Issue link: https://github.com/Tencent/APIJSON/issues/48 - - /** - * @param key - the key passed in - * @param exactMatch - whether it is exact match - * @return

use entrySet+getValue() to replace keySet+get() to enhance efficiency

- */ - @JSONField(serialize = false) - @Override - public Object getWhere(String key, boolean exactMatch) { - if (exactMatch) { - return where == null ? null : where.get(key); - } - - if (key == null || where == null) { - return null; - } - synchronized (where) { - if (where != null) { - int index; - for (Entry entry : where.entrySet()) { - String k = entry.getKey(); - index = k.indexOf(key); - if (index >= 0 && StringUtil.isName(k.substring(index)) == false) { - return entry.getValue(); - } - } - } - } - return null; - } - - @Override - public AbstractSQLConfig putWhere(String key, Object value, boolean prior) { - if (key != null) { - if (where == null) { - where = new LinkedHashMap(); - } - if (value == null) { - where.remove(key); - } else { - where.put(key, value); - } - - combine = getCombine(); - List andList = combine.get("&"); - if (value == null) { - if (andList != null) { - andList.remove(key); - } - } else if (andList == null || andList.contains(key) == false) { - int i = 0; - if (andList == null) { - andList = new ArrayList<>(); - } else if (prior && andList.isEmpty() == false) { - - String idKey = getIdKey(); - String idInKey = idKey + "{}"; - String userIdKey = getUserIdKey(); - String userIdInKey = userIdKey + "{}"; - - if (andList.contains(idKey)) { - i++; - } - if (andList.contains(idInKey)) { - i++; - } - if (andList.contains(userIdKey)) { - i++; - } - if (andList.contains(userIdInKey)) { - i++; - } - } - - if (prior) { - andList.add(i, key); //userId的优先级不能比id高 0, key); - } else { - andList.add(key); //AbstractSQLExecutor.onPutColumn里getSQL,要保证缓存的SQL和查询的SQL里 where 的 key:value 顺序一致 - } - } - combine.put("&", andList); - } - return this; - } - - /** - * 获取WHERE - * - * @return - * @throws Exception - */ - @JSONField(serialize = false) - @Override - public String getWhereString(boolean hasPrefix) throws Exception { - return getWhereString(hasPrefix, getMethod(), getWhere(), getCombine(), getJoinList(), !isTest()); - } - - /** - * 获取WHERE - * - * @param method - * @param where - * @return - * @throws Exception - */ - @JSONField(serialize = false) - public String getWhereString(boolean hasPrefix, RequestMethod method, Map where, Map> combine, List joinList, boolean verifyName) throws Exception { - Set>> combineSet = combine == null ? null : combine.entrySet(); - if (combineSet == null || combineSet.isEmpty()) { - Log.w(TAG, "getWhereString combineSet == null || combineSet.isEmpty() >> return \"\";"); - return ""; - } - - List keyList; - - String whereString = ""; - - boolean isCombineFirst = true; - int logic; - - boolean isItemFirst; - String c; - String cs; - - for (Entry> ce : combineSet) { - keyList = ce == null ? null : ce.getValue(); - if (keyList == null || keyList.isEmpty()) { - continue; - } - - if ("|".equals(ce.getKey())) { - logic = Logic.TYPE_OR; - } else if ("!".equals(ce.getKey())) { - logic = Logic.TYPE_NOT; - } else { - logic = Logic.TYPE_AND; - } - - - isItemFirst = true; - cs = ""; - for (String key : keyList) { - c = getWhereItem(key, where.get(key), method, verifyName); - - if (StringUtil.isEmpty(c, true)) {//避免SQL条件连接错误 - continue; - } - - cs += (isItemFirst ? "" : (Logic.isAnd(logic) ? AND : OR)) + "(" + c + ")"; - - isItemFirst = false; - } - - if (StringUtil.isEmpty(cs, true)) {//避免SQL条件连接错误 - continue; - } - - whereString += (isCombineFirst ? "" : AND) + (Logic.isNot(logic) ? NOT : "") + " ( " + cs + " ) "; - isCombineFirst = false; - } - - - if (joinList != null) { - - String newWs = ""; - String ws = whereString; - - List newPvl = new ArrayList<>(); - List pvl = new ArrayList<>(preparedValueList); - - SQLConfig jc; - String js; - - boolean changed = false; - //各种 JOIN 没办法统一用 & | !连接,只能按优先级,和 @combine 一样? - for (Join j : joinList) { - String jt = j.getJoinType(); - - switch (jt) { - case "*": // CROSS JOIN - case "@": // APP JOIN - case "<": // LEFT JOIN - case ">": // RIGHT JOIN - break; - - case "&": // INNER JOIN: A & B - case "": // FULL JOIN: A | B - case "|": // FULL JOIN: A | B - case "!": // OUTER JOIN: ! (A | B) - case "^": // SIDE JOIN: ! (A & B) - case "(": // ANTI JOIN: A & ! B - case ")": // FOREIGN JOIN: B & ! A - jc = j.getJoinConfig(); - boolean isMain = jc.isMain(); - jc.setMain(false).setPrepared(isPrepared()).setPreparedValueList(new ArrayList()); - js = jc.getWhereString(false); - jc.setMain(isMain); - - boolean isOuterJoin = "!".equals(jt); - boolean isSideJoin = "^".equals(jt); - boolean isAntiJoin = "(".equals(jt); - boolean isForeignJoin = ")".equals(jt); - boolean isWsEmpty = StringUtil.isEmpty(ws, true); - - if (isWsEmpty) { - if (isOuterJoin) { // ! OUTER JOIN: ! (A | B) - throw new NotExistException("no result for ! OUTER JOIN( ! (A | B) ) when A or B is empty!"); - } - if (isForeignJoin) { // ) FOREIGN JOIN: B & ! A - throw new NotExistException("no result for ) FOREIGN JOIN( B & ! A ) when A is empty!"); - } - } - - if (StringUtil.isEmpty(js, true)) { - if (isOuterJoin) { // ! OUTER JOIN: ! (A | B) - throw new NotExistException("no result for ! OUTER JOIN( ! (A | B) ) when A or B is empty!"); - } - if (isAntiJoin) { // ( ANTI JOIN: A & ! B - throw new NotExistException("no result for ( ANTI JOIN( A & ! B ) when B is empty!"); - } - - if (isWsEmpty) { - if (isSideJoin) { - throw new NotExistException("no result for ^ SIDE JOIN( ! (A & B) ) when both A and B are empty!"); - } - } else { - if (isSideJoin || isForeignJoin) { - newWs += " ( " + getCondition(true, ws) + " ) "; - - newPvl.addAll(pvl); - newPvl.addAll(jc.getPreparedValueList()); - changed = true; - } - } - - continue; - } - - if (StringUtil.isEmpty(newWs, true) == false) { - newWs += AND; - } - - if (isAntiJoin) { // ( ANTI JOIN: A & ! B - newWs += " ( " + (isWsEmpty ? "" : ws + AND) + NOT + " ( " + js + " ) " + " ) "; - } else if (isForeignJoin) { // ) FOREIGN JOIN: (! A) & B // preparedValueList.add 不好反过来 B & ! A - newWs += " ( " + NOT + " ( " + ws + " ) ) " + AND + " ( " + js + " ) "; - } else if (isSideJoin) { // ^ SIDE JOIN: ! (A & B) - //MySQL 因为 NULL 值处理问题,(A & ! B) | (B & ! A) 与 ! (A & B) 返回结果不一样,后者往往更多 - newWs += " ( " + getCondition( - true, - (isWsEmpty ? "" : ws + AND) + " ( " + js + " ) " - ) + " ) "; - } else { // & INNER JOIN: A & B; | FULL JOIN: A | B; OUTER JOIN: ! (A | B) - logic = Logic.getType(jt); - newWs += " ( " - + getCondition( - Logic.isNot(logic), - ws - + (isWsEmpty ? "" : (Logic.isAnd(logic) ? AND : OR)) - + " ( " + js + " ) " - ) - + " ) "; - } - - newPvl.addAll(pvl); - newPvl.addAll(jc.getPreparedValueList()); - - changed = true; - break; - default: - throw new UnsupportedOperationException( - "join:value 中 value 里的 " + jt + "/" + j.getPath() - + "错误!不支持 " + jt + " 等 [ @ APP, < LEFT, > RIGHT, * CROSS" - + ", & INNER, | FULL, ! OUTER, ^ SIDE, ( ANTI, ) FOREIGN ] 之外的 JOIN 类型 !" - ); - } - } - - if (changed) { - whereString = newWs; - preparedValueList = newPvl; - } - } - - String s = StringUtil.isEmpty(whereString, true) ? "" : (hasPrefix ? " WHERE " : "") + whereString; - - if (s.isEmpty() && RequestMethod.isQueryMethod(method) == false) { - throw new UnsupportedOperationException("写操作请求必须带条件!!!"); - } - - return s; - } - - /** - * @param key - * @param value - * @param method - * @param verifyName - * @return - * @throws Exception - */ - protected String getWhereItem(String key, Object value, RequestMethod method, boolean verifyName) throws Exception { - Log.d(TAG, "getWhereItem key = " + key); - //避免筛选到全部 value = key == null ? null : where.get(key); - if (key == null || value == null || key.endsWith("()") || key.startsWith("@")) { //关键字||方法, +或-直接报错 - Log.d(TAG, "getWhereItem key == null || value == null" - + " || key.startsWith(@) || key.endsWith(()) >> continue;"); - return null; - } - if (key.endsWith("@")) {//引用 - // key = key.substring(0, key.lastIndexOf("@")); - throw new IllegalArgumentException(TAG + ".getWhereItem: 字符 " + key + " 不合法!"); - } - - // 原始 SQL 片段 - String rawSQL = getRawSQL(key, value); - - int keyType; - if (key.endsWith("$")) { - keyType = 1; - } else if (key.endsWith("~")) { - keyType = key.charAt(key.length() - 2) == '*' ? -2 : 2; //FIXME StringIndexOutOfBoundsException - } else if (key.endsWith("%")) { - keyType = 3; - } else if (key.endsWith("{}")) { - keyType = 4; - } else if (key.endsWith("}{")) { - keyType = 5; - } else if (key.endsWith("<>")) { - keyType = 6; - } else if (key.endsWith(">=")) { - keyType = 7; - } else if (key.endsWith("<=")) { - keyType = 8; - } else if (key.endsWith(">")) { - keyType = 9; - } else if (key.endsWith("<")) { - keyType = 10; - } else { // else绝对不能省,避免再次踩坑! keyType = 0; 写在for循环外面都没注意! - keyType = 0; - } - - key = getRealKey(method, key, false, true, verifyName); - - switch (keyType) { - case 1: - return getSearchString(key, value, rawSQL); - case -2: - case 2: - return getRegExpString(key, value, keyType < 0, rawSQL); - case 3: - return getBetweenString(key, value, rawSQL); - case 4: - return getRangeString(key, value, rawSQL); - case 5: - return getExistsString(key, value, rawSQL); - case 6: - return getContainString(key, value, rawSQL); - case 7: - return getCompareString(key, value, ">=", rawSQL); - case 8: - return getCompareString(key, value, "<=", rawSQL); - case 9: - return getCompareString(key, value, ">", rawSQL); - case 10: - return getCompareString(key, value, "<", rawSQL); - default: // TODO MySQL JSON类型的字段对比 key='[]' 会无结果! key LIKE '[1, 2, 3]' //TODO MySQL , 后面有空格! - return getEqualString(key, value, rawSQL); - } - } - - - @JSONField(serialize = false) - public String getEqualString(String key, Object value, String rawSQL) throws Exception { - if (JSON.isBooleanOrNumberOrString(value) == false && value instanceof Subquery == false) { - throw new IllegalArgumentException(key + ":value 中value不合法!非PUT请求只支持 [Boolean, Number, String] 内的类型 !"); - } - - boolean not = key.endsWith("!"); // & | 没有任何意义,写法多了不好控制 - if (not) { - key = key.substring(0, key.length() - 1); - } - if (StringUtil.isName(key) == false) { - throw new IllegalArgumentException(key + ":value 中key不合法!不支持 ! 以外的逻辑符 !"); - } - - return getKey(key) + (not ? " != " : " = ") + (value instanceof Subquery ? getSubqueryString((Subquery) value) : (rawSQL != null ? rawSQL : getValue(value))); - } - - @JSONField(serialize = false) - public String getCompareString(String key, Object value, String type, String rawSQL) throws Exception { - if (JSON.isBooleanOrNumberOrString(value) == false && value instanceof Subquery == false) { - throw new IllegalArgumentException(key + type + ":value 中value不合法!比较运算 [>, <, >=, <=] 只支持 [Boolean, Number, String] 内的类型 !"); - } - if (StringUtil.isName(key) == false) { - throw new IllegalArgumentException(key + type + ":value 中key不合法!比较运算 [>, <, >=, <=] 不支持 [&, !, |] 中任何逻辑运算符 !"); - } - - return getKey(key) + " " + type + " " + (value instanceof Subquery ? getSubqueryString((Subquery) value) : (rawSQL != null ? rawSQL : getValue(value))); - } - - public String getKey(String key) { - if (isTest()) { - if (key.contains("'")) { // || key.contains("#") || key.contains("--")) { - throw new IllegalArgumentException("参数 " + key + " 不合法!key 中不允许有单引号 ' !"); - } - return getSQLValue(key).toString(); - } - - return getSQLKey(key); - } - - public String getSQLKey(String key) { - String q = getQuote(); - return (isKeyPrefix() ? getAliasWithQuote() + "." : "") + q + key + q; - } - - /** - * 使用prepareStatement预编译,值为 ? ,后续动态set进去 - */ - private List preparedValueList = new ArrayList<>(); - - private Object getValue(@NotNull Object value) { - if (isPrepared()) { - preparedValueList.add(value); - return "?"; - } - return getSQLValue(value); - } - - public Object getSQLValue(@NotNull Object value) { - // return (value instanceof Number || value instanceof Boolean) && DATABASE_POSTGRESQL.equals(getDatabase()) ? value : "'" + value + "'"; - return (value instanceof Number || value instanceof Boolean) ? value : "'" + value + "'"; //MySQL 隐式转换用不了索引 - } - - @Override - public List getPreparedValueList() { - return preparedValueList; - } - - @Override - public AbstractSQLConfig setPreparedValueList(List preparedValueList) { - this.preparedValueList = preparedValueList; - return this; - } - - //$ search <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< - - /** - * search key match value - * - * @param in - * @return {@link #getSearchString(String, Object[], int)} - * @throws IllegalArgumentException - */ - @JSONField(serialize = false) - public String getSearchString(String key, Object value, String rawSQL) throws IllegalArgumentException { - if (rawSQL != null) { - throw new UnsupportedOperationException("@raw:value 中 " + key + " 不合法!@raw 不支持 key$ 这种功能符 !只支持 key, key!, key<, key{} 等比较运算 和 @column, @having !"); - } - if (value == null) { - return ""; - } - - Logic logic = new Logic(key); - key = logic.getKey(); - Log.i(TAG, "getSearchString key = " + key); - - JSONArray arr = newJSONArray(value); - if (arr.isEmpty()) { - return ""; - } - return getSearchString(key, arr.toArray(), logic.getType()); - } - - /** - * search key match values - * - * @param in - * @return LOGIC [ key LIKE 'values[i]' ] - * @throws IllegalArgumentException - */ - @JSONField(serialize = false) - public String getSearchString(String key, Object[] values, int type) throws IllegalArgumentException { - if (values == null || values.length <= 0) { - return ""; - } - - String condition = ""; - for (int i = 0; i < values.length; i++) { - Object v = values[i]; - if (v instanceof String == false) { - throw new IllegalArgumentException(key + "$:value 中 value 的类型只能为 String 或 String[]!"); - } - if (((String) v).isEmpty()) { // 允许查空格 StringUtil.isEmpty((String) v, true) - throw new IllegalArgumentException(key + "$:value 中 value 值 " + v + "是空字符串,没有意义,不允许这样传!"); - } - // if (((String) v).contains("%%")) { // 需要通过 %\%% 来模糊搜索 % - // throw new IllegalArgumentException(key + "$:value 中 value 值 " + v + " 中包含 %% !不允许有连续的 % !"); - // } - - condition += (i <= 0 ? "" : (Logic.isAnd(type) ? AND : OR)) + getLikeString(key, v); - } - - return getCondition(Logic.isNot(type), condition); - } - - /** - * WHERE key LIKE 'value' - * - * @param key - * @param value - * @return key LIKE 'value' - */ - @JSONField(serialize = false) - public String getLikeString(String key, Object value) { - return getKey(key) + " LIKE " + getValue(value); - } - - //$ search >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> - - - //~ regexp <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< - - /** - * search key match RegExp values - * - * @param key - * @param value - * @param ignoreCase - * @return {@link #getRegExpString(String, Object[], int, boolean)} - * @throws IllegalArgumentException - */ - @JSONField(serialize = false) - public String getRegExpString(String key, Object value, boolean ignoreCase, String rawSQL) throws IllegalArgumentException { - if (rawSQL != null) { - throw new UnsupportedOperationException("@raw:value 中 " + key + " 不合法!@raw 不支持 key~ 这种功能符 !只支持 key, key!, key<, key{} 等比较运算 和 @column, @having !"); - } - if (value == null) { - return ""; - } - - Logic logic = new Logic(key); - key = logic.getKey(); - Log.i(TAG, "getRegExpString key = " + key); - - JSONArray arr = newJSONArray(value); - if (arr.isEmpty()) { - return ""; - } - return getRegExpString(key, arr.toArray(), logic.getType(), ignoreCase); - } - - /** - * search key match RegExp values - * - * @param key - * @param values - * @param type - * @param ignoreCase - * @return LOGIC [ key REGEXP 'values[i]' ] - * @throws IllegalArgumentException - */ - @JSONField(serialize = false) - public String getRegExpString(String key, Object[] values, int type, boolean ignoreCase) throws IllegalArgumentException { - if (values == null || values.length <= 0) { - return ""; - } - - String condition = ""; - for (int i = 0; i < values.length; i++) { - if (values[i] instanceof String == false) { - throw new IllegalArgumentException(key + "$:value 中value的类型只能为String或String[]!"); - } - condition += (i <= 0 ? "" : (Logic.isAnd(type) ? AND : OR)) + getRegExpString(key, (String) values[i], ignoreCase); - } - - return getCondition(Logic.isNot(type), condition); - } - - /** - * WHERE key REGEXP 'value' - * - * @param key - * @param value - * @param ignoreCase - * @return key REGEXP 'value' - */ - @JSONField(serialize = false) - public String getRegExpString(String key, String value, boolean ignoreCase) { - if (isPostgreSQL()) { - return getKey(key) + " ~" + (ignoreCase ? "* " : " ") + getValue(value); - } - if (isOracle()) { - return "regexp_like(" + getKey(key) + ", " + getValue(value) + (ignoreCase ? ", 'i'" : ", 'c'") + ")"; - } - return getKey(key) + " REGEXP " + (ignoreCase ? "" : "BINARY ") + getValue(value); - } - //~ regexp >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> - - - //% between <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< - - /** - * WHERE key BETWEEN 'start' AND 'end' - * - * @param key - * @param value 'start,end' - * @return LOGIC [ key BETWEEN 'start' AND 'end' ] - * @throws IllegalArgumentException - */ - @JSONField(serialize = false) - public String getBetweenString(String key, Object value, String rawSQL) throws IllegalArgumentException { - if (rawSQL != null) { - throw new UnsupportedOperationException("@raw:value 中 " + key + " 不合法!@raw 不支持 key% 这种功能符 !只支持 key, key!, key<, key{} 等比较运算 和 @column, @having !"); - } - if (value == null) { - return ""; - } - - Logic logic = new Logic(key); - key = logic.getKey(); - Log.i(TAG, "getBetweenString key = " + key); - - JSONArray arr = newJSONArray(value); - if (arr.isEmpty()) { - return ""; - } - return getBetweenString(key, arr.toArray(), logic.getType()); - } - - /** - * WHERE key BETWEEN 'start' AND 'end' - * - * @param key - * @param value 'start,end' TODO 在 '1,2' 和 ['1,2', '3,4'] 基础上新增支持 [1, 2] 和 [[1,2], [3,4]] ? - * @return LOGIC [ key BETWEEN 'start' AND 'end' ] - * @throws IllegalArgumentException - */ - @JSONField(serialize = false) - public String getBetweenString(String key, Object[] values, int type) throws IllegalArgumentException { - if (values == null || values.length <= 0) { - return ""; - } - - String condition = ""; - String[] vs; - for (int i = 0; i < values.length; i++) { - if (values[i] instanceof String == false) { - throw new IllegalArgumentException(key + "%:value 中 value 的类型只能为 String 或 String[] !"); - } - - vs = StringUtil.split((String) values[i]); - if (vs == null || vs.length != 2) { - throw new IllegalArgumentException(key + "%:value 中 value 不合法!类型为 String 时必须包括1个逗号 , 且左右两侧都有值!类型为 String[] 里面每个元素要符合前面类型为 String 的规则 !"); - } - - condition += (i <= 0 ? "" : (Logic.isAnd(type) ? AND : OR)) + "(" + getBetweenString(key, (Object) vs[0], (Object) vs[1]) + ")"; - } - - return getCondition(Logic.isNot(type), condition); - } - - /** - * WHERE key BETWEEN 'start' AND 'end' - * - * @param key - * @param value 'start,end' TODO 在 '1,2' 和 ['1,2', '3,4'] 基础上新增支持 [1, 2] 和 [[1,2], [3,4]] ? - * @return key BETWEEN 'start' AND 'end' - * @throws IllegalArgumentException - */ - @JSONField(serialize = false) - public String getBetweenString(String key, Object start, Object end) throws IllegalArgumentException { - if (JSON.isBooleanOrNumberOrString(start) == false || JSON.isBooleanOrNumberOrString(end) == false) { - throw new IllegalArgumentException(key + "%:value 中 value 不合法!类型为 String 时必须包括1个逗号 , 且左右两侧都有值!类型为 String[] 里面每个元素要符合前面类型为 String 的规则 !"); - } - return getKey(key) + " BETWEEN " + getValue(start) + AND + getValue(end); - } - - - //% between >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> - - - //{} range <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< - - - /** - * WHERE key > 'key0' AND key <= 'key1' AND ... - * - * @param key - * @param range "condition0,condition1..." - * @return key condition0 AND key condition1 AND ... - * @throws Exception - */ - @JSONField(serialize = false) - public String getRangeString(String key, Object range, String rawSQL) throws Exception { - Log.i(TAG, "getRangeString key = " + key); - if (range == null) {//依赖的对象都没有给出有效值,这个存在无意义。如果是客户端传的,那就能在客户端确定了。 - throw new NotExistException(TAG + "getRangeString(" + key + ", " + range - + ") range == null"); - } - - Logic logic = new Logic(key); - String k = logic.getKey(); - Log.i(TAG, "getRangeString k = " + k); - - if (range instanceof List) { - if (rawSQL != null) { - throw new UnsupportedOperationException("@raw:value 的 value 中 " + key + "{} 不合法!" - + "Raw SQL 不支持 key{}:[] 这种键值对!"); - } - - if (logic.isOr() || logic.isNot()) { - List l = (List) range; - if (logic.isNot() && l.isEmpty()) { - return ""; // key!{}: [] 这个条件无效,加到 SQL 语句中 key IN() 会报错,getInString 里不好处理 - } - return getKey(k) + getInString(k, l.toArray(), logic.isNot()); - } - throw new IllegalArgumentException(key + "{}\":[] 中 {} 前面的逻辑运算符错误!只能用'|','!'中的一种 !"); - } else if (range instanceof String) {//非Number类型需要客户端拼接成 < 'value0', >= 'value1'这种 - String condition = ""; - String[] cs = rawSQL != null ? null : StringUtil.split((String) range, false); - - if (rawSQL != null) { - int index = rawSQL == null ? -1 : rawSQL.indexOf("("); - condition = (index >= 0 && index < rawSQL.lastIndexOf(")") ? "" : getKey(k) + " ") + rawSQL; - } - - // 还是只支持整段为 Raw SQL 比较好 - // boolean appendRaw = false; - // if ("".equals(rawSQL)) { - // condition = rawSQL; - // cs = null; - // } - // else { - // if (rawSQL != null) { //先找出所有 rawSQL 的位置,然后去掉,再最后按原位置来拼接 - // String[] rs = StringUtil.split((String) range, rawSQL, false); - // - // if (rs != null && rs.length > 0) { - // String cond = ""; - // for (int i = 0; i < rs.length; i++) { - // cond += rs[i]; - // } - // range = cond; - // appendRaw = true; - // } - // } - // - // cs = StringUtil.split((String) range, false); - // } - - if (cs != null) { - String c; - int index; - for (int i = 0; i < cs.length; i++) {//对函数条件length(key)<=5这种不再在开头加key - c = cs[i]; - - if ("=null".equals(c)) { - c = SQL.isNull(); - } else if ("!=null".equals(c)) { - c = SQL.isNull(false); - } else if (isPrepared() && (c.contains("--") || PATTERN_RANGE.matcher(c).matches() == false)) { - throw new UnsupportedOperationException(key + "{}:value 的 value 中 " + c + " 不合法!" - + "预编译模式下 key{}:\"condition\" 中 condition 必须 为 =null 或 !=null 或 符合正则表达式 " + PATTERN_RANGE + " !不允许连续减号 -- !不允许空格!"); - } - - index = c == null ? -1 : c.indexOf("("); - condition += ((i <= 0 ? "" : (logic.isAnd() ? AND : OR)) //连接方式 - + (index >= 0 && index < c.lastIndexOf(")") ? "" : getKey(k) + " ") //函数和非函数条件 - + c); // 还是只支持整段为 Raw SQL 比较好 (appendRaw && index > 0 ? rawSQL : "") + c); //单个条件,如果有 Raw SQL 则按原来位置拼接 - } - } - if (condition.isEmpty()) { - return ""; - } - - return getCondition(logic.isNot(), condition); - } else if (range instanceof Subquery) { //如果在 Parser 解析成 SQL 字符串再引用,没法保证安全性,毕竟可以再通过远程函数等方式来拼接再替代,最后引用的字符串就能注入 - return getKey(k) + (logic.isNot() ? NOT : "") + " IN " + getSubqueryString((Subquery) range); - } - - throw new IllegalArgumentException(key + "{}:range 类型为" + range.getClass().getSimpleName() - + "!range 只能是 用','分隔条件的字符串 或者 可取选项JSONArray!"); - } - - /** - * WHERE key IN ('key0', 'key1', ... ) - * - * @param in - * @return IN ('key0', 'key1', ... ) - * @throws NotExistException - */ - @JSONField(serialize = false) - public String getInString(String key, Object[] in, boolean not) throws NotExistException { - String condition = ""; - if (in != null) {//返回 "" 会导致 id:[] 空值时效果和没有筛选id一样! - for (int i = 0; i < in.length; i++) { - condition += ((i > 0 ? "," : "") + getValue(in[i])); - } - } - if (condition.isEmpty()) {//条件如果存在必须执行,不能忽略。条件为空会导致出错,又很难保证条件不为空(@:条件),所以还是这样好 - throw new NotExistException(TAG + ".getInString(" + key + ", [], " + not - + ") >> condition.isEmpty() >> IN()"); - } - return (not ? NOT : "") + " IN (" + condition + ")"; - } - //{} range >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> - - - //}{ exists <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< - - /** - * WHERE EXISTS subquery - * 如果合并到 getRangeString,一方面支持不了 [1,2,2] 和 ">1" (转成 EXISTS(SELECT IN ) 需要static newSQLConfig,但它不能传入子类实例,除非不是 static),另一方面多了子查询临时表性能会比 IN 差 - * - * @param key - * @param value - * @return EXISTS ALL(SELECT ...) - * @throws NotExistException - */ - @JSONField(serialize = false) - public String getExistsString(String key, Object value, String rawSQL) throws Exception { - if (rawSQL != null) { - throw new UnsupportedOperationException("@raw:value 中 " + key + " 不合法!@raw 不支持 key}{ 这种功能符 !只支持 key, key!, key<, key{} 等比较运算 和 @column, @having !"); - } - if (value == null) { - return ""; - } - if (value instanceof Subquery == false) { - throw new IllegalArgumentException(key + "}{:subquery 类型为" + value.getClass().getSimpleName() - + "!subquery 只能是 子查询JSONObejct!"); - } - - Logic logic = new Logic(key); - key = logic.getKey(); - Log.i(TAG, "getExistsString key = " + key); - - return (logic.isNot() ? NOT : "") + " EXISTS " + getSubqueryString((Subquery) value); - } - //}{ exists >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> - - //<> contain <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< - - /** - * WHERE key contains value - * - * @param key - * @param value - * @return {@link #getContainString(String, Object[], int)} - * @throws NotExistException - */ - @JSONField(serialize = false) - public String getContainString(String key, Object value, String rawSQL) throws IllegalArgumentException { - if (rawSQL != null) { - throw new UnsupportedOperationException("@raw:value 中 " + key + " 不合法!@raw 不支持 key<> 这种功能符 !只支持 key, key!, key<, key{} 等比较运算 和 @column, @having !"); - } - if (value == null) { - return ""; - } - - Logic logic = new Logic(key); - key = logic.getKey(); - Log.i(TAG, "getContainString key = " + key); - - return getContainString(key, newJSONArray(value).toArray(), logic.getType()); - } - - /** - * WHERE key contains childs - * - * @param key - * @param childs null ? "" : (empty ? no child : contains childs) - * @param type |, &, ! - * @return LOGIC [ ( key LIKE '[" + childs[i] + "]' OR key LIKE '[" + childs[i] + ", %' - * OR key LIKE '%, " + childs[i] + ", %' OR key LIKE '%, " + childs[i] + "]' ) ] - * @throws IllegalArgumentException - */ - @JSONField(serialize = false) - public String getContainString(String key, Object[] childs, int type) throws IllegalArgumentException { - boolean not = Logic.isNot(type); - String condition = ""; - if (childs != null) { - for (int i = 0; i < childs.length; i++) { - Object c = childs[i]; - if (c != null) { - if (c instanceof JSON) { - throw new IllegalArgumentException(key + "<>:value 中value类型不能为JSON!"); - } - - condition += (i <= 0 ? "" : (Logic.isAnd(type) ? AND : OR)); - if (isPostgreSQL()) { - condition += (getKey(key) + " @> " + getValue(newJSONArray(c))); //operator does not exist: jsonb @> character varying "[" + c + "]"); - } else if (isOracle()) { - condition += ("json_textcontains(" + getKey(key) + ", '$', " + getValue(c.toString()) + ")"); - } else { - boolean isNum = c instanceof Number; - String v = (isNum ? "" : "\"") + childs[i] + (isNum ? "" : "\""); - condition += ("json_contains(" + getKey(key) + ", " + getValue(v) + ")"); - } - } - } - if (condition.isEmpty()) { - condition = (getKey(key) + SQL.isNull(true) + OR + getLikeString(key, "[]")); // key = '[]' 无结果! - } else { - condition = (getKey(key) + SQL.isNull(false) + AND + "(" + condition + ")"); - } - } - if (condition.isEmpty()) { - return ""; - } - return getCondition(not, condition); - } - //<> contain >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> - - - //key@:{} Subquery <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< - - @Override - public String getSubqueryString(Subquery subquery) throws Exception { - String range = subquery.getRange(); - SQLConfig cfg = subquery.getConfig(); - - cfg.setPreparedValueList(new ArrayList<>()); - String sql = (range == null || range.isEmpty() ? "" : range) + "(" + cfg.getSQL(isPrepared()) + ") "; - - preparedValueList.addAll(cfg.getPreparedValueList()); - - return sql; - } - - //key@:{} Subquery >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> - - - /** - * 拼接条件 - * - * @param not - * @param condition - * @return - */ - private static String getCondition(boolean not, String condition) { - return not ? NOT + "(" + condition + ")" : condition; - } - - - /** - * 转为JSONArray - * - * @param tv - * @return - */ - @NotNull - public static JSONArray newJSONArray(Object obj) { - JSONArray array = new JSONArray(); - if (obj != null) { - if (obj instanceof Collection) { - array.addAll((Collection) obj); - } else { - array.add(obj); - } - } - return array; - } - - //WHERE >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> - - - //SET <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< - - /** - * 获取SET - * - * @return - * @throws Exception - */ - @JSONField(serialize = false) - public String getSetString() throws Exception { - return getSetString(getMethod(), getContent(), !isTest()); - } - //CS304 Issue link: https://github.com/Tencent/APIJSON/issues/48 - - /** - * 获取SET - * - * @param method -the method used - * @param content -the content map - * @return - * @throws Exception

use entrySet+getValue() to replace keySet+get() to enhance efficiency

- */ - @JSONField(serialize = false) - public String getSetString(RequestMethod method, Map content, boolean verifyName) throws Exception { - Set set = content == null ? null : content.keySet(); - String setString = ""; - - if (set != null && set.size() > 0) { - boolean isFirst = true; - int keyType;// 0 - =; 1 - +, 2 - - - Object value; - - String idKey = getIdKey(); - for (Entry entry : content.entrySet()) { - String key = entry.getKey(); - //避免筛选到全部 value = key == null ? null : content.get(key); - if (key == null || idKey.equals(key)) { - continue; - } - - if (key.endsWith("+")) { - keyType = 1; - } else if (key.endsWith("-")) { - keyType = 2; - } else { - keyType = 0; //注意重置类型,不然不该加减的字段会跟着加减 - } - value = entry.getValue(); - key = getRealKey(method, key, false, true, verifyName); - - setString += (isFirst ? "" : ", ") + (getKey(key) + " = " + (keyType == 1 ? getAddString(key, value) : (keyType == 2 - ? getRemoveString(key, value) : getValue(value)))); - - isFirst = false; - } - } - - if (setString.isEmpty()) { - throw new IllegalArgumentException("PUT 请求必须在Table内设置要修改的 key:value !"); - } - return " SET " + setString; - } - - /** - * SET key = concat(key, 'value') - * - * @param key - * @param value - * @return concat(key, ' value ') - * @throws IllegalArgumentException - */ - @JSONField(serialize = false) - public String getAddString(String key, Object value) throws IllegalArgumentException { - if (value instanceof Number) { - return getKey(key) + " + " + value; - } - if (value instanceof String) { - return SQL.concat(getKey(key), (String) getValue(value)); - } - throw new IllegalArgumentException(key + "+ 对应的值 " + value + " 不是Number,String,Array中的任何一种!"); - } - - /** - * SET key = replace(key, 'value', '') - * - * @param key - * @param value - * @return REPLACE (key, 'value', '') - * @throws IllegalArgumentException - */ - @JSONField(serialize = false) - public String getRemoveString(String key, Object value) throws IllegalArgumentException { - if (value instanceof Number) { - return getKey(key) + " - " + value; - } - if (value instanceof String) { - return SQL.replace(getKey(key), (String) getValue(value), "''");// " replace(" + key + ", '" + value + "', '') "; - } - throw new IllegalArgumentException(key + "- 对应的值 " + value + " 不是Number,String,Array中的任何一种!"); - } - //SET >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> - - - /** - * @return - * @throws Exception - */ - @JSONField(serialize = false) - @Override - public String getSQL(boolean prepared) throws Exception { - return getSQL(this.setPrepared(prepared)); - } - - /** - * @param config - * @return - * @throws Exception - */ - public static String getSQL(AbstractSQLConfig config) throws Exception { - if (config == null) { - Log.i(TAG, "getSQL config == null >> return null;"); - return null; - } - - //TODO procedure 改为 List procedureList; behind : true; function: callFunction(); String key; ... - // for (...) { Call procedure1();\n SQL \n; Call procedure2(); ... } - // 貌似不需要,因为 ObjecParser 里就已经处理的顺序等,只是这里要解决下 Schema 问题。 - - String sch = config.getSQLSchema(); - if (StringUtil.isNotEmpty(config.getProcedure(), true)) { - String q = config.getQuote(); - return "CALL " + q + sch + q + "." + config.getProcedure(); - } - - String tablePath = config.getTablePath(); - if (StringUtil.isNotEmpty(tablePath, true) == false) { - Log.i(TAG, "getSQL StringUtil.isNotEmpty(tablePath, true) == false >> return null;"); - return null; - } - - switch (config.getMethod()) { - case POST: - return "INSERT INTO " + tablePath + config.getColumnString() + " VALUES" + config.getValuesString(); - case PUT: - return "UPDATE " + tablePath + config.getSetString() + config.getWhereString(true) + (config.isMySQL() ? config.getLimitString() : ""); - case DELETE: - return "DELETE FROM " + tablePath + config.getWhereString(true) + (config.isMySQL() ? config.getLimitString() : ""); // PostgreSQL 不允许 LIMIT - default: - String explain = (config.isExplain() ? (config.isSQLServer() || config.isOracle() ? "SET STATISTICS PROFILE ON " : "EXPLAIN ") : ""); - if (config.isTest() && RequestMethod.isGetMethod(config.getMethod(), true)) { - String q = config.getQuote(); // 生成 SELECT ( (24 >=0 AND 24 <3) ) AS `code` LIMIT 1 OFFSET 0 - return explain + "SELECT " + config.getWhereString(false) + " AS " + q + JSONResponse.KEY_CODE + q + config.getLimitString(); - } - - config.setPreparedValueList(new ArrayList()); - String column = config.getColumnString(); - if (config.isOracle()) { - //When config's database is oracle,Using subquery since Oracle12 below does not support OFFSET FETCH paging syntax. - return explain + "SELECT * FROM (SELECT" + (config.getCache() == JSONRequest.CACHE_RAM ? "SQL_NO_CACHE " : "") + column + " FROM " + getConditionString(column, tablePath, config) + ") " + config.getLimitString(); - } - - return explain + "SELECT " + (config.getCache() == JSONRequest.CACHE_RAM ? "SQL_NO_CACHE " : "") + column + " FROM " + getConditionString(column, tablePath, config) + config.getLimitString(); - } - } - - /** - * 获取条件SQL字符串 - * - * @param page - * @param column - * @param table - * @param where - * @return - * @throws Exception - */ - private static String getConditionString(String column, String table, AbstractSQLConfig config) throws Exception { - String where = config.getWhereString(true); - - Subquery from = config.getFrom(); - if (from != null) { - table = config.getSubqueryString(from) + " AS " + config.getAliasWithQuote() + " "; - } - - String condition = table + config.getJoinString() + where + ( - RequestMethod.isGetMethod(config.getMethod(), true) == false ? - "" : config.getGroupString(true) + config.getHavingString(true) + config.getOrderString(true) - ); //+ config.getLimitString(); - - //no need to optimize - // if (config.getPage() <= 0 || ID.equals(column.trim())) { - return condition; // config.isOracle() ? condition : condition + config.getLimitString(); - // } - // - // - // //order: id+ -> id >= idOfStartIndex; id- -> id <= idOfStartIndex <<<<<<<<<<<<<<<<<<< - // String order = StringUtil.getNoBlankString(config.getOrder()); - // List orderList = order.isEmpty() ? null : Arrays.asList(StringUtil.split(order)); - // - // int type = 0; - // if (BaseModel.isEmpty(orderList) || BaseModel.isContain(orderList, ID+"+")) { - // type = 1; - // } - // else if (BaseModel.isContain(orderList, ID+"-")) { - // type = 2; - // } - // - // if (type > 0) { - // return condition.replace("WHERE", - // "WHERE id " + (type == 1 ? ">=" : "<=") + " (SELECT id FROM " + table - // + where + " ORDER BY id " + (type == 1 ? "ASC" : "DESC") + " LIMIT " + config.getOffset() + ", 1) AND" - // ) - // + " LIMIT " + config.getCount(); //子查询起始id不一定准确,只能作为最小可能! ;// - // } - // //order: id+ -> id >= idOfStartIndex; id- -> id <= idOfStartIndex >>>>>>>>>>>>>>>>>> - // - // - // //结果错误!SELECT * FROM User AS t0 INNER JOIN - // (SELECT id FROM User ORDER BY date ASC LIMIT 20, 10) AS t1 ON t0.id = t1.id - // //common case, inner join - // condition += config.getLimitString(); - // return table + " AS t0 INNER JOIN (SELECT id FROM " + condition + ") AS t1 ON t0.id = t1.id"; - } - - - private boolean keyPrefix; - - @Override - public boolean isKeyPrefix() { - return keyPrefix; - } - - @Override - public AbstractSQLConfig setKeyPrefix(boolean keyPrefix) { - this.keyPrefix = keyPrefix; - return this; - } - - - public String getJoinString() throws Exception { - String joinOns = ""; - - if (joinList != null) { - String quote = getQuote(); - List pvl = new ArrayList<>(); - boolean changed = false; - - String sql = null; - SQLConfig jc; - String jt; - String tt; - // 主表不用别名 String ta; - for (Join j : joinList) { - if (j.isAppJoin()) { // APP JOIN,只是作为一个标记,执行完主表的查询后自动执行副表的查询 User.id IN($commentIdList) - continue; - } - String type = j.getJoinType(); - - //LEFT JOIN sys.apijson_user AS User ON User.id = Moment.userId, 都是用 = ,通过relateType处理缓存 - // <"INNER JOIN User ON User.id = Moment.userId", UserConfig> TODO AS 放 getSQLTable 内 - jc = j.getJoinConfig(); - jc.setPrepared(isPrepared()); - - jt = StringUtil.isEmpty(jc.getAlias(), true) ? jc.getTable() : jc.getAlias(); - tt = j.getTargetTable(); - - //如果要强制小写,则可在子类重写这个方法再 toLowerCase - // if (DATABASE_POSTGRESQL.equals(getDatabase())) { - // jt = jt.toLowerCase(); - // tn = tn.toLowerCase(); - // } - - switch (type) { - //前面已跳过 case "@": // APP JOIN - // continue; - - case "*": // CROSS JOIN - onGetCrossJoinString(j); - case "<": // LEFT JOIN - case ">": // RIGHT JOIN - jc.setMain(true).setKeyPrefix(false); - sql = ("<".equals(type) ? " LEFT" : (">".equals(type) ? " RIGHT" : " CROSS")) - + " JOIN ( " + jc.getSQL(isPrepared()) + " ) AS " - + quote + jt + quote + " ON " + quote + jt + quote + "." + quote + j.getKey() + quote + " = " - + quote + tt + quote + "." + quote + j.getTargetKey() + quote; - jc.setMain(false).setKeyPrefix(true); - - pvl.addAll(jc.getPreparedValueList()); - changed = true; - break; - - case "&": // INNER JOIN: A & B - case "": // FULL JOIN: A | B - case "|": // FULL JOIN: A | B - case "!": // OUTER JOIN: ! (A | B) - case "^": // SIDE JOIN: ! (A & B) - case "(": // ANTI JOIN: A & ! B - case ")": // FOREIGN JOIN: B & ! A - sql = " INNER JOIN " + jc.getTablePath() - + " ON " + quote + jt + quote + "." + quote + j.getKey() + quote + " = " + quote + tt + quote + "." + quote + j.getTargetKey() + quote; - break; - default: - throw new UnsupportedOperationException( - "join:value 中 value 里的 " + jt + "/" + j.getPath() - + "错误!不支持 " + jt + " 等 [ @ APP, < LEFT, > RIGHT, * CROSS" - + ", & INNER, | FULL, ! OUTER, ^ SIDE, ( ANTI, ) FOREIGN ] 之外的 JOIN 类型 !" - ); - } - - joinOns += " \n " + sql; - } - - - if (changed) { - pvl.addAll(preparedValueList); - preparedValueList = pvl; - } - - } - - return joinOns; - } - - protected void onGetCrossJoinString(Join j) throws UnsupportedOperationException { - throw new UnsupportedOperationException("已禁用 * CROSS JOIN !性能很差、需求极少,如要取消禁用可在后端重写相关方法!"); - } - - /** - * 新建SQL配置 - * - * @param table - * @param request - * @param joinList - * @param isProcedure - * @param callback - * @return - * @throws Exception - */ - public static SQLConfig newSQLConfig(RequestMethod method, String table, String alias, JSONObject request, List joinList, boolean isProcedure, Callback callback) throws Exception { - if (request == null) { // User:{} 这种空内容在查询时也有效 - throw new NullPointerException(TAG + ": newSQLConfig request == null!"); - } - - boolean explain = request.getBooleanValue(KEY_EXPLAIN); - if (explain && Log.DEBUG == false) { //不在 config.setExplain 抛异常,一方面处理更早性能更好,另一方面为了内部调用可以绕过这个限制 - throw new UnsupportedOperationException("DEBUG 模式下不允许传 " + KEY_EXPLAIN + " !"); - } - - String database = request.getString(KEY_DATABASE); - if (StringUtil.isEmpty(database, false) == false && DATABASE_LIST.contains(database) == false) { - throw new UnsupportedDataTypeException("@database:value 中 value 错误,只能是 [" + StringUtil.getString(DATABASE_LIST.toArray()) + "] 中的一种!"); - } - - String schema = request.getString(KEY_SCHEMA); - String datasource = request.getString(KEY_DATASOURCE); - - SQLConfig config = callback.getSQLConfig(method, database, schema, table); - config.setAlias(alias); - - config.setDatabase(database); //不删,后面表对象还要用的,必须放在 parseJoin 前 - config.setSchema(schema); //不删,后面表对象还要用的 - config.setDatasource(datasource); //不删,后面表对象还要用的 - - if (isProcedure) { - return config; - } - - config = parseJoin(method, config, joinList, callback); //放后面会导致主表是空对象时 joinList 未解析 - - if (request.isEmpty()) { // User:{} 这种空内容在查询时也有效 - return config; //request.remove(key); 前都可以直接return,之后必须保证 put 回去 - } - - - String idKey = callback.getIdKey(database, schema, table); - String idInKey = idKey + "{}"; - String userIdKey = callback.getUserIdKey(database, schema, table); - String userIdInKey = userIdKey + "{}"; - - //对id和id{}处理,这两个一定会作为条件 - - Object idIn = request.get(idInKey); //可能是 id{}:">0" - if (idIn instanceof List) { // 排除掉 0, 负数, 空字符串 等无效 id 值 - List ids = ((List) idIn); - List newIdIn = new ArrayList<>(); - Object d; - for (int i = 0; i < ids.size(); i++) { //不用 idIn.contains(id) 因为 idIn 里存到很可能是 Integer,id 又是 Long! - d = ids.get(i); - if ((d instanceof Number && ((Number) d).longValue() > 0) || (d instanceof String && StringUtil.isNotEmpty(d, true))) { - newIdIn.add(d); - } - } - if (newIdIn.isEmpty()) { - throw new NotExistException(TAG + ": newSQLConfig idIn instanceof List >> 去掉无效 id 后 newIdIn.isEmpty()"); - } - idIn = newIdIn; - - if (method == DELETE || method == PUT) { - config.setCount(newIdIn.size()); - } - } - - Object id = request.get(idKey); - boolean hasId = id != null; - if (method == POST && hasId == false) { - id = callback.newId(method, database, schema, table); // null 表示数据库自增 id - } - - if (id != null) { //null无效 - if (id instanceof Number) { - if (((Number) id).longValue() <= 0) { //一定没有值 - throw new NotExistException(TAG + ": newSQLConfig " + table + ".id <= 0"); - } - } else if (id instanceof String) { - if (StringUtil.isEmpty(id, true)) { //一定没有值 - throw new NotExistException(TAG + ": newSQLConfig StringUtil.isEmpty(" + table + ".id, true)"); - } - } else if (id instanceof Subquery) { - } else { - throw new IllegalArgumentException(idKey + ":value 中 value 的类型只能是 Long , String 或 Subquery !"); - } - - if (idIn instanceof List) { //共用idIn场景少性能差 - boolean contains = false; - List ids = ((List) idIn); - Object d; - for (int i = 0; i < ids.size(); i++) { //不用 idIn.contains(id) 因为 idIn 里存到很可能是 Integer,id 又是 Long! - d = ids.get(i); - if (d != null && id.toString().equals(d.toString())) { - contains = true; - break; - } - } - if (contains == false) {//empty有效 BaseModel.isEmpty(idIn) == false) { - throw new NotExistException(TAG + ": newSQLConfig idIn != null && (((List) idIn).contains(id) == false"); - } - } - - if (method == DELETE || method == PUT) { - config.setCount(1); - } - } - - - String role = request.getString(KEY_ROLE); - String cache = request.getString(KEY_CACHE); - String combine = request.getString(KEY_COMBINE); - Subquery from = (Subquery) request.get(KEY_FROM); - String column = request.getString(KEY_COLUMN); - String group = request.getString(KEY_GROUP); - String having = request.getString(KEY_HAVING); - String order = request.getString(KEY_ORDER); - String raw = request.getString(KEY_RAW); - String json = request.getString(KEY_JSON); - - try { - //强制作为条件且放在最前面优化性能 - request.remove(idKey); - request.remove(idInKey); - //关键词 - request.remove(KEY_ROLE); - request.remove(KEY_EXPLAIN); - request.remove(KEY_CACHE); - request.remove(KEY_DATABASE); - request.remove(KEY_SCHEMA); - request.remove(KEY_COMBINE); - request.remove(KEY_FROM); - request.remove(KEY_COLUMN); - request.remove(KEY_GROUP); - request.remove(KEY_HAVING); - request.remove(KEY_ORDER); - request.remove(KEY_RAW); - request.remove(KEY_JSON); - - String[] rawArr = StringUtil.split(raw); - config.setRaw(rawArr == null || rawArr.length <= 0 ? null : new ArrayList<>(Arrays.asList(rawArr))); - - Map tableWhere = new LinkedHashMap();//保证顺序好优化 WHERE id > 1 AND name LIKE... - - //已经remove了id和id{},以及@key - Set set = request.keySet(); //前面已经判断request是否为空 - if (method == POST) { //POST操作 - if (idIn != null) { - throw new IllegalArgumentException("POST 请求中不允许传 " + idInKey + " !"); - } - - if (set != null && set.isEmpty() == false) { //不能直接return,要走完下面的流程 - String[] columns = set.toArray(new String[]{}); - - Collection valueCollection = request.values(); - Object[] values = valueCollection == null ? null : valueCollection.toArray(); - - if (values == null || values.length != columns.length) { - throw new Exception("服务器内部错误:\n" + TAG - + " newSQLConfig values == null || values.length != columns.length !"); - } - column = (id == null ? "" : idKey + ",") + StringUtil.getString(columns); //set已经判断过不为空 - - List> valuess = new ArrayList<>(1); - List items; //(item0, item1, ...) - if (id == null) { //数据库自增 id - items = Arrays.asList(values); //FIXME 是否还需要进行 add 或 remove 操作?Arrays.ArrayList 不允许修改,会抛异常 - } else { - int size = columns.length + (id == null ? 0 : 1); //以key数量为准 - - items = new ArrayList<>(size); - items.add(id); //idList.get(i)); //第0个就是id - - for (int j = 1; j < size; j++) { - items.add(values[j - 1]); //从第1个开始,允许"null" - } - } - - valuess.add(items); - config.setValues(valuess); - } - } else { //非POST操作 - final boolean isWhere = method != PUT;//除了POST,PUT,其它全是条件!!! - - //条件<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< - List whereList = null; - - Map> combineMap = new LinkedHashMap<>(); - List andList = new ArrayList<>(); - List orList = new ArrayList<>(); - List notList = new ArrayList<>(); - - //强制作为条件且放在最前面优化性能 - if (id != null) { - tableWhere.put(idKey, id); - andList.add(idKey); - } - if (idIn != null) { - tableWhere.put(idInKey, idIn); - andList.add(idInKey); - } - - String[] ws = StringUtil.split(combine); - if (ws != null) { - if (method == DELETE || method == GETS || method == HEADS) { - throw new IllegalArgumentException("DELETE,GETS,HEADS 请求不允许传 @combine:value !"); - } - whereList = new ArrayList<>(); - - String w; - for (int i = 0; i < ws.length; i++) { //去除 &,|,! 前缀 - w = ws[i]; - if (w != null) { - if (w.startsWith("&")) { - w = w.substring(1); - andList.add(w); - } else if (w.startsWith("|")) { - if (method == PUT) { - throw new IllegalArgumentException(table + ":{} 里的 @combine:value 中的value里条件 " + ws[i] + " 不合法!" - + "PUT请求的 @combine:\"key0,key1,...\" 不允许传 |key 或 !key !"); - } - w = w.substring(1); - orList.add(w); - } else if (w.startsWith("!")) { - if (method == PUT) { - throw new IllegalArgumentException(table + ":{} 里的 @combine:value 中的value里条件 " + ws[i] + " 不合法!" - + "PUT请求的 @combine:\"key0,key1,...\" 不允许传 |key 或 !key !"); - } - w = w.substring(1); - notList.add(w); - } else { - orList.add(w); - } - - if (w.isEmpty()) { - throw new IllegalArgumentException(table + ":{} 里的 @combine:value 中的value里条件 " + ws[i] + " 不合法!不允许为空值!"); - } else { - if (idKey.equals(w) || idInKey.equals(w) || userIdKey.equals(w) || userIdInKey.equals(w)) { - throw new UnsupportedOperationException(table + ":{} 里的 @combine:value 中的value里 " + ws[i] + " 不合法!" - + "不允许传 [" + idKey + ", " + idInKey + ", " + userIdKey + ", " + userIdInKey + "] 其中任何一个!"); - } - } - - whereList.add(w); - } - - // 可重写回调方法自定义处理 // 动态设置的场景似乎很少,而且去掉后不方便用户排错!//去掉判断,有时候不在没关系,如果是对增删改等非开放请求强制要求传对应的条件,可以用 Operation.NECESSARY - if (request.containsKey(w) == false) { //和 request.get(w) == null 没区别,前面 Parser 已经过滤了 null - // throw new IllegalArgumentException(table + ":{} 里的 @combine:value 中的value里 " + ws[i] + " 对应的 " + w + " 不在它里面!"); - callback.onMissingKey4Combine(table, request, combine, ws[i], w); - } - } - - } - - //条件>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> - - Map tableContent = new LinkedHashMap(); - Object value; - for (String key : set) { - value = request.get(key); - - if (value instanceof Map) {//只允许常规Object - throw new IllegalArgumentException("不允许 " + key + " 等任何key的value类型为 {JSONObject} !"); - } - - //解决AccessVerifier新增userId没有作为条件,而是作为内容,导致PUT,DELETE出错 - if (isWhere) { - tableWhere.put(key, value); - if (whereList == null || whereList.contains(key) == false) { - andList.add(key); - } - } else if (whereList != null && whereList.contains(key)) { - tableWhere.put(key, value); - } else { - tableContent.put(key, value);//一样 instanceof JSONArray ? JSON.toJSONString(value) : value); - } - } - - combineMap.put("&", andList); - combineMap.put("|", orList); - combineMap.put("!", notList); - config.setCombine(combineMap); - - config.setContent(tableContent); - } - - - List cs = new ArrayList<>(); - - List rawList = config.getRaw(); - boolean containColumnRaw = rawList != null && rawList.contains(KEY_COLUMN); - - String rawColumnSQL = null; - if (containColumnRaw) { - try { - rawColumnSQL = config.getRawSQL(KEY_COLUMN, column); - if (rawColumnSQL != null) { - cs.add(rawColumnSQL); - } - } catch (Exception e) { - Log.e(TAG, "newSQLConfig config instanceof AbstractSQLConfig >> try { " - + " rawColumnSQL = ((AbstractSQLConfig) config).getRawSQL(KEY_COLUMN, column); " - + "} catch (Exception e) = " + e.getMessage()); - } - } - - boolean distinct = column == null || rawColumnSQL != null ? false : column.startsWith(PREFFIX_DISTINCT); - if (rawColumnSQL == null) { - String[] fks = StringUtil.split(distinct ? column.substring(PREFFIX_DISTINCT.length()) : column, ";"); // key0,key1;fun0(key0,...);fun1(key0,...);key3;fun2(key0,...) - if (fks != null) { - String[] ks; - for (String fk : fks) { - if (containColumnRaw) { - try { - String rawSQL = config.getRawSQL(KEY_COLUMN, fk); - if (rawSQL != null) { - cs.add(rawSQL); - continue; - } - } catch (Exception e) { - Log.e(TAG, "newSQLConfig rawColumnSQL == null >> try { " - + " String rawSQL = ((AbstractSQLConfig) config).getRawSQL(KEY_COLUMN, fk); ... " - + "} catch (Exception e) = " + e.getMessage()); - } - } - - if (fk.contains("(")) { // fun0(key0,...) - cs.add(fk); - } else { //key0,key1... - ks = StringUtil.split(fk); - if (ks != null && ks.length > 0) { - cs.addAll(Arrays.asList(ks)); - } - } - } - } - } - - config.setExplain(explain); - config.setCache(getCache(cache)); - config.setFrom(from); - config.setDistinct(distinct); - config.setColumn(column == null ? null : cs); //解决总是 config.column != null,总是不能得到 * - config.setWhere(tableWhere); - - config.setId(id); - //在 tableWhere 第0个 config.setIdIn(idIn); - - config.setRole(RequestRole.get(role)); - config.setGroup(group); - config.setHaving(having); - config.setOrder(order); - - String[] jsonArr = StringUtil.split(json); - config.setJson(jsonArr == null || jsonArr.length <= 0 ? null : new ArrayList<>(Arrays.asList(jsonArr))); - - //TODO 解析JOIN,包括 @column,@group 等要合并 - - } finally {//后面还可能用到,要还原 - //id或id{}条件 - if (hasId) { - request.put(idKey, id); - } - request.put(idInKey, idIn); - //关键词 - request.put(KEY_DATABASE, database); - request.put(KEY_ROLE, role); - request.put(KEY_EXPLAIN, explain); - request.put(KEY_CACHE, cache); - request.put(KEY_SCHEMA, schema); - request.put(KEY_COMBINE, combine); - request.put(KEY_FROM, from); - request.put(KEY_COLUMN, column); - request.put(KEY_GROUP, group); - request.put(KEY_HAVING, having); - request.put(KEY_ORDER, order); - request.put(KEY_RAW, raw); - request.put(KEY_JSON, json); - } - - return config; - } - - - /** - * @param method - * @param config - * @param joinList - * @param callback - * @return - * @throws Exception - */ - public static SQLConfig parseJoin(RequestMethod method, SQLConfig config, List joinList, Callback callback) throws Exception { - boolean isQuery = RequestMethod.isQueryMethod(method); - config.setKeyPrefix(isQuery && config.isMain() == false); - - //TODO 解析出 SQLConfig 再合并 column, order, group 等 - if (joinList == null || joinList.isEmpty() || RequestMethod.isQueryMethod(method) == false) { - return config; - } - - - String table; - String alias; - for (Join j : joinList) { - table = j.getTable(); - alias = j.getAlias(); - //JOIN子查询不能设置LIMIT,因为ON关系是在子查询后处理的,会导致结果会错误 - SQLConfig joinConfig = newSQLConfig(method, table, alias, j.getRequest(), null, false, callback); - SQLConfig cacheConfig = j.canCacheViceTable() == false ? null : newSQLConfig(method, table, alias, j.getRequest(), null, false, callback).setCount(1); - - if (j.isAppJoin() == false) { //除了 @ APP JOIN,其它都是 SQL JOIN,则副表要这样配置 - if (joinConfig.getDatabase() == null) { - joinConfig.setDatabase(config.getDatabase()); //解决主表 JOIN 副表,引号不一致 - } else if (joinConfig.getDatabase().equals(config.getDatabase()) == false) { - throw new IllegalArgumentException("主表 " + config.getTable() + " 的 @database:" + config.getDatabase() + " 和它 SQL JOIN 的副表 " + table + " 的 @database:" + joinConfig.getDatabase() + " 不一致!"); - } - if (joinConfig.getSchema() == null) { - joinConfig.setSchema(config.getSchema()); //主表 JOIN 副表,默认 schema 一致 - } - - if (cacheConfig != null) { - cacheConfig.setDatabase(joinConfig.getDatabase()).setSchema(joinConfig.getSchema()); //解决主表 JOIN 副表,引号不一致 - } - - - if (isQuery) { - config.setKeyPrefix(true); - } - - joinConfig.setMain(false).setKeyPrefix(true); - - if (j.isLeftOrRightJoin()) { - SQLConfig outterConfig = newSQLConfig(method, table, alias, j.getOuter(), null, false, callback); - outterConfig.setMain(false).setKeyPrefix(true).setDatabase(joinConfig.getDatabase()).setSchema(joinConfig.getSchema()); //解决主表 JOIN 副表,引号不一致 - j.setOuterConfig(outterConfig); - } - } - - //解决 query: 1/2 查数量时报错 + private static final String TAG = "AbstractSQLConfig"; + + public static String DEFAULT_DATABASE = DATABASE_MYSQL; + public static String DEFAULT_SCHEMA = "sys"; + public static String PREFFIX_DISTINCT = "DISTINCT "; + + // * 和 / 不能同时出现,防止 /* */ 段注释! # 和 -- 不能出现,防止行注释! ; 不能出现,防止隔断SQL语句!空格不能出现,防止 CRUD,DROP,SHOW TABLES等语句! + private static final Pattern PATTERN_RANGE; + private static final Pattern PATTERN_FUNCTION; + + /** + * 表名映射,隐藏真实表名,对安全要求很高的表可以这么做 + */ + public static final Map TABLE_KEY_MAP; + public static final List CONFIG_TABLE_LIST; + public static final List DATABASE_LIST; + // 自定义原始 SQL 片段 Map:当 substring 为 null 时忽略;当 substring 为 "" 时整个 value 是 raw SQL;其它情况则只是 substring 这段为 raw SQL + public static final Map RAW_MAP; + // 允许调用的 SQL 函数:当 substring 为 null 时忽略;当 substring 为 "" 时整个 value 是 raw SQL;其它情况则只是 substring 这段为 raw SQL + public static final Map SQL_FUNCTION_MAP; + static { // 凡是 SQL 边界符、分隔符、注释符 都不允许,例如 ' " ` ( ) ; # -- ,以免拼接 SQL 时被注入意外可执行指令 + PATTERN_RANGE = Pattern.compile("^[0-9%,!=\\<\\>/\\.\\+\\-\\*\\^]+$"); // ^[a-zA-Z0-9_*%!=<>(),"]+$ 导致 exists(select*from(Comment)) 通过! + PATTERN_FUNCTION = Pattern.compile("^[A-Za-z0-9%,:_@&~!=\\<\\>\\|\\[\\]\\{\\} /\\.\\+\\-\\*\\^\\?\\$]+$"); //TODO 改成更好的正则,校验前面为单词,中间为操作符,后面为值 + + + TABLE_KEY_MAP = new HashMap(); + TABLE_KEY_MAP.put(Table.class.getSimpleName(), Table.TABLE_NAME); + TABLE_KEY_MAP.put(Column.class.getSimpleName(), Column.TABLE_NAME); + TABLE_KEY_MAP.put(PgClass.class.getSimpleName(), PgClass.TABLE_NAME); + TABLE_KEY_MAP.put(PgAttribute.class.getSimpleName(), PgAttribute.TABLE_NAME); + TABLE_KEY_MAP.put(SysTable.class.getSimpleName(), SysTable.TABLE_NAME); + TABLE_KEY_MAP.put(SysColumn.class.getSimpleName(), SysColumn.TABLE_NAME); + TABLE_KEY_MAP.put(ExtendedProperty.class.getSimpleName(), ExtendedProperty.TABLE_NAME); + + CONFIG_TABLE_LIST = new ArrayList<>(); // Table, Column 等是系统表 AbstractVerifier.SYSTEM_ACCESS_MAP.keySet()); + CONFIG_TABLE_LIST.add(Function.class.getSimpleName()); + CONFIG_TABLE_LIST.add(Request.class.getSimpleName()); + CONFIG_TABLE_LIST.add(Response.class.getSimpleName()); + CONFIG_TABLE_LIST.add(Access.class.getSimpleName()); + CONFIG_TABLE_LIST.add(Document.class.getSimpleName()); + CONFIG_TABLE_LIST.add(TestRecord.class.getSimpleName()); + + + DATABASE_LIST = new ArrayList<>(); + DATABASE_LIST.add(DATABASE_MYSQL); + DATABASE_LIST.add(DATABASE_POSTGRESQL); + DATABASE_LIST.add(DATABASE_SQLSERVER); + DATABASE_LIST.add(DATABASE_ORACLE); + DATABASE_LIST.add(DATABASE_DB2); + + + RAW_MAP = new LinkedHashMap<>(); // 保证顺序,避免配置冲突等意外情况 + + + SQL_FUNCTION_MAP = new LinkedHashMap<>(); // 保证顺序,避免配置冲突等意外情况 + + // MySQL 字符串函数 + SQL_FUNCTION_MAP.put("ascii", ""); // ASCII(s) 返回字符串 s 的第一个字符的 ASCII 码。 + SQL_FUNCTION_MAP.put("char_length", ""); // CHAR_LENGTH(s) 返回字符串 s 的字符数 + SQL_FUNCTION_MAP.put("character_length", ""); // CHARACTER_LENGTH(s) 返回字符串 s 的字符数 + SQL_FUNCTION_MAP.put("concat", ""); // CONCAT(s1, s2...sn) 字符串 s1,s2 等多个字符串合并为一个字符串 + SQL_FUNCTION_MAP.put("concat_ws", ""); // CONCAT_WS(x, s1, s2...sn) 同 CONCAT(s1, s2 ...) 函数,但是每个字符串之间要加上 x,x 可以是分隔符 + SQL_FUNCTION_MAP.put("field", ""); // FIELD(s, s1, s2...) 返回第一个字符串 s 在字符串列表 (s1, s2...)中的位置 + SQL_FUNCTION_MAP.put("find_in_set", ""); // FIND_IN_SET(s1, s2) 返回在字符串s2中与s1匹配的字符串的位置 + SQL_FUNCTION_MAP.put("format", ""); // FORMAT(x, n) 函数可以将数字 x 进行格式化 "#,###.##", 将 x 保留到小数点后 n 位,最后一位四舍五入。 + SQL_FUNCTION_MAP.put("insert", ""); // INSERT(s1, x, len, s2) 字符串 s2 替换 s1 的 x 位置开始长度为 len 的字符串 + SQL_FUNCTION_MAP.put("locate", ""); // LOCATE(s1, s) 从字符串 s 中获取 s1 的开始位置 + SQL_FUNCTION_MAP.put("lcase", ""); // LCASE(s) 将字符串 s 的所有字母变成小写字母 + SQL_FUNCTION_MAP.put("left", ""); // LEFT(s, n) 返回字符串 s 的前 n 个字符 + SQL_FUNCTION_MAP.put("length", ""); // LENGTH(s) 返回字符串 s 的字符数 + SQL_FUNCTION_MAP.put("lower", ""); // LOWER(s) 将字符串 s 的所有字母变成小写字母 + SQL_FUNCTION_MAP.put("lpad", ""); // LPAD(s1, len, s2) 在字符串 s1 的开始处填充字符串 s2,使字符串长度达到 len + SQL_FUNCTION_MAP.put("ltrim", ""); // LTRIM(s) 去掉字符串 s 开始处的空格 + SQL_FUNCTION_MAP.put("mid", ""); // MID(s, n, len) 从字符串 s 的 n 位置截取长度为 len 的子字符串,同 SUBSTRING(s, n, len) + SQL_FUNCTION_MAP.put("position", ""); // POSITION(s, s1); 从字符串 s 中获取 s1 的开始位置 + SQL_FUNCTION_MAP.put("repeat", ""); // REPEAT(s, n) 将字符串 s 重复 n 次 + SQL_FUNCTION_MAP.put("replace", ""); // REPLACE(s, s1, s2) 将字符串 s2 替代字符串 s 中的字符串 s1 + SQL_FUNCTION_MAP.put("reverse", ""); // REVERSE(s); // ) 将字符串s的顺序反过来 + SQL_FUNCTION_MAP.put("right", ""); // RIGHT(s, n) 返回字符串 s 的后 n 个字符 + SQL_FUNCTION_MAP.put("rpad", ""); // RPAD(s1, len, s2) 在字符串 s1 的结尾处添加字符串 s2,使字符串的长度达到 len + SQL_FUNCTION_MAP.put("rtrim", ""); // RTRIM", ""); // ) 去掉字符串 s 结尾处的空格 + SQL_FUNCTION_MAP.put("space", ""); // SPACE(n) 返回 n 个空格 + SQL_FUNCTION_MAP.put("strcmp", ""); // STRCMP(s1, s2) 比较字符串 s1 和 s2,如果 s1 与 s2 相等返回 0 ,如果 s1>s2 返回 1,如果 s1d2 之间相隔的天数 + SQL_FUNCTION_MAP.put("date_add", ""); // DATE_ADD(d,INTERVAL expr type) 计算起始日期 d 加上一个时间段后的日期 + SQL_FUNCTION_MAP.put("date_format", ""); // DATE_FORMAT(d,f) 按表达式 f的要求显示日期 d + SQL_FUNCTION_MAP.put("date_sub", ""); // DATE_SUB(date,INTERVAL expr type) 函数从日期减去指定的时间间隔。 + SQL_FUNCTION_MAP.put("day", ""); // DAY(d) 返回日期值 d 的日期部分 + SQL_FUNCTION_MAP.put("dayname", ""); // DAYNAME(d) 返回日期 d 是星期几,如 Monday,Tuesday + SQL_FUNCTION_MAP.put("dayofmonth", ""); // DAYOFMONTH(d) 计算日期 d 是本月的第几天 + SQL_FUNCTION_MAP.put("dayofweek", ""); // DAYOFWEEK(d) 日期 d 今天是星期几,1 星期日,2 星期一,以此类推 + SQL_FUNCTION_MAP.put("dayofyear", ""); // DAYOFYEAR(d) 计算日期 d 是本年的第几天 + SQL_FUNCTION_MAP.put("extract", ""); // EXTRACT(type FROM d) 从日期 d 中获取指定的值,type 指定返回的值。 + SQL_FUNCTION_MAP.put("from_days", ""); // FROM_DAYS(n) 计算从 0000 年 1 月 1 日开始 n 天后的日期 + SQL_FUNCTION_MAP.put("hour", ""); // 'HOUR(t) 返回 t 中的小时值 + SQL_FUNCTION_MAP.put("last_day", ""); // LAST_DAY(d) 返回给给定日期的那一月份的最后一天 + SQL_FUNCTION_MAP.put("localtime", ""); // LOCALTIME() 返回当前日期和时间 + SQL_FUNCTION_MAP.put("localtimestamp", ""); // LOCALTIMESTAMP() 返回当前日期和时间 + SQL_FUNCTION_MAP.put("makedate", ""); // MAKEDATE(year, day-of-year) 基于给定参数年份 year 和所在年中的天数序号 day-of-year 返回一个日期 + SQL_FUNCTION_MAP.put("maketime", ""); // MAKETIME(hour, minute, second) 组合时间,参数分别为小时、分钟、秒 + SQL_FUNCTION_MAP.put("microsecond", ""); // MICROSECOND(date) 返回日期参数所对应的微秒数 + SQL_FUNCTION_MAP.put("minute", ""); // MINUTE(t) 返回 t 中的分钟值 + SQL_FUNCTION_MAP.put("monthname", ""); // MONTHNAME(d) 返回日期当中的月份名称,如 November + SQL_FUNCTION_MAP.put("month", ""); // MONTH(d) 返回日期d中的月份值,1 到 12 + SQL_FUNCTION_MAP.put("now", ""); // NOW() 返回当前日期和时间 + SQL_FUNCTION_MAP.put("period_add", ""); // PERIOD_ADD(period, number) 为 年-月 组合日期添加一个时段 + SQL_FUNCTION_MAP.put("period_diff", ""); // PERIOD_DIFF(period1, period2) 返回两个时段之间的月份差值 + SQL_FUNCTION_MAP.put("quarter", ""); // QUARTER(d) 返回日期d是第几季节,返回 1 到 4 + SQL_FUNCTION_MAP.put("second", ""); // SECOND(t) 返回 t 中的秒钟值 + SQL_FUNCTION_MAP.put("sec_to_time", ""); // SEC_TO_TIME", ""); // ) 将以秒为单位的时间 s 转换为时分秒的格式 + SQL_FUNCTION_MAP.put("str_to_date", ""); // STR_TO_DATE", ""); // tring, format_mask) 将字符串转变为日期 + SQL_FUNCTION_MAP.put("subdate", ""); // SUBDATE(d,n) 日期 d 减去 n 天后的日期 + SQL_FUNCTION_MAP.put("subtime", ""); // SUBTIME(t,n) 时间 t 减去 n 秒的时间 + SQL_FUNCTION_MAP.put("sysdate", ""); // SYSDATE() 返回当前日期和时间 + SQL_FUNCTION_MAP.put("time", ""); // TIME(expression) 提取传入表达式的时间部分 + SQL_FUNCTION_MAP.put("time_format", ""); // TIME_FORMAT(t,f) 按表达式 f 的要求显示时间 t + SQL_FUNCTION_MAP.put("time_to_sec", ""); // TIME_TO_SEC(t) 将时间 t 转换为秒 + SQL_FUNCTION_MAP.put("timediff", ""); // TIMEDIFF(time1, time2) 计算时间差值 + SQL_FUNCTION_MAP.put("timestamp", ""); // TIMESTAMP(expression, interval) 单个参数时,函数返回日期或日期时间表达式;有2个参数时,将参数加和 + SQL_FUNCTION_MAP.put("to_days", ""); // TO_DAYS(d) 计算日期 d 距离 0000 年 1 月 1 日的天数 + SQL_FUNCTION_MAP.put("week", ""); // WEEK(d) 计算日期 d 是本年的第几个星期,范围是 0 到 53 + SQL_FUNCTION_MAP.put("weekday", ""); // WEEKDAY(d) 日期 d 是星期几,0 表示星期一,1 表示星期二 + SQL_FUNCTION_MAP.put("weekofyear", ""); // WEEKOFYEAR(d) 计算日期 d 是本年的第几个星期,范围是 0 到 53 + SQL_FUNCTION_MAP.put("year", ""); // YEAR(d) 返回年份 + SQL_FUNCTION_MAP.put("yearweek", ""); // YEARWEEK(date, mode) 返回年份及第几周(0到53),mode 中 0 表示周天,1表示周一,以此类推 + SQL_FUNCTION_MAP.put("unix_timestamp", ""); // UNIX_TIMESTAMP(date) 获取UNIX时间戳函数,返回一个以 UNIX 时间戳为基础的无符号整数 + SQL_FUNCTION_MAP.put("from_unixtime", ""); // FROM_UNIXTIME(date) 将 UNIX 时间戳转换为时间格式,与UNIX_TIMESTAMP互为反函数 + + // MYSQL JSON 函数 + SQL_FUNCTION_MAP.put("json_append", ""); // JSON_APPEND(json_doc, path, val[, path, val] ...)) 插入JSON数组 + SQL_FUNCTION_MAP.put("json_array", ""); // JSON_ARRAY(val1, val2...) 创建JSON数组 + SQL_FUNCTION_MAP.put("json_array_append", ""); // JSON_ARRAY_APPEND(json_doc, val) 将数据附加到JSON文档 + SQL_FUNCTION_MAP.put("json_array_insert", ""); // JSON_ARRAY_INSERT(json_doc, val) 插入JSON数组 + SQL_FUNCTION_MAP.put("json_contains", ""); // JSON_CONTAINS(json_doc, val) JSON文档是否在路径中包含特定对象 + SQL_FUNCTION_MAP.put("json_contains_path", ""); // JSON_CONTAINS_PATH(json_doc, path) JSON文档是否在路径中包含任何数据 + SQL_FUNCTION_MAP.put("json_depth", ""); // JSON_DEPTH(json_doc) JSON文档的最大深度 + SQL_FUNCTION_MAP.put("json_extract", ""); // JSON_EXTRACT(json_doc, path) 从JSON文档返回数据 + SQL_FUNCTION_MAP.put("json_insert", ""); // JSON_INSERT(json_doc, val) 将数据插入JSON文档 + SQL_FUNCTION_MAP.put("json_keys", ""); // JSON_KEYS(json_doc[, path]) JSON文档中的键数组 + SQL_FUNCTION_MAP.put("json_length", ""); // JSON_LENGTH(json_doc) JSON文档中的元素数 + SQL_FUNCTION_MAP.put("json_merge", ""); // JSON_MERGE(json_doc1, json_doc2) (已弃用) 合并JSON文档,保留重复的键。JSON_MERGE_PRESERVE()的已弃用同义词 + SQL_FUNCTION_MAP.put("json_merge_patch", ""); // JSON_MERGE_PATCH(json_doc1, json_doc2) 合并JSON文档,替换重复键的值 + SQL_FUNCTION_MAP.put("json_merge_preserve", ""); // JSON_MERGE_PRESERVE(json_doc1, json_doc2) 合并JSON文档,保留重复的键 + SQL_FUNCTION_MAP.put("json_object", ""); // JSON_OBJECT(key1, val1, key2, val2...) 创建JSON对象 + SQL_FUNCTION_MAP.put("json_overlaps", ""); // JSON_OVERLAPS(json_doc1, json_doc2) (引入8.0.17) 比较两个JSON文档,如果它们具有相同的键值对或数组元素,则返回TRUE(1),否则返回FALSE(0) + SQL_FUNCTION_MAP.put("json_pretty", ""); // JSON_PRETTY(json_doc) 以易于阅读的格式打印JSON文档 + SQL_FUNCTION_MAP.put("json_quote", ""); // JSON_QUOTE(json_doc1) 引用JSON文档 + SQL_FUNCTION_MAP.put("json_remove", ""); // JSON_REMOVE(json_doc1, path) 从JSON文档中删除数据 + SQL_FUNCTION_MAP.put("json_replace", ""); // JSON_REPLACE(json_doc1, val1, val2) 替换JSON文档中的值 + SQL_FUNCTION_MAP.put("json_schema_valid", ""); // JSON_SCHEMA_VALID(json_doc) (引入8.0.17) 根据JSON模式验证JSON文档;如果文档针对架构进行验证,则返回TRUE / 1;否则,则返回FALSE / 0 + SQL_FUNCTION_MAP.put("json_schema_validation_report", ""); // JSON_SCHEMA_VALIDATION_REPORT(json_doc, mode) (引入8.0.17) 根据JSON模式验证JSON文档;以JSON格式返回有关验证结果的报告,包括成功或失败以及失败原因 + SQL_FUNCTION_MAP.put("json_search", ""); // JSON_SEARCH(json_doc, val) JSON文档中值的路径 + SQL_FUNCTION_MAP.put("json_set", ""); // JSON_SET(json_doc, val) 将数据插入JSON文档 + // SQL_FUNCTION_MAP.put("json_storage_free", ""); // JSON_STORAGE_FREE() 部分更新后,JSON列值的二进制表示形式中的可用空间 + // SQL_FUNCTION_MAP.put("json_storage_size", ""); // JSON_STORAGE_SIZE() 用于存储JSON文档的二进制表示的空间 + SQL_FUNCTION_MAP.put("json_table", ""); // JSON_TABLE() 从JSON表达式返回数据作为关系表 + SQL_FUNCTION_MAP.put("json_type", ""); // JSON_TYPE(json_doc) JSON值类型 + SQL_FUNCTION_MAP.put("json_unquote", ""); // JSON_UNQUOTE(json_doc) 取消引用JSON值 + SQL_FUNCTION_MAP.put("json_valid", ""); // JSON_VALID(json_doc) JSON值是否有效 + SQL_FUNCTION_MAP.put("json_arrayagg", ""); // JSON_ARRAYAGG(key) 将每个表达式转换为 JSON 值,然后返回一个包含这些 JSON 值的 JSON 数组 + SQL_FUNCTION_MAP.put("json_objectagg", ""); // JSON_OBJECTAGG(key, val)) 将每个表达式转换为 JSON 值,然后返回一个包含这些 JSON 值的 JSON 对象 + + // MySQL 高级函数 + // SQL_FUNCTION_MAP.put("bin", ""); // BIN(x) 返回 x 的二进制编码 + // SQL_FUNCTION_MAP.put("binary", ""); // BINARY(s) 将字符串 s 转换为二进制字符串 + SQL_FUNCTION_MAP.put("case", ""); // CASE 表示函数开始,END 表示函数结束。如果 condition1 成立,则返回 result1, 如果 condition2 成立,则返回 result2,当全部不成立则返回 result,而当有一个成立之后,后面的就不执行了。 + SQL_FUNCTION_MAP.put("cast", ""); // CAST(x AS type) 转换数据类型 + SQL_FUNCTION_MAP.put("coalesce", ""); // COALESCE(expr1, expr2, ...., expr_n) 返回参数中的第一个非空表达式(从左向右) + // SQL_FUNCTION_MAP.put("conv", ""); // CONV(x,f1,f2) 返回 f1 进制数变成 f2 进制数 + // SQL_FUNCTION_MAP.put("convert", ""); // CONVERT(s, cs) 函数将字符串 s 的字符集变成 cs + SQL_FUNCTION_MAP.put("if", ""); // IF(expr,v1,v2) 如果表达式 expr 成立,返回结果 v1;否则,返回结果 v2。 + SQL_FUNCTION_MAP.put("ifnull", ""); // IFNULL(v1,v2) 如果 v1 的值不为 NULL,则返回 v1,否则返回 v2。 + SQL_FUNCTION_MAP.put("isnull", ""); // ISNULL(expression) 判断表达式是否为 NULL + SQL_FUNCTION_MAP.put("nullif", ""); // NULLIF(expr1, expr2) 比较两个字符串,如果字符串 expr1 与 expr2 相等 返回 NULL,否则返回 expr1 + SQL_FUNCTION_MAP.put("group_concat", ""); // GROUP_CONCAT([DISTINCT], s1, s2...) + + } + + + @Override + public boolean limitSQLCount() { + return Log.DEBUG == false || AbstractVerifier.SYSTEM_ACCESS_MAP.containsKey(getTable()) == false; + } + + @NotNull + @Override + public String getIdKey() { + return KEY_ID; + } + @NotNull + @Override + public String getUserIdKey() { + return KEY_USER_ID; + } + + + private Object id; //Table的id + private RequestMethod method; //操作方法 + private boolean prepared = true; //预编译 + private boolean main = true; + /** + * TODO 被关联的表通过就忽略关联的表?(这个不行 User:{"sex@":"/Comment/toId"}) + */ + private RequestRole role; //发送请求的用户的角色 + private boolean distinct = false; + private String database; //表所在的数据库类型 + private String schema; //表所在的数据库名 + private String datasource; //数据源 + private String table; //表名 + private String alias; //表别名 + private String group; //分组方式的字符串数组,','分隔 + private String having; //聚合函数的字符串数组,','分隔 + private String order; //排序方式的字符串数组,','分隔 + private List raw; //需要保留原始 SQL 的字段,','分隔 + private List json; //需要转为 JSON 的字段,','分隔 + private Subquery from; //子查询临时表 + private List column; //表内字段名(或函数名,仅查询操作可用)的字符串数组,','分隔 + private List> values; //对应表内字段的值的字符串数组,','分隔 + private Map content; //Request内容,key:value形式,column = content.keySet(),values = content.values() + private Map where; //筛选条件,key:value形式 + private Map> combine; //条件组合,{ "&":[key], "|":[key], "!":[key] } + + + //array item <<<<<<<<<< + private int count; //Table数量 + private int page; //Table所在页码 + private int position; //Table在[]中的位置 + private int query; //JSONRequest.query + private int type; //ObjectParser.type + private int cache; + private boolean explain; + + private List joinList; //连表 配置列表 + //array item >>>>>>>>>> + private boolean test; //测试 + + private String procedure; + + public SQLConfig setProcedure(String procedure) { + this.procedure = procedure; + return this; + } + public String getProcedure() { + return procedure; + } + + public AbstractSQLConfig(RequestMethod method) { + setMethod(method); + } + public AbstractSQLConfig(RequestMethod method, String table) { + this(method); + setTable(table); + } + public AbstractSQLConfig(RequestMethod method, int count, int page) { + this(method); + setCount(count); + setPage(page); + } + + @NotNull + @Override + public RequestMethod getMethod() { + if (method == null) { + method = GET; + } + return method; + } + @Override + public AbstractSQLConfig setMethod(RequestMethod method) { + this.method = method; + return this; + } + @Override + public boolean isPrepared() { + return prepared; + } + @Override + public AbstractSQLConfig setPrepared(boolean prepared) { + this.prepared = prepared; + return this; + } + @Override + public boolean isMain() { + return main; + } + @Override + public AbstractSQLConfig setMain(boolean main) { + this.main = main; + return this; + } + + + @Override + public Object getId() { + return id; + } + @Override + public AbstractSQLConfig setId(Object id) { + this.id = id; + return this; + } + + @Override + public RequestRole getRole() { + //不能 @NotNull , AbstractParser#getSQLObject 内当getRole() == null时填充默认值 + return role; + } + public AbstractSQLConfig setRole(String roleName) throws Exception { + return setRole(RequestRole.get(roleName)); + } + @Override + public AbstractSQLConfig setRole(RequestRole role) { + this.role = role; + return this; + } + + @Override + public boolean isDistinct() { + return distinct; + } + @Override + public SQLConfig setDistinct(boolean distinct) { + this.distinct = distinct; + return this; + } + + @Override + public String getDatabase() { + return database; + } + @Override + public SQLConfig setDatabase(String database) { + this.database = database; + return this; + } + /** + * @return db == null ? DEFAULT_DATABASE : db + */ + @NotNull + public String getSQLDatabase() { + String db = getDatabase(); + return db == null ? DEFAULT_DATABASE : db; // "" 表示已设置,不需要用全局默认的 StringUtil.isEmpty(db, false)) { + } + + @Override + public boolean isMySQL() { + return isMySQL(getSQLDatabase()); + } + public static boolean isMySQL(String db) { + return DATABASE_MYSQL.equals(db); + } + @Override + public boolean isPostgreSQL() { + return isPostgreSQL(getSQLDatabase()); + } + public static boolean isPostgreSQL(String db) { + return DATABASE_POSTGRESQL.equals(db); + } + @Override + public boolean isSQLServer() { + return isSQLServer(getSQLDatabase()); + } + public static boolean isSQLServer(String db) { + return DATABASE_SQLSERVER.equals(db); + } + @Override + public boolean isOracle() { + return isOracle(getSQLDatabase()); + } + public static boolean isOracle(String db) { + return DATABASE_ORACLE.equals(db); + } + @Override + public boolean isDb2() { + return isDb2(getSQLDatabase()); + } + public static boolean isDb2(String db) { + return DATABASE_DB2.equals(db); + } + + @Override + public String getQuote() { + return isMySQL() ? "`" : "\""; + } + + @Override + public String getSchema() { + return schema; + } + /** + * @param sqlTable + * @return + */ + @NotNull + public String getSQLSchema() { + String table = getTable(); + //强制,避免因为全局默认的 @schema 自动填充进来,导致这几个类的 schema 为 sys 等其它值 + if (Table.TAG.equals(table) || Column.TAG.equals(table)) { + return SCHEMA_INFORMATION; //MySQL, PostgreSQL, SQL Server 都有的 + } + if (PgClass.TAG.equals(table) || PgAttribute.TAG.equals(table)) { + return ""; //PostgreSQL 的 pg_class 和 pg_attribute 表好像不属于任何 Schema + } + if (SysTable.TAG.equals(table) || SysColumn.TAG.equals(table) || ExtendedProperty.TAG.equals(table)) { + return SCHEMA_SYS; //SQL Server 在 sys 中的属性比 information_schema 中的要全,能拿到注释 + } + + String sch = getSchema(); + return sch == null ? DEFAULT_SCHEMA : sch; + } + @Override + public AbstractSQLConfig setSchema(String schema) { + if (schema != null) { + String quote = getQuote(); + String s = schema.startsWith(quote) && schema.endsWith(quote) ? schema.substring(1, schema.length() - 1) : schema; + if (StringUtil.isEmpty(s, true) == false && StringUtil.isName(s) == false) { + throw new IllegalArgumentException("@schema:value 中value必须是1个单词!"); + } + } + this.schema = schema; + return this; + } + + @Override + public String getDatasource() { + return datasource; + } + @Override + public SQLConfig setDatasource(String datasource) { + this.datasource = datasource; + return this; + } + + /**请求传进来的Table名 + * @return + * @see {@link #getSQLTable()} + */ + @Override + public String getTable() { + return table; + } + /**数据库里的真实Table名 + * 通过 {@link #TABLE_KEY_MAP} 映射 + * @return + */ + @JSONField(serialize = false) + @Override + public String getSQLTable() { + // String t = TABLE_KEY_MAP.containsKey(table) ? TABLE_KEY_MAP.get(table) : table; + //如果要强制小写,则可在子类重写这个方法再 toLowerCase return DATABASE_POSTGRESQL.equals(getDatabase()) ? t.toLowerCase() : t; + return TABLE_KEY_MAP.containsKey(table) ? TABLE_KEY_MAP.get(table) : table; + } + @JSONField(serialize = false) + @Override + public String getTablePath() { + String q = getQuote(); + + String sch = getSQLSchema(); + String sqlTable = getSQLTable(); + + return (StringUtil.isEmpty(sch, true) ? "" : q + sch + q + ".") + q + sqlTable + q + ( isKeyPrefix() ? " AS " + getAliasWithQuote() : ""); + } + @Override + public AbstractSQLConfig setTable(String table) { //Table已经在Parser中校验,所以这里不用防SQL注入 + this.table = table; + return this; + } + + @Override + public String getAlias() { + return alias; + } + @Override + public AbstractSQLConfig setAlias(String alias) { + this.alias = alias; + return this; + } + public String getAliasWithQuote() { + String a = getAlias(); + if (StringUtil.isEmpty(a, true)) { + a = getTable(); + } + String q = getQuote(); + //getTable 不能小写,因为Verifier用大小写敏感的名称判断权限 + //如果要强制小写,则可在子类重写这个方法再 toLowerCase return q + (DATABASE_POSTGRESQL.equals(getDatabase()) ? a.toLowerCase() : a) + q; + return q + a + q; + } + + @Override + public String getGroup() { + return group; + } + public AbstractSQLConfig setGroup(String... keys) { + return setGroup(StringUtil.getString(keys)); + } + @Override + public AbstractSQLConfig setGroup(String group) { + this.group = group; + return this; + } + @JSONField(serialize = false) + public String getGroupString(boolean hasPrefix) { + //加上子表的 group + String joinGroup = ""; + if (joinList != null) { + SQLConfig cfg; + String c; + boolean first = true; + for (Join j : joinList) { + if (j.isAppJoin()) { + continue; + } + + cfg = j.isLeftOrRightJoin() ? j.getOuterConfig() : j.getJoinConfig(); + if (StringUtil.isEmpty(cfg.getAlias(), true)) { + cfg.setAlias(cfg.getTable()); + } + + c = ((AbstractSQLConfig) cfg).getGroupString(false); + if (StringUtil.isEmpty(c, true) == false) { + joinGroup += (first ? "" : ", ") + c; + first = false; + } + + } + } + + + group = StringUtil.getTrimedString(group); + String[] keys = StringUtil.split(group); + if (keys == null || keys.length <= 0) { + return StringUtil.isEmpty(joinGroup, true) ? "" : (hasPrefix ? " GROUP BY " : "") + joinGroup; + } + + for (int i = 0; i < keys.length; i++) { + if (isPrepared()) { //不能通过 ? 来代替,因为SQLExecutor statement.setString后 GROUP BY 'userId' 有单引号,只能返回一条数据,必须去掉单引号才行! + if (StringUtil.isName(keys[i]) == false) { + throw new IllegalArgumentException("@group:value 中 value里面用 , 分割的每一项都必须是1个单词!并且不要有空格!"); + } + } + + keys[i] = getKey(keys[i]); + } + + return (hasPrefix ? " GROUP BY " : "") + StringUtil.concat(StringUtil.getString(keys), joinGroup, ", "); + } + + @Override + public String getHaving() { + return having; + } + public AbstractSQLConfig setHaving(String... conditions) { + return setHaving(StringUtil.getString(conditions)); + } + @Override + public AbstractSQLConfig setHaving(String having) { + this.having = having; + return this; + } + /**TODO @having 改为默认 | 或连接,且支持 @having: { "key1>": 1, "key{}": "length(key2)>0", "@combine": "key1,key2" } + * @return HAVING conditoin0 AND condition1 OR condition2 ... + */ + @JSONField(serialize = false) + public String getHavingString(boolean hasPrefix) { + //加上子表的 having + String joinHaving = ""; + if (joinList != null) { + SQLConfig cfg; + String c; + boolean first = true; + for (Join j : joinList) { + if (j.isAppJoin()) { + continue; + } + + cfg = j.isLeftOrRightJoin() ? j.getOuterConfig() : j.getJoinConfig(); + if (StringUtil.isEmpty(cfg.getAlias(), true)) { + cfg.setAlias(cfg.getTable()); + } + + c = ((AbstractSQLConfig) cfg).getHavingString(false); + if (StringUtil.isEmpty(c, true) == false) { + joinHaving += (first ? "" : ", ") + c; + first = false; + } + + } + } + + String[] keys = StringUtil.split(getHaving(), ";"); + if (keys == null || keys.length <= 0) { + return StringUtil.isEmpty(joinHaving, true) ? "" : (hasPrefix ? " HAVING " : "") + joinHaving; + } + + String quote = getQuote(); + String tableAlias = getAliasWithQuote(); + + List raw = getRaw(); + boolean containRaw = raw != null && raw.contains(KEY_HAVING); + + String expression; + String method; + //暂时不允许 String prefix; + String suffix; + + //fun0(arg0,arg1,...);fun1(arg0,arg1,...) + for (int i = 0; i < keys.length; i++) { + + //fun(arg0,arg1,...) + expression = keys[i]; + if (containRaw) { + try { + String rawSQL = getRawSQL(KEY_HAVING, expression); + if (rawSQL != null) { + keys[i] = rawSQL; + continue; + } + } catch (Exception e) { + Log.e(TAG, "newSQLConfig rawColumnSQL == null >> try { " + + " String rawSQL = ((AbstractSQLConfig) config).getRawSQL(KEY_COLUMN, fk); ... " + + "} catch (Exception e) = " + e.getMessage()); + } + } + + if (expression.length() > 50) { + throw new UnsupportedOperationException("@having:value 的 value 中字符串 " + expression + " 不合法!" + + "不允许传超过 50 个字符的函数或表达式!请用 @raw 简化传参!"); + } + + int start = expression.indexOf("("); + if (start < 0) { + if (isPrepared() && PATTERN_FUNCTION.matcher(expression).matches() == false) { + throw new UnsupportedOperationException("字符串 " + expression + " 不合法!" + + "预编译模式下 @having:\"column?value;function(arg0,arg1,...)?value...\"" + + " 中 column?value 必须符合正则表达式 " + PATTERN_FUNCTION + " 且不包含连续减号 -- !不允许空格!"); + } + continue; + } + + int end = expression.lastIndexOf(")"); + if (start >= end) { + throw new IllegalArgumentException("字符 " + expression + " 不合法!" + + "@having:value 中 value 里的 SQL函数必须为 function(arg0,arg1,...) 这种格式!"); + } + + method = expression.substring(0, start); + if (method.isEmpty() == false) { + if (SQL_FUNCTION_MAP == null || SQL_FUNCTION_MAP.isEmpty()) { + if (StringUtil.isName(method) == false) { + throw new IllegalArgumentException("字符 " + method + " 不合法!" + + "预编译模式下 @having:\"column?value;function(arg0,arg1,...)?value...\"" + + " 中 function 必须符合小写英文单词的 SQL 函数名格式!"); + } + } + else if (SQL_FUNCTION_MAP.containsKey(method) == false) { + throw new IllegalArgumentException("字符 " + method + " 不合法!" + + "预编译模式下 @column:\"column0,column1:alias;function0(arg0,arg1,...);function1(...):alias...\"" + + " 中 function 必须符合小写英文单词的 SQL 函数名格式!且必须是后端允许调用的 SQL 函数!"); + } + } + + suffix = expression.substring(end + 1, expression.length()); + + if (isPrepared() && (((String) suffix).contains("--") || ((String) suffix).contains("/*") || PATTERN_RANGE.matcher((String) suffix).matches() == false)) { + throw new UnsupportedOperationException("字符串 " + suffix + " 不合法!" + + "预编译模式下 @having:\"column?value;function(arg0,arg1,...)?value...\"" + + " 中 ?value 必须符合正则表达式 " + PATTERN_RANGE + " 且不包含连续减号 -- 或注释符 /* !不允许多余的空格!"); + } + + String[] ckeys = StringUtil.split(expression.substring(start + 1, end)); + + if (ckeys != null) { + for (int j = 0; j < ckeys.length; j++) { + String origin = ckeys[j]; + + if (isPrepared()) { + if (origin.startsWith("_") || origin.contains("--") || PATTERN_FUNCTION.matcher(origin).matches() == false) { + throw new IllegalArgumentException("字符 " + ckeys[j] + " 不合法!" + + "预编译模式下 @having:\"column?value;function(arg0,arg1,...)?value...\"" + + " 中所有 column, arg 都必须是1个不以 _ 开头的单词 或者 符合正则表达式 " + PATTERN_FUNCTION + " 且不包含连续减号 -- !不允许多余的空格!"); + } + } + + //JOIN 副表不再在外层加副表名前缀 userId AS `Commet.userId`, 而是直接 userId AS `userId` + boolean isName = false; + if (StringUtil.isNumer(origin)) { + //do nothing + } + else if (StringUtil.isName(origin)) { + origin = quote + origin + quote; + isName = true; + } + else { + origin = getValue(origin).toString(); + } + + ckeys[j] = (isName && isKeyPrefix() ? tableAlias + "." : "") + origin; + } + } + + keys[i] = method + "(" + StringUtil.getString(ckeys) + ")" + suffix; + } + + //TODO 支持 OR, NOT 参考 @combine:"&key0,|key1,!key2" + return (hasPrefix ? " HAVING " : "") + StringUtil.concat(StringUtil.getString(keys, AND), joinHaving, AND); + } + + @Override + public String getOrder() { + return order; + } + public AbstractSQLConfig setOrder(String... conditions) { + return setOrder(StringUtil.getString(conditions)); + } + @Override + public AbstractSQLConfig setOrder(String order) { + this.order = order; + return this; + } + @JSONField(serialize = false) + public String getOrderString(boolean hasPrefix) { + //加上子表的 order + String joinOrder = ""; + if (joinList != null) { + SQLConfig cfg; + String c; + boolean first = true; + for (Join j : joinList) { + if (j.isAppJoin()) { + continue; + } + + cfg = j.isLeftOrRightJoin() ? j.getOuterConfig() : j.getJoinConfig(); + if (StringUtil.isEmpty(cfg.getAlias(), true)) { + cfg.setAlias(cfg.getTable()); + } + + c = ((AbstractSQLConfig) cfg).getOrderString(false); + if (StringUtil.isEmpty(c, true) == false) { + joinOrder += (first ? "" : ", ") + c; + first = false; + } + + } + } + + + String order = StringUtil.getTrimedString(getOrder()); + // SELECT * FROM sys.Moment ORDER BY userId ASC, rand(); 前面的 userId ASC 和后面的 rand() 都有效 + // if ("rand()".equals(order)) { + // return (hasPrefix ? " ORDER BY " : "") + StringUtil.concat(order, joinOrder, ", "); + // } + + if (getCount() > 0 && (isOracle() || isSQLServer() || isDb2())) { // Oracle, SQL Server, DB2 的 OFFSET 必须加 ORDER BY + + // String[] ss = StringUtil.split(order); + if (StringUtil.isEmpty(order, true)) { //SQL Server 子查询内必须指定 OFFSET 才能用 ORDER BY + String idKey = getIdKey(); + if (StringUtil.isEmpty(idKey, true)) { + idKey = "id"; //ORDER BY NULL 不行,SQL Server 会报错,必须要有排序,才能使用 OFFSET FETCH,如果没有 idKey,请求中指定 @order 即可 + } + order = idKey; //让数据库调控默认升序还是降序 + "+"; + } + + //不用这么全面,毕竟没有语法问题还浪费性能,如果有其它问题,让前端传的 JSON 直接加上 @order 来解决 + // boolean contains = false; + // if (ss != null) { + // for (String s : ss) { + // if (s != null && s.startsWith(idKey)) { + // s = s.substring(idKey.length()); + // if ("+".equals(s) || "-".equals(s)) {// || " ASC ".equals(s) || " DESC ".equals(s)) { + // contains = true; + // break; + // } + // } + // } + // } + + // if (contains == false) { + // order = (ss == null || ss.length <= 0 ? "" : order + ",") + idKey + "+"; + // } + } + + + String[] keys = StringUtil.split(order); + if (keys == null || keys.length <= 0) { + return StringUtil.isEmpty(joinOrder, true) ? "" : (hasPrefix ? " ORDER BY " : "") + joinOrder; + } + + for (int i = 0; i < keys.length; i++) { + String item = keys[i]; + if ("rand()".equals(item)) { + continue; + } + + int index = item.endsWith("+") ? item.length() - 1 : -1; //StringUtil.split返回数组中,子项不会有null + String sort; + if (index < 0) { + index = item.endsWith("-") ? item.length() - 1 : -1; + sort = index <= 0 ? "" : " DESC "; + } + else { + sort = " ASC "; + } + + String origin = index < 0 ? item : item.substring(0, index); + + if (isPrepared()) { //不能通过 ? 来代替,SELECT 'id','name' 返回的就是 id:"id", name:"name",而不是数据库里的值! + //这里既不对origin trim,也不对 ASC/DESC ignoreCase,希望前端严格传没有任何空格的字符串过来,减少传输数据量,节约服务器性能 + if (StringUtil.isName(origin) == false) { + throw new IllegalArgumentException("预编译模式下 @order:value 中 " + item + " 不合法! value 里面用 , 分割的" + + "每一项必须是 随机函数 rand() 或 column+ / column- 且其中 column 必须是 1 个单词!并且不要有多余的空格!"); + } + } + + keys[i] = getKey(origin) + sort; + } + + return (hasPrefix ? " ORDER BY " : "") + StringUtil.concat(StringUtil.getString(keys), joinOrder, ", "); + } + + @Override + public List getRaw() { + return raw; + } + @Override + public SQLConfig setRaw(List raw) { + this.raw = raw; + return this; + } + + /**获取原始 SQL 片段 + * @param key + * @param value + * @return + * @throws Exception + */ + @Override + public String getRawSQL(String key, Object value) throws Exception { + List rawList = getRaw(); + boolean containRaw = rawList != null && rawList.contains(key); + if (containRaw && value instanceof String == false) { + throw new UnsupportedOperationException("@raw:value 的 value 中 " + key + " 不合法!" + + "对应的 " + key + ":value 中 value 类型只能为 String!"); + } + + String rawSQL = containRaw ? RAW_MAP.get(value) : null; + if (containRaw) { + if (rawSQL == null) { + throw new UnsupportedOperationException("@raw:value 的 value 中 " + key + " 不合法!" + + "对应的 " + key + ":value 中 value 值 " + value + " 未在后端 RAW_MAP 中配置 !"); + } + + if ("".equals(rawSQL)) { + return (String) value; + } + } + + return rawSQL; + } + + + @Override + public List getJson() { + return json; + } + @Override + public AbstractSQLConfig setJson(List json) { + this.json = json; + return this; + } + + + @Override + public Subquery getFrom() { + return from; + } + @Override + public AbstractSQLConfig setFrom(Subquery from) { + this.from = from; + return this; + } + + @Override + public List getColumn() { + return column; + } + @Override + public AbstractSQLConfig setColumn(List column) { + this.column = column; + return this; + } + @JSONField(serialize = false) + public String getColumnString() throws Exception { + return getColumnString(false); + } + @JSONField(serialize = false) + public String getColumnString(boolean inSQLJoin) throws Exception { + List column = getColumn(); + + switch (getMethod()) { + case HEAD: + case HEADS: //StringUtil.isEmpty(column, true) || column.contains(",") 时SQL.count(column)会return "*" + if (isPrepared() && column != null) { + + List raw = getRaw(); + boolean containRaw = raw != null && raw.contains(KEY_COLUMN); + + String origin; + String alias; + int index; + + for (String c : column) { + if (containRaw) { + // 由于 HashMap 对 key 做了 hash 处理,所以 get 比 containsValue 更快 + if ("".equals(RAW_MAP.get(c)) || RAW_MAP.containsValue(c)) { // newSQLConfig 提前处理好的 + //排除@raw中的值,以避免使用date_format(date,'%Y-%m-%d %H:%i:%s') 时,冒号的解析出错 + column.remove(c); + continue; + } + } + + index = c.lastIndexOf(":"); //StringUtil.split返回数组中,子项不会有null + origin = index < 0 ? c : c.substring(0, index); + alias = index < 0 ? null : c.substring(index + 1); + + if (alias != null && StringUtil.isName(alias) == false) { + throw new IllegalArgumentException("HEAD请求: 字符 " + alias + " 不合法!预编译模式下 @column:value 中 value里面用 , 分割的每一项" + + " column:alias 中 column 必须是1个单词!如果有alias,则alias也必须为1个单词!并且不要有多余的空格!"); + } + + if (StringUtil.isName(origin) == false) { + int start = origin.indexOf("("); + if (start < 0 || origin.lastIndexOf(")") <= start) { + throw new IllegalArgumentException("HEAD请求: 字符" + origin + " 不合法!预编译模式下 @column:value 中 value里面用 , 分割的每一项" + + " column:alias 中 column 必须是1个单词!如果有alias,则alias也必须为1个单词!并且不要有多余的空格!"); + } + + if (start > 0 && StringUtil.isName(origin.substring(0, start)) == false) { + throw new IllegalArgumentException("HEAD请求: 字符 " + origin.substring(0, start) + " 不合法!预编译模式下 @column:value 中 value里面用 , 分割的每一项" + + " column:alias 中 column 必须是1个单词!如果有alias,则alias也必须为1个单词!并且不要有多余的空格!"); + } + } + } + } + + return SQL.count(column != null && column.size() == 1 && StringUtil.isName(column.get(0)) ? getKey(column.get(0)) : "*"); + case POST: + if (column == null || column.isEmpty()) { + throw new IllegalArgumentException("POST 请求必须在Table内设置要保存的 key:value !"); + } + + String s = ""; + boolean pfirst = true; + for (String c : column) { + if (isPrepared() && StringUtil.isName(c) == false) { //不能通过 ? 来代替,SELECT 'id','name' 返回的就是 id:"id", name:"name",而不是数据库里的值! + throw new IllegalArgumentException("POST请求: 每一个 key:value 中的key都必须是1个单词!"); + } + s += ((pfirst ? "" : ",") + getKey(c)); + + pfirst = false; + } + + return "(" + s + ")"; + case GET: + case GETS: + boolean isQuery = RequestMethod.isQueryMethod(method); //TODO 这个有啥用?上面应是 getMethod 的值 GET 和 GETS 了。 + String joinColumn = ""; + if (isQuery && joinList != null) { + SQLConfig ecfg; + SQLConfig cfg; + String c; + boolean first = true; + for (Join j : joinList) { + if (j.isAppJoin()) { + continue; + } + + ecfg = j.getOuterConfig(); + if (ecfg != null && ecfg.getColumn() != null) { //优先级更高 + cfg = ecfg; + } + else { + cfg = j.getJoinConfig(); + } + + if (StringUtil.isEmpty(cfg.getAlias(), true)) { + cfg.setAlias(cfg.getTable()); + } + + c = ((AbstractSQLConfig) cfg).getColumnString(true); + if (StringUtil.isEmpty(c, true) == false) { + joinColumn += (first ? "" : ", ") + c; + first = false; + } + + inSQLJoin = true; + } + } + + String tableAlias = getAliasWithQuote(); + + // String c = StringUtil.getString(column); //id,name;json_length(contactIdList):contactCount;... + + String[] keys = column == null ? null : column.toArray(new String[]{}); //StringUtil.split(c, ";"); + if (keys == null || keys.length <= 0) { + + boolean noColumn = column != null && inSQLJoin; + String mc = isKeyPrefix() == false ? (noColumn ? "" : "*") : (noColumn ? "" : tableAlias + ".*"); + + return StringUtil.concat(mc, joinColumn, ", ", true); + } + + + List raw = getRaw(); + boolean containRaw = raw != null && raw.contains(KEY_COLUMN); + + String expression; + String method = null; + + //...;fun0(arg0,arg1,...):fun0;fun1(arg0,arg1,...):fun1;... + for (int i = 0; i < keys.length; i++) { + + //fun(arg0,arg1,...) + expression = keys[i]; + + if (containRaw) { // 由于 HashMap 对 key 做了 hash 处理,所以 get 比 containsValue 更快 + if ("".equals(RAW_MAP.get(expression)) || RAW_MAP.containsValue(expression)) { // newSQLConfig 提前处理好的 + continue; + } + + // 简单点, 后台配置就带上 AS + // int index = expression.lastIndexOf(":"); + // String alias = expression.substring(index+1); + // boolean hasAlias = StringUtil.isName(alias); + // String pre = index > 0 && hasAlias ? expression.substring(0, index) : expression; + // if (RAW_MAP.containsValue(pre) || "".equals(RAW_MAP.get(pre))) { // newSQLConfig 提前处理好的 + // expression = pre + (hasAlias ? " AS " + alias : ""); + // continue; + // } + } + + if (expression.length() > 50) { + throw new UnsupportedOperationException("@column:value 的 value 中字符串 " + expression + " 不合法!" + + "不允许传超过 50 个字符的函数或表达式!请用 @raw 简化传参!"); + } + + + int start = expression.indexOf("("); + int end = 0; + if (start >= 0) { + end = expression.lastIndexOf(")"); + if (start >= end) { + throw new IllegalArgumentException("字符 " + expression + " 不合法!" + + "@column:value 中 value 里的 SQL函数必须为 function(arg0,arg1,...) 这种格式!"); + } + + method = expression.substring(0, start); + boolean distinct = i <= 0 && method.startsWith(PREFFIX_DISTINCT); + String fun = distinct ? method.substring(PREFFIX_DISTINCT.length()) : method; + + if (fun.isEmpty() == false) { + if (SQL_FUNCTION_MAP == null || SQL_FUNCTION_MAP.isEmpty()) { + if (StringUtil.isName(fun) == false) { + throw new IllegalArgumentException("字符 " + method + " 不合法!" + + "预编译模式下 @column:\"column0,column1:alias;function0(arg0,arg1,...);function1(...):alias...\"" + + " 中 function 必须符合小写英文单词的 SQL 函数名格式!"); + } + } + else if (SQL_FUNCTION_MAP.containsKey(fun) == false) { + throw new IllegalArgumentException("字符 " + method + " 不合法!" + + "预编译模式下 @column:\"column0,column1:alias;function0(arg0,arg1,...);function1(...):alias...\"" + + " 中 function 必须符合小写英文单词的 SQL 函数名格式!且必须是后端允许调用的 SQL 函数!"); + } + } + + } + + boolean isColumn = start < 0; + + String[] ckeys = StringUtil.split(isColumn ? expression : expression.substring(start + 1, end)); + String quote = getQuote(); + + // if (isPrepared()) { //不能通过 ? 来代替,SELECT 'id','name' 返回的就是 id:"id", name:"name",而不是数据库里的值! + if (ckeys != null && ckeys.length > 0) { + + boolean distinct; + String origin; + String alias; + int index; + for (int j = 0; j < ckeys.length; j++) { + index = isColumn ? ckeys[j].lastIndexOf(":") : -1; //StringUtil.split返回数组中,子项不会有null + origin = index < 0 ? ckeys[j] : ckeys[j].substring(0, index); + alias = index < 0 ? null : ckeys[j].substring(index + 1); + + distinct = j <= 0 && origin.startsWith(PREFFIX_DISTINCT); + if (distinct) { + origin = origin.substring(PREFFIX_DISTINCT.length()); + } + + if (isPrepared()) { + if (isColumn) { + if (StringUtil.isName(origin) == false || (alias != null && StringUtil.isName(alias) == false)) { + throw new IllegalArgumentException("字符 " + ckeys[j] + " 不合法!" + + "预编译模式下 @column:value 中 value里面用 , 分割的每一项" + + " column:alias 中 column 必须是1个单词!如果有alias,则alias也必须为1个单词!" + + "DISTINCT 必须全大写,且后面必须有且只有 1 个空格!其它情况不允许空格!"); + } + } + else { + // if ((StringUtil.isName(origin) == false || origin.startsWith("_"))) { + if (origin.startsWith("_") || origin.contains("--") || PATTERN_FUNCTION.matcher(origin).matches() == false) { + throw new IllegalArgumentException("字符 " + ckeys[j] + " 不合法!" + + "预编译模式下 @column:\"column0,column1:alias;function0(arg0,arg1,...);function1(...):alias...\"" + + " 中所有 arg 都必须是1个不以 _ 开头的单词 或者符合正则表达式 " + PATTERN_FUNCTION + " 且不包含连续减号 -- !DISTINCT 必须全大写,且后面必须有且只有 1 个空格!其它情况不允许空格!"); + } + } + } + + //JOIN 副表不再在外层加副表名前缀 userId AS `Commet.userId`, 而是直接 userId AS `userId` + boolean isName = false; + if (StringUtil.isNumer(origin)) { + //do nothing + } + else if (StringUtil.isName(origin)) { + origin = quote + origin + quote; + isName = true; + } + else { + origin = getValue(origin).toString(); + } + + if (isName && isKeyPrefix()) { + ckeys[j] = tableAlias + "." + origin; + // if (isColumn) { + // ckeys[j] += " AS " + quote + (isMain() ? "" : tableAlias + ".") + (StringUtil.isEmpty(alias, true) ? origin : alias) + quote; + // } + if (isColumn && StringUtil.isEmpty(alias, true) == false) { + ckeys[j] += " AS " + quote + alias + quote; + } + } else { + ckeys[j] = origin + (StringUtil.isEmpty(alias, true) ? "" : " AS " + quote + alias + quote); + } + + if (distinct) { + ckeys[j] = PREFFIX_DISTINCT + ckeys[j]; + } + } + // } + + } + + if (isColumn) { + keys[i] = StringUtil.getString(ckeys); + } + else { + String suffix = expression.substring(end + 1, expression.length()); //:contactCount + int index = suffix.lastIndexOf(":"); + String alias = index < 0 ? "" : suffix.substring(index + 1); //contactCount + suffix = index < 0 ? suffix : suffix.substring(0, index); + + if (alias.isEmpty() == false && StringUtil.isName(alias) == false) { + throw new IllegalArgumentException("字符串 " + alias + " 不合法!" + + "预编译模式下 @column:value 中 value里面用 ; 分割的每一项" + + " function(arg0,arg1,...):alias 中 alias 必须是1个单词!并且不要有多余的空格!"); + } + + if (suffix.isEmpty() == false && (((String) suffix).contains("--") || ((String) suffix).contains("/*") || PATTERN_RANGE.matcher((String) suffix).matches() == false)) { + throw new UnsupportedOperationException("字符串 " + suffix + " 不合法!" + + "预编译模式下 @column:\"column?value;function(arg0,arg1,...)?value...\"" + + " 中 ?value 必须符合正则表达式 " + PATTERN_RANGE + " 且不包含连续减号 -- 或注释符 /* !不允许多余的空格!"); + } + + String origin = method + "(" + StringUtil.getString(ckeys) + ")" + suffix; + // if (isKeyPrefix()) { + // keys[i] = origin + " AS " + quote + (isMain() ? "" : tableAlias + ".") + (StringUtil.isEmpty(alias, true) ? method : alias) + quote; + // } + // else { + keys[i] = origin + (StringUtil.isEmpty(alias, true) ? "" : " AS " + quote + alias + quote); + // } + } + + } + + String c = StringUtil.getString(keys); + c = c + (StringUtil.isEmpty(joinColumn, true) ? "" : ", " + joinColumn);//不能在这里改,后续还要用到: + return isMain() && isDistinct() ? PREFFIX_DISTINCT + c : c; + default: + throw new UnsupportedOperationException( + "服务器内部错误:getColumnString 不支持 " + RequestMethod.getName(getMethod()) + + " 等 [GET,GETS,HEAD,HEADS,POST] 外的ReuqestMethod!" + ); + } + } + + + @Override + public List> getValues() { + return values; + } + @JSONField(serialize = false) + public String getValuesString() { + String s = ""; + if (values != null && values.size() > 0) { + Object[] items = new Object[values.size()]; + List vs; + for (int i = 0; i < values.size(); i++) { + vs = values.get(i); + if (vs == null) { + continue; + } + + items[i] = "("; + for (int j = 0; j < vs.size(); j++) { + items[i] += ((j <= 0 ? "" : ",") + getValue(vs.get(j))); + } + items[i] += ")"; + } + s = StringUtil.getString(items); + } + return s; + } + @Override + public AbstractSQLConfig setValues(List> valuess) { + this.values = valuess; + return this; + } + + @Override + public Map getContent() { + return content; + } + @Override + public AbstractSQLConfig setContent(Map content) { + this.content = content; + return this; + } + + @Override + public int getCount() { + return count; + } + @Override + public AbstractSQLConfig setCount(int count) { + this.count = count; + return this; + } + @Override + public int getPage() { + return page; + } + @Override + public AbstractSQLConfig setPage(int page) { + this.page = page; + return this; + } + @Override + public int getPosition() { + return position; + } + @Override + public AbstractSQLConfig setPosition(int position) { + this.position = position; + return this; + } + + @Override + public int getQuery() { + return query; + } + @Override + public AbstractSQLConfig setQuery(int query) { + this.query = query; + return this; + } + @Override + public int getType() { + return type; + } + @Override + public AbstractSQLConfig setType(int type) { + this.type = type; + return this; + } + + @Override + public int getCache() { + return cache; + } + @Override + public AbstractSQLConfig setCache(int cache) { + this.cache = cache; + return this; + } + + public AbstractSQLConfig setCache(String cache) { + return setCache(getCache(cache)); + } + public static int getCache(String cache) { + int cache2; + if (cache == null) { + cache2 = JSONRequest.CACHE_ALL; + } + else { + // if (isSubquery) { + // throw new IllegalArgumentException("子查询内不支持传 " + JSONRequest.KEY_CACHE + "!"); + // } + + switch (cache) { + case "0": + case JSONRequest.CACHE_ALL_STRING: + cache2 = JSONRequest.CACHE_ALL; + break; + case "1": + case JSONRequest.CACHE_ROM_STRING: + cache2 = JSONRequest.CACHE_ROM; + break; + case "2": + case JSONRequest.CACHE_RAM_STRING: + cache2 = JSONRequest.CACHE_RAM; + break; + default: + throw new IllegalArgumentException(JSONRequest.KEY_CACHE + ":value 中 value 的值不合法!必须在 [0,1,2] 或 [ALL, ROM, RAM] 内 !"); + } + } + return cache2; + } + + @Override + public boolean isExplain() { + return explain; + } + @Override + public AbstractSQLConfig setExplain(boolean explain) { + this.explain = explain; + return this; + } + + @Override + public List getJoinList() { + return joinList; + } + @Override + public SQLConfig setJoinList(List joinList) { + this.joinList = joinList; + return this; + } + @Override + public boolean hasJoin() { + return joinList != null && joinList.isEmpty() == false; + } + + + @Override + public boolean isTest() { + return test; + } + @Override + public AbstractSQLConfig setTest(boolean test) { + this.test = test; + return this; + } + + /**获取初始位置offset + * @return + */ + @JSONField(serialize = false) + public int getOffset() { + return getOffset(getPage(), getCount()); + } + /**获取初始位置offset + * @param page + * @param count + * @return + */ + public static int getOffset(int page, int count) { + return page*count; + } + /**获取限制数量 + * @return + */ + @JSONField(serialize = false) + public String getLimitString() { + if (count <= 0 || RequestMethod.isHeadMethod(getMethod(), true)) { + return ""; + } + return getLimitString(getPage(), getCount(), isOracle() || isSQLServer() || isDb2(), isOracle()); + } + /**获取限制数量 + * @param limit + * @return + */ + public static String getLimitString(int page, int count, boolean isTSQL, boolean isOracle) { + int offset = getOffset(page, count); + + if (isTSQL) { // OFFSET FECTH 中所有关键词都不可省略, 另外 Oracle 数据库使用子查询加 where 分页 + return isOracle? " WHERE ROWNUM BETWEEN "+ offset +" AND "+ (offset + count): " OFFSET " + offset + " ROWS FETCH FIRST " + count + " ROWS ONLY"; + } + + return " LIMIT " + count + (offset <= 0 ? "" : " OFFSET " + offset); // DELETE, UPDATE 不支持 OFFSET + } + + //WHERE <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< + @Override + public Map getWhere() { + return where; + } + @Override + public AbstractSQLConfig setWhere(Map where) { + this.where = where; + return this; + } + @NotNull + @Override + public Map> getCombine() { + List andList = combine == null ? null : combine.get("&"); + if (andList == null) { + andList = where == null ? new ArrayList() : new ArrayList(where.keySet()); + if (combine == null) { + combine = new HashMap<>(); + } + combine.put("&", andList); + } + return combine; + } + @Override + public AbstractSQLConfig setCombine(Map> combine) { + this.combine = combine; + return this; + } + /** + * noFunctionChar = false + * @param key + * @return + */ + @JSONField(serialize = false) + @Override + public Object getWhere(String key) { + return getWhere(key, false); + } + //CS304 Issue link: https://github.com/Tencent/APIJSON/issues/48 + /** + * @param key - the key passed in + * @param exactMatch - whether it is exact match + * @return + *

use entrySet+getValue() to replace keySet+get() to enhance efficiency

+ */ + @JSONField(serialize = false) + @Override + public Object getWhere(String key, boolean exactMatch) { + if (exactMatch) { + return where == null ? null : where.get(key); + } + + if (key == null || where == null){ + return null; + } + synchronized (where) { + if (where != null) { + int index; + for (Entry entry : where.entrySet()) { + String k = entry.getKey(); + index = k.indexOf(key); + if (index >= 0 && StringUtil.isName(k.substring(index)) == false) { + return entry.getValue(); + } + } + } + } + return null; + } + @Override + public AbstractSQLConfig putWhere(String key, Object value, boolean prior) { + if (key != null) { + if (where == null) { + where = new LinkedHashMap(); + } + if (value == null) { + where.remove(key); + } else { + where.put(key, value); + } + + combine = getCombine(); + List andList = combine.get("&"); + if (value == null) { + if (andList != null) { + andList.remove(key); + } + } + else if (andList == null || andList.contains(key) == false) { + int i = 0; + if (andList == null) { + andList = new ArrayList<>(); + } + else if (prior && andList.isEmpty() == false) { + + String idKey = getIdKey(); + String idInKey = idKey + "{}"; + String userIdKey = getUserIdKey(); + String userIdInKey = userIdKey + "{}"; + + if (andList.contains(idKey)) { + i ++; + } + if (andList.contains(idInKey)) { + i ++; + } + if (andList.contains(userIdKey)) { + i ++; + } + if (andList.contains(userIdInKey)) { + i ++; + } + } + + if (prior) { + andList.add(i, key); //userId的优先级不能比id高 0, key); + } else { + andList.add(key); //AbstractSQLExecutor.onPutColumn里getSQL,要保证缓存的SQL和查询的SQL里 where 的 key:value 顺序一致 + } + } + combine.put("&", andList); + } + return this; + } + + /**获取WHERE + * @return + * @throws Exception + */ + @JSONField(serialize = false) + @Override + public String getWhereString(boolean hasPrefix) throws Exception { + return getWhereString(hasPrefix, getMethod(), getWhere(), getCombine(), getJoinList(), ! isTest()); + } + /**获取WHERE + * @param method + * @param where + * @return + * @throws Exception + */ + @JSONField(serialize = false) + public String getWhereString(boolean hasPrefix, RequestMethod method, Map where, Map> combine, List joinList, boolean verifyName) throws Exception { + Set>> combineSet = combine == null ? null : combine.entrySet(); + if (combineSet == null || combineSet.isEmpty()) { + Log.w(TAG, "getWhereString combineSet == null || combineSet.isEmpty() >> return \"\";"); + return ""; + } + + List keyList; + + String whereString = ""; + + boolean isCombineFirst = true; + int logic; + + boolean isItemFirst; + String c; + String cs; + + for (Entry> ce : combineSet) { + keyList = ce == null ? null : ce.getValue(); + if (keyList == null || keyList.isEmpty()) { + continue; + } + + if ("|".equals(ce.getKey())) { + logic = Logic.TYPE_OR; + } + else if ("!".equals(ce.getKey())) { + logic = Logic.TYPE_NOT; + } + else { + logic = Logic.TYPE_AND; + } + + + isItemFirst = true; + cs = ""; + for (String key : keyList) { + c = getWhereItem(key, where.get(key), method, verifyName); + + if (StringUtil.isEmpty(c, true)) {//避免SQL条件连接错误 + continue; + } + + cs += (isItemFirst ? "" : (Logic.isAnd(logic) ? AND : OR)) + "(" + c + ")"; + + isItemFirst = false; + } + + if (StringUtil.isEmpty(cs, true)) {//避免SQL条件连接错误 + continue; + } + + whereString += (isCombineFirst ? "" : AND) + (Logic.isNot(logic) ? NOT : "") + " ( " + cs + " ) "; + isCombineFirst = false; + } + + + if (joinList != null) { + + String newWs = ""; + String ws = whereString; + + List newPvl = new ArrayList<>(); + List pvl = new ArrayList<>(preparedValueList); + + SQLConfig jc; + String js; + + boolean changed = false; + //各种 JOIN 没办法统一用 & | !连接,只能按优先级,和 @combine 一样? + for (Join j : joinList) { + String jt = j.getJoinType(); + + switch (jt) { + case "*": // CROSS JOIN + case "@": // APP JOIN + case "<": // LEFT JOIN + case ">": // RIGHT JOIN + break; + + case "&": // INNER JOIN: A & B + case "": // FULL JOIN: A | B + case "|": // FULL JOIN: A | B + case "!": // OUTER JOIN: ! (A | B) + case "^": // SIDE JOIN: ! (A & B) + case "(": // ANTI JOIN: A & ! B + case ")": // FOREIGN JOIN: B & ! A + jc = j.getJoinConfig(); + boolean isMain = jc.isMain(); + jc.setMain(false).setPrepared(isPrepared()).setPreparedValueList(new ArrayList()); + js = jc.getWhereString(false); + jc.setMain(isMain); + + boolean isOuterJoin = "!".equals(jt); + boolean isSideJoin = "^".equals(jt); + boolean isAntiJoin = "(".equals(jt); + boolean isForeignJoin = ")".equals(jt); + boolean isWsEmpty = StringUtil.isEmpty(ws, true); + + if (isWsEmpty) { + if (isOuterJoin) { // ! OUTER JOIN: ! (A | B) + throw new NotExistException("no result for ! OUTER JOIN( ! (A | B) ) when A or B is empty!"); + } + if (isForeignJoin) { // ) FOREIGN JOIN: B & ! A + throw new NotExistException("no result for ) FOREIGN JOIN( B & ! A ) when A is empty!"); + } + } + + if (StringUtil.isEmpty(js, true)) { + if (isOuterJoin) { // ! OUTER JOIN: ! (A | B) + throw new NotExistException("no result for ! OUTER JOIN( ! (A | B) ) when A or B is empty!"); + } + if (isAntiJoin) { // ( ANTI JOIN: A & ! B + throw new NotExistException("no result for ( ANTI JOIN( A & ! B ) when B is empty!"); + } + + if (isWsEmpty) { + if (isSideJoin) { + throw new NotExistException("no result for ^ SIDE JOIN( ! (A & B) ) when both A and B are empty!"); + } + } + else { + if (isSideJoin || isForeignJoin) { + newWs += " ( " + getCondition(true, ws) + " ) "; + + newPvl.addAll(pvl); + newPvl.addAll(jc.getPreparedValueList()); + changed = true; + } + } + + continue; + } + + if (StringUtil.isEmpty(newWs, true) == false) { + newWs += AND; + } + + if (isAntiJoin) { // ( ANTI JOIN: A & ! B + newWs += " ( " + ( isWsEmpty ? "" : ws + AND ) + NOT + " ( " + js + " ) " + " ) "; + } + else if (isForeignJoin) { // ) FOREIGN JOIN: (! A) & B // preparedValueList.add 不好反过来 B & ! A + newWs += " ( " + NOT + " ( " + ws + " ) ) " + AND + " ( " + js + " ) "; + } + else if (isSideJoin) { // ^ SIDE JOIN: ! (A & B) + //MySQL 因为 NULL 值处理问题,(A & ! B) | (B & ! A) 与 ! (A & B) 返回结果不一样,后者往往更多 + newWs += " ( " + getCondition( + true, + ( isWsEmpty ? "" : ws + AND ) + " ( " + js + " ) " + ) + " ) "; + } + else { // & INNER JOIN: A & B; | FULL JOIN: A | B; OUTER JOIN: ! (A | B) + logic = Logic.getType(jt); + newWs += " ( " + + getCondition( + Logic.isNot(logic), + ws + + ( isWsEmpty ? "" : (Logic.isAnd(logic) ? AND : OR) ) + + " ( " + js + " ) " + ) + + " ) "; + } + + newPvl.addAll(pvl); + newPvl.addAll(jc.getPreparedValueList()); + + changed = true; + break; + default: + throw new UnsupportedOperationException( + "join:value 中 value 里的 " + jt + "/" + j.getPath() + + "错误!不支持 " + jt + " 等 [ @ APP, < LEFT, > RIGHT, * CROSS" + + ", & INNER, | FULL, ! OUTER, ^ SIDE, ( ANTI, ) FOREIGN ] 之外的 JOIN 类型 !" + ); + } + } + + if (changed) { + whereString = newWs; + preparedValueList = newPvl; + } + } + + String s = StringUtil.isEmpty(whereString, true) ? "" : (hasPrefix ? " WHERE " : "") + whereString; + + if (s.isEmpty() && RequestMethod.isQueryMethod(method) == false) { + throw new UnsupportedOperationException("写操作请求必须带条件!!!"); + } + + return s; + } + + /** + * @param key + * @param value + * @param method + * @param verifyName + * @return + * @throws Exception + */ + protected String getWhereItem(String key, Object value, RequestMethod method, boolean verifyName) throws Exception { + Log.d(TAG, "getWhereItem key = " + key); + //避免筛选到全部 value = key == null ? null : where.get(key); + if (key == null || value == null || key.endsWith("()") || key.startsWith("@")) { //关键字||方法, +或-直接报错 + Log.d(TAG, "getWhereItem key == null || value == null" + + " || key.startsWith(@) || key.endsWith(()) >> continue;"); + return null; + } + if (key.endsWith("@")) {//引用 + // key = key.substring(0, key.lastIndexOf("@")); + throw new IllegalArgumentException(TAG + ".getWhereItem: 字符 " + key + " 不合法!"); + } + + // 原始 SQL 片段 + String rawSQL = getRawSQL(key, value); + + int keyType; + if (key.endsWith("$")) { + keyType = 1; + } + else if (key.endsWith("~")) { + keyType = key.charAt(key.length() - 2) == '*' ? -2 : 2; //FIXME StringIndexOutOfBoundsException + } + else if (key.endsWith("%")) { + keyType = 3; + } + else if (key.endsWith("{}")) { + keyType = 4; + } + else if (key.endsWith("}{")) { + keyType = 5; + } + else if (key.endsWith("<>")) { + keyType = 6; + } + else if (key.endsWith(">=")) { + keyType = 7; + } + else if (key.endsWith("<=")) { + keyType = 8; + } + else if (key.endsWith(">")) { + keyType = 9; + } + else if (key.endsWith("<")) { + keyType = 10; + } else { // else绝对不能省,避免再次踩坑! keyType = 0; 写在for循环外面都没注意! + keyType = 0; + } + + key = getRealKey(method, key, false, true, verifyName); + + switch (keyType) { + case 1: + return getSearchString(key, value, rawSQL); + case -2: + case 2: + return getRegExpString(key, value, keyType < 0, rawSQL); + case 3: + return getBetweenString(key, value, rawSQL); + case 4: + return getRangeString(key, value, rawSQL); + case 5: + return getExistsString(key, value, rawSQL); + case 6: + return getContainString(key, value, rawSQL); + case 7: + return getCompareString(key, value, ">=", rawSQL); + case 8: + return getCompareString(key, value, "<=", rawSQL); + case 9: + return getCompareString(key, value, ">", rawSQL); + case 10: + return getCompareString(key, value, "<", rawSQL); + default: // TODO MySQL JSON类型的字段对比 key='[]' 会无结果! key LIKE '[1, 2, 3]' //TODO MySQL , 后面有空格! + return getEqualString(key, value, rawSQL); + } + } + + + @JSONField(serialize = false) + public String getEqualString(String key, Object value, String rawSQL) throws Exception { + if (JSON.isBooleanOrNumberOrString(value) == false && value instanceof Subquery == false) { + throw new IllegalArgumentException(key + ":value 中value不合法!非PUT请求只支持 [Boolean, Number, String] 内的类型 !"); + } + + boolean not = key.endsWith("!"); // & | 没有任何意义,写法多了不好控制 + if (not) { + key = key.substring(0, key.length() - 1); + } + if (StringUtil.isName(key) == false) { + throw new IllegalArgumentException(key + ":value 中key不合法!不支持 ! 以外的逻辑符 !"); + } + + return getKey(key) + (not ? " != " : " = ") + (value instanceof Subquery ? getSubqueryString((Subquery) value) : (rawSQL != null ? rawSQL : getValue(value))); + } + + @JSONField(serialize = false) + public String getCompareString(String key, Object value, String type, String rawSQL) throws Exception { + if (JSON.isBooleanOrNumberOrString(value) == false && value instanceof Subquery == false) { + throw new IllegalArgumentException(key + type + ":value 中value不合法!比较运算 [>, <, >=, <=] 只支持 [Boolean, Number, String] 内的类型 !"); + } + if (StringUtil.isName(key) == false) { + throw new IllegalArgumentException(key + type + ":value 中key不合法!比较运算 [>, <, >=, <=] 不支持 [&, !, |] 中任何逻辑运算符 !"); + } + + return getKey(key) + " " + type + " " + (value instanceof Subquery ? getSubqueryString((Subquery) value) : (rawSQL != null ? rawSQL : getValue(value))); + } + + public String getKey(String key) { + if (isTest()) { + if (key.contains("'")) { // || key.contains("#") || key.contains("--")) { + throw new IllegalArgumentException("参数 " + key + " 不合法!key 中不允许有单引号 ' !"); + } + return getSQLValue(key).toString(); + } + + return getSQLKey(key); + } + public String getSQLKey(String key) { + String q = getQuote(); + return (isKeyPrefix() ? getAliasWithQuote() + "." : "") + q + key + q; + } + + /** + * 使用prepareStatement预编译,值为 ? ,后续动态set进去 + */ + private List preparedValueList = new ArrayList<>(); + private Object getValue(@NotNull Object value) { + if (isPrepared()) { + preparedValueList.add(value); + return "?"; + } + return getSQLValue(value); + } + public Object getSQLValue(@NotNull Object value) { + // return (value instanceof Number || value instanceof Boolean) && DATABASE_POSTGRESQL.equals(getDatabase()) ? value : "'" + value + "'"; + return (value instanceof Number || value instanceof Boolean) ? value : "'" + value + "'"; //MySQL 隐式转换用不了索引 + } + + @Override + public List getPreparedValueList() { + return preparedValueList; + } + @Override + public AbstractSQLConfig setPreparedValueList(List preparedValueList) { + this.preparedValueList = preparedValueList; + return this; + } + + //$ search <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< + /**search key match value + * @param in + * @return {@link #getSearchString(String, Object[], int)} + * @throws IllegalArgumentException + */ + @JSONField(serialize = false) + public String getSearchString(String key, Object value, String rawSQL) throws IllegalArgumentException { + if (rawSQL != null) { + throw new UnsupportedOperationException("@raw:value 中 " + key + " 不合法!@raw 不支持 key$ 这种功能符 !只支持 key, key!, key<, key{} 等比较运算 和 @column, @having !"); + } + if (value == null) { + return ""; + } + + Logic logic = new Logic(key); + key = logic.getKey(); + Log.i(TAG, "getSearchString key = " + key); + + JSONArray arr = newJSONArray(value); + if (arr.isEmpty()) { + return ""; + } + return getSearchString(key, arr.toArray(), logic.getType()); + } + /**search key match values + * @param in + * @return LOGIC [ key LIKE 'values[i]' ] + * @throws IllegalArgumentException + */ + @JSONField(serialize = false) + public String getSearchString(String key, Object[] values, int type) throws IllegalArgumentException { + if (values == null || values.length <= 0) { + return ""; + } + + String condition = ""; + for (int i = 0; i < values.length; i++) { + Object v = values[i]; + if (v instanceof String == false) { + throw new IllegalArgumentException(key + "$:value 中 value 的类型只能为 String 或 String[]!"); + } + if (((String) v).isEmpty()) { // 允许查空格 StringUtil.isEmpty((String) v, true) + throw new IllegalArgumentException(key + "$:value 中 value 值 " + v + "是空字符串,没有意义,不允许这样传!"); + } + // if (((String) v).contains("%%")) { // 需要通过 %\%% 来模糊搜索 % + // throw new IllegalArgumentException(key + "$:value 中 value 值 " + v + " 中包含 %% !不允许有连续的 % !"); + // } + + condition += (i <= 0 ? "" : (Logic.isAnd(type) ? AND : OR)) + getLikeString(key, v); + } + + return getCondition(Logic.isNot(type), condition); + } + + /**WHERE key LIKE 'value' + * @param key + * @param value + * @return key LIKE 'value' + */ + @JSONField(serialize = false) + public String getLikeString(String key, Object value) { + return getKey(key) + " LIKE " + getValue(value); + } + + //$ search >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> + + + + //~ regexp <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< + /**search key match RegExp values + * @param key + * @param value + * @param ignoreCase + * @return {@link #getRegExpString(String, Object[], int, boolean)} + * @throws IllegalArgumentException + */ + @JSONField(serialize = false) + public String getRegExpString(String key, Object value, boolean ignoreCase, String rawSQL) throws IllegalArgumentException { + if (rawSQL != null) { + throw new UnsupportedOperationException("@raw:value 中 " + key + " 不合法!@raw 不支持 key~ 这种功能符 !只支持 key, key!, key<, key{} 等比较运算 和 @column, @having !"); + } + if (value == null) { + return ""; + } + + Logic logic = new Logic(key); + key = logic.getKey(); + Log.i(TAG, "getRegExpString key = " + key); + + JSONArray arr = newJSONArray(value); + if (arr.isEmpty()) { + return ""; + } + return getRegExpString(key, arr.toArray(), logic.getType(), ignoreCase); + } + /**search key match RegExp values + * @param key + * @param values + * @param type + * @param ignoreCase + * @return LOGIC [ key REGEXP 'values[i]' ] + * @throws IllegalArgumentException + */ + @JSONField(serialize = false) + public String getRegExpString(String key, Object[] values, int type, boolean ignoreCase) throws IllegalArgumentException { + if (values == null || values.length <= 0) { + return ""; + } + + String condition = ""; + for (int i = 0; i < values.length; i++) { + if (values[i] instanceof String == false) { + throw new IllegalArgumentException(key + "$:value 中value的类型只能为String或String[]!"); + } + condition += (i <= 0 ? "" : (Logic.isAnd(type) ? AND : OR)) + getRegExpString(key, (String) values[i], ignoreCase); + } + + return getCondition(Logic.isNot(type), condition); + } + + /**WHERE key REGEXP 'value' + * @param key + * @param value + * @param ignoreCase + * @return key REGEXP 'value' + */ + @JSONField(serialize = false) + public String getRegExpString(String key, String value, boolean ignoreCase) { + if (isPostgreSQL()) { + return getKey(key) + " ~" + (ignoreCase ? "* " : " ") + getValue(value); + } + if (isOracle()) { + return "regexp_like(" + getKey(key) + ", " + getValue(value) + (ignoreCase ? ", 'i'" : ", 'c'") + ")"; + } + return getKey(key) + " REGEXP " + (ignoreCase ? "" : "BINARY ") + getValue(value); + } + //~ regexp >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> + + + + //% between <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< + + /**WHERE key BETWEEN 'start' AND 'end' + * @param key + * @param value 'start,end' + * @return LOGIC [ key BETWEEN 'start' AND 'end' ] + * @throws IllegalArgumentException + */ + @JSONField(serialize = false) + public String getBetweenString(String key, Object value, String rawSQL) throws IllegalArgumentException { + if (rawSQL != null) { + throw new UnsupportedOperationException("@raw:value 中 " + key + " 不合法!@raw 不支持 key% 这种功能符 !只支持 key, key!, key<, key{} 等比较运算 和 @column, @having !"); + } + if (value == null) { + return ""; + } + + Logic logic = new Logic(key); + key = logic.getKey(); + Log.i(TAG, "getBetweenString key = " + key); + + JSONArray arr = newJSONArray(value); + if (arr.isEmpty()) { + return ""; + } + return getBetweenString(key, arr.toArray(), logic.getType()); + } + + /**WHERE key BETWEEN 'start' AND 'end' + * @param key + * @param value 'start,end' TODO 在 '1,2' 和 ['1,2', '3,4'] 基础上新增支持 [1, 2] 和 [[1,2], [3,4]] ? + * @return LOGIC [ key BETWEEN 'start' AND 'end' ] + * @throws IllegalArgumentException + */ + @JSONField(serialize = false) + public String getBetweenString(String key, Object[] values, int type) throws IllegalArgumentException { + if (values == null || values.length <= 0) { + return ""; + } + + String condition = ""; + String[] vs; + for (int i = 0; i < values.length; i++) { + if (values[i] instanceof String == false) { + throw new IllegalArgumentException(key + "%:value 中 value 的类型只能为 String 或 String[] !"); + } + + vs = StringUtil.split((String) values[i]); + if (vs == null || vs.length != 2) { + throw new IllegalArgumentException(key + "%:value 中 value 不合法!类型为 String 时必须包括1个逗号 , 且左右两侧都有值!类型为 String[] 里面每个元素要符合前面类型为 String 的规则 !"); + } + + condition += (i <= 0 ? "" : (Logic.isAnd(type) ? AND : OR)) + "(" + getBetweenString(key, (Object) vs[0], (Object) vs[1]) + ")"; + } + + return getCondition(Logic.isNot(type), condition); + } + + /**WHERE key BETWEEN 'start' AND 'end' + * @param key + * @param value 'start,end' TODO 在 '1,2' 和 ['1,2', '3,4'] 基础上新增支持 [1, 2] 和 [[1,2], [3,4]] ? + * @return key BETWEEN 'start' AND 'end' + * @throws IllegalArgumentException + */ + @JSONField(serialize = false) + public String getBetweenString(String key, Object start, Object end) throws IllegalArgumentException { + if (JSON.isBooleanOrNumberOrString(start) == false || JSON.isBooleanOrNumberOrString(end) == false) { + throw new IllegalArgumentException(key + "%:value 中 value 不合法!类型为 String 时必须包括1个逗号 , 且左右两侧都有值!类型为 String[] 里面每个元素要符合前面类型为 String 的规则 !"); + } + return getKey(key) + " BETWEEN " + getValue(start) + AND + getValue(end); + } + + + //% between >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> + + + + //{} range <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< + + + /**WHERE key > 'key0' AND key <= 'key1' AND ... + * @param key + * @param range "condition0,condition1..." + * @return key condition0 AND key condition1 AND ... + * @throws Exception + */ + @JSONField(serialize = false) + public String getRangeString(String key, Object range, String rawSQL) throws Exception { + Log.i(TAG, "getRangeString key = " + key); + if (range == null) {//依赖的对象都没有给出有效值,这个存在无意义。如果是客户端传的,那就能在客户端确定了。 + throw new NotExistException(TAG + "getRangeString(" + key + ", " + range + + ") range == null"); + } + + Logic logic = new Logic(key); + String k = logic.getKey(); + Log.i(TAG, "getRangeString k = " + k); + + if (range instanceof List) { + if (rawSQL != null) { + throw new UnsupportedOperationException("@raw:value 的 value 中 " + key + "{} 不合法!" + + "Raw SQL 不支持 key{}:[] 这种键值对!"); + } + + if (logic.isOr() || logic.isNot()) { + List l = (List) range; + if (logic.isNot() && l.isEmpty()) { + return ""; // key!{}: [] 这个条件无效,加到 SQL 语句中 key IN() 会报错,getInString 里不好处理 + } + return getKey(k) + getInString(k, l.toArray(), logic.isNot()); + } + throw new IllegalArgumentException(key + "{}\":[] 中 {} 前面的逻辑运算符错误!只能用'|','!'中的一种 !"); + } + else if (range instanceof String) {//非Number类型需要客户端拼接成 < 'value0', >= 'value1'这种 + String condition = ""; + String[] cs = rawSQL != null ? null : StringUtil.split((String) range, false); + + if (rawSQL != null) { + int index = rawSQL == null ? -1 : rawSQL.indexOf("("); + condition = (index >= 0 && index < rawSQL.lastIndexOf(")") ? "" : getKey(k) + " ") + rawSQL; + } + + // 还是只支持整段为 Raw SQL 比较好 + // boolean appendRaw = false; + // if ("".equals(rawSQL)) { + // condition = rawSQL; + // cs = null; + // } + // else { + // if (rawSQL != null) { //先找出所有 rawSQL 的位置,然后去掉,再最后按原位置来拼接 + // String[] rs = StringUtil.split((String) range, rawSQL, false); + // + // if (rs != null && rs.length > 0) { + // String cond = ""; + // for (int i = 0; i < rs.length; i++) { + // cond += rs[i]; + // } + // range = cond; + // appendRaw = true; + // } + // } + // + // cs = StringUtil.split((String) range, false); + // } + + if (cs != null) { + String c; + int index; + for (int i = 0; i < cs.length; i++) {//对函数条件length(key)<=5这种不再在开头加key + c = cs[i]; + + if ("=null".equals(c)) { + c = SQL.isNull(); + } + else if ("!=null".equals(c)) { + c = SQL.isNull(false); + } + else if (isPrepared() && (c.contains("--") || PATTERN_RANGE.matcher(c).matches() == false)) { + throw new UnsupportedOperationException(key + "{}:value 的 value 中 " + c + " 不合法!" + + "预编译模式下 key{}:\"condition\" 中 condition 必须 为 =null 或 !=null 或 符合正则表达式 " + PATTERN_RANGE + " !不允许连续减号 -- !不允许空格!"); + } + + index = c == null ? -1 : c.indexOf("("); + condition += ((i <= 0 ? "" : (logic.isAnd() ? AND : OR)) //连接方式 + + (index >= 0 && index < c.lastIndexOf(")") ? "" : getKey(k) + " ") //函数和非函数条件 + + c); // 还是只支持整段为 Raw SQL 比较好 (appendRaw && index > 0 ? rawSQL : "") + c); //单个条件,如果有 Raw SQL 则按原来位置拼接 + } + } + if (condition.isEmpty()) { + return ""; + } + + return getCondition(logic.isNot(), condition); + } + else if (range instanceof Subquery) { //如果在 Parser 解析成 SQL 字符串再引用,没法保证安全性,毕竟可以再通过远程函数等方式来拼接再替代,最后引用的字符串就能注入 + return getKey(k) + (logic.isNot() ? NOT : "") + " IN " + getSubqueryString((Subquery) range); + } + + throw new IllegalArgumentException(key + "{}:range 类型为" + range.getClass().getSimpleName() + + "!range 只能是 用','分隔条件的字符串 或者 可取选项JSONArray!"); + } + /**WHERE key IN ('key0', 'key1', ... ) + * @param in + * @return IN ('key0', 'key1', ... ) + * @throws NotExistException + */ + @JSONField(serialize = false) + public String getInString(String key, Object[] in, boolean not) throws NotExistException { + String condition = ""; + if (in != null) {//返回 "" 会导致 id:[] 空值时效果和没有筛选id一样! + for (int i = 0; i < in.length; i++) { + condition += ((i > 0 ? "," : "") + getValue(in[i])); + } + } + if (condition.isEmpty()) {//条件如果存在必须执行,不能忽略。条件为空会导致出错,又很难保证条件不为空(@:条件),所以还是这样好 + throw new NotExistException(TAG + ".getInString(" + key + ", [], " + not + + ") >> condition.isEmpty() >> IN()"); + } + return (not ? NOT : "") + " IN (" + condition + ")"; + } + //{} range >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> + + + //}{ exists <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< + /**WHERE EXISTS subquery + * 如果合并到 getRangeString,一方面支持不了 [1,2,2] 和 ">1" (转成 EXISTS(SELECT IN ) 需要static newSQLConfig,但它不能传入子类实例,除非不是 static),另一方面多了子查询临时表性能会比 IN 差 + * @param key + * @param value + * @return EXISTS ALL(SELECT ...) + * @throws NotExistException + */ + @JSONField(serialize = false) + public String getExistsString(String key, Object value, String rawSQL) throws Exception { + if (rawSQL != null) { + throw new UnsupportedOperationException("@raw:value 中 " + key + " 不合法!@raw 不支持 key}{ 这种功能符 !只支持 key, key!, key<, key{} 等比较运算 和 @column, @having !"); + } + if (value == null) { + return ""; + } + if (value instanceof Subquery == false) { + throw new IllegalArgumentException(key + "}{:subquery 类型为" + value.getClass().getSimpleName() + + "!subquery 只能是 子查询JSONObejct!"); + } + + Logic logic = new Logic(key); + key = logic.getKey(); + Log.i(TAG, "getExistsString key = " + key); + + return (logic.isNot() ? NOT : "") + " EXISTS " + getSubqueryString((Subquery) value); + } + //}{ exists >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> + + //<> contain <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< + /**WHERE key contains value + * @param key + * @param value + * @return {@link #getContainString(String, Object[], int)} + * @throws NotExistException + */ + @JSONField(serialize = false) + public String getContainString(String key, Object value, String rawSQL) throws IllegalArgumentException { + if (rawSQL != null) { + throw new UnsupportedOperationException("@raw:value 中 " + key + " 不合法!@raw 不支持 key<> 这种功能符 !只支持 key, key!, key<, key{} 等比较运算 和 @column, @having !"); + } + if (value == null) { + return ""; + } + + Logic logic = new Logic(key); + key = logic.getKey(); + Log.i(TAG, "getContainString key = " + key); + + return getContainString(key, newJSONArray(value).toArray(), logic.getType()); + } + /**WHERE key contains childs + * @param key + * @param childs null ? "" : (empty ? no child : contains childs) + * @param type |, &, ! + * @return LOGIC [ ( key LIKE '[" + childs[i] + "]' OR key LIKE '[" + childs[i] + ", %' + * OR key LIKE '%, " + childs[i] + ", %' OR key LIKE '%, " + childs[i] + "]' ) ] + * @throws IllegalArgumentException + */ + @JSONField(serialize = false) + public String getContainString(String key, Object[] childs, int type) throws IllegalArgumentException { + boolean not = Logic.isNot(type); + String condition = ""; + if (childs != null) { + for (int i = 0; i < childs.length; i++) { + Object c = childs[i]; + if (c != null) { + if (c instanceof JSON) { + throw new IllegalArgumentException(key + "<>:value 中value类型不能为JSON!"); + } + + condition += (i <= 0 ? "" : (Logic.isAnd(type) ? AND : OR)); + if (isPostgreSQL()) { + condition += (getKey(key) + " @> " + getValue(newJSONArray(c))); //operator does not exist: jsonb @> character varying "[" + c + "]"); + } + else if (isOracle()) { + condition += ("json_textcontains(" + getKey(key) + ", '$', " + getValue(c.toString()) + ")"); + } + else { + boolean isNum = c instanceof Number; + String v = (isNum ? "" : "\"") + childs[i] + (isNum ? "" : "\""); + condition += ("json_contains(" + getKey(key) + ", " + getValue(v) + ")"); + } + } + } + if (condition.isEmpty()) { + condition = (getKey(key) + SQL.isNull(true) + OR + getLikeString(key, "[]")); // key = '[]' 无结果! + } else { + condition = (getKey(key) + SQL.isNull(false) + AND + "(" + condition + ")"); + } + } + if (condition.isEmpty()) { + return ""; + } + return getCondition(not, condition); + } + //<> contain >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> + + + + //key@:{} Subquery <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< + + @Override + public String getSubqueryString(Subquery subquery) throws Exception { + String range = subquery.getRange(); + SQLConfig cfg = subquery.getConfig(); + + cfg.setPreparedValueList(new ArrayList<>()); + String sql = (range == null || range.isEmpty() ? "" : range) + "(" + cfg.getSQL(isPrepared()) + ") "; + + preparedValueList.addAll(cfg.getPreparedValueList()); + + return sql; + } + + //key@:{} Subquery >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> + + + + /**拼接条件 + * @param not + * @param condition + * @return + */ + private static String getCondition(boolean not, String condition) { + return not ? NOT + "(" + condition + ")" : condition; + } + + + /**转为JSONArray + * @param tv + * @return + */ + @NotNull + public static JSONArray newJSONArray(Object obj) { + JSONArray array = new JSONArray(); + if (obj != null) { + if (obj instanceof Collection) { + array.addAll((Collection) obj); + } else { + array.add(obj); + } + } + return array; + } + + //WHERE >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> + + + //SET <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< + /**获取SET + * @return + * @throws Exception + */ + @JSONField(serialize = false) + public String getSetString() throws Exception { + return getSetString(getMethod(), getContent(), ! isTest()); + } + //CS304 Issue link: https://github.com/Tencent/APIJSON/issues/48 + /**获取SET + * @param method -the method used + * @param content -the content map + * @return + * @throws Exception + *

use entrySet+getValue() to replace keySet+get() to enhance efficiency

+ */ + @JSONField(serialize = false) + public String getSetString(RequestMethod method, Map content, boolean verifyName) throws Exception { + Set set = content == null ? null : content.keySet(); + String setString = ""; + + if (set != null && set.size() > 0) { + boolean isFirst = true; + int keyType;// 0 - =; 1 - +, 2 - - + Object value; + + String idKey = getIdKey(); + for (Entry entry : content.entrySet()) { + String key = entry.getKey(); + //避免筛选到全部 value = key == null ? null : content.get(key); + if (key == null || idKey.equals(key)) { + continue; + } + + if (key.endsWith("+")) { + keyType = 1; + } else if (key.endsWith("-")) { + keyType = 2; + } else { + keyType = 0; //注意重置类型,不然不该加减的字段会跟着加减 + } + value = entry.getValue(); + key = getRealKey(method, key, false, true, verifyName); + + setString += (isFirst ? "" : ", ") + (getKey(key) + " = " + (keyType == 1 ? getAddString(key, value) : (keyType == 2 + ? getRemoveString(key, value) : getValue(value)) ) ); + + isFirst = false; + } + } + + if (setString.isEmpty()) { + throw new IllegalArgumentException("PUT 请求必须在Table内设置要修改的 key:value !"); + } + return " SET " + setString; + } + + /**SET key = concat(key, 'value') + * @param key + * @param value + * @return concat(key, 'value') + * @throws IllegalArgumentException + */ + @JSONField(serialize = false) + public String getAddString(String key, Object value) throws IllegalArgumentException { + if (value instanceof Number) { + return getKey(key) + " + " + value; + } + if (value instanceof String) { + return SQL.concat(getKey(key), (String) getValue(value)); + } + throw new IllegalArgumentException(key + "+ 对应的值 " + value + " 不是Number,String,Array中的任何一种!"); + } + /**SET key = replace(key, 'value', '') + * @param key + * @param value + * @return REPLACE (key, 'value', '') + * @throws IllegalArgumentException + */ + @JSONField(serialize = false) + public String getRemoveString(String key, Object value) throws IllegalArgumentException { + if (value instanceof Number) { + return getKey(key) + " - " + value; + } + if (value instanceof String) { + return SQL.replace(getKey(key), (String) getValue(value), "''");// " replace(" + key + ", '" + value + "', '') "; + } + throw new IllegalArgumentException(key + "- 对应的值 " + value + " 不是Number,String,Array中的任何一种!"); + } + //SET >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> + + + /** + * @return + * @throws Exception + */ + @JSONField(serialize = false) + @Override + public String getSQL(boolean prepared) throws Exception { + return getSQL(this.setPrepared(prepared)); + } + /** + * @param config + * @return + * @throws Exception + */ + public static String getSQL(AbstractSQLConfig config) throws Exception { + if (config == null) { + Log.i(TAG, "getSQL config == null >> return null;"); + return null; + } + + //TODO procedure 改为 List procedureList; behind : true; function: callFunction(); String key; ... + // for (...) { Call procedure1();\n SQL \n; Call procedure2(); ... } + // 貌似不需要,因为 ObjecParser 里就已经处理的顺序等,只是这里要解决下 Schema 问题。 + + String sch = config.getSQLSchema(); + if (StringUtil.isNotEmpty(config.getProcedure(), true)) { + String q = config.getQuote(); + return "CALL " + q + sch + q + "."+ config.getProcedure(); + } + + String tablePath = config.getTablePath(); + if (StringUtil.isNotEmpty(tablePath, true) == false) { + Log.i(TAG, "getSQL StringUtil.isNotEmpty(tablePath, true) == false >> return null;"); + return null; + } + + switch (config.getMethod()) { + case POST: + return "INSERT INTO " + tablePath + config.getColumnString() + " VALUES" + config.getValuesString(); + case PUT: + return "UPDATE " + tablePath + config.getSetString() + config.getWhereString(true) + (config.isMySQL() ? config.getLimitString() : ""); + case DELETE: + return "DELETE FROM " + tablePath + config.getWhereString(true) + (config.isMySQL() ? config.getLimitString() : ""); // PostgreSQL 不允许 LIMIT + default: + String explain = (config.isExplain() ? (config.isSQLServer() || config.isOracle() ? "SET STATISTICS PROFILE ON " : "EXPLAIN ") : ""); + if (config.isTest() && RequestMethod.isGetMethod(config.getMethod(), true)) { + String q = config.getQuote(); // 生成 SELECT ( (24 >=0 AND 24 <3) ) AS `code` LIMIT 1 OFFSET 0 + return explain + "SELECT " + config.getWhereString(false) + " AS " + q + JSONResponse.KEY_CODE + q + config.getLimitString(); + } + + config.setPreparedValueList(new ArrayList()); + String column = config.getColumnString(); + if (config.isOracle()) { + //When config's database is oracle,Using subquery since Oracle12 below does not support OFFSET FETCH paging syntax. + return explain + "SELECT * FROM (SELECT"+ (config.getCache() == JSONRequest.CACHE_RAM ? "SQL_NO_CACHE " : "") + column + " FROM " + getConditionString(column, tablePath, config) + ") " + config.getLimitString(); + } + + return explain + "SELECT " + (config.getCache() == JSONRequest.CACHE_RAM ? "SQL_NO_CACHE " : "") + column + " FROM " + getConditionString(column, tablePath, config) + config.getLimitString(); + } + } + + /**获取条件SQL字符串 + * @param page + * @param column + * @param table + * @param where + * @return + * @throws Exception + */ + private static String getConditionString(String column, String table, AbstractSQLConfig config) throws Exception { + String where = config.getWhereString(true); + + Subquery from = config.getFrom(); + if (from != null) { + table = config.getSubqueryString(from) + " AS " + config.getAliasWithQuote() + " "; + } + + String condition = table + config.getJoinString() + where + ( + RequestMethod.isGetMethod(config.getMethod(), true) == false ? + "" : config.getGroupString(true) + config.getHavingString(true) + config.getOrderString(true) + ) + ; //+ config.getLimitString(); + + //no need to optimize + // if (config.getPage() <= 0 || ID.equals(column.trim())) { + return condition; // config.isOracle() ? condition : condition + config.getLimitString(); + // } + // + // + // //order: id+ -> id >= idOfStartIndex; id- -> id <= idOfStartIndex <<<<<<<<<<<<<<<<<<< + // String order = StringUtil.getNoBlankString(config.getOrder()); + // List orderList = order.isEmpty() ? null : Arrays.asList(StringUtil.split(order)); + // + // int type = 0; + // if (BaseModel.isEmpty(orderList) || BaseModel.isContain(orderList, ID+"+")) { + // type = 1; + // } + // else if (BaseModel.isContain(orderList, ID+"-")) { + // type = 2; + // } + // + // if (type > 0) { + // return condition.replace("WHERE", + // "WHERE id " + (type == 1 ? ">=" : "<=") + " (SELECT id FROM " + table + // + where + " ORDER BY id " + (type == 1 ? "ASC" : "DESC") + " LIMIT " + config.getOffset() + ", 1) AND" + // ) + // + " LIMIT " + config.getCount(); //子查询起始id不一定准确,只能作为最小可能! ;// + // } + // //order: id+ -> id >= idOfStartIndex; id- -> id <= idOfStartIndex >>>>>>>>>>>>>>>>>> + // + // + // //结果错误!SELECT * FROM User AS t0 INNER JOIN + // (SELECT id FROM User ORDER BY date ASC LIMIT 20, 10) AS t1 ON t0.id = t1.id + // //common case, inner join + // condition += config.getLimitString(); + // return table + " AS t0 INNER JOIN (SELECT id FROM " + condition + ") AS t1 ON t0.id = t1.id"; + } + + + private boolean keyPrefix; + @Override + public boolean isKeyPrefix() { + return keyPrefix; + } + @Override + public AbstractSQLConfig setKeyPrefix(boolean keyPrefix) { + this.keyPrefix = keyPrefix; + return this; + } + + + + public String getJoinString() throws Exception { + String joinOns = ""; + + if (joinList != null) { + String quote = getQuote(); + List pvl = new ArrayList<>(); + boolean changed = false; + + String sql = null; + SQLConfig jc; + String jt; + String tt; + // 主表不用别名 String ta; + for (Join j : joinList) { + if (j.isAppJoin()) { // APP JOIN,只是作为一个标记,执行完主表的查询后自动执行副表的查询 User.id IN($commentIdList) + continue; + } + String type = j.getJoinType(); + + //LEFT JOIN sys.apijson_user AS User ON User.id = Moment.userId, 都是用 = ,通过relateType处理缓存 + // <"INNER JOIN User ON User.id = Moment.userId", UserConfig> TODO AS 放 getSQLTable 内 + jc = j.getJoinConfig(); + jc.setPrepared(isPrepared()); + + jt = StringUtil.isEmpty(jc.getAlias(), true) ? jc.getTable() : jc.getAlias(); + tt = j.getTargetTable(); + + //如果要强制小写,则可在子类重写这个方法再 toLowerCase + // if (DATABASE_POSTGRESQL.equals(getDatabase())) { + // jt = jt.toLowerCase(); + // tn = tn.toLowerCase(); + // } + + switch (type) { + //前面已跳过 case "@": // APP JOIN + // continue; + + case "*": // CROSS JOIN + onGetCrossJoinString(j); + case "<": // LEFT JOIN + case ">": // RIGHT JOIN + jc.setMain(true).setKeyPrefix(false); + sql = ( "<".equals(type) ? " LEFT" : (">".equals(type) ? " RIGHT" : " CROSS") ) + + " JOIN ( " + jc.getSQL(isPrepared()) + " ) AS " + + quote + jt + quote + " ON " + quote + jt + quote + "." + quote + j.getKey() + quote + " = " + + quote + tt + quote + "." + quote + j.getTargetKey() + quote; + jc.setMain(false).setKeyPrefix(true); + + pvl.addAll(jc.getPreparedValueList()); + changed = true; + break; + + case "&": // INNER JOIN: A & B + case "": // FULL JOIN: A | B + case "|": // FULL JOIN: A | B + case "!": // OUTER JOIN: ! (A | B) + case "^": // SIDE JOIN: ! (A & B) + case "(": // ANTI JOIN: A & ! B + case ")": // FOREIGN JOIN: B & ! A + sql = " INNER JOIN " + jc.getTablePath() + + " ON " + quote + jt + quote + "." + quote + j.getKey() + quote + " = " + quote + tt + quote + "." + quote + j.getTargetKey() + quote; + break; + default: + throw new UnsupportedOperationException( + "join:value 中 value 里的 " + jt + "/" + j.getPath() + + "错误!不支持 " + jt + " 等 [ @ APP, < LEFT, > RIGHT, * CROSS" + + ", & INNER, | FULL, ! OUTER, ^ SIDE, ( ANTI, ) FOREIGN ] 之外的 JOIN 类型 !" + ); + } + + joinOns += " \n " + sql; + } + + + if (changed) { + pvl.addAll(preparedValueList); + preparedValueList = pvl; + } + + } + + return joinOns; + } + + protected void onGetCrossJoinString(Join j) throws UnsupportedOperationException { + throw new UnsupportedOperationException("已禁用 * CROSS JOIN !性能很差、需求极少,如要取消禁用可在后端重写相关方法!"); + } + + /**新建SQL配置 + * @param table + * @param request + * @param joinList + * @param isProcedure + * @param callback + * @return + * @throws Exception + */ + public static SQLConfig newSQLConfig(RequestMethod method, String table, String alias, JSONObject request, List joinList, boolean isProcedure, Callback callback) throws Exception { + if (request == null) { // User:{} 这种空内容在查询时也有效 + throw new NullPointerException(TAG + ": newSQLConfig request == null!"); + } + + boolean explain = request.getBooleanValue(KEY_EXPLAIN); + if (explain && Log.DEBUG == false) { //不在 config.setExplain 抛异常,一方面处理更早性能更好,另一方面为了内部调用可以绕过这个限制 + throw new UnsupportedOperationException("DEBUG 模式下不允许传 " + KEY_EXPLAIN + " !"); + } + + String database = request.getString(KEY_DATABASE); + if (StringUtil.isEmpty(database, false) == false && DATABASE_LIST.contains(database) == false) { + throw new UnsupportedDataTypeException("@database:value 中 value 错误,只能是 [" + StringUtil.getString(DATABASE_LIST.toArray()) + "] 中的一种!"); + } + + String schema = request.getString(KEY_SCHEMA); + String datasource = request.getString(KEY_DATASOURCE); + + SQLConfig config = callback.getSQLConfig(method, database, schema, table); + config.setAlias(alias); + + config.setDatabase(database); //不删,后面表对象还要用的,必须放在 parseJoin 前 + config.setSchema(schema); //不删,后面表对象还要用的 + config.setDatasource(datasource); //不删,后面表对象还要用的 + + if (isProcedure) { + return config; + } + + config = parseJoin(method, config, joinList, callback); //放后面会导致主表是空对象时 joinList 未解析 + + if (request.isEmpty()) { // User:{} 这种空内容在查询时也有效 + return config; //request.remove(key); 前都可以直接return,之后必须保证 put 回去 + } + + + String idKey = callback.getIdKey(database, schema, table); + String idInKey = idKey + "{}"; + String userIdKey = callback.getUserIdKey(database, schema, table); + String userIdInKey = userIdKey + "{}"; + + //对id和id{}处理,这两个一定会作为条件 + + Object idIn = request.get(idInKey); //可能是 id{}:">0" + if (idIn instanceof List) { // 排除掉 0, 负数, 空字符串 等无效 id 值 + List ids = ((List) idIn); + List newIdIn = new ArrayList<>(); + Object d; + for (int i = 0; i < ids.size(); i++) { //不用 idIn.contains(id) 因为 idIn 里存到很可能是 Integer,id 又是 Long! + d = ids.get(i); + if ((d instanceof Number && ((Number) d).longValue() > 0) || (d instanceof String && StringUtil.isNotEmpty(d, true))) { + newIdIn.add(d); + } + } + if (newIdIn.isEmpty()) { + throw new NotExistException(TAG + ": newSQLConfig idIn instanceof List >> 去掉无效 id 后 newIdIn.isEmpty()"); + } + idIn = newIdIn; + + if (method == DELETE || method == PUT) { + config.setCount(newIdIn.size()); + } + } + + Object id = request.get(idKey); + boolean hasId = id != null; + if (method == POST && hasId == false) { + id = callback.newId(method, database, schema, table); // null 表示数据库自增 id + } + + if (id != null) { //null无效 + if (id instanceof Number) { + if (((Number) id).longValue() <= 0) { //一定没有值 + throw new NotExistException(TAG + ": newSQLConfig " + table + ".id <= 0"); + } + } + else if (id instanceof String) { + if (StringUtil.isEmpty(id, true)) { //一定没有值 + throw new NotExistException(TAG + ": newSQLConfig StringUtil.isEmpty(" + table + ".id, true)"); + } + } + else if (id instanceof Subquery) {} + else { + throw new IllegalArgumentException(idKey + ":value 中 value 的类型只能是 Long , String 或 Subquery !"); + } + + if (idIn instanceof List) { //共用idIn场景少性能差 + boolean contains = false; + List ids = ((List) idIn); + Object d; + for (int i = 0; i < ids.size(); i++) { //不用 idIn.contains(id) 因为 idIn 里存到很可能是 Integer,id 又是 Long! + d = ids.get(i); + if (d != null && id.toString().equals(d.toString())) { + contains = true; + break; + } + } + if (contains == false) {//empty有效 BaseModel.isEmpty(idIn) == false) { + throw new NotExistException(TAG + ": newSQLConfig idIn != null && (((List) idIn).contains(id) == false"); + } + } + + if (method == DELETE || method == PUT) { + config.setCount(1); + } + } + + + String role = request.getString(KEY_ROLE); + String cache = request.getString(KEY_CACHE); + String combine = request.getString(KEY_COMBINE); + Subquery from = (Subquery) request.get(KEY_FROM); + String column = request.getString(KEY_COLUMN); + String group = request.getString(KEY_GROUP); + String having = request.getString(KEY_HAVING); + String order = request.getString(KEY_ORDER); + String raw = request.getString(KEY_RAW); + String json = request.getString(KEY_JSON); + + try { + //强制作为条件且放在最前面优化性能 + request.remove(idKey); + request.remove(idInKey); + //关键词 + request.remove(KEY_ROLE); + request.remove(KEY_EXPLAIN); + request.remove(KEY_CACHE); + request.remove(KEY_DATABASE); + request.remove(KEY_SCHEMA); + request.remove(KEY_COMBINE); + request.remove(KEY_FROM); + request.remove(KEY_COLUMN); + request.remove(KEY_GROUP); + request.remove(KEY_HAVING); + request.remove(KEY_ORDER); + request.remove(KEY_RAW); + request.remove(KEY_JSON); + + String[] rawArr = StringUtil.split(raw); + config.setRaw(rawArr == null || rawArr.length <= 0 ? null : new ArrayList<>(Arrays.asList(rawArr))); + + Map tableWhere = new LinkedHashMap();//保证顺序好优化 WHERE id > 1 AND name LIKE... + + //已经remove了id和id{},以及@key + Set set = request.keySet(); //前面已经判断request是否为空 + if (method == POST) { //POST操作 + if (idIn != null) { + throw new IllegalArgumentException("POST 请求中不允许传 " + idInKey + " !"); + } + + if (set != null && set.isEmpty() == false) { //不能直接return,要走完下面的流程 + String[] columns = set.toArray(new String[]{}); + + Collection valueCollection = request.values(); + Object[] values = valueCollection == null ? null : valueCollection.toArray(); + + if (values == null || values.length != columns.length) { + throw new Exception("服务器内部错误:\n" + TAG + + " newSQLConfig values == null || values.length != columns.length !"); + } + column = (id == null ? "" : idKey + ",") + StringUtil.getString(columns); //set已经判断过不为空 + + List> valuess = new ArrayList<>(1); + List items; //(item0, item1, ...) + if (id == null) { //数据库自增 id + items = Arrays.asList(values); //FIXME 是否还需要进行 add 或 remove 操作?Arrays.ArrayList 不允许修改,会抛异常 + } + else { + int size = columns.length + (id == null ? 0 : 1); //以key数量为准 + + items = new ArrayList<>(size); + items.add(id); //idList.get(i)); //第0个就是id + + for (int j = 1; j < size; j++) { + items.add(values[j-1]); //从第1个开始,允许"null" + } + } + + valuess.add(items); + config.setValues(valuess); + } + } + else { //非POST操作 + final boolean isWhere = method != PUT;//除了POST,PUT,其它全是条件!!! + + //条件<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< + List whereList = null; + + Map> combineMap = new LinkedHashMap<>(); + List andList = new ArrayList<>(); + List orList = new ArrayList<>(); + List notList = new ArrayList<>(); + + //强制作为条件且放在最前面优化性能 + if (id != null) { + tableWhere.put(idKey, id); + andList.add(idKey); + } + if (idIn != null) { + tableWhere.put(idInKey, idIn); + andList.add(idInKey); + } + + String[] ws = StringUtil.split(combine); + if (ws != null) { + if (method == DELETE || method == GETS || method == HEADS) { + throw new IllegalArgumentException("DELETE,GETS,HEADS 请求不允许传 @combine:value !"); + } + whereList = new ArrayList<>(); + + String w; + for (int i = 0; i < ws.length; i++) { //去除 &,|,! 前缀 + w = ws[i]; + if (w != null) { + if (w.startsWith("&")) { + w = w.substring(1); + andList.add(w); + } + else if (w.startsWith("|")) { + if (method == PUT) { + throw new IllegalArgumentException(table + ":{} 里的 @combine:value 中的value里条件 " + ws[i] + " 不合法!" + + "PUT请求的 @combine:\"key0,key1,...\" 不允许传 |key 或 !key !"); + } + w = w.substring(1); + orList.add(w); + } + else if (w.startsWith("!")) { + if (method == PUT) { + throw new IllegalArgumentException(table + ":{} 里的 @combine:value 中的value里条件 " + ws[i] + " 不合法!" + + "PUT请求的 @combine:\"key0,key1,...\" 不允许传 |key 或 !key !"); + } + w = w.substring(1); + notList.add(w); + } + else { + orList.add(w); + } + + if (w.isEmpty()) { + throw new IllegalArgumentException(table + ":{} 里的 @combine:value 中的value里条件 " + ws[i] + " 不合法!不允许为空值!"); + } + else { + if (idKey.equals(w) || idInKey.equals(w) || userIdKey.equals(w) || userIdInKey.equals(w)) { + throw new UnsupportedOperationException(table + ":{} 里的 @combine:value 中的value里 " + ws[i] + " 不合法!" + + "不允许传 [" + idKey + ", " + idInKey + ", " + userIdKey + ", " + userIdInKey + "] 其中任何一个!"); + } + } + + whereList.add(w); + } + + // 可重写回调方法自定义处理 // 动态设置的场景似乎很少,而且去掉后不方便用户排错!//去掉判断,有时候不在没关系,如果是对增删改等非开放请求强制要求传对应的条件,可以用 Operation.NECESSARY + if (request.containsKey(w) == false) { //和 request.get(w) == null 没区别,前面 Parser 已经过滤了 null + // throw new IllegalArgumentException(table + ":{} 里的 @combine:value 中的value里 " + ws[i] + " 对应的 " + w + " 不在它里面!"); + callback.onMissingKey4Combine(table, request, combine, ws[i], w); + } + } + + } + + //条件>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> + + Map tableContent = new LinkedHashMap(); + Object value; + for (String key : set) { + value = request.get(key); + + if (value instanceof Map) {//只允许常规Object + throw new IllegalArgumentException("不允许 " + key + " 等任何key的value类型为 {JSONObject} !"); + } + + //解决AccessVerifier新增userId没有作为条件,而是作为内容,导致PUT,DELETE出错 + if (isWhere) { + tableWhere.put(key, value); + if (whereList == null || whereList.contains(key) == false) { + andList.add(key); + } + } + else if (whereList != null && whereList.contains(key)) { + tableWhere.put(key, value); + } + else { + tableContent.put(key, value);//一样 instanceof JSONArray ? JSON.toJSONString(value) : value); + } + } + + combineMap.put("&", andList); + combineMap.put("|", orList); + combineMap.put("!", notList); + config.setCombine(combineMap); + + config.setContent(tableContent); + } + + + List cs = new ArrayList<>(); + + List rawList = config.getRaw(); + boolean containColumnRaw = rawList != null && rawList.contains(KEY_COLUMN); + + String rawColumnSQL = null; + if (containColumnRaw) { + try { + rawColumnSQL = config.getRawSQL(KEY_COLUMN, column); + if (rawColumnSQL != null) { + cs.add(rawColumnSQL); + } + } catch (Exception e) { + Log.e(TAG, "newSQLConfig config instanceof AbstractSQLConfig >> try { " + + " rawColumnSQL = ((AbstractSQLConfig) config).getRawSQL(KEY_COLUMN, column); " + + "} catch (Exception e) = " + e.getMessage()); + } + } + + boolean distinct = column == null || rawColumnSQL != null ? false : column.startsWith(PREFFIX_DISTINCT); + if (rawColumnSQL == null) { + String[] fks = StringUtil.split(distinct ? column.substring(PREFFIX_DISTINCT.length()) : column, ";"); // key0,key1;fun0(key0,...);fun1(key0,...);key3;fun2(key0,...) + if (fks != null) { + String[] ks; + for (String fk : fks) { + if (containColumnRaw) { + try { + String rawSQL = config.getRawSQL(KEY_COLUMN, fk); + if (rawSQL != null) { + cs.add(rawSQL); + continue; + } + } catch (Exception e) { + Log.e(TAG, "newSQLConfig rawColumnSQL == null >> try { " + + " String rawSQL = ((AbstractSQLConfig) config).getRawSQL(KEY_COLUMN, fk); ... " + + "} catch (Exception e) = " + e.getMessage()); + } + } + + if (fk.contains("(")) { // fun0(key0,...) + cs.add(fk); + } + else { //key0,key1... + ks = StringUtil.split(fk); + if (ks != null && ks.length > 0) { + cs.addAll(Arrays.asList(ks)); + } + } + } + } + } + + config.setExplain(explain); + config.setCache(getCache(cache)); + config.setFrom(from); + config.setDistinct(distinct); + config.setColumn(column == null ? null : cs); //解决总是 config.column != null,总是不能得到 * + config.setWhere(tableWhere); + + config.setId(id); + //在 tableWhere 第0个 config.setIdIn(idIn); + + config.setRole(RequestRole.get(role)); + config.setGroup(group); + config.setHaving(having); + config.setOrder(order); + + String[] jsonArr = StringUtil.split(json); + config.setJson(jsonArr == null || jsonArr.length <= 0 ? null : new ArrayList<>(Arrays.asList(jsonArr))); + + //TODO 解析JOIN,包括 @column,@group 等要合并 + + } + finally {//后面还可能用到,要还原 + //id或id{}条件 + if (hasId) { + request.put(idKey, id); + } + request.put(idInKey, idIn); + //关键词 + request.put(KEY_DATABASE, database); + request.put(KEY_ROLE, role); + request.put(KEY_EXPLAIN, explain); + request.put(KEY_CACHE, cache); + request.put(KEY_SCHEMA, schema); + request.put(KEY_COMBINE, combine); + request.put(KEY_FROM, from); + request.put(KEY_COLUMN, column); + request.put(KEY_GROUP, group); + request.put(KEY_HAVING, having); + request.put(KEY_ORDER, order); + request.put(KEY_RAW, raw); + request.put(KEY_JSON, json); + } + + return config; + } + + + + /** + * @param method + * @param config + * @param joinList + * @param callback + * @return + * @throws Exception + */ + public static SQLConfig parseJoin(RequestMethod method, SQLConfig config, List joinList, Callback callback) throws Exception { + boolean isQuery = RequestMethod.isQueryMethod(method); + config.setKeyPrefix(isQuery && config.isMain() == false); + + //TODO 解析出 SQLConfig 再合并 column, order, group 等 + if (joinList == null || joinList.isEmpty() || RequestMethod.isQueryMethod(method) == false) { + return config; + } + + + String table; + String alias; + for (Join j : joinList) { + table = j.getTable(); + alias = j.getAlias(); + //JOIN子查询不能设置LIMIT,因为ON关系是在子查询后处理的,会导致结果会错误 + SQLConfig joinConfig = newSQLConfig(method, table, alias, j.getRequest(), null, false, callback); + SQLConfig cacheConfig = j.canCacheViceTable() == false ? null : newSQLConfig(method, table, alias, j.getRequest(), null, false, callback).setCount(1); + + if (j.isAppJoin() == false) { //除了 @ APP JOIN,其它都是 SQL JOIN,则副表要这样配置 + if (joinConfig.getDatabase() == null) { + joinConfig.setDatabase(config.getDatabase()); //解决主表 JOIN 副表,引号不一致 + } + else if (joinConfig.getDatabase().equals(config.getDatabase()) == false) { + throw new IllegalArgumentException("主表 " + config.getTable() + " 的 @database:" + config.getDatabase() + " 和它 SQL JOIN 的副表 " + table + " 的 @database:" + joinConfig.getDatabase() + " 不一致!"); + } + if (joinConfig.getSchema() == null) { + joinConfig.setSchema(config.getSchema()); //主表 JOIN 副表,默认 schema 一致 + } + + if (cacheConfig != null) { + cacheConfig.setDatabase(joinConfig.getDatabase()).setSchema(joinConfig.getSchema()); //解决主表 JOIN 副表,引号不一致 + } + + + if (isQuery) { + config.setKeyPrefix(true); + } + + joinConfig.setMain(false).setKeyPrefix(true); + + if (j.isLeftOrRightJoin()) { + SQLConfig outterConfig = newSQLConfig(method, table, alias, j.getOuter(), null, false, callback); + outterConfig.setMain(false).setKeyPrefix(true).setDatabase(joinConfig.getDatabase()).setSchema(joinConfig.getSchema()); //解决主表 JOIN 副表,引号不一致 + j.setOuterConfig(outterConfig); + } + } + + //解决 query: 1/2 查数量时报错 /* SELECT count(*) AS count FROM sys.Moment AS Moment LEFT JOIN ( SELECT count(*) AS count FROM sys.Comment ) AS Comment ON Comment.momentId = Moment.id LIMIT 1 OFFSET 0 */ - if (RequestMethod.isHeadMethod(method, true)) { - joinConfig.setMethod(GET); //子查询不能为 SELECT count(*) ,而应该是 SELECT momentId - joinConfig.setColumn(Arrays.asList(j.getKey())); //优化性能,不取非必要的字段 - - if (cacheConfig != null) { - cacheConfig.setMethod(GET); //子查询不能为 SELECT count(*) ,而应该是 SELECT momentId - cacheConfig.setColumn(Arrays.asList(j.getKey())); //优化性能,不取非必要的字段 - } - } - - j.setJoinConfig(joinConfig); - j.setCacheConfig(cacheConfig); - } - - config.setJoinList(joinList); - - return config; - } - - - /** - * 获取客户端实际需要的key - * verifyName = true - * - * @param method - * @param originKey - * @param isTableKey - * @param saveLogic 保留逻辑运算符 & | ! - * @return - */ - public static String getRealKey(RequestMethod method, String originKey - , boolean isTableKey, boolean saveLogic) throws Exception { - return getRealKey(method, originKey, isTableKey, saveLogic, true); - } - - /** - * 获取客户端实际需要的key - * - * @param method - * @param originKey - * @param isTableKey - * @param saveLogic 保留逻辑运算符 & | ! - * @param verifyName 验证key名是否符合代码变量/常量名 - * @return - */ - public static String getRealKey(RequestMethod method, String originKey - , boolean isTableKey, boolean saveLogic, boolean verifyName) throws Exception { - Log.i(TAG, "getRealKey saveLogic = " + saveLogic + "; originKey = " + originKey); - if (originKey == null || apijson.JSONObject.isArrayKey(originKey)) { - Log.w(TAG, "getRealKey originKey == null || apijson.JSONObject.isArrayKey(originKey) >> return originKey;"); - return originKey; - } - - String key = new String(originKey); - if (key.endsWith("$")) {//搜索 LIKE,查询时处理 - key = key.substring(0, key.length() - 1); - } else if (key.endsWith("~")) {//匹配正则表达式 REGEXP,查询时处理 - key = key.substring(0, key.length() - 1); - if (key.endsWith("*")) {//忽略大小写 - key = key.substring(0, key.length() - 1); - } - } else if (key.endsWith("%")) {//数字、文本、日期范围 BETWEEN AND - key = key.substring(0, key.length() - 1); - } else if (key.endsWith("{}")) {//被包含 IN,或者说key对应值处于value的范围内。查询时处理 - key = key.substring(0, key.length() - 2); - } else if (key.endsWith("}{")) {//被包含 EXISTS,或者说key对应值处于value的范围内。查询时处理 - key = key.substring(0, key.length() - 2); - } else if (key.endsWith("<>")) {//包含 json_contains,或者说value处于key对应值的范围内。查询时处理 - key = key.substring(0, key.length() - 2); - } else if (key.endsWith("()")) {//方法,查询完后处理,先用一个Map保存 - key = key.substring(0, key.length() - 2); - } else if (key.endsWith("@")) {//引用,引用对象查询完后处理。fillTarget中暂时不用处理,因为非GET请求都是由给定的id确定,不需要引用 - key = key.substring(0, key.length() - 1); - } else if (key.endsWith(">=")) {//比较。查询时处理 - key = key.substring(0, key.length() - 2); - } else if (key.endsWith("<=")) {//比较。查询时处理 - key = key.substring(0, key.length() - 2); - } else if (key.endsWith(">")) {//比较。查询时处理 - key = key.substring(0, key.length() - 1); - } else if (key.endsWith("<")) {//比较。查询时处理 - key = key.substring(0, key.length() - 1); - } else if (key.endsWith("+")) {//延长,PUT查询时处理 - if (method == PUT) {//不为PUT就抛异常 - key = key.substring(0, key.length() - 1); - } - } else if (key.endsWith("-")) {//缩减,PUT查询时处理 - if (method == PUT) {//不为PUT就抛异常 - key = key.substring(0, key.length() - 1); - } - } - - String last = null;//不用Logic优化代码,否则 key 可能变为 key| 导致 key=value 变成 key|=value 而出错 - if (RequestMethod.isQueryMethod(method)) {//逻辑运算符仅供GET,HEAD方法使用 - last = key.isEmpty() ? "" : key.substring(key.length() - 1); - if ("&".equals(last) || "|".equals(last) || "!".equals(last)) { - key = key.substring(0, key.length() - 1); - } else { - last = null;//避免key + StringUtil.getString(last)错误延长 - } - } - - //"User:toUser":User转换"toUser":User, User为查询同名Table得到的JSONObject。交给客户端处理更好 - if (isTableKey) {//不允许在column key中使用Type:key形式 - key = Pair.parseEntry(key, true).getKey();//table以左边为准 - } else { - key = Pair.parseEntry(key).getValue();//column以右边为准 - } - - if (verifyName && StringUtil.isName(key.startsWith("@") ? key.substring(1) : key) == false) { - throw new IllegalArgumentException(method + "请求,字符 " + originKey + " 不合法!" - + " key:value 中的key只能关键词 '@key' 或 'key[逻辑符][条件符]' 或 PUT请求下的 'key+' / 'key-' !"); - } - - if (saveLogic && last != null) { - key = key + last; - } - Log.i(TAG, "getRealKey return key = " + key); - return key; - } - - - public static interface IdCallback { - /** - * 为 post 请求新建 id, 只能是 Long 或 String - * - * @param method - * @param database - * @param schema - * @param table - * @return - */ - Object newId(RequestMethod method, String database, String schema, String table); - - /** - * 已废弃,最早 5.0.0 移除,改用 {@link #getIdKey(String, String, String, String)} - * - * @param database - * @param schema - * @param table - * @return - */ - @Deprecated - String getIdKey(String database, String schema, String table); - - /** - * 获取主键名 - * - * @param database - * @param schema - * @param table - * @return - */ - String getIdKey(String database, String schema, String datasource, String table); - - /** - * 已废弃,最早 5.0.0 移除,改用 {@link #getUserIdKey(String, String, String, String)} - * - * @param database - * @param schema - * @param table - * @return - */ - @Deprecated - String getUserIdKey(String database, String schema, String table); - - /** - * 获取 User 的主键名 - * - * @param database - * @param schema - * @param table - * @return - */ - String getUserIdKey(String database, String schema, String datasource, String table); - } - - public static interface Callback extends IdCallback { - /** - * 获取 SQLConfig 的实例 - * - * @param method - * @param database - * @param schema - * @param table - * @return - */ - SQLConfig getSQLConfig(RequestMethod method, String database, String schema, String table); - - /** - * combine 里的 key 在 request 中 value 为 null 或不存在,即 request 中缺少用来作为 combine 条件的 key: value - * - * @param combine - * @param key - * @param request - */ - public void onMissingKey4Combine(String name, JSONObject request, String combine, String item, String key) throws Exception; - } - - public static abstract class SimpleCallback implements Callback { - - - @Override - public Object newId(RequestMethod method, String database, String schema, String table) { - return System.currentTimeMillis(); - } - - @Override - public String getIdKey(String database, String schema, String table) { - return KEY_ID; - } - - @Override - public String getIdKey(String database, String schema, String datasource, String table) { - return getIdKey(database, schema, table); - } - - @Override - public String getUserIdKey(String database, String schema, String table) { - return KEY_USER_ID; - } - - @Override - public String getUserIdKey(String database, String schema, String datasource, String table) { - return getUserIdKey(database, schema, table); - } - - @Override - public void onMissingKey4Combine(String name, JSONObject request, String combine, String item, String key) throws Exception { - throw new IllegalArgumentException(name + ":{} 里的 @combine:value 中的value里 " + item + " 对应的条件 " + key + ":value 中 value 不能为 null!"); - } - - } + if (RequestMethod.isHeadMethod(method, true)) { + joinConfig.setMethod(GET); //子查询不能为 SELECT count(*) ,而应该是 SELECT momentId + joinConfig.setColumn(Arrays.asList(j.getKey())); //优化性能,不取非必要的字段 + + if (cacheConfig != null) { + cacheConfig.setMethod(GET); //子查询不能为 SELECT count(*) ,而应该是 SELECT momentId + cacheConfig.setColumn(Arrays.asList(j.getKey())); //优化性能,不取非必要的字段 + } + } + + j.setJoinConfig(joinConfig); + j.setCacheConfig(cacheConfig); + } + + config.setJoinList(joinList); + + return config; + } + + + + /**获取客户端实际需要的key + * verifyName = true + * @param method + * @param originKey + * @param isTableKey + * @param saveLogic 保留逻辑运算符 & | ! + * @return + */ + public static String getRealKey(RequestMethod method, String originKey + , boolean isTableKey, boolean saveLogic) throws Exception { + return getRealKey(method, originKey, isTableKey, saveLogic, true); + } + /**获取客户端实际需要的key + * @param method + * @param originKey + * @param isTableKey + * @param saveLogic 保留逻辑运算符 & | ! + * @param verifyName 验证key名是否符合代码变量/常量名 + * @return + */ + public static String getRealKey(RequestMethod method, String originKey + , boolean isTableKey, boolean saveLogic, boolean verifyName) throws Exception { + Log.i(TAG, "getRealKey saveLogic = " + saveLogic + "; originKey = " + originKey); + if (originKey == null || apijson.JSONObject.isArrayKey(originKey)) { + Log.w(TAG, "getRealKey originKey == null || apijson.JSONObject.isArrayKey(originKey) >> return originKey;"); + return originKey; + } + + String key = new String(originKey); + if (key.endsWith("$")) {//搜索 LIKE,查询时处理 + key = key.substring(0, key.length() - 1); + } + else if (key.endsWith("~")) {//匹配正则表达式 REGEXP,查询时处理 + key = key.substring(0, key.length() - 1); + if (key.endsWith("*")) {//忽略大小写 + key = key.substring(0, key.length() - 1); + } + } + else if (key.endsWith("%")) {//数字、文本、日期范围 BETWEEN AND + key = key.substring(0, key.length() - 1); + } + else if (key.endsWith("{}")) {//被包含 IN,或者说key对应值处于value的范围内。查询时处理 + key = key.substring(0, key.length() - 2); + } + else if (key.endsWith("}{")) {//被包含 EXISTS,或者说key对应值处于value的范围内。查询时处理 + key = key.substring(0, key.length() - 2); + } + else if (key.endsWith("<>")) {//包含 json_contains,或者说value处于key对应值的范围内。查询时处理 + key = key.substring(0, key.length() - 2); + } + else if (key.endsWith("()")) {//方法,查询完后处理,先用一个Map保存 + key = key.substring(0, key.length() - 2); + } + else if (key.endsWith("@")) {//引用,引用对象查询完后处理。fillTarget中暂时不用处理,因为非GET请求都是由给定的id确定,不需要引用 + key = key.substring(0, key.length() - 1); + } + else if (key.endsWith(">=")) {//比较。查询时处理 + key = key.substring(0, key.length() - 2); + } + else if (key.endsWith("<=")) {//比较。查询时处理 + key = key.substring(0, key.length() - 2); + } + else if (key.endsWith(">")) {//比较。查询时处理 + key = key.substring(0, key.length() - 1); + } + else if (key.endsWith("<")) {//比较。查询时处理 + key = key.substring(0, key.length() - 1); + } + else if (key.endsWith("+")) {//延长,PUT查询时处理 + if (method == PUT) {//不为PUT就抛异常 + key = key.substring(0, key.length() - 1); + } + } + else if (key.endsWith("-")) {//缩减,PUT查询时处理 + if (method == PUT) {//不为PUT就抛异常 + key = key.substring(0, key.length() - 1); + } + } + + String last = null;//不用Logic优化代码,否则 key 可能变为 key| 导致 key=value 变成 key|=value 而出错 + if (RequestMethod.isQueryMethod(method)) {//逻辑运算符仅供GET,HEAD方法使用 + last = key.isEmpty() ? "" : key.substring(key.length() - 1); + if ("&".equals(last) || "|".equals(last) || "!".equals(last)) { + key = key.substring(0, key.length() - 1); + } else { + last = null;//避免key + StringUtil.getString(last)错误延长 + } + } + + //"User:toUser":User转换"toUser":User, User为查询同名Table得到的JSONObject。交给客户端处理更好 + if (isTableKey) {//不允许在column key中使用Type:key形式 + key = Pair.parseEntry(key, true).getKey();//table以左边为准 + } else { + key = Pair.parseEntry(key).getValue();//column以右边为准 + } + + if (verifyName && StringUtil.isName(key.startsWith("@") ? key.substring(1) : key) == false) { + throw new IllegalArgumentException(method + "请求,字符 " + originKey + " 不合法!" + + " key:value 中的key只能关键词 '@key' 或 'key[逻辑符][条件符]' 或 PUT请求下的 'key+' / 'key-' !"); + } + + if (saveLogic && last != null) { + key = key + last; + } + Log.i(TAG, "getRealKey return key = " + key); + return key; + } + + + public static interface IdCallback { + /**为 post 请求新建 id, 只能是 Long 或 String + * @param method + * @param database + * @param schema + * @param table + * @return + */ + Object newId(RequestMethod method, String database, String schema, String table); + + /**已废弃,最早 5.0.0 移除,改用 {@link #getIdKey(String, String, String, String)} + * @param database + * @param schema + * @param table + * @return + */ + @Deprecated + String getIdKey(String database, String schema, String table); + + /**获取主键名 + * @param database + * @param schema + * @param table + * @return + */ + String getIdKey(String database, String schema, String datasource, String table); + + /**已废弃,最早 5.0.0 移除,改用 {@link #getUserIdKey(String, String, String, String)} + * @param database + * @param schema + * @param table + * @return + */ + @Deprecated + String getUserIdKey(String database, String schema, String table); + + /**获取 User 的主键名 + * @param database + * @param schema + * @param table + * @return + */ + String getUserIdKey(String database, String schema, String datasource, String table); + } + + public static interface Callback extends IdCallback { + /**获取 SQLConfig 的实例 + * @param method + * @param database + * @param schema + * @param table + * @return + */ + SQLConfig getSQLConfig(RequestMethod method, String database, String schema, String table); + + /**combine 里的 key 在 request 中 value 为 null 或不存在,即 request 中缺少用来作为 combine 条件的 key: value + * @param combine + * @param key + * @param request + */ + public void onMissingKey4Combine(String name, JSONObject request, String combine, String item, String key) throws Exception; + } + + public static abstract class SimpleCallback implements Callback { + + + @Override + public Object newId(RequestMethod method, String database, String schema, String table) { + return System.currentTimeMillis(); + } + + @Override + public String getIdKey(String database, String schema, String table) { + return KEY_ID; + } + + @Override + public String getIdKey(String database, String schema, String datasource, String table) { + return getIdKey(database, schema, table); + } + + @Override + public String getUserIdKey(String database, String schema, String table) { + return KEY_USER_ID; + } + + @Override + public String getUserIdKey(String database, String schema, String datasource, String table) { + return getUserIdKey(database, schema, table); + } + + @Override + public void onMissingKey4Combine(String name, JSONObject request, String combine, String item, String key) throws Exception { + throw new IllegalArgumentException(name + ":{} 里的 @combine:value 中的value里 " + item + " 对应的条件 " + key + ":value 中 value 不能为 null!"); + } + + } } From 817c2365bd38e28edbb4d096f692c06914b251bb Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Mon, 21 Jun 2021 15:58:50 +0800 Subject: [PATCH 0387/1181] =?UTF-8?q?=E6=A0=BC=E5=BC=8F=E5=8C=96=E9=83=A8?= =?UTF-8?q?=E5=88=86=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/apijson/orm/AbstractSQLConfig.java | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java index 7b14facfa..ce84abde7 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java @@ -1035,21 +1035,21 @@ public String getColumnString(boolean inSQLJoin) throws Exception { if (isPrepared() && column != null) { List raw = getRaw(); - boolean containRaw = raw != null && raw.contains(KEY_COLUMN); + boolean containRaw = raw != null && raw.contains(KEY_COLUMN); String origin; String alias; int index; - for (String c : column) { - if (containRaw) { - // 由于 HashMap 对 key 做了 hash 处理,所以 get 比 containsValue 更快 - if ("".equals(RAW_MAP.get(c)) || RAW_MAP.containsValue(c)) { // newSQLConfig 提前处理好的 - //排除@raw中的值,以避免使用date_format(date,'%Y-%m-%d %H:%i:%s') 时,冒号的解析出错 - column.remove(c); - continue; - } - } + for (String c : column) { + if (containRaw) { + // 由于 HashMap 对 key 做了 hash 处理,所以 get 比 containsValue 更快 + if ("".equals(RAW_MAP.get(c)) || RAW_MAP.containsValue(c)) { // newSQLConfig 提前处理好的 + //排除@raw中的值,以避免使用date_format(date,'%Y-%m-%d %H:%i:%s') 时,冒号的解析出错 + column.remove(c); + continue; + } + } index = c.lastIndexOf(":"); //StringUtil.split返回数组中,子项不会有null origin = index < 0 ? c : c.substring(0, index); From 38454e2bff0b015ae6d7ff06eaf64428141976af Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Mon, 21 Jun 2021 16:08:16 +0800 Subject: [PATCH 0388/1181] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E4=B8=80=E4=BD=8D?= =?UTF-8?q?=E8=B4=A1=E7=8C=AE=E8=80=85=EF=BC=8C=E6=84=9F=E8=B0=A2=E4=BF=AE?= =?UTF-8?q?=E5=A4=8D=20bug(=E5=88=86=E9=A1=B5=E6=9F=A5=E8=AF=A2=20query=3D?= =?UTF-8?q?2=20=E4=B8=8E=20@raw=20=E4=B8=AD=20SQL=20=E5=87=BD=E6=95=B0?= =?UTF-8?q?=E4=B8=8D=E5=85=BC=E5=AE=B9)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit https://github.com/Tencent/APIJSON/pull/253 --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 49ec6ac0b..ac3661783 100644 --- a/README.md +++ b/README.md @@ -262,6 +262,7 @@ https://github.com/Tencent/APIJSON/issues/187 +
Date: Mon, 21 Jun 2021 16:11:28 +0800 Subject: [PATCH 0389/1181] =?UTF-8?q?=E8=B4=A1=E7=8C=AE=E8=80=85=E5=90=8D?= =?UTF-8?q?=E5=8D=95=E6=96=B0=E5=A2=9E=20JieJo=EF=BC=8C=E6=84=9F=E8=B0=A2?= =?UTF-8?q?=E4=BF=AE=E5=A4=8D=20query=3D2=20=E4=B8=8E=20@raw=20=E4=B8=AD?= =?UTF-8?q?=20SQL=20=E5=87=BD=E6=95=B0=E4=B8=8D=E5=85=BC=E5=AE=B9=E7=9A=84?= =?UTF-8?q?=20bug?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit https://github.com/Tencent/APIJSON/pull/253 --- CONTRIBUTING.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index eb3ea87f3..0fafca62b 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -30,6 +30,7 @@ - [caohao-php](https://github.com/caohao-php)(腾讯工程师) - [Wscats](https://github.com/Wscats)(腾讯工程师) - [jun0315](https://github.com/jun0315)(腾讯工程师) +- [JieJo](https://github.com/JieJo) #### 其中特别致谢:
From cd83f506f2bc415a4c8c1292ef6dde37dd13a046 Mon Sep 17 00:00:00 2001 From: iceewei Date: Mon, 28 Jun 2021 14:56:01 +0800 Subject: [PATCH 0390/1181] =?UTF-8?q?=E8=8E=B7=E5=8F=96=E8=BF=9E=E6=8E=A5?= =?UTF-8?q?=E7=9A=84key=E5=A2=9E=E5=8A=A0datasource=E7=BB=B4=E5=BA=A6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- APIJSONORM/build.sh | 0 .../src/main/java/apijson/orm/AbstractSQLExecutor.java | 5 +++-- 2 files changed, 3 insertions(+), 2 deletions(-) create mode 100644 APIJSONORM/build.sh diff --git a/APIJSONORM/build.sh b/APIJSONORM/build.sh new file mode 100644 index 000000000..e69de29bb diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java index 15b178be4..d0f70ed45 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java @@ -732,12 +732,13 @@ public PreparedStatement setArgument(@NotNull SQLConfig config, @NotNull Prepare @NotNull @Override public Connection getConnection(@NotNull SQLConfig config) throws Exception { - connection = connectionMap.get(config.getDatabase()); + String connectionKey = config.getDatasource() + "-" + config.getDatabase() + connection = connectionMap.get(connectionKey); if (connection == null || connection.isClosed()) { Log.i(TAG, "select connection " + (connection == null ? " = null" : ("isClosed = " + connection.isClosed()))) ; // PostgreSQL 不允许 cross-database connection = DriverManager.getConnection(config.getDBUri(), config.getDBAccount(), config.getDBPassword()); - connectionMap.put(config.getDatabase(), connection); + connectionMap.put(connectionKey, connection); } int ti = getTransactionIsolation(); From f4e5ea487eb630f90ef2861f92aba9a739096d9b Mon Sep 17 00:00:00 2001 From: iceewei Date: Tue, 29 Jun 2021 11:06:21 +0800 Subject: [PATCH 0391/1181] =?UTF-8?q?=E8=8E=B7=E5=8F=96=E8=BF=9E=E6=8E=A5?= =?UTF-8?q?=E5=A2=9E=E5=8A=A0datasource=E5=8F=82=E6=95=B0=20&&=20=E5=9B=9E?= =?UTF-8?q?=E5=8C=85=E9=87=8C=E7=A7=BB=E9=99=A4@datasource?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java | 3 +++ APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java index ce84abde7..59be413d6 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java @@ -70,6 +70,7 @@ import apijson.orm.model.SysTable; import apijson.orm.model.Table; import apijson.orm.model.TestRecord; +import org.omg.PortableServer.REQUEST_PROCESSING_POLICY_ID; /**config sql for JSON Request * @author Lemon @@ -2965,6 +2966,7 @@ else if (id instanceof Subquery) {} request.remove(KEY_ROLE); request.remove(KEY_EXPLAIN); request.remove(KEY_CACHE); + request.remove(KEY_DATASOURCE); request.remove(KEY_DATABASE); request.remove(KEY_SCHEMA); request.remove(KEY_COMBINE); @@ -3217,6 +3219,7 @@ else if (whereList != null && whereList.contains(key)) { request.put(KEY_ROLE, role); request.put(KEY_EXPLAIN, explain); request.put(KEY_CACHE, cache); + request.put(KEY_DATASOURCE, datasource); request.put(KEY_SCHEMA, schema); request.put(KEY_COMBINE, combine); request.put(KEY_FROM, from); diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java index d0f70ed45..100fe7926 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java @@ -732,7 +732,7 @@ public PreparedStatement setArgument(@NotNull SQLConfig config, @NotNull Prepare @NotNull @Override public Connection getConnection(@NotNull SQLConfig config) throws Exception { - String connectionKey = config.getDatasource() + "-" + config.getDatabase() + String connectionKey = config.getDatasource() + "-" + config.getDatabase(); connection = connectionMap.get(connectionKey); if (connection == null || connection.isClosed()) { Log.i(TAG, "select connection " + (connection == null ? " = null" : ("isClosed = " + connection.isClosed()))) ; From 657bc89707c4afcb0558ac5b221079b1b2bdb22e Mon Sep 17 00:00:00 2001 From: iceewei Date: Tue, 29 Jun 2021 11:17:49 +0800 Subject: [PATCH 0392/1181] =?UTF-8?q?=E8=8E=B7=E5=8F=96=E8=BF=9E=E6=8E=A5?= =?UTF-8?q?=E5=A2=9E=E5=8A=A0datasource=E5=8F=82=E6=95=B0=20&&=20=E5=9B=9E?= =?UTF-8?q?=E5=8C=85=E9=87=8C=E7=A7=BB=E9=99=A4@datasource?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java | 1 - 1 file changed, 1 deletion(-) diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java index 59be413d6..1b63d9ba8 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java @@ -70,7 +70,6 @@ import apijson.orm.model.SysTable; import apijson.orm.model.Table; import apijson.orm.model.TestRecord; -import org.omg.PortableServer.REQUEST_PROCESSING_POLICY_ID; /**config sql for JSON Request * @author Lemon From 8dd76f3f5798f3131fee68decec6dd8cc5736f73 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Tue, 29 Jun 2021 15:01:42 +0800 Subject: [PATCH 0393/1181] =?UTF-8?q?=E7=94=9F=E6=80=81=E9=A1=B9=E7=9B=AE?= =?UTF-8?q?=E6=96=B0=E5=A2=9E=20APIJSON=20=E5=AD=A6=E4=B9=A0=E7=AC=94?= =?UTF-8?q?=E8=AE=B0=E5=92=8C=E6=BA=90=E7=A0=81=E8=A7=A3=E6=9E=90=EF=BC=8C?= =?UTF-8?q?=E6=84=9F=E8=B0=A2=E8=B4=A1=E7=8C=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit https://github.com/rainboy-learn/apijson-learn --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index ac3661783..b782365bb 100644 --- a/README.md +++ b/README.md @@ -404,6 +404,8 @@ https://github.com/Tencent/APIJSON/blob/master/CONTRIBUTING.md [FfApiJson](https://gitee.com/own_3_0/ff-api-json) 用 JSON 格式直接生成 SQL,借鉴 APIJSON 支持多数据源 +[apijson-learn](https://github.com/rainboy-learn/apijson-learn) APIJSON 学习笔记和源码解析 + [apijson-sample](https://gitee.com/greyzeng/apijson-sample) APIJSON 简单使用 Demo 及教程 [apijson-examples](https://gitee.com/drone/apijson-examples) APIJSON 的前端、业务后端、管理后端 Demo From abbfcf4d0bad85353a532deb81c62e82cb53ef46 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Wed, 7 Jul 2021 11:33:10 +0800 Subject: [PATCH 0394/1181] =?UTF-8?q?=E7=94=9F=E6=80=81=E9=A1=B9=E7=9B=AE?= =?UTF-8?q?=E6=96=B0=E5=A2=9E=20=20APIJSONDemo=5FClickHouse=EF=BC=8C?= =?UTF-8?q?=E6=89=93=E5=93=8D=20OLAP=20=E7=AC=AC=E4=B8=80=E6=9E=AA?= =?UTF-8?q?=EF=BC=8C=E6=84=9F=E8=B0=A2=E4=BD=9C=E8=80=85=E7=9A=84=E8=B4=A1?= =?UTF-8?q?=E7=8C=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit https://github.com/chenyanlann/APIJSONDemo_ClickHouse --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index b782365bb..ee66aec17 100644 --- a/README.md +++ b/README.md @@ -418,6 +418,8 @@ https://github.com/Tencent/APIJSON/blob/master/CONTRIBUTING.md [ApiJsonByJFinal](https://gitee.com/zhiyuexin/ApiJsonByJFinal) 整合 APIJSON 和 JFinal 的 Demo +[APIJSONDemo_ClickHouse](https://github.com/chenyanlann/APIJSONDemo_ClickHouse) APIJSON + SpringBoot 连接 ClickHouse 使用的 Demo + [apijson-builder](https://github.com/pengxianggui/apijson-builder) 一个方便为 APIJSON 构建 RESTful 请求的 JavaScript 库 [AbsGrade](https://github.com/APIJSON/AbsGrade) 列表级联算法,支持微信朋友圈单层评论、QQ空间双层评论、百度网盘多层(无限层)文件夹等 From ef3971ac3f646c4dd7a10bfb165d709f57e83fcb Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Mon, 19 Jul 2021 16:08:47 +0800 Subject: [PATCH 0395/1181] =?UTF-8?q?=E8=B0=83=E6=95=B4=E8=A7=86=E9=A2=91?= =?UTF-8?q?=E6=95=99=E7=A8=8B=E7=9A=84=E9=93=BE=E6=8E=A5=EF=BC=8C=E4=BC=98?= =?UTF-8?q?=E9=85=B7=E5=AE=98=E6=96=B9=E5=8F=B7=E6=94=B9=E4=B8=BA=20Bilibi?= =?UTF-8?q?li=20=E5=AE=98=E6=96=B9=E5=8F=B7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit https://space.bilibili.com/437134249?spm_id_from=333.788.b_765f7570696e666f.2 --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index ee66aec17..686d3239b 100644 --- a/README.md +++ b/README.md @@ -33,7 +33,7 @@ This source code is licensed under the Apache License Version 2.0

English  通用文档 - 视频教程 + 视频教程 在线体验

From d63fb9dd659ea3ef57ca13db3375b3d4207db5fe Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Mon, 19 Jul 2021 16:12:20 +0800 Subject: [PATCH 0396/1181] =?UTF-8?q?=E8=B0=83=E6=95=B4=E8=A7=86=E9=A2=91?= =?UTF-8?q?=E6=95=99=E7=A8=8B=E9=93=BE=E6=8E=A5=EF=BC=8CBilibili=20?= =?UTF-8?q?=E5=AE=98=E6=96=B9=E5=8F=B7=E6=94=B9=E4=B8=BA=E5=85=A8=E7=AB=99?= =?UTF-8?q?=E6=90=9C=E7=B4=A2=20APIJSON?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit https://search.bilibili.com/all?keyword=APIJSON&from_source=webtop_search&spm_id_from=333.999 --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 686d3239b..4e04cf505 100644 --- a/README.md +++ b/README.md @@ -33,7 +33,7 @@ This source code is licensed under the Apache License Version 2.0

English  通用文档 - 视频教程 + 视频教程 在线体验

From c113c2300f763bc750abeee2730e35be1d0d8ed5 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Thu, 22 Jul 2021 00:21:24 +0800 Subject: [PATCH 0397/1181] Update README.md --- README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.md b/README.md index 4e04cf505..1ae1832d5 100644 --- a/README.md +++ b/README.md @@ -220,6 +220,10 @@ https://github.com/Tencent/APIJSON/issues/36 如果您在使用 APIJSON,请让我们知道,您的使用对我们非常重要(按登记顺序排列):
https://github.com/Tencent/APIJSON/issues/187
+ + + +
From 72a6bc6161dbc0e26173a731e144d94d45677067 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Thu, 22 Jul 2021 00:33:05 +0800 Subject: [PATCH 0398/1181] Update README.md --- README.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 1ae1832d5..671f6c945 100644 --- a/README.md +++ b/README.md @@ -220,10 +220,11 @@ https://github.com/Tencent/APIJSON/issues/36 如果您在使用 APIJSON,请让我们知道,您的使用对我们非常重要(按登记顺序排列):
https://github.com/Tencent/APIJSON/issues/187
- - - + + +
+ From a252a7630194056931e360867bf0fff83213e745 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Thu, 22 Jul 2021 00:39:39 +0800 Subject: [PATCH 0399/1181] =?UTF-8?q?=E7=94=9F=E6=80=81=E9=A1=B9=E7=9B=AE?= =?UTF-8?q?=E6=96=B0=E5=A2=9E=20Go=20=E7=89=88=20APIJSON=20=EF=BC=8C?= =?UTF-8?q?=E6=94=AF=E6=8C=81=E5=8D=95=E8=A1=A8=E3=80=81=E6=95=B0=E7=BB=84?= =?UTF-8?q?=E3=80=81=E5=A4=9A=E8=A1=A8=E4=B8=80=E5=AF=B9=E4=B8=80=E5=92=8C?= =?UTF-8?q?=E5=A4=9A=E8=A1=A8=E4=B8=80=E5=AF=B9=E5=A4=9A=20=E5=85=B3?= =?UTF-8?q?=E8=81=94=E6=9F=A5=E8=AF=A2=20=E7=AD=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit https://gitee.com/tiangao/apijson-go https://github.com/keepfoo/apijson-go --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 671f6c945..771434257 100644 --- a/README.md +++ b/README.md @@ -397,6 +397,8 @@ https://github.com/Tencent/APIJSON/blob/master/CONTRIBUTING.md [APIJSON.NET](https://github.com/liaozb/APIJSON.NET) C# 版 APIJSON ,支持 MySQL, PostgreSQL, SQL Server, Oracle, SQLite +[apijson-go](https://gitee.com/tiangao/apijson-go) Go 版 APIJSON ,支持单表查询、数组查询、多表一对一关联查询、多表一对多关联查询 等 + [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 等 From 63d117f4ff1f5109ccf00c20e81e453a26566365 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Thu, 22 Jul 2021 01:12:52 +0800 Subject: [PATCH 0400/1181] =?UTF-8?q?=E8=B4=A1=E7=8C=AE=E8=80=85=E4=BB=AC?= =?UTF-8?q?=E6=96=B0=E5=A2=9E=E5=AF=B9=205=20=E4=B8=AA=E8=85=BE=E8=AE=AF?= =?UTF-8?q?=E5=B7=A5=E7=A8=8B=E5=B8=88=E3=80=811=20=E4=B8=AA=E7=9F=A5?= =?UTF-8?q?=E4=B9=8E=E5=9F=BA=E7=A1=80=E7=A0=94=E5=8F=91=E6=9E=B6=E6=9E=84?= =?UTF-8?q?=E5=B8=88=E3=80=811=20=E4=B8=AA=E5=AD=97=E8=8A=82=E8=B7=B3?= =?UTF-8?q?=E5=8A=A8=E5=B7=A5=E7=A8=8B=E5=B8=88=20=E7=9A=84=E8=AF=B4?= =?UTF-8?q?=E6=98=8E?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit https://github.com/Tencent/APIJSON#%E8%B4%A1%E7%8C%AE%E8%80%85%E4%BB%AC --- README.md | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 771434257..7c7f2a34a 100644 --- a/README.md +++ b/README.md @@ -241,7 +241,8 @@ https://github.com/Tencent/APIJSON/issues/187 ### 贡献者们 -主项目 APIJSON 的贡献者们和 生态周边项目 的作者们: +主项目 APIJSON 的贡献者们(5 个腾讯工程师、1 个知乎基础研发架构师 等):
+https://github.com/Tencent/APIJSON/blob/master/CONTRIBUTING.md
@@ -268,8 +269,13 @@ https://github.com/Tencent/APIJSON/issues/187 - -
+
+
+ +生态周边项目的作者们(1 个腾讯工程师、1 个字节跳动工程师 等):
+https://github.com/search?o=desc&q=apijson&s=stars&type=Repositories
+https://search.gitee.com/?skin=rec&type=repository&q=apijson&sort=stars_count
+
Date: Fri, 30 Jul 2021 17:33:55 +0800 Subject: [PATCH 0401/1181] =?UTF-8?q?=E9=A6=96=E9=A1=B5=E6=96=B0=E5=A2=9E?= =?UTF-8?q?=20=E8=85=BE=E8=AE=AF=E7=8A=80=E7=89=9B=E9=B8=9F=E5=BC=80?= =?UTF-8?q?=E6=BA=90=E4=BA=BA=E6=89=8D=E5=9F=B9=E5=85=BB=E8=AE=A1=E5=88=92?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit https://github.com/Tencent/APIJSON#%E8%85%BE%E8%AE%AF%E7%8A%80%E7%89%9B%E9%B8%9F%E5%BC%80%E6%BA%90%E4%BA%BA%E6%89%8D%E5%9F%B9%E5%85%BB%E8%AE%A1%E5%88%92 --- README.md | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 7c7f2a34a..a5dec9aaf 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@ This source code is licensed under the Apache License Version 2.0
APIJSON -

零代码、热更新、自动化 ORM 库
🚀 后端接口和文档零代码,前端(客户端) 定制返回 JSON 的数据和结构

+

零代码、热更新、全自动 ORM 库
🚀 后端接口和文档零代码,前端(客户端) 定制返回 JSON 的数据和结构

@@ -444,7 +444,12 @@ https://github.com/Tencent/APIJSON/blob/master/CONTRIBUTING.md 感谢热心的作者们的贡献,点 ⭐Star 支持下他们吧。 +### 腾讯犀牛鸟开源人才培养计划 + +#### zhouzuobiao 1.完善入门介绍视频 +https://lexiangla.com/teams/k100046/classes/a4eba9f4b6d711eba2ec268dd73d15f1?type=0&company_from=79350bd4d06911ea91f05254002f1020 + ### 持续更新 https://github.com/Tencent/APIJSON/commits/master From 914b50af113a22dc0b4e1e3aa266792525dfea60 Mon Sep 17 00:00:00 2001 From: "bin.li" <626732147@qq.com> Date: Fri, 30 Jul 2021 17:38:37 +0800 Subject: [PATCH 0402/1181] =?UTF-8?q?fix:bug=E4=BF=AE=E5=A4=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1:修复List集合使用remove方法引发ConcurrentModificationException 2:修复remove RAW_MAP字段后,引发自定义column列@raw报错 --- APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java index 1b63d9ba8..fc20567a2 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java @@ -1046,7 +1046,7 @@ public String getColumnString(boolean inSQLJoin) throws Exception { // 由于 HashMap 对 key 做了 hash 处理,所以 get 比 containsValue 更快 if ("".equals(RAW_MAP.get(c)) || RAW_MAP.containsValue(c)) { // newSQLConfig 提前处理好的 //排除@raw中的值,以避免使用date_format(date,'%Y-%m-%d %H:%i:%s') 时,冒号的解析出错 - column.remove(c); + //column.remove(c); continue; } } From d5047ebaef8f4586412876074e6b6325970182ec Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Fri, 30 Jul 2021 18:20:55 +0800 Subject: [PATCH 0403/1181] Update README.md --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index a5dec9aaf..0a9a1357a 100644 --- a/README.md +++ b/README.md @@ -447,6 +447,7 @@ https://github.com/Tencent/APIJSON/blob/master/CONTRIBUTING.md ### 腾讯犀牛鸟开源人才培养计划 #### zhouzuobiao 1.完善入门介绍视频 +APIJSON- 后端零代码接口和文档ORM库 https://lexiangla.com/teams/k100046/classes/a4eba9f4b6d711eba2ec268dd73d15f1?type=0&company_from=79350bd4d06911ea91f05254002f1020 From 730ba4d73780a77441e1b250faa37e642657e315 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Fri, 30 Jul 2021 18:21:13 +0800 Subject: [PATCH 0404/1181] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 0a9a1357a..0fe768917 100644 --- a/README.md +++ b/README.md @@ -447,7 +447,7 @@ https://github.com/Tencent/APIJSON/blob/master/CONTRIBUTING.md ### 腾讯犀牛鸟开源人才培养计划 #### zhouzuobiao 1.完善入门介绍视频 -APIJSON- 后端零代码接口和文档ORM库 +APIJSON- 后端零代码接口和文档ORM库
https://lexiangla.com/teams/k100046/classes/a4eba9f4b6d711eba2ec268dd73d15f1?type=0&company_from=79350bd4d06911ea91f05254002f1020 From eb5230e8921f6cce6a425c7cc1831ee9045127fb Mon Sep 17 00:00:00 2001 From: kenlig <28685375+kenlig@users.noreply.github.com> Date: Fri, 30 Jul 2021 19:05:04 +0800 Subject: [PATCH 0405/1181] add:Add introduction to APIJSON-Java-Server Demo --- README.md | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 0fe768917..cb539fe39 100644 --- a/README.md +++ b/README.md @@ -450,7 +450,18 @@ https://github.com/Tencent/APIJSON/blob/master/CONTRIBUTING.md APIJSON- 后端零代码接口和文档ORM库
https://lexiangla.com/teams/k100046/classes/a4eba9f4b6d711eba2ec268dd73d15f1?type=0&company_from=79350bd4d06911ea91f05254002f1020 - +#### zhaoqiming 1.完善入门介绍视频 +APIJSON 后端教程(1):简介 +https://www.bilibili.com/video/BV1vL411W7yd +APIJSON 后端教程(2):数据库 +https://www.bilibili.com/video/BV1eB4y1N77s +APIJSON 后端教程(3):Demo +https://www.bilibili.com/video/BV1FX4y1c7ug +APIJSON 后端教程(4):Boot +https://www.bilibili.com/video/BV18h411z7FK +APIJSON 后端教程(5):Final +https://www.bilibili.com/video/BV1GM4y1N7XJ + ### 持续更新 https://github.com/Tencent/APIJSON/commits/master From f718852646b58c20201095698425aed758e6462c Mon Sep 17 00:00:00 2001 From: kenlig <28685375+kenlig@users.noreply.github.com> Date: Fri, 30 Jul 2021 19:06:00 +0800 Subject: [PATCH 0406/1181] Add introduction to APIJSON-Java-Server Demo --- README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.md b/README.md index cb539fe39..59999b0b3 100644 --- a/README.md +++ b/README.md @@ -453,12 +453,16 @@ https://lexiangla.com/teams/k100046/classes/a4eba9f4b6d711eba2ec268dd73d15f1?typ #### zhaoqiming 1.完善入门介绍视频 APIJSON 后端教程(1):简介 https://www.bilibili.com/video/BV1vL411W7yd + APIJSON 后端教程(2):数据库 https://www.bilibili.com/video/BV1eB4y1N77s + APIJSON 后端教程(3):Demo https://www.bilibili.com/video/BV1FX4y1c7ug + APIJSON 后端教程(4):Boot https://www.bilibili.com/video/BV18h411z7FK + APIJSON 后端教程(5):Final https://www.bilibili.com/video/BV1GM4y1N7XJ From 5bb9df6bd3ac73966f180e06f495fca7e93fdd2b Mon Sep 17 00:00:00 2001 From: andream7 <60541766+andream7@users.noreply.github.com> Date: Fri, 30 Jul 2021 23:39:09 +0800 Subject: [PATCH 0407/1181] Update README.md Add apijson-db2 demo --- README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.md b/README.md index 0fe768917..50801efec 100644 --- a/README.md +++ b/README.md @@ -450,6 +450,10 @@ https://github.com/Tencent/APIJSON/blob/master/CONTRIBUTING.md APIJSON- 后端零代码接口和文档ORM库
https://lexiangla.com/teams/k100046/classes/a4eba9f4b6d711eba2ec268dd73d15f1?type=0&company_from=79350bd4d06911ea91f05254002f1020 +#### zhangshukun 2.接入 presto/hive/clickhouse/db2 任意一个 +APIJSON-Demo接入db2
+https://github.com/andream7/apijson-db2 + ### 持续更新 https://github.com/Tencent/APIJSON/commits/master From 7a1d1a07e996e71e9a04b58b05cf900df8236fcf Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Sat, 31 Jul 2021 01:49:53 +0800 Subject: [PATCH 0408/1181] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E5=9C=86=E9=80=9A?= =?UTF-8?q?=E5=B7=A5=E7=A8=8B=E5=B8=88=E7=AD=89=203=20=E4=B8=AA=E8=B4=A1?= =?UTF-8?q?=E7=8C=AE=E8=80=85=EF=BC=9B=E6=96=B0=E5=A2=9E=20apijson-go=20?= =?UTF-8?q?=E4=BD=9C=E8=80=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/README.md b/README.md index 4bb2105f3..1fe7d4344 100644 --- a/README.md +++ b/README.md @@ -269,6 +269,10 @@ https://github.com/Tencent/APIJSON/blob/master/CONTRIBUTING.md
+ + + +


@@ -279,6 +283,8 @@ https://search.gitee.com/?skin=rec&type=repository&q=apijson&sort=stars_count + From 5ba9132aaeaced2b9e18d824f01c5f4f5e510865 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Sat, 31 Jul 2021 01:52:13 +0800 Subject: [PATCH 0409/1181] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E5=9C=86=E9=80=9A?= =?UTF-8?q?=E5=B7=A5=E7=A8=8B=E5=B8=88=E7=AD=89=203=20=E4=B8=AA=E8=B4=A1?= =?UTF-8?q?=E7=8C=AE=E8=80=85=EF=BC=9B=E6=96=B0=E5=A2=9E=20apijson-go=20?= =?UTF-8?q?=E4=BD=9C=E8=80=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit https://gitee.com/tiangao/apijson-go --- README.md | 5 ----- 1 file changed, 5 deletions(-) diff --git a/README.md b/README.md index 1fe7d4344..a3e9d6e30 100644 --- a/README.md +++ b/README.md @@ -269,7 +269,6 @@ https://github.com/Tencent/APIJSON/blob/master/CONTRIBUTING.md
- @@ -452,10 +451,6 @@ https://github.com/Tencent/APIJSON/blob/master/CONTRIBUTING.md ### 腾讯犀牛鸟开源人才培养计划 -#### zhouzuobiao 1.完善入门介绍视频 -APIJSON- 后端零代码接口和文档ORM库
-https://lexiangla.com/teams/k100046/classes/a4eba9f4b6d711eba2ec268dd73d15f1?type=0&company_from=79350bd4d06911ea91f05254002f1020 - #### zhangshukun 2.接入 presto/hive/clickhouse/db2 任意一个 APIJSON-Demo接入db2
https://github.com/andream7/apijson-db2 From ae7bfa6b64cbef8f0c9c0e81fb6f437643011801 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Sat, 31 Jul 2021 02:00:23 +0800 Subject: [PATCH 0410/1181] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index a3e9d6e30..d4d654b2c 100644 --- a/README.md +++ b/README.md @@ -241,7 +241,7 @@ https://github.com/Tencent/APIJSON/issues/187 ### 贡献者们 -主项目 APIJSON 的贡献者们(5 个腾讯工程师、1 个知乎基础研发架构师 等):
+主项目 APIJSON 的贡献者们(5 个腾讯工程师、1 个知乎基础研发架构师、1 个圆通工程师 等):
https://github.com/Tencent/APIJSON/blob/master/CONTRIBUTING.md
Date: Sat, 31 Jul 2021 02:02:23 +0800 Subject: [PATCH 0411/1181] Update README.md --- README.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/README.md b/README.md index d4d654b2c..c18a27667 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,3 @@ -[【腾讯犀牛鸟开源人才培养计划】开始啦!加入我们开源共建吧~](https://github.com/Tencent/APIJSON/issues/229) - Tencent is pleased to support the open source community by making APIJSON available.
Copyright (C) 2020 THL A29 Limited, a Tencent company. All rights reserved.
This source code is licensed under the Apache License Version 2.0
@@ -450,6 +448,7 @@ https://github.com/Tencent/APIJSON/blob/master/CONTRIBUTING.md 感谢热心的作者们的贡献,点 ⭐Star 支持下他们吧。 ### 腾讯犀牛鸟开源人才培养计划 +https://github.com/Tencent/APIJSON/issues/229 #### zhangshukun 2.接入 presto/hive/clickhouse/db2 任意一个 APIJSON-Demo接入db2
From 3451f346019d77e56395715e3bfe4b09338bb38e Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Sat, 31 Jul 2021 02:47:07 +0800 Subject: [PATCH 0412/1181] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index c18a27667..3bf01dd29 100644 --- a/README.md +++ b/README.md @@ -383,7 +383,7 @@ https://github.com/Tencent/APIJSON/blob/master/CONTRIBUTING.md [APIJSON复杂业务深入实践(类似12306订票系统)](https://blog.csdn.net/aa330233789/article/details/105309571) -[全国行政区划数据抓取与处理](https://my.oschina.net/hwxia/blog/4999897) +[全国行政区划数据抓取与处理](https://www.xlongwei.com/detail/21032616) ### 生态项目 [APIJSON-Demo](https://github.com/APIJSON/APIJSON-Demo) APIJSON 各种语言、各种框架 的 使用示例项目、上手文档、测试数据 SQL 文件 等 From 2fc8526863fe18157010b1b613cb79c4fd76774e Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Sat, 31 Jul 2021 02:55:00 +0800 Subject: [PATCH 0413/1181] =?UTF-8?q?=E5=8D=87=E7=BA=A7=E7=89=88=E6=9C=AC?= =?UTF-8?q?=E4=B8=BA=204.7.1=EF=BC=9B=E4=BC=98=E5=8C=96=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- APIJSONORM/pom.xml | 2 +- APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/APIJSONORM/pom.xml b/APIJSONORM/pom.xml index 4c1041bc9..ca67738f9 100755 --- a/APIJSONORM/pom.xml +++ b/APIJSONORM/pom.xml @@ -5,7 +5,7 @@ apijson.orm apijson-orm - 4.6.7 + 4.7.1 jar APIJSONORM diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java index fc20567a2..cde3deb20 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java @@ -1525,7 +1525,7 @@ public static String getLimitString(int page, int count, boolean isTSQL, boolean int offset = getOffset(page, count); if (isTSQL) { // OFFSET FECTH 中所有关键词都不可省略, 另外 Oracle 数据库使用子查询加 where 分页 - return isOracle? " WHERE ROWNUM BETWEEN "+ offset +" AND "+ (offset + count): " OFFSET " + offset + " ROWS FETCH FIRST " + count + " ROWS ONLY"; + return isOracle ? " WHERE ROWNUM BETWEEN "+ offset +" AND "+ (offset + count) : " OFFSET " + offset + " ROWS FETCH FIRST " + count + " ROWS ONLY"; } return " LIMIT " + count + (offset <= 0 ? "" : " OFFSET " + offset); // DELETE, UPDATE 不支持 OFFSET From 042716d3e28125f2f8d4833c32513e72518d9467 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Sat, 31 Jul 2021 03:22:23 +0800 Subject: [PATCH 0414/1181] =?UTF-8?q?=E5=88=A0=E9=99=A4=E5=A4=9A=E4=BD=99?= =?UTF-8?q?=E7=9A=84=E6=96=87=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- APIJSONORM/build.sh | 0 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 APIJSONORM/build.sh diff --git a/APIJSONORM/build.sh b/APIJSONORM/build.sh deleted file mode 100644 index e69de29bb..000000000 From b0e10107a6d7ad04933eed2401c034de713f8470 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Sat, 31 Jul 2021 03:32:46 +0800 Subject: [PATCH 0415/1181] =?UTF-8?q?=E8=B4=A1=E7=8C=AE=E8=80=85=E5=90=8D?= =?UTF-8?q?=E5=8D=95=E6=96=B0=E5=A2=9E=E5=9C=86=E9=80=9A=E5=B7=A5=E7=A8=8B?= =?UTF-8?q?=E5=B8=88=E7=AD=89=203=20=E4=BA=BA=EF=BC=8C=E6=84=9F=E8=B0=A2?= =?UTF-8?q?=E8=B4=A1=E7=8C=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit https://github.com/Tencent/APIJSON/pull/278 --- CONTRIBUTING.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 0fafca62b..b737956dd 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -31,6 +31,9 @@ - [Wscats](https://github.com/Wscats)(腾讯工程师) - [jun0315](https://github.com/jun0315)(腾讯工程师) - [JieJo](https://github.com/JieJo) +- [yeyuezhishou](https://github.com/yeyuezhishou)(圆通工程师) +- [kenlig](https://github.com/kenlig) +- [andream7](https://github.com/andream7) #### 其中特别致谢:
From 3ccd9674ce572e36907dd961ae9386e31abc2483 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Sun, 1 Aug 2021 01:02:11 +0800 Subject: [PATCH 0416/1181] =?UTF-8?q?=E8=A7=A3=E5=86=B3=E8=BF=9C=E7=A8=8B?= =?UTF-8?q?=E5=87=BD=E6=95=B0=E6=8B=BF=E4=B8=8D=E5=88=B0=E6=9C=89=E6=95=88?= =?UTF-8?q?=E7=9A=84=E5=BD=93=E5=89=8D=E5=AF=B9=E8=B1=A1=EF=BC=8C=E5=AF=BC?= =?UTF-8?q?=E8=87=B4=E6=A0=A1=E9=AA=8C=E5=8F=82=E6=95=B0=E5=AE=B9=E6=98=93?= =?UTF-8?q?=E6=94=BE=E8=A1=8C=E7=AD=89=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/main/java/apijson/orm/AbstractObjectParser.java | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractObjectParser.java b/APIJSONORM/src/main/java/apijson/orm/AbstractObjectParser.java index 5244abc9a..673348935 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractObjectParser.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractObjectParser.java @@ -774,12 +774,10 @@ public void onFunctionResponse(String type) throws Exception { //解析函数function Set> functionSet = map == null ? null : map.entrySet(); if (functionSet != null && functionSet.isEmpty() == false) { - // JSONObject json = "-".equals(type) ? request : response; // key-():function 是实时执行,而不是在这里批量执行 + JSONObject json = "-".equals(type) ? request : response; // key-():function 是实时执行,而不是在这里批量执行 for (Entry entry : functionSet) { - - // parseFunction(json, entry.getKey(), entry.getValue()); - parseFunction(entry.getKey(), entry.getValue(), parentPath, name, response); + parseFunction(entry.getKey(), entry.getValue(), parentPath, name, json); } } } From 646bed418655a4ff80d3c4c883e310dd64447b9d Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Sun, 1 Aug 2021 01:19:29 +0800 Subject: [PATCH 0417/1181] =?UTF-8?q?=E8=A7=A3=E5=86=B3=E6=93=8D=E4=BD=9C?= =?UTF-8?q?=E7=AC=A6=20UNIQUE=20=E6=A0=A1=E9=AA=8C=E4=B8=8D=E5=85=81?= =?UTF-8?q?=E8=AE=B8=E9=87=8D=E5=A4=8D=E5=A4=B1=E6=95=88?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/main/java/apijson/orm/AbstractVerifier.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractVerifier.java b/APIJSONORM/src/main/java/apijson/orm/AbstractVerifier.java index 30aeb5f15..448b54155 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractVerifier.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractVerifier.java @@ -447,14 +447,14 @@ public void verifyRepeat(String table, String key, Object value, long exceptId) if (exceptId > 0) {//允许修改自己的属性为该属性原来的值 request.put(JSONRequest.KEY_ID + "!", exceptId); // FIXME 这里 id 写死了,不支持自定义 } - JSONObject repeat = createParser().setMethod(GET).setNeedVerify(true).parseResponse( + JSONObject repeat = createParser().setMethod(HEAD).setNeedVerify(true).parseResponse( new JSONRequest(table, request) ); repeat = repeat == null ? null : repeat.getJSONObject(table); if (repeat == null) { throw new Exception("服务器内部错误 verifyRepeat repeat == null"); } - if (repeat.getIntValue(JSONResponse.KEY_CODE) > 0) { + if (repeat.getIntValue(JSONResponse.KEY_COUNT) > 0) { throw new ConflictException(key + ": " + value + " 已经存在,不能重复!"); } } @@ -1424,7 +1424,7 @@ public static void verifyRepeat(String table, String key, Object value, long exc String finalIdKey = StringUtil.isEmpty(idKey, false) ? apijson.JSONObject.KEY_ID : idKey; - SQLConfig config = creator.createSQLConfig().setMethod(RequestMethod.GET).setCount(1).setPage(0); + SQLConfig config = creator.createSQLConfig().setMethod(RequestMethod.HEAD).setCount(1).setPage(0); config.setTable(table); if (exceptId > 0) { //允许修改自己的属性为该属性原来的值 config.putWhere(finalIdKey + "!", exceptId, false); @@ -1437,7 +1437,7 @@ public static void verifyRepeat(String table, String key, Object value, long exc if (result == null) { throw new Exception("服务器内部错误 verifyRepeat result == null"); } - if (result.getIntValue(JSONResponse.KEY_CODE) > 0) { + if (result.getIntValue(JSONResponse.KEY_COUNT) > 0) { throw new ConflictException(key + ": " + value + " 已经存在,不能重复!"); } } finally { From af9a1fc0c1ffc6ccaa2a12dc049649e7541fc54c Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Sun, 1 Aug 2021 01:47:10 +0800 Subject: [PATCH 0418/1181] =?UTF-8?q?=E5=8D=87=E7=BA=A7=E7=89=88=E6=9C=AC?= =?UTF-8?q?=E5=8F=B7=E4=B8=BA=204.7.2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- APIJSONORM/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/APIJSONORM/pom.xml b/APIJSONORM/pom.xml index ca67738f9..00b542ab0 100755 --- a/APIJSONORM/pom.xml +++ b/APIJSONORM/pom.xml @@ -5,7 +5,7 @@ apijson.orm apijson-orm - 4.7.1 + 4.7.2 jar APIJSONORM From 3d00f906976a48be1e88ad3d3c1719c58c86639c Mon Sep 17 00:00:00 2001 From: qiujunlin <1757591067@qq.com> Date: Sun, 1 Aug 2021 14:55:47 +0800 Subject: [PATCH 0419/1181] readme --- README.md | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 0fe768917..d74bfbdae 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ This source code is licensed under the Apache License Version 2.0

APIJSON

- +

零代码、热更新、全自动 ORM 库
🚀 后端接口和文档零代码,前端(客户端) 定制返回 JSON 的数据和结构

@@ -224,7 +224,7 @@ https://github.com/Tencent/APIJSON/issues/187
- + @@ -271,7 +271,7 @@ https://github.com/Tencent/APIJSON/blob/master/CONTRIBUTING.md


- + 生态周边项目的作者们(1 个腾讯工程师、1 个字节跳动工程师 等):
https://github.com/search?o=desc&q=apijson&s=stars&type=Repositories
https://search.gitee.com/?skin=rec&type=repository&q=apijson&sort=stars_count
@@ -445,13 +445,20 @@ https://github.com/Tencent/APIJSON/blob/master/CONTRIBUTING.md 感谢热心的作者们的贡献,点 ⭐Star 支持下他们吧。 ### 腾讯犀牛鸟开源人才培养计划 - -#### zhouzuobiao 1.完善入门介绍视频 + +#### qiujunlin **2.接入 presto/hive/clickhouse/db2 任意一个** + +APIJSON 接入 clickhouse 使用demo + +https://github.com/qiujunlin/APIJSONDemo + +#### zhouzuobiao 1.完善入门介绍视频 + APIJSON- 后端零代码接口和文档ORM库
https://lexiangla.com/teams/k100046/classes/a4eba9f4b6d711eba2ec268dd73d15f1?type=0&company_from=79350bd4d06911ea91f05254002f1020 - ### 持续更新 + https://github.com/Tencent/APIJSON/commits/master ### 工蜂主页 From b36739c709e91ccf6b66c7c72fe830be359545df Mon Sep 17 00:00:00 2001 From: Hanxu2018 Date: Sun, 1 Aug 2021 16:12:33 +0800 Subject: [PATCH 0420/1181] Update README.md --- README.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/README.md b/README.md index 190115912..ba38c1b97 100644 --- a/README.md +++ b/README.md @@ -449,6 +449,7 @@ https://github.com/Tencent/APIJSON/blob/master/CONTRIBUTING.md ### 腾讯犀牛鸟开源人才培养计划 https://github.com/Tencent/APIJSON/issues/229 + #### qiujunlin **2.接入 presto/hive/clickhouse/db2 任意一个** @@ -458,6 +459,14 @@ https://github.com/qiujunlin/APIJSONDemo #### zhangshukun 2.接入 presto/hive/clickhouse/db2 任意一个 APIJSON-Demo接入db2
https://github.com/andream7/apijson-db2 + +#### hanxu 1.完善入门介绍视频 +重构 APIJSON 文档 +https://hanxu2018.github.io/APIJSON-DOC/ +文档源码 +https://github.com/HANXU2018/APIJSON-DOC +配套评论区 apijson-doc-Comment +https://github.com/HANXU2018/apijson-doc-Comment #### zhaoqiming 1.完善入门介绍视频 APIJSON 后端教程(1):简介 From b8dc48fdecf21f4b8a36f874d90203f48fc072a2 Mon Sep 17 00:00:00 2001 From: clown <32100214+hclown9804@users.noreply.github.com> Date: Sun, 1 Aug 2021 16:20:57 +0800 Subject: [PATCH 0421/1181] Update README.md add github link --- README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.md b/README.md index 190115912..a128afd48 100644 --- a/README.md +++ b/README.md @@ -474,6 +474,10 @@ https://www.bilibili.com/video/BV18h411z7FK APIJSON 后端教程(5):Final https://www.bilibili.com/video/BV1GM4y1N7XJ + +#### huwen 2.接入 presto/hive/clickhouse/db2 任意一个 +APIJSON-Demo 接入presto +https://github.com/hclown9804/APIJSONDemo_presto ### 持续更新 From 9c37249a9b724745983a1e9e7c74bc1440597154 Mon Sep 17 00:00:00 2001 From: haolingzhang1 <55579125+haolingzhang1@users.noreply.github.com> Date: Sun, 1 Aug 2021 16:31:33 +0800 Subject: [PATCH 0422/1181] Update README.md --- README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.md b/README.md index 190115912..448e518b2 100644 --- a/README.md +++ b/README.md @@ -475,6 +475,10 @@ https://www.bilibili.com/video/BV18h411z7FK APIJSON 后端教程(5):Final https://www.bilibili.com/video/BV1GM4y1N7XJ +#### zhanghaoling 1.完善入门介绍视频 +APIJSON结合已有项目,简化开发流程 +https://github.com/haolingzhang1/APIJson--demo + ### 持续更新 https://github.com/Tencent/APIJSON/commits/master From 5c3d066bc99764fb8ffbc9d3cf455788d10bfc9f Mon Sep 17 00:00:00 2001 From: chenyanlann <62465397+chenyanlann@users.noreply.github.com> Date: Sun, 1 Aug 2021 18:55:35 +0800 Subject: [PATCH 0423/1181] Update README.md --- README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.md b/README.md index 190115912..a2d1a2f9e 100644 --- a/README.md +++ b/README.md @@ -459,6 +459,10 @@ https://github.com/qiujunlin/APIJSONDemo APIJSON-Demo接入db2
https://github.com/andream7/apijson-db2 +#### chenyanlan 2.接入 presto/hive/clickhouse/db2 任意一个 +APIJSON + SpringBoot连接ClickHouse使用的Demo
+https://github.com/chenyanlann/APIJSONDemo_ClickHouse + #### zhaoqiming 1.完善入门介绍视频 APIJSON 后端教程(1):简介 https://www.bilibili.com/video/BV1vL411W7yd From cd9827f18d22cf92749368516e5875889bf24389 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Sun, 1 Aug 2021 19:21:42 +0800 Subject: [PATCH 0424/1181] Update README.md --- README.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 1b1e4f0bb..5910b16ea 100644 --- a/README.md +++ b/README.md @@ -461,11 +461,11 @@ APIJSON-Demo接入db2
https://github.com/andream7/apijson-db2 #### hanxu 1.完善入门介绍视频 -重构 APIJSON 文档 -https://hanxu2018.github.io/APIJSON-DOC/ -文档源码 -https://github.com/HANXU2018/APIJSON-DOC -配套评论区 apijson-doc-Comment +重构 APIJSON 文档
+https://hanxu2018.github.io/APIJSON-DOC/
+文档源码
+https://github.com/HANXU2018/APIJSON-DOC
+配套评论区 apijson-doc-Comment
https://github.com/HANXU2018/apijson-doc-Comment #### chenyanlan 2.接入 presto/hive/clickhouse/db2 任意一个 From 9b5f3f25975ef4a44d1c68e027903262a858738b Mon Sep 17 00:00:00 2001 From: chenyanlann <32511cyl@gmail.com> Date: Sun, 1 Aug 2021 20:11:14 +0800 Subject: [PATCH 0425/1181] Add:ORM's support for ClickHouse --- .../java/apijson/orm/AbstractSQLConfig.java | 24 +++++++++++++++---- .../src/main/java/apijson/orm/SQLConfig.java | 2 ++ 2 files changed, 22 insertions(+), 4 deletions(-) diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java index cde3deb20..6d3610ed6 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java @@ -124,6 +124,7 @@ public abstract class AbstractSQLConfig implements SQLConfig { DATABASE_LIST.add(DATABASE_SQLSERVER); DATABASE_LIST.add(DATABASE_ORACLE); DATABASE_LIST.add(DATABASE_DB2); + DATABASE_LIST.add(DATABASE_CLICKHOUSE); RAW_MAP = new LinkedHashMap<>(); // 保证顺序,避免配置冲突等意外情况 @@ -508,10 +509,17 @@ public boolean isDb2() { public static boolean isDb2(String db) { return DATABASE_DB2.equals(db); } + @Override + public boolean isClickHouse() { + return isClickHouse(getSQLDatabase()); + } + public static boolean isClickHouse(String db) { + return DATABASE_CLICKHOUSE.equals(db); + } @Override public String getQuote() { - return isMySQL() ? "`" : "\""; + return isMySQL() ? "`" : ( isClickHouse()? "" : "\""); } @Override @@ -2158,6 +2166,9 @@ public String getRegExpString(String key, String value, boolean ignoreCase) { if (isOracle()) { return "regexp_like(" + getKey(key) + ", " + getValue(value) + (ignoreCase ? ", 'i'" : ", 'c'") + ")"; } + if (isClickHouse()) { + return "match(" + (ignoreCase ? "lower(" : "") + getKey(key) + (ignoreCase ? ")" : "") + ", " + (ignoreCase ? "lower(" : "") + getValue(value) + (ignoreCase ? ")" : "") + ")"; + } return getKey(key) + " REGEXP " + (ignoreCase ? "" : "BINARY ") + getValue(value); } //~ regexp >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> @@ -2448,7 +2459,12 @@ else if (isOracle()) { else { boolean isNum = c instanceof Number; String v = (isNum ? "" : "\"") + childs[i] + (isNum ? "" : "\""); - condition += ("json_contains(" + getKey(key) + ", " + getValue(v) + ")"); + if (isClickHouse()) { + condition += condition + "has(JSONExtractArrayRaw(assumeNotNull(" + getKey(key) + "))" + ", " + getValue(v) + ")"; + } + else { + condition += ("json_contains(" + getKey(key) + ", " + getValue(v) + ")"); + } } } } @@ -2649,9 +2665,9 @@ public static String getSQL(AbstractSQLConfig config) throws Exception { case POST: return "INSERT INTO " + tablePath + config.getColumnString() + " VALUES" + config.getValuesString(); case PUT: - return "UPDATE " + tablePath + config.getSetString() + config.getWhereString(true) + (config.isMySQL() ? config.getLimitString() : ""); + return "UPDATE " + tablePath + config.getSetString() + config.getWhereString(true) + (config.isMySQL()||config.isClickHouse() ? config.getLimitString() : ""); case DELETE: - return "DELETE FROM " + tablePath + config.getWhereString(true) + (config.isMySQL() ? config.getLimitString() : ""); // PostgreSQL 不允许 LIMIT + return "DELETE FROM " + tablePath + config.getWhereString(true) + (config.isMySQL()||config.isClickHouse() ? config.getLimitString() : ""); // PostgreSQL 不允许 LIMIT default: String explain = (config.isExplain() ? (config.isSQLServer() || config.isOracle() ? "SET STATISTICS PROFILE ON " : "EXPLAIN ") : ""); if (config.isTest() && RequestMethod.isGetMethod(config.getMethod(), true)) { diff --git a/APIJSONORM/src/main/java/apijson/orm/SQLConfig.java b/APIJSONORM/src/main/java/apijson/orm/SQLConfig.java index 9abce83b9..8710b6e97 100755 --- a/APIJSONORM/src/main/java/apijson/orm/SQLConfig.java +++ b/APIJSONORM/src/main/java/apijson/orm/SQLConfig.java @@ -22,6 +22,7 @@ public interface SQLConfig { String DATABASE_SQLSERVER = "SQLSERVER"; String DATABASE_ORACLE = "ORACLE"; String DATABASE_DB2 = "DB2"; + String DATABASE_CLICKHOUSE = "CLICKHOUSE"; String SCHEMA_INFORMATION = "information_schema"; //MySQL, PostgreSQL, SQL Server 都有的系统模式 String SCHEMA_SYS = "sys"; //SQL Server 系统模式 @@ -37,6 +38,7 @@ public interface SQLConfig { boolean isSQLServer(); boolean isOracle(); boolean isDb2(); + boolean isClickHouse(); //暂时只兼容以上 5 种 // boolean isSQL(); // boolean isTSQL(); From 691d167f22e04c6dbbd2d958e87877901f8aa7b0 Mon Sep 17 00:00:00 2001 From: Neko Null Date: Sun, 1 Aug 2021 22:49:58 +0800 Subject: [PATCH 0426/1181] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E4=B8=80=E4=B8=AA?= =?UTF-8?q?=E7=A4=BA=E4=BE=8B=E9=A1=B9=E7=9B=AE=E5=92=8C=E4=B8=80=E7=AF=87?= =?UTF-8?q?=E6=96=87=E7=AB=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 参见 https://github.com/jerrylususu/apijson_todo_demo --- README.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 5910b16ea..3a3e275ce 100644 --- a/README.md +++ b/README.md @@ -385,6 +385,8 @@ https://github.com/Tencent/APIJSON/blob/master/CONTRIBUTING.md [全国行政区划数据抓取与处理](https://www.xlongwei.com/detail/21032616) +[新手搭建 APIJSON 项目指北](https://github.com/jerrylususu/apijson_todo_demo/blob/master/FULLTEXT.md) + ### 生态项目 [APIJSON-Demo](https://github.com/APIJSON/APIJSON-Demo) APIJSON 各种语言、各种框架 的 使用示例项目、上手文档、测试数据 SQL 文件 等 @@ -444,7 +446,8 @@ https://github.com/Tencent/APIJSON/blob/master/CONTRIBUTING.md [Android-ZBLibrary](https://github.com/TommyLemon/Android-ZBLibrary) Android MVP快速开发框架,Demo全面,注释详细,使用简单,代码严谨 - +[APIJSON-ToDo-Demo](https://github.com/jerrylususu/apijson_todo_demo) 一个简单的 todo 示例项目,精简数据,简化上手流程,带自定义鉴权逻辑 + 感谢热心的作者们的贡献,点 ⭐Star 支持下他们吧。 ### 腾讯犀牛鸟开源人才培养计划 From 59d689507cafb601b32f8f84346f23d4388ed3be Mon Sep 17 00:00:00 2001 From: haolingzhang1 <55579125+haolingzhang1@users.noreply.github.com> Date: Sun, 1 Aug 2021 23:43:31 +0800 Subject: [PATCH 0427/1181] Update README.md --- README.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/README.md b/README.md index 448e518b2..9d1fae723 100644 --- a/README.md +++ b/README.md @@ -476,9 +476,19 @@ APIJSON 后端教程(5):Final https://www.bilibili.com/video/BV1GM4y1N7XJ #### zhanghaoling 1.完善入门介绍视频 + APIJSON结合已有项目,简化开发流程 https://github.com/haolingzhang1/APIJson--demo +说明文档 +https://github.com/haolingzhang1/APIJson--demo/tree/main/APIJson集成项目说明 + +(1)官方demo +https://github.com/haolingzhang1/APIJson--demo/blob/main/APIJson集成项目说明/APIJson集成现有项目(1)-%20官方demo.pdf + +(2)单表配置 +https://github.com/haolingzhang1/APIJson--demo/blob/main/APIJson集成项目说明/APIJson集成现有项目(2)-%20单表配置.pdf + ### 持续更新 https://github.com/Tencent/APIJSON/commits/master From ae09a87b8f21e8f519a754377e2168e6f9876d60 Mon Sep 17 00:00:00 2001 From: haolingzhang1 <55579125+haolingzhang1@users.noreply.github.com> Date: Sun, 1 Aug 2021 23:48:37 +0800 Subject: [PATCH 0428/1181] =?UTF-8?q?update=20README.md=20=E5=A2=9E?= =?UTF-8?q?=E5=8A=A0=E8=AF=B4=E6=98=8E=E6=96=87=E6=A1=A3=E9=93=BE=E6=8E=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/README.md b/README.md index 9d1fae723..7061af207 100644 --- a/README.md +++ b/README.md @@ -476,7 +476,6 @@ APIJSON 后端教程(5):Final https://www.bilibili.com/video/BV1GM4y1N7XJ #### zhanghaoling 1.完善入门介绍视频 - APIJSON结合已有项目,简化开发流程 https://github.com/haolingzhang1/APIJson--demo From 526da6df86d1acc98366f27631e8bf271b650867 Mon Sep 17 00:00:00 2001 From: funkiz <1244503766@qq.com> Date: Mon, 2 Aug 2021 01:56:20 +0800 Subject: [PATCH 0429/1181] =?UTF-8?q?=E8=85=BE=E8=AE=AF=E7=8A=80=E7=89=9B?= =?UTF-8?q?=E9=B8=9F=E5=BC=80=E6=BA=90=E4=BA=BA=E6=89=8D=E5=9F=B9=E5=85=BB?= =?UTF-8?q?=E8=AE=A1=E5=88=92?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 5910b16ea..8e02b8dc6 100644 --- a/README.md +++ b/README.md @@ -449,13 +449,13 @@ https://github.com/Tencent/APIJSON/blob/master/CONTRIBUTING.md ### 腾讯犀牛鸟开源人才培养计划 https://github.com/Tencent/APIJSON/issues/229 - + #### qiujunlin **2.接入 presto/hive/clickhouse/db2 任意一个** APIJSON 接入 clickhouse 使用demo
https://github.com/qiujunlin/APIJSONDemo - + #### zhangshukun 2.接入 presto/hive/clickhouse/db2 任意一个 APIJSON-Demo接入db2
https://github.com/andream7/apijson-db2 @@ -467,11 +467,11 @@ https://hanxu2018.github.io/APIJSON-DOC/
https://github.com/HANXU2018/APIJSON-DOC
配套评论区 apijson-doc-Comment
https://github.com/HANXU2018/apijson-doc-Comment - + #### chenyanlan 2.接入 presto/hive/clickhouse/db2 任意一个 APIJSON + SpringBoot连接ClickHouse使用的Demo
https://github.com/chenyanlann/APIJSONDemo_ClickHouse - + #### zhaoqiming 1.完善入门介绍视频 APIJSON 后端教程(1):简介 https://www.bilibili.com/video/BV1vL411W7yd @@ -487,7 +487,7 @@ https://www.bilibili.com/video/BV18h411z7FK APIJSON 后端教程(5):Final https://www.bilibili.com/video/BV1GM4y1N7XJ - + #### huwen 2.接入 presto/hive/clickhouse/db2 任意一个 APIJSON-Demo 接入presto https://github.com/hclown9804/APIJSONDemo_presto @@ -495,7 +495,15 @@ https://github.com/hclown9804/APIJSONDemo_presto #### zhanghaoling 1.完善入门介绍视频 APIJSON结合已有项目,简化开发流程 https://github.com/haolingzhang1/APIJson--demo - + +#### zhoukaile 1.完善入门介绍视频 + +视频链接:https://www.bilibili.com/video/BV1Uh411z7kZ/ + +文档链接:https://gitee.com/funkiz/apijson_camp + + + ### 持续更新 https://github.com/Tencent/APIJSON/commits/master From f6c3ed0b267db0d104f63ecdc12ed998e62cf699 Mon Sep 17 00:00:00 2001 From: "bin.li" <626732147@qq.com> Date: Mon, 2 Aug 2021 10:17:16 +0800 Subject: [PATCH 0430/1181] =?UTF-8?q?perft:=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1:解决column加了函数(例如date_format)后,导致表达式超过50字符,改成100字符 --- APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java index 1b63d9ba8..8a15a05c9 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java @@ -1171,9 +1171,9 @@ public String getColumnString(boolean inSQLJoin) throws Exception { // } } - if (expression.length() > 50) { + if (expression.length() > 100) { throw new UnsupportedOperationException("@column:value 的 value 中字符串 " + expression + " 不合法!" - + "不允许传超过 50 个字符的函数或表达式!请用 @raw 简化传参!"); + + "不允许传超过 100 个字符的函数或表达式!请用 @raw 简化传参!"); } From 311c39257eb18b7753b9427e2bb0e7fd6a8e39f2 Mon Sep 17 00:00:00 2001 From: aaronlinv <1546848781@qq.com> Date: Mon, 2 Aug 2021 12:03:21 +0800 Subject: [PATCH 0431/1181] Update README.md --- README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.md b/README.md index 854708e3f..9fbd41db8 100644 --- a/README.md +++ b/README.md @@ -514,6 +514,10 @@ https://github.com/haolingzhang1/APIJson--demo/blob/main/APIJson集成项目说 文档链接:https://gitee.com/funkiz/apijson_camp +#### lintao 1.完善入门介绍视频 + +APIJSON 上手教程:https://www.bilibili.com/video/BV1Pq4y1n7rJ + ### 持续更新 https://github.com/Tencent/APIJSON/commits/master From 774c7074b05eab4cb8ab05ab12bcefa6a7e19149 Mon Sep 17 00:00:00 2001 From: Neko Null Date: Tue, 3 Aug 2021 15:45:50 +0300 Subject: [PATCH 0432/1181] Update README.md --- README.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 9fbd41db8..6a7276633 100644 --- a/README.md +++ b/README.md @@ -310,6 +310,7 @@ https://search.gitee.com/?skin=rec&type=repository&q=apijson&sort=stars_count +

@@ -422,6 +423,8 @@ https://github.com/Tencent/APIJSON/blob/master/CONTRIBUTING.md [FfApiJson](https://gitee.com/own_3_0/ff-api-json) 用 JSON 格式直接生成 SQL,借鉴 APIJSON 支持多数据源 +[APIJSON-ToDo-Demo](https://github.com/jerrylususu/apijson_todo_demo) 一个简单的 todo 示例项目,精简数据,简化上手流程,带自定义鉴权逻辑 + [apijson-learn](https://github.com/rainboy-learn/apijson-learn) APIJSON 学习笔记和源码解析 [apijson-sample](https://gitee.com/greyzeng/apijson-sample) APIJSON 简单使用 Demo 及教程 @@ -445,8 +448,6 @@ https://github.com/Tencent/APIJSON/blob/master/CONTRIBUTING.md [APIJSON-Android-RxJava](https://github.com/TommyLemon/APIJSON-Android-RxJava) 仿微信朋友圈动态实战项目,ZBLibrary(UI) + APIJSON(HTTP) + RxJava(Data) [Android-ZBLibrary](https://github.com/TommyLemon/Android-ZBLibrary) Android MVP快速开发框架,Demo全面,注释详细,使用简单,代码严谨 - -[APIJSON-ToDo-Demo](https://github.com/jerrylususu/apijson_todo_demo) 一个简单的 todo 示例项目,精简数据,简化上手流程,带自定义鉴权逻辑 感谢热心的作者们的贡献,点 ⭐Star 支持下他们吧。 From 6981010a5e2493dd708ecee4bc0e0751fe1b0d7c Mon Sep 17 00:00:00 2001 From: qiujunlin <1757591067@qq.com> Date: Thu, 5 Aug 2021 22:12:13 +0800 Subject: [PATCH 0433/1181] update clickhouse gettable --- .../src/main/java/apijson/orm/AbstractSQLConfig.java | 2 +- .../src/main/java/apijson/orm/AbstractSQLExecutor.java | 7 +++++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java index 26391c0d4..e3f71ccbb 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java @@ -519,7 +519,7 @@ public static boolean isClickHouse(String db) { @Override public String getQuote() { - return isMySQL() ? "`" : ( isClickHouse()? "" : "\""); + return isMySQL()||isClickHouse() ? "`" : "\""; } @Override diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java index 100fe7926..1a7d0f3f6 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java @@ -287,11 +287,14 @@ public JSONObject execute(@NotNull SQLConfig config, boolean unknowType) throws // bugfix-修复非常规数据库字段,获取表名失败导致输出异常 if (isExplain == false && hasJoin && viceColumnStart > length) { List column = config.getColumn(); - + String sqlTable = rsmd.getTableName(i); + if (config.isClickHouse()&&(sqlTable.startsWith("`")||sqlTable.startsWith("\""))){ + sqlTable = sqlTable.substring(1,sqlTable.length()-1); + } if (column != null && column.isEmpty() == false) { viceColumnStart = column.size() + 1; } - else if (config.getSQLTable().equalsIgnoreCase(rsmd.getTableName(i)) == false) { + else if (config.getSQLTable().equalsIgnoreCase(sqlTable) == false) { viceColumnStart = i; } } From c9490e261d671b62925e60b5b41e525e14decda0 Mon Sep 17 00:00:00 2001 From: kenlig <28685375+kenlig@users.noreply.github.com> Date: Mon, 9 Aug 2021 19:28:32 +0800 Subject: [PATCH 0434/1181] Update README.md --- README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.md b/README.md index 6a7276633..0f592316c 100644 --- a/README.md +++ b/README.md @@ -491,6 +491,9 @@ https://www.bilibili.com/video/BV18h411z7FK APIJSON 后端教程(5):Final https://www.bilibili.com/video/BV1GM4y1N7XJ + +APIJSON配套文档: +https://github.com/kenlig/apijsondocs #### huwen 2.接入 presto/hive/clickhouse/db2 任意一个 APIJSON-Demo 接入presto From 86e5f78e8058183b1b6ca4391ad4e164e80ff45d Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Sun, 15 Aug 2021 22:30:11 +0800 Subject: [PATCH 0435/1181] =?UTF-8?q?=E9=80=82=E7=94=A8=E8=8C=83=E5=9B=B4?= =?UTF-8?q?=E6=96=B0=E5=A2=9E=20=E4=BD=8E=E4=BB=A3=E7=A0=81/=E9=9B=B6?= =?UTF-8?q?=E4=BB=A3=E7=A0=81=E5=B9=B3=E5=8F=B0=E3=80=81=E5=B0=8F=E7=A8=8B?= =?UTF-8?q?=E5=BA=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 0f592316c..58161b681 100644 --- a/README.md +++ b/README.md @@ -47,7 +47,7 @@ This source code is licensed under the Apache License Version 2.0
APIJSON 是一种专为 API 而生的 JSON 网络传输协议 以及 基于这套协议实现的 ORM 库。
为 简单的增删改查、复杂的查询、简单的事务操作 提供了完全自动化的万能 API。
能大幅降低开发和沟通成本,简化开发流程,缩短开发周期。
-适合中小型前后端分离的项目,尤其是 BaaS、Serverless、互联网创业项目和企业自用项目。
+适合中小型前后端分离的项目,尤其是 互联网创业项目、企业自用项目、低代码/零代码平台、小程序、BaaS、Serverless 等。
通过万能的 API,前端可以定制任何数据、任何结构。
大部分 HTTP 请求后端再也不用写接口了,更不用写文档了。
From 6e4894d5f6191b15d31ea1eaf5f9d9c2b103081e Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Sun, 15 Aug 2021 22:31:01 +0800 Subject: [PATCH 0436/1181] =?UTF-8?q?=E9=80=82=E7=94=A8=E8=8C=83=E5=9B=B4?= =?UTF-8?q?=E6=96=B0=E5=A2=9E=20=E4=BD=8E=E4=BB=A3=E7=A0=81/=E9=9B=B6?= =?UTF-8?q?=E4=BB=A3=E7=A0=81=E5=B9=B3=E5=8F=B0=E3=80=81=E5=B0=8F=E7=A8=8B?= =?UTF-8?q?=E5=BA=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 58161b681..21107f029 100644 --- a/README.md +++ b/README.md @@ -47,7 +47,7 @@ This source code is licensed under the Apache License Version 2.0
APIJSON 是一种专为 API 而生的 JSON 网络传输协议 以及 基于这套协议实现的 ORM 库。
为 简单的增删改查、复杂的查询、简单的事务操作 提供了完全自动化的万能 API。
能大幅降低开发和沟通成本,简化开发流程,缩短开发周期。
-适合中小型前后端分离的项目,尤其是 互联网创业项目、企业自用项目、低代码/零代码平台、小程序、BaaS、Serverless 等。
+适合中小型前后端分离的项目,尤其是 创业项目、内部项目、低代码/零代码、小程序、BaaS、Serverless 等。
通过万能的 API,前端可以定制任何数据、任何结构。
大部分 HTTP 请求后端再也不用写接口了,更不用写文档了。
From bb387c2b1922e7ef9a315a3559d355ac3044781a Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Sun, 15 Aug 2021 22:32:23 +0800 Subject: [PATCH 0437/1181] =?UTF-8?q?=E9=80=82=E7=94=A8=E8=8C=83=E5=9B=B4?= =?UTF-8?q?=EF=BC=9A=E5=88=9D=E5=88=9B=E9=A1=B9=E7=9B=AE=E3=80=81=E5=86=85?= =?UTF-8?q?=E9=83=A8=E9=A1=B9=E7=9B=AE=E3=80=81=E4=BD=8E=E4=BB=A3=E7=A0=81?= =?UTF-8?q?/=E9=9B=B6=E4=BB=A3=E7=A0=81=E3=80=81=E5=B0=8F=E7=A8=8B?= =?UTF-8?q?=E5=BA=8F=E3=80=81BaaS=E3=80=81Serverless=20=E7=AD=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 21107f029..448ded30e 100644 --- a/README.md +++ b/README.md @@ -47,7 +47,7 @@ This source code is licensed under the Apache License Version 2.0
APIJSON 是一种专为 API 而生的 JSON 网络传输协议 以及 基于这套协议实现的 ORM 库。
为 简单的增删改查、复杂的查询、简单的事务操作 提供了完全自动化的万能 API。
能大幅降低开发和沟通成本,简化开发流程,缩短开发周期。
-适合中小型前后端分离的项目,尤其是 创业项目、内部项目、低代码/零代码、小程序、BaaS、Serverless 等。
+适合中小型前后端分离的项目,尤其是 初创项目、内部项目、低代码/零代码、小程序、BaaS、Serverless 等。
通过万能的 API,前端可以定制任何数据、任何结构。
大部分 HTTP 请求后端再也不用写接口了,更不用写文档了。
From 3487e6b0a210ca5c8ea418639d1887dd8a24d62e Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Wed, 18 Aug 2021 11:03:37 +0800 Subject: [PATCH 0438/1181] =?UTF-8?q?=E6=95=B0=E6=8D=AE=E5=BA=93=E6=96=B0?= =?UTF-8?q?=E5=A2=9E=20DB2=20=E5=92=8C=20ClickHouse=20=E7=9A=84=E9=93=BE?= =?UTF-8?q?=E6=8E=A5=EF=BC=8C=E6=84=9F=E8=B0=A2=E4=B8=A4=E4=BD=8D=E4=BD=9C?= =?UTF-8?q?=E8=80=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit https://github.com/andream7/apijson-db2 https://github.com/chenyanlann/APIJSONDemo_ClickHouse --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 448ded30e..722cf554b 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,9 @@ This source code is licensed under the Apache License Version 2.0
    + +

From a2135b7bf34c16ef25a8fa983f4e1c7f8bea0a88 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Thu, 19 Aug 2021 23:39:47 +0800 Subject: [PATCH 0439/1181] =?UTF-8?q?=E6=9D=A5=E8=87=AA=E8=85=BE=E8=AE=AF?= =?UTF-8?q?=E7=9A=84=E4=B8=BB=E9=A1=B9=E7=9B=AE=E8=B4=A1=E7=8C=AE=E8=80=85?= =?UTF-8?q?=E5=92=8C=E7=94=9F=E6=80=81=E9=A1=B9=E7=9B=AE=E4=BD=9C=E8=80=85?= =?UTF-8?q?=E5=88=86=E5=88=AB=20+=201=EF=BC=8C=E6=84=9F=E8=B0=A2=E8=B4=A1?= =?UTF-8?q?=E7=8C=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit https://github.com/Tencent/APIJSON/pull/292 https://github.com/haolingzhang1/APIJson--demo --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 722cf554b..f781ddc7b 100644 --- a/README.md +++ b/README.md @@ -241,7 +241,7 @@ https://github.com/Tencent/APIJSON/issues/187 ### 贡献者们 -主项目 APIJSON 的贡献者们(5 个腾讯工程师、1 个知乎基础研发架构师、1 个圆通工程师 等):
+主项目 APIJSON 的贡献者们(6 个腾讯工程师、1 个知乎基础研发架构师、1 个圆通工程师 等):
https://github.com/Tencent/APIJSON/blob/master/CONTRIBUTING.md


-生态周边项目的作者们(1 个腾讯工程师、1 个字节跳动工程师 等):
+生态周边项目的作者们(2 个腾讯工程师、1 个字节跳动工程师 等):
https://github.com/search?o=desc&q=apijson&s=stars&type=Repositories
https://search.gitee.com/?skin=rec&type=repository&q=apijson&sort=stars_count
From bc8086c268538cf9843760a53089995695b03fdb Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Tue, 24 Aug 2021 16:45:44 +0800 Subject: [PATCH 0440/1181] =?UTF-8?q?=E8=B4=A1=E7=8C=AE=E8=80=85=E5=90=8D?= =?UTF-8?q?=E5=8D=95=E6=96=B0=E5=A2=9E=E4=B8=80=E4=B8=AA=E8=85=BE=E8=AE=AF?= =?UTF-8?q?=E5=B7=A5=E7=A8=8B=E5=B8=88=EF=BC=8CPull=20Request=20=E6=96=B0?= =?UTF-8?q?=E5=A2=9E=E5=B0=8F=E6=94=B9=E6=96=87=E6=A1=A3=E6=88=96=E4=BB=A3?= =?UTF-8?q?=E7=A0=81=20=E7=9A=84=E7=AE=80=E8=A6=81=E6=AD=A5=E9=AA=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit https://github.com/Tencent/APIJSON/blob/master/CONTRIBUTING.md --- CONTRIBUTING.md | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index b737956dd..cbeeda0f5 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -34,6 +34,7 @@ - [yeyuezhishou](https://github.com/yeyuezhishou)(圆通工程师) - [kenlig](https://github.com/kenlig) - [andream7](https://github.com/andream7) +- [haolingzhang1](https://github.com/haolingzhang1)(腾讯工程师,还开源了 APIJson--demo) #### 其中特别致谢:
@@ -74,6 +75,19 @@ APIJSON 作为腾讯开源的知名热门项目,贡献代码除了可以给简 我们除了希望听到您的反馈和建议外,我们也希望您接受代码形式的直接帮助,对我们的 GitHub 发出 Pull Request 请求。 +### 如果是小改文档或代码 + +直接点文件右上角的编辑图标按钮
+![image](https://user-images.githubusercontent.com/5738175/130585672-8bd49ae5-2978-4ad6-a7a6-de0a0c2d0b68.png) + +
+ +然后底部简要输入修改说明,点击 Commit Change 按钮
+![image](https://user-images.githubusercontent.com/5738175/130586073-4a6aea74-3c88-4cd9-9c93-ffaba1270ab8.png) + + +### 如果有比较大的改动 + 以下是具体步骤:(如果使用本步骤,GitHub 可能不会把贡献者添加到 Contributors 中,推荐用以下 [详细的图文步骤](https://github.com/Tencent/APIJSON/blob/master/CONTRIBUTING.md#%E8%AF%A6%E7%BB%86%E7%9A%84%E5%9B%BE%E6%96%87%E6%AD%A5%E9%AA%A4%E5%8F%AF%E5%8F%82%E8%80%83%E4%BB%A5%E4%B8%8B%E4%BB%BB%E6%84%8F%E4%B8%80%E7%AF%87)) #### Fork 仓库 From 31e04e255843b47d271ff4809eaa8101d0e5c8f7 Mon Sep 17 00:00:00 2001 From: kenlig <28685375+kenlig@users.noreply.github.com> Date: Wed, 25 Aug 2021 23:21:04 +0800 Subject: [PATCH 0441/1181] Update README.md --- README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.md b/README.md index f781ddc7b..ec4942a67 100644 --- a/README.md +++ b/README.md @@ -493,6 +493,9 @@ https://www.bilibili.com/video/BV18h411z7FK APIJSON 后端教程(5):Final https://www.bilibili.com/video/BV1GM4y1N7XJ + +APIJSON 后端教程(6):uliweb_apijson +https://www.bilibili.com/video/BV1yb4y1S79v/ APIJSON配套文档: https://github.com/kenlig/apijsondocs From 31530941337b00cb85288d97aa63978939daca0f Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Thu, 2 Sep 2021 11:15:30 +0800 Subject: [PATCH 0442/1181] =?UTF-8?q?=E5=AE=8C=E5=96=84=20=E4=BF=9D?= =?UTF-8?q?=E6=8C=81=E4=B8=8E=20APIJSON=20=E4=BB=93=E5=BA=93=E7=9A=84?= =?UTF-8?q?=E5=90=8C=E6=AD=A5=20=E7=9A=84=E5=8F=AF=E8=A7=86=E5=8C=96?= =?UTF-8?q?=E6=93=8D=E4=BD=9C=E6=AD=A5=E9=AA=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CONTRIBUTING.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index cbeeda0f5..d782601c9 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -114,8 +114,10 @@ $ git remote add APIJSON git@github.com:Tencent/APIJSON.git #### 保持与 APIJSON 仓库的同步 -更新上游仓库: +直接在 fork Repo 的首页点 Contribute > Open pull request +![image](https://user-images.githubusercontent.com/5738175/131775861-15a2bf9e-4e85-4588-bd2d-f9d78b76541c.png) +或者 ```bash $ git pull --rebase # 等同于以下两条命令 From f7a05907de2c2ff3c80b09b5d156308e43091510 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Thu, 2 Sep 2021 11:15:56 +0800 Subject: [PATCH 0443/1181] Update CONTRIBUTING.md --- CONTRIBUTING.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index d782601c9..3d9510094 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -115,6 +115,7 @@ $ git remote add APIJSON git@github.com:Tencent/APIJSON.git #### 保持与 APIJSON 仓库的同步 直接在 fork Repo 的首页点 Contribute > Open pull request + ![image](https://user-images.githubusercontent.com/5738175/131775861-15a2bf9e-4e85-4588-bd2d-f9d78b76541c.png) 或者 From 77177d1ac53a7d93eb3b14aa20403fe09b75e146 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Thu, 2 Sep 2021 11:16:23 +0800 Subject: [PATCH 0444/1181] Update CONTRIBUTING.md --- CONTRIBUTING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 3d9510094..0f4b61858 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -116,7 +116,7 @@ $ git remote add APIJSON git@github.com:Tencent/APIJSON.git 直接在 fork Repo 的首页点 Contribute > Open pull request -![image](https://user-images.githubusercontent.com/5738175/131775861-15a2bf9e-4e85-4588-bd2d-f9d78b76541c.png) +![image](https://user-images.githubusercontent.com/5738175/131776033-74caf279-ebbf-45f1-a9c1-beff937a87fb.png) 或者 ```bash From 454ec0399f96f9b2122c8d094f5e3e9852cdb7bf Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Thu, 2 Sep 2021 16:12:14 +0800 Subject: [PATCH 0445/1181] =?UTF-8?q?=E7=AE=80=E4=BB=8B=E6=96=B0=E5=A2=9E?= =?UTF-8?q?=20"=E9=9B=B6=E4=BB=A3=E7=A0=81=E5=AE=9E=E6=97=B6=E6=BB=A1?= =?UTF-8?q?=E8=B6=B3=E5=8D=83=E5=8F=98=E4=B8=87=E5=8C=96=E7=9A=84=E5=90=84?= =?UTF-8?q?=E7=A7=8D=E6=96=B0=E5=A2=9E=E5=92=8C=E5=8F=98=E6=9B=B4=E9=9C=80?= =?UTF-8?q?=E6=B1=82"?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index ec4942a67..272440207 100644 --- a/README.md +++ b/README.md @@ -47,7 +47,7 @@ This source code is licensed under the Apache License Version 2.0
APIJSON 是一种专为 API 而生的 JSON 网络传输协议 以及 基于这套协议实现的 ORM 库。
-为 简单的增删改查、复杂的查询、简单的事务操作 提供了完全自动化的万能 API。
+为各种增删改查提供了完全自动化的万能 API,零代码实时满足千变万化的各种新增和变更需求。
能大幅降低开发和沟通成本,简化开发流程,缩短开发周期。
适合中小型前后端分离的项目,尤其是 初创项目、内部项目、低代码/零代码、小程序、BaaS、Serverless 等。
From 685c835c15b38398a92d8c9fc3005dfa0f064d3c Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Thu, 2 Sep 2021 21:37:31 +0800 Subject: [PATCH 0446/1181] update users(companies) and contributors --- README-English.md | 72 ++++++++++++++++++++++++++++++++++++----------- 1 file changed, 56 insertions(+), 16 deletions(-) diff --git a/README-English.md b/README-English.md index 40ab91fa7..e5bffa45c 100644 --- a/README-English.md +++ b/README-English.md @@ -308,24 +308,48 @@ If you have any questions or suggestions, you can [create an issue](https://gith ### Users of this project:
- - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + +

[More APIJSON Users](https://github.com/Tencent/APIJSON/issues/73) ### Contributers of APIJSON: -Here are the contributers of this project and authors of other projects for ecosystem of APIJSON: +Contributers for the APIJSON core project(6 Tencent engineers、1 Zhihu architect、1 Yuantong engineer, etc.):
+https://github.com/Tencent/APIJSON/blob/master/CONTRIBUTING.md
@@ -350,11 +374,23 @@ Here are the contributers of this project and authors of other projects for ecos - -
+ + + + + +
+
+ +Authors of other projects for ecosystem of APIJSON(2 Tencent engineers、1 Bytedance(TikTok) engineer, etc.):
+https://github.com/search?o=desc&q=apijson&s=stars&type=Repositories
+https://search.gitee.com/?skin=rec&type=repository&q=apijson&sort=stars_count
+
+ @@ -383,8 +419,12 @@ Here are the contributers of this project and authors of other projects for ecos +

-Thanks to all contributers of APIJSON! + +
+Thanks to all contributers of APIJSON! +
From e89f8baf1e19d6fa7707aaf71d5c0491aa7bfd9c Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Thu, 2 Sep 2021 21:43:05 +0800 Subject: [PATCH 0447/1181] Update users(Tencent) and contributors(from Tencent, Zhihu, YTO Express) --- README-English.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README-English.md b/README-English.md index e5bffa45c..f49d8385a 100644 --- a/README-English.md +++ b/README-English.md @@ -348,7 +348,7 @@ If you have any questions or suggestions, you can [create an issue](https://gith [More APIJSON Users](https://github.com/Tencent/APIJSON/issues/73) ### Contributers of APIJSON: -Contributers for the APIJSON core project(6 Tencent engineers、1 Zhihu architect、1 Yuantong engineer, etc.):
+Contributers for the APIJSON core project(6 Tencent engineers、1 Zhihu architect、1 YTO Express engineer, etc.):
https://github.com/Tencent/APIJSON/blob/master/CONTRIBUTING.md
Date: Thu, 2 Sep 2021 21:44:19 +0800 Subject: [PATCH 0448/1181] updated users(Tencent) --- README-English.md | 53 ++++++++++++++++------------------------------- 1 file changed, 18 insertions(+), 35 deletions(-) diff --git a/README-English.md b/README-English.md index f49d8385a..cef9f1c02 100644 --- a/README-English.md +++ b/README-English.md @@ -307,42 +307,25 @@ If you have any questions or suggestions, you can [create an issue](https://gith ### Users of this project: +https://github.com/Tencent/APIJSON/issues/187
- - - - - - - - - - - - - - - - - - - - - - - - - - - -
+ + + +
+ + + + + + + + + + + + +
[More APIJSON Users](https://github.com/Tencent/APIJSON/issues/73) From 4fa1d53c1c8aa547dd905bd31c2a6edf62838fa4 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Thu, 2 Sep 2021 21:58:19 +0800 Subject: [PATCH 0449/1181] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E5=8C=85=E6=8B=AC?= =?UTF-8?q?=201=20=E4=B8=AA=E8=85=BE=E8=AE=AF=E5=B7=A5=E7=A8=8B=E5=B8=88?= =?UTF-8?q?=E5=9C=A8=E5=86=85=E7=9A=84=208=20=E4=B8=AA=E8=B4=A1=E7=8C=AE?= =?UTF-8?q?=E8=80=85=EF=BC=8C=E6=84=9F=E8=B0=A2=E5=A4=A7=E5=AE=B6=E7=9A=84?= =?UTF-8?q?=E8=B4=A1=E7=8C=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/README.md b/README.md index 272440207..746389473 100644 --- a/README.md +++ b/README.md @@ -272,6 +272,14 @@ https://github.com/Tencent/APIJSON/blob/master/CONTRIBUTING.md
+ + + + + + + +

From 0a68549610c69466f89f0b7219d7982b1d3f88ba Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Thu, 2 Sep 2021 22:12:53 +0800 Subject: [PATCH 0450/1181] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E5=8C=85=E6=8B=AC?= =?UTF-8?q?=201=20=E4=B8=AA=E8=85=BE=E8=AE=AF=E5=B7=A5=E7=A8=8B=E5=B8=88?= =?UTF-8?q?=E5=9C=A8=E5=86=85=E7=9A=84=208=20=E4=B8=AA=E8=B4=A1=E7=8C=AE?= =?UTF-8?q?=E8=80=85=EF=BC=8C=E6=84=9F=E8=B0=A2=E5=A4=A7=E5=AE=B6=E7=9A=84?= =?UTF-8?q?=E8=B4=A1=E7=8C=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CONTRIBUTING.md | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 0f4b61858..7329001d3 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -32,9 +32,15 @@ - [jun0315](https://github.com/jun0315)(腾讯工程师) - [JieJo](https://github.com/JieJo) - [yeyuezhishou](https://github.com/yeyuezhishou)(圆通工程师) -- [kenlig](https://github.com/kenlig) -- [andream7](https://github.com/andream7) +- [kenlig](https://github.com/kenlig)(还开源了 apijsondocs) +- [andream7](https://github.com/andream7)(还开源了 apijson-db2) +- [qiujunlin](https://github.com/qiujunlin)(还开源了 APIJSONDemo) +- [HANXU2018](https://github.com/HANXU2018)(还开源了 APIJSON-DOC) +- [hclown9804](https://github.com/hclown9804) +- [chenyanlann](https://github.com/chenyanlann)(还开源了 APIJSONDemo_ClickHouse) - [haolingzhang1](https://github.com/haolingzhang1)(腾讯工程师,还开源了 APIJson--demo) +- [jerrylususu](https://github.com/jerrylususu)(还开源了 apijson_todo_demo 和 apijson_role_extend) +- [Dalezee](https://github.com/Dalezee)(还开源了 apijson_camp) #### 其中特别致谢:
From ee5adc0b818f6645d6506f2623cc72780ea9970a Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Thu, 2 Sep 2021 22:28:00 +0800 Subject: [PATCH 0451/1181] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E5=8C=85=E6=8B=AC?= =?UTF-8?q?=201=20=E4=B8=AA=E8=85=BE=E8=AE=AF=E5=B7=A5=E7=A8=8B=E5=B8=88?= =?UTF-8?q?=E5=9C=A8=E5=86=85=E7=9A=84=208=20=E4=B8=AA=E8=B4=A1=E7=8C=AE?= =?UTF-8?q?=E8=80=85=EF=BC=8C=E6=84=9F=E8=B0=A2=E5=A4=A7=E5=AE=B6=E7=9A=84?= =?UTF-8?q?=E8=B4=A1=E7=8C=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CONTRIBUTING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 7329001d3..de776b2b9 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -41,7 +41,7 @@ - [haolingzhang1](https://github.com/haolingzhang1)(腾讯工程师,还开源了 APIJson--demo) - [jerrylususu](https://github.com/jerrylususu)(还开源了 apijson_todo_demo 和 apijson_role_extend) - [Dalezee](https://github.com/Dalezee)(还开源了 apijson_camp) - +- [aaronlinv](https://github.com/aaronlinv) #### 其中特别致谢:
justinfengchen 提交的 6 个 Commits, 对 APIJSON 做出了 3,130 增加和 0 处删减(截止 2020/11/04 日);
From 01b310a3e33e54e3d0a73864132492115ac82add Mon Sep 17 00:00:00 2001 From: qiujunlin <1757591067@qq.com> Date: Fri, 3 Sep 2021 02:18:02 +0800 Subject: [PATCH 0452/1181] add orm support --- .../src/main/java/apijson/StringUtil.java | 16 +- .../java/apijson/orm/AbstractSQLConfig.java | 522 ++++++++---------- .../java/apijson/orm/FunctionsAndRaws.java | 519 +++++++++++++++++ 3 files changed, 756 insertions(+), 301 deletions(-) create mode 100644 APIJSONORM/src/main/java/apijson/orm/FunctionsAndRaws.java diff --git a/APIJSONORM/src/main/java/apijson/StringUtil.java b/APIJSONORM/src/main/java/apijson/StringUtil.java index 9fac4e34e..bd3b87f7b 100755 --- a/APIJSONORM/src/main/java/apijson/StringUtil.java +++ b/APIJSONORM/src/main/java/apijson/StringUtil.java @@ -725,7 +725,21 @@ public static String getPrice(double price, int formatType) { } } - + /** 数组以指定分隔s拼接 + * @param arr + * @param s + * @return + */ + public static String join(String[] arr, String s) { + StringBuilder stringBuilder = new StringBuilder(); + for (int i = 0; i < arr.length; i++) { + stringBuilder.append(arr[i]); + if(i TABLE_KEY_MAP; public static final List CONFIG_TABLE_LIST; public static final List DATABASE_LIST; - // 自定义原始 SQL 片段 Map:当 substring 为 null 时忽略;当 substring 为 "" 时整个 value 是 raw SQL;其它情况则只是 substring 这段为 raw SQL - public static final Map RAW_MAP; - // 允许调用的 SQL 函数:当 substring 为 null 时忽略;当 substring 为 "" 时整个 value 是 raw SQL;其它情况则只是 substring 这段为 raw SQL - public static final Map SQL_FUNCTION_MAP; + static { // 凡是 SQL 边界符、分隔符、注释符 都不允许,例如 ' " ` ( ) ; # -- ,以免拼接 SQL 时被注入意外可执行指令 PATTERN_RANGE = Pattern.compile("^[0-9%,!=\\<\\>/\\.\\+\\-\\*\\^]+$"); // ^[a-zA-Z0-9_*%!=<>(),"]+$ 导致 exists(select*from(Comment)) 通过! - PATTERN_FUNCTION = Pattern.compile("^[A-Za-z0-9%,:_@&~!=\\<\\>\\|\\[\\]\\{\\} /\\.\\+\\-\\*\\^\\?\\$]+$"); //TODO 改成更好的正则,校验前面为单词,中间为操作符,后面为值 - + PATTERN_FUNCTION = Pattern.compile("^[A-Za-z0-9%,:_@&~!=\\<\\>\\|\\[\\]\\{\\} /\\.\\+\\-\\*\\^\\?\\(\\)\\$]+$"); //TODO 改成更好的正则,校验前面为单词,中间为操作符,后面为值 + PATTERN_STRING = Pattern.compile("^[,#;\"`]+$"); TABLE_KEY_MAP = new HashMap(); TABLE_KEY_MAP.put(Table.class.getSimpleName(), Table.TABLE_NAME); @@ -126,186 +124,6 @@ public abstract class AbstractSQLConfig implements SQLConfig { DATABASE_LIST.add(DATABASE_DB2); DATABASE_LIST.add(DATABASE_CLICKHOUSE); - - RAW_MAP = new LinkedHashMap<>(); // 保证顺序,避免配置冲突等意外情况 - - - SQL_FUNCTION_MAP = new LinkedHashMap<>(); // 保证顺序,避免配置冲突等意外情况 - - // MySQL 字符串函数 - SQL_FUNCTION_MAP.put("ascii", ""); // ASCII(s) 返回字符串 s 的第一个字符的 ASCII 码。 - SQL_FUNCTION_MAP.put("char_length", ""); // CHAR_LENGTH(s) 返回字符串 s 的字符数 - SQL_FUNCTION_MAP.put("character_length", ""); // CHARACTER_LENGTH(s) 返回字符串 s 的字符数 - SQL_FUNCTION_MAP.put("concat", ""); // CONCAT(s1, s2...sn) 字符串 s1,s2 等多个字符串合并为一个字符串 - SQL_FUNCTION_MAP.put("concat_ws", ""); // CONCAT_WS(x, s1, s2...sn) 同 CONCAT(s1, s2 ...) 函数,但是每个字符串之间要加上 x,x 可以是分隔符 - SQL_FUNCTION_MAP.put("field", ""); // FIELD(s, s1, s2...) 返回第一个字符串 s 在字符串列表 (s1, s2...)中的位置 - SQL_FUNCTION_MAP.put("find_in_set", ""); // FIND_IN_SET(s1, s2) 返回在字符串s2中与s1匹配的字符串的位置 - SQL_FUNCTION_MAP.put("format", ""); // FORMAT(x, n) 函数可以将数字 x 进行格式化 "#,###.##", 将 x 保留到小数点后 n 位,最后一位四舍五入。 - SQL_FUNCTION_MAP.put("insert", ""); // INSERT(s1, x, len, s2) 字符串 s2 替换 s1 的 x 位置开始长度为 len 的字符串 - SQL_FUNCTION_MAP.put("locate", ""); // LOCATE(s1, s) 从字符串 s 中获取 s1 的开始位置 - SQL_FUNCTION_MAP.put("lcase", ""); // LCASE(s) 将字符串 s 的所有字母变成小写字母 - SQL_FUNCTION_MAP.put("left", ""); // LEFT(s, n) 返回字符串 s 的前 n 个字符 - SQL_FUNCTION_MAP.put("length", ""); // LENGTH(s) 返回字符串 s 的字符数 - SQL_FUNCTION_MAP.put("lower", ""); // LOWER(s) 将字符串 s 的所有字母变成小写字母 - SQL_FUNCTION_MAP.put("lpad", ""); // LPAD(s1, len, s2) 在字符串 s1 的开始处填充字符串 s2,使字符串长度达到 len - SQL_FUNCTION_MAP.put("ltrim", ""); // LTRIM(s) 去掉字符串 s 开始处的空格 - SQL_FUNCTION_MAP.put("mid", ""); // MID(s, n, len) 从字符串 s 的 n 位置截取长度为 len 的子字符串,同 SUBSTRING(s, n, len) - SQL_FUNCTION_MAP.put("position", ""); // POSITION(s, s1); 从字符串 s 中获取 s1 的开始位置 - SQL_FUNCTION_MAP.put("repeat", ""); // REPEAT(s, n) 将字符串 s 重复 n 次 - SQL_FUNCTION_MAP.put("replace", ""); // REPLACE(s, s1, s2) 将字符串 s2 替代字符串 s 中的字符串 s1 - SQL_FUNCTION_MAP.put("reverse", ""); // REVERSE(s); // ) 将字符串s的顺序反过来 - SQL_FUNCTION_MAP.put("right", ""); // RIGHT(s, n) 返回字符串 s 的后 n 个字符 - SQL_FUNCTION_MAP.put("rpad", ""); // RPAD(s1, len, s2) 在字符串 s1 的结尾处添加字符串 s2,使字符串的长度达到 len - SQL_FUNCTION_MAP.put("rtrim", ""); // RTRIM", ""); // ) 去掉字符串 s 结尾处的空格 - SQL_FUNCTION_MAP.put("space", ""); // SPACE(n) 返回 n 个空格 - SQL_FUNCTION_MAP.put("strcmp", ""); // STRCMP(s1, s2) 比较字符串 s1 和 s2,如果 s1 与 s2 相等返回 0 ,如果 s1>s2 返回 1,如果 s1d2 之间相隔的天数 - SQL_FUNCTION_MAP.put("date_add", ""); // DATE_ADD(d,INTERVAL expr type) 计算起始日期 d 加上一个时间段后的日期 - SQL_FUNCTION_MAP.put("date_format", ""); // DATE_FORMAT(d,f) 按表达式 f的要求显示日期 d - SQL_FUNCTION_MAP.put("date_sub", ""); // DATE_SUB(date,INTERVAL expr type) 函数从日期减去指定的时间间隔。 - SQL_FUNCTION_MAP.put("day", ""); // DAY(d) 返回日期值 d 的日期部分 - SQL_FUNCTION_MAP.put("dayname", ""); // DAYNAME(d) 返回日期 d 是星期几,如 Monday,Tuesday - SQL_FUNCTION_MAP.put("dayofmonth", ""); // DAYOFMONTH(d) 计算日期 d 是本月的第几天 - SQL_FUNCTION_MAP.put("dayofweek", ""); // DAYOFWEEK(d) 日期 d 今天是星期几,1 星期日,2 星期一,以此类推 - SQL_FUNCTION_MAP.put("dayofyear", ""); // DAYOFYEAR(d) 计算日期 d 是本年的第几天 - SQL_FUNCTION_MAP.put("extract", ""); // EXTRACT(type FROM d) 从日期 d 中获取指定的值,type 指定返回的值。 - SQL_FUNCTION_MAP.put("from_days", ""); // FROM_DAYS(n) 计算从 0000 年 1 月 1 日开始 n 天后的日期 - SQL_FUNCTION_MAP.put("hour", ""); // 'HOUR(t) 返回 t 中的小时值 - SQL_FUNCTION_MAP.put("last_day", ""); // LAST_DAY(d) 返回给给定日期的那一月份的最后一天 - SQL_FUNCTION_MAP.put("localtime", ""); // LOCALTIME() 返回当前日期和时间 - SQL_FUNCTION_MAP.put("localtimestamp", ""); // LOCALTIMESTAMP() 返回当前日期和时间 - SQL_FUNCTION_MAP.put("makedate", ""); // MAKEDATE(year, day-of-year) 基于给定参数年份 year 和所在年中的天数序号 day-of-year 返回一个日期 - SQL_FUNCTION_MAP.put("maketime", ""); // MAKETIME(hour, minute, second) 组合时间,参数分别为小时、分钟、秒 - SQL_FUNCTION_MAP.put("microsecond", ""); // MICROSECOND(date) 返回日期参数所对应的微秒数 - SQL_FUNCTION_MAP.put("minute", ""); // MINUTE(t) 返回 t 中的分钟值 - SQL_FUNCTION_MAP.put("monthname", ""); // MONTHNAME(d) 返回日期当中的月份名称,如 November - SQL_FUNCTION_MAP.put("month", ""); // MONTH(d) 返回日期d中的月份值,1 到 12 - SQL_FUNCTION_MAP.put("now", ""); // NOW() 返回当前日期和时间 - SQL_FUNCTION_MAP.put("period_add", ""); // PERIOD_ADD(period, number) 为 年-月 组合日期添加一个时段 - SQL_FUNCTION_MAP.put("period_diff", ""); // PERIOD_DIFF(period1, period2) 返回两个时段之间的月份差值 - SQL_FUNCTION_MAP.put("quarter", ""); // QUARTER(d) 返回日期d是第几季节,返回 1 到 4 - SQL_FUNCTION_MAP.put("second", ""); // SECOND(t) 返回 t 中的秒钟值 - SQL_FUNCTION_MAP.put("sec_to_time", ""); // SEC_TO_TIME", ""); // ) 将以秒为单位的时间 s 转换为时分秒的格式 - SQL_FUNCTION_MAP.put("str_to_date", ""); // STR_TO_DATE", ""); // tring, format_mask) 将字符串转变为日期 - SQL_FUNCTION_MAP.put("subdate", ""); // SUBDATE(d,n) 日期 d 减去 n 天后的日期 - SQL_FUNCTION_MAP.put("subtime", ""); // SUBTIME(t,n) 时间 t 减去 n 秒的时间 - SQL_FUNCTION_MAP.put("sysdate", ""); // SYSDATE() 返回当前日期和时间 - SQL_FUNCTION_MAP.put("time", ""); // TIME(expression) 提取传入表达式的时间部分 - SQL_FUNCTION_MAP.put("time_format", ""); // TIME_FORMAT(t,f) 按表达式 f 的要求显示时间 t - SQL_FUNCTION_MAP.put("time_to_sec", ""); // TIME_TO_SEC(t) 将时间 t 转换为秒 - SQL_FUNCTION_MAP.put("timediff", ""); // TIMEDIFF(time1, time2) 计算时间差值 - SQL_FUNCTION_MAP.put("timestamp", ""); // TIMESTAMP(expression, interval) 单个参数时,函数返回日期或日期时间表达式;有2个参数时,将参数加和 - SQL_FUNCTION_MAP.put("to_days", ""); // TO_DAYS(d) 计算日期 d 距离 0000 年 1 月 1 日的天数 - SQL_FUNCTION_MAP.put("week", ""); // WEEK(d) 计算日期 d 是本年的第几个星期,范围是 0 到 53 - SQL_FUNCTION_MAP.put("weekday", ""); // WEEKDAY(d) 日期 d 是星期几,0 表示星期一,1 表示星期二 - SQL_FUNCTION_MAP.put("weekofyear", ""); // WEEKOFYEAR(d) 计算日期 d 是本年的第几个星期,范围是 0 到 53 - SQL_FUNCTION_MAP.put("year", ""); // YEAR(d) 返回年份 - SQL_FUNCTION_MAP.put("yearweek", ""); // YEARWEEK(date, mode) 返回年份及第几周(0到53),mode 中 0 表示周天,1表示周一,以此类推 - SQL_FUNCTION_MAP.put("unix_timestamp", ""); // UNIX_TIMESTAMP(date) 获取UNIX时间戳函数,返回一个以 UNIX 时间戳为基础的无符号整数 - SQL_FUNCTION_MAP.put("from_unixtime", ""); // FROM_UNIXTIME(date) 将 UNIX 时间戳转换为时间格式,与UNIX_TIMESTAMP互为反函数 - - // MYSQL JSON 函数 - SQL_FUNCTION_MAP.put("json_append", ""); // JSON_APPEND(json_doc, path, val[, path, val] ...)) 插入JSON数组 - SQL_FUNCTION_MAP.put("json_array", ""); // JSON_ARRAY(val1, val2...) 创建JSON数组 - SQL_FUNCTION_MAP.put("json_array_append", ""); // JSON_ARRAY_APPEND(json_doc, val) 将数据附加到JSON文档 - SQL_FUNCTION_MAP.put("json_array_insert", ""); // JSON_ARRAY_INSERT(json_doc, val) 插入JSON数组 - SQL_FUNCTION_MAP.put("json_contains", ""); // JSON_CONTAINS(json_doc, val) JSON文档是否在路径中包含特定对象 - SQL_FUNCTION_MAP.put("json_contains_path", ""); // JSON_CONTAINS_PATH(json_doc, path) JSON文档是否在路径中包含任何数据 - SQL_FUNCTION_MAP.put("json_depth", ""); // JSON_DEPTH(json_doc) JSON文档的最大深度 - SQL_FUNCTION_MAP.put("json_extract", ""); // JSON_EXTRACT(json_doc, path) 从JSON文档返回数据 - SQL_FUNCTION_MAP.put("json_insert", ""); // JSON_INSERT(json_doc, val) 将数据插入JSON文档 - SQL_FUNCTION_MAP.put("json_keys", ""); // JSON_KEYS(json_doc[, path]) JSON文档中的键数组 - SQL_FUNCTION_MAP.put("json_length", ""); // JSON_LENGTH(json_doc) JSON文档中的元素数 - SQL_FUNCTION_MAP.put("json_merge", ""); // JSON_MERGE(json_doc1, json_doc2) (已弃用) 合并JSON文档,保留重复的键。JSON_MERGE_PRESERVE()的已弃用同义词 - SQL_FUNCTION_MAP.put("json_merge_patch", ""); // JSON_MERGE_PATCH(json_doc1, json_doc2) 合并JSON文档,替换重复键的值 - SQL_FUNCTION_MAP.put("json_merge_preserve", ""); // JSON_MERGE_PRESERVE(json_doc1, json_doc2) 合并JSON文档,保留重复的键 - SQL_FUNCTION_MAP.put("json_object", ""); // JSON_OBJECT(key1, val1, key2, val2...) 创建JSON对象 - SQL_FUNCTION_MAP.put("json_overlaps", ""); // JSON_OVERLAPS(json_doc1, json_doc2) (引入8.0.17) 比较两个JSON文档,如果它们具有相同的键值对或数组元素,则返回TRUE(1),否则返回FALSE(0) - SQL_FUNCTION_MAP.put("json_pretty", ""); // JSON_PRETTY(json_doc) 以易于阅读的格式打印JSON文档 - SQL_FUNCTION_MAP.put("json_quote", ""); // JSON_QUOTE(json_doc1) 引用JSON文档 - SQL_FUNCTION_MAP.put("json_remove", ""); // JSON_REMOVE(json_doc1, path) 从JSON文档中删除数据 - SQL_FUNCTION_MAP.put("json_replace", ""); // JSON_REPLACE(json_doc1, val1, val2) 替换JSON文档中的值 - SQL_FUNCTION_MAP.put("json_schema_valid", ""); // JSON_SCHEMA_VALID(json_doc) (引入8.0.17) 根据JSON模式验证JSON文档;如果文档针对架构进行验证,则返回TRUE / 1;否则,则返回FALSE / 0 - SQL_FUNCTION_MAP.put("json_schema_validation_report", ""); // JSON_SCHEMA_VALIDATION_REPORT(json_doc, mode) (引入8.0.17) 根据JSON模式验证JSON文档;以JSON格式返回有关验证结果的报告,包括成功或失败以及失败原因 - SQL_FUNCTION_MAP.put("json_search", ""); // JSON_SEARCH(json_doc, val) JSON文档中值的路径 - SQL_FUNCTION_MAP.put("json_set", ""); // JSON_SET(json_doc, val) 将数据插入JSON文档 - // SQL_FUNCTION_MAP.put("json_storage_free", ""); // JSON_STORAGE_FREE() 部分更新后,JSON列值的二进制表示形式中的可用空间 - // SQL_FUNCTION_MAP.put("json_storage_size", ""); // JSON_STORAGE_SIZE() 用于存储JSON文档的二进制表示的空间 - SQL_FUNCTION_MAP.put("json_table", ""); // JSON_TABLE() 从JSON表达式返回数据作为关系表 - SQL_FUNCTION_MAP.put("json_type", ""); // JSON_TYPE(json_doc) JSON值类型 - SQL_FUNCTION_MAP.put("json_unquote", ""); // JSON_UNQUOTE(json_doc) 取消引用JSON值 - SQL_FUNCTION_MAP.put("json_valid", ""); // JSON_VALID(json_doc) JSON值是否有效 - SQL_FUNCTION_MAP.put("json_arrayagg", ""); // JSON_ARRAYAGG(key) 将每个表达式转换为 JSON 值,然后返回一个包含这些 JSON 值的 JSON 数组 - SQL_FUNCTION_MAP.put("json_objectagg", ""); // JSON_OBJECTAGG(key, val)) 将每个表达式转换为 JSON 值,然后返回一个包含这些 JSON 值的 JSON 对象 - - // MySQL 高级函数 - // SQL_FUNCTION_MAP.put("bin", ""); // BIN(x) 返回 x 的二进制编码 - // SQL_FUNCTION_MAP.put("binary", ""); // BINARY(s) 将字符串 s 转换为二进制字符串 - SQL_FUNCTION_MAP.put("case", ""); // CASE 表示函数开始,END 表示函数结束。如果 condition1 成立,则返回 result1, 如果 condition2 成立,则返回 result2,当全部不成立则返回 result,而当有一个成立之后,后面的就不执行了。 - SQL_FUNCTION_MAP.put("cast", ""); // CAST(x AS type) 转换数据类型 - SQL_FUNCTION_MAP.put("coalesce", ""); // COALESCE(expr1, expr2, ...., expr_n) 返回参数中的第一个非空表达式(从左向右) - // SQL_FUNCTION_MAP.put("conv", ""); // CONV(x,f1,f2) 返回 f1 进制数变成 f2 进制数 - // SQL_FUNCTION_MAP.put("convert", ""); // CONVERT(s, cs) 函数将字符串 s 的字符集变成 cs - SQL_FUNCTION_MAP.put("if", ""); // IF(expr,v1,v2) 如果表达式 expr 成立,返回结果 v1;否则,返回结果 v2。 - SQL_FUNCTION_MAP.put("ifnull", ""); // IFNULL(v1,v2) 如果 v1 的值不为 NULL,则返回 v1,否则返回 v2。 - SQL_FUNCTION_MAP.put("isnull", ""); // ISNULL(expression) 判断表达式是否为 NULL - SQL_FUNCTION_MAP.put("nullif", ""); // NULLIF(expr1, expr2) 比较两个字符串,如果字符串 expr1 与 expr2 相等 返回 NULL,否则返回 expr1 - SQL_FUNCTION_MAP.put("group_concat", ""); // GROUP_CONCAT([DISTINCT], s1, s2...) - } @@ -784,14 +602,14 @@ public String getHavingString(boolean hasPrefix) { method = expression.substring(0, start); if (method.isEmpty() == false) { - if (SQL_FUNCTION_MAP == null || SQL_FUNCTION_MAP.isEmpty()) { + if (FunctionsAndRaws.SQL_FUNCTION_MAP == null || FunctionsAndRaws.SQL_FUNCTION_MAP.isEmpty()) { if (StringUtil.isName(method) == false) { throw new IllegalArgumentException("字符 " + method + " 不合法!" + "预编译模式下 @having:\"column?value;function(arg0,arg1,...)?value...\"" + " 中 function 必须符合小写英文单词的 SQL 函数名格式!"); } } - else if (SQL_FUNCTION_MAP.containsKey(method) == false) { + else if (FunctionsAndRaws.SQL_FUNCTION_MAP.containsKey(method) == false) { throw new IllegalArgumentException("字符 " + method + " 不合法!" + "预编译模式下 @column:\"column0,column1:alias;function0(arg0,arg1,...);function1(...):alias...\"" + " 中 function 必须符合小写英文单词的 SQL 函数名格式!且必须是后端允许调用的 SQL 函数!"); @@ -983,7 +801,7 @@ public String getRawSQL(String key, Object value) throws Exception { + "对应的 " + key + ":value 中 value 类型只能为 String!"); } - String rawSQL = containRaw ? RAW_MAP.get(value) : null; + String rawSQL = containRaw ? FunctionsAndRaws.RAW_MAP.get(value) : null; if (containRaw) { if (rawSQL == null) { throw new UnsupportedOperationException("@raw:value 的 value 中 " + key + " 不合法!" @@ -1052,7 +870,7 @@ public String getColumnString(boolean inSQLJoin) throws Exception { for (String c : column) { if (containRaw) { // 由于 HashMap 对 key 做了 hash 处理,所以 get 比 containsValue 更快 - if ("".equals(RAW_MAP.get(c)) || RAW_MAP.containsValue(c)) { // newSQLConfig 提前处理好的 + if ("".equals(FunctionsAndRaws.RAW_MAP.get(c)) || FunctionsAndRaws.RAW_MAP.containsValue(c)) { // newSQLConfig 提前处理好的 //排除@raw中的值,以避免使用date_format(date,'%Y-%m-%d %H:%i:%s') 时,冒号的解析出错 //column.remove(c); continue; @@ -1164,7 +982,7 @@ public String getColumnString(boolean inSQLJoin) throws Exception { expression = keys[i]; if (containRaw) { // 由于 HashMap 对 key 做了 hash 处理,所以 get 比 containsValue 更快 - if ("".equals(RAW_MAP.get(expression)) || RAW_MAP.containsValue(expression)) { // newSQLConfig 提前处理好的 + if ("".equals(FunctionsAndRaws.RAW_MAP.get(expression)) || FunctionsAndRaws.RAW_MAP.containsValue(expression)) { // newSQLConfig 提前处理好的 continue; } @@ -1183,153 +1001,257 @@ public String getColumnString(boolean inSQLJoin) throws Exception { throw new UnsupportedOperationException("@column:value 的 value 中字符串 " + expression + " 不合法!" + "不允许传超过 100 个字符的函数或表达式!请用 @raw 简化传参!"); } + keys[i] = getColumnPrase(expression); + } + String c = StringUtil.getString(keys); + c = c + (StringUtil.isEmpty(joinColumn, true) ? "" : ", " + joinColumn);//不能在这里改,后续还要用到: + return c; + default: + throw new UnsupportedOperationException( + "服务器内部错误:getColumnString 不支持 " + RequestMethod.getName(getMethod()) + + " 等 [GET,GETS,HEAD,HEADS,POST] 外的ReuqestMethod!" + ); + } + } - int start = expression.indexOf("("); - int end = 0; - if (start >= 0) { - end = expression.lastIndexOf(")"); - if (start >= end) { - throw new IllegalArgumentException("字符 " + expression + " 不合法!" - + "@column:value 中 value 里的 SQL函数必须为 function(arg0,arg1,...) 这种格式!"); + /** + * 解析@column 中以“;”分隔的表达式("@column":"expression1;expression2;expression2;....")中的expression + * + * @param expression + * @return + */ + public String getColumnPrase(String expression) { + String quote = getQuote(); + int start = expression.indexOf('('); + if (start < 0) { + //没有函数 ,可能是字段,也可能是 DISTINCT xx + String cks[] = parseArgsSplitWithComma(expression, true); + expression = StringUtil.getString(cks); + } else { + //有函数,但不是窗口函数 + if (expression.indexOf("OVER") < 0) { + int end = expression.lastIndexOf(")"); + if (start >= end) { + throw new IllegalArgumentException("字符 " + expression + " 不合法!" + + "@column:value 中 value 里的 SQL函数必须为 function(arg0,arg1,...) 这种格式!"); + } + String fun = expression.substring(0, start); + if (fun.isEmpty() == false) { + if (FunctionsAndRaws.SQL_FUNCTION_MAP == null || FunctionsAndRaws.SQL_FUNCTION_MAP.isEmpty()) { + if (StringUtil.isName(fun) == false) { + throw new IllegalArgumentException("字符 " + fun + " 不合法!" + + "预编译模式下 @column:\"column0,column1:alias;function0(arg0,arg1,...);function1(...):alias...\"" + + " 中 function 必须符合小写英文单词的 SQL 函数名格式!"); + } + } else if (FunctionsAndRaws.SQL_FUNCTION_MAP.containsKey(fun) == false) { + throw new IllegalArgumentException("字符 " + fun + " 不合法!" + + "预编译模式下 @column:\"column0,column1:alias;function0(arg0,arg1,...);function1(...):alias...\"" + + " 中 function 必须符合小写英文单词的 SQL 函数名格式!且必须是后端允许调用的 SQL 函数!"); } + } - method = expression.substring(0, start); - boolean distinct = i <= 0 && method.startsWith(PREFFIX_DISTINCT); - String fun = distinct ? method.substring(PREFFIX_DISTINCT.length()) : method; + String s = expression.substring(start + 1, end); + // 解析函数内的参数 + String ckeys[] = parseArgsSplitWithComma(s, false); + + String suffix = expression.substring(end + 1, expression.length()); //:contactCount + int index = suffix.lastIndexOf(":"); + String alias = index < 0 ? "" : suffix.substring(index + 1); //contactCount + suffix = index < 0 ? suffix : suffix.substring(0, index); + if (alias.isEmpty() == false && StringUtil.isName(alias) == false) { + throw new IllegalArgumentException("字符串 " + alias + " 不合法!" + + "预编译模式下 @column:value 中 value里面用 ; 分割的每一项" + + " function(arg0,arg1,...):alias 中 alias 必须是1个单词!并且不要有多余的空格!"); + } - if (fun.isEmpty() == false) { - if (SQL_FUNCTION_MAP == null || SQL_FUNCTION_MAP.isEmpty()) { - if (StringUtil.isName(fun) == false) { - throw new IllegalArgumentException("字符 " + method + " 不合法!" - + "预编译模式下 @column:\"column0,column1:alias;function0(arg0,arg1,...);function1(...):alias...\"" - + " 中 function 必须符合小写英文单词的 SQL 函数名格式!"); - } - } - else if (SQL_FUNCTION_MAP.containsKey(fun) == false) { - throw new IllegalArgumentException("字符 " + method + " 不合法!" + if (suffix.isEmpty() == false && (((String) suffix).contains("--") || ((String) suffix).contains("/*") || PATTERN_RANGE.matcher((String) suffix).matches() == false)) { + throw new UnsupportedOperationException("字符串 " + suffix + " 不合法!" + + "预编译模式下 @column:\"column?value;function(arg0,arg1,...)?value...\"" + + " 中 ?value 必须符合正则表达式 " + PATTERN_RANGE + " 且不包含连续减号 -- 或注释符 /* !不允许多余的空格!"); + } + + String origin = fun + "(" + StringUtil.getString(ckeys) + ")" + suffix; + expression = origin + (StringUtil.isEmpty(alias, true) ? "" : " AS " + quote + alias + quote); + + } else { + //是窗口函数 fun(arg0,agr1) OVER (agr0 agr1 ...) + int overindex = expression.indexOf("OVER"); // OVER 的位置 + String s1 = expression.substring(0, overindex); // OVER 前半部分 + String s2 = expression.substring(overindex); // OVER 后半部分 + + int index1 = s1.indexOf("("); // 函数 "(" 的起始位置 + String fun = s1.substring(0, index1); // 函数名称 + int end = s2.lastIndexOf(")"); // 后半部分 “)” 的位置 + + if (index1 >= end) { + throw new IllegalArgumentException("字符 " + expression + " 不合法!" + + "@column:value 中 value 里的 SQL函数必须为 function(arg0,arg1,...) 这种格式!"); + } + if (fun.isEmpty() == false) { + if (FunctionsAndRaws.SQL_FUNCTION_MAP == null || FunctionsAndRaws.SQL_FUNCTION_MAP.isEmpty()) { + if (StringUtil.isName(fun) == false) { + throw new IllegalArgumentException("字符 " + fun + " 不合法!" + "预编译模式下 @column:\"column0,column1:alias;function0(arg0,arg1,...);function1(...):alias...\"" - + " 中 function 必须符合小写英文单词的 SQL 函数名格式!且必须是后端允许调用的 SQL 函数!"); + + " 中 function 必须符合小写英文单词的 SQL 函数名格式!"); } + } else if (FunctionsAndRaws.SQL_FUNCTION_MAP.containsKey(fun) == false) { + throw new IllegalArgumentException("字符 " + fun + " 不合法!" + + "预编译模式下 @column:\"column0,column1:alias;function0(arg0,arg1,...);function1(...):alias...\"" + + " 中 function 必须符合小写英文单词的 SQL 函数名格式!且必须是后端允许调用的 SQL 函数!"); } - } - boolean isColumn = start < 0; + // 获取前半部分函数的参数解析 fun(arg0,agr1) + String agrsString1[] = parseArgsSplitWithComma(s1.substring(index1 + 1, s1.lastIndexOf(")")), false); - String[] ckeys = StringUtil.split(isColumn ? expression : expression.substring(start + 1, end)); - String quote = getQuote(); + int index2 = s2.indexOf("("); // 后半部分 “(”的起始位置 + String argString2 = s2.substring(index2 + 1, end); // 后半部分的参数 + // 别名 + String alias = s2.lastIndexOf(":") < 0 ? null : s2.substring(s2.lastIndexOf(":") + 1); + // 获取后半部分的参数解析 (agr0 agr1 ...) + String argsString2[] = parseArgsSplitWithComma(argString2,false); + expression = fun + "(" + StringUtil.getString(agrsString1) + ")" + " OVER " + "(" + StringUtil.getString(argsString2) + ")" + (StringUtil.isEmpty(alias, true) ? "" : " AS " + quote + alias + quote); } + } + return expression; - // if (isPrepared()) { //不能通过 ? 来代替,SELECT 'id','name' 返回的就是 id:"id", name:"name",而不是数据库里的值! - if (ckeys != null && ckeys.length > 0) { + } - boolean distinct; - String origin; - String alias; - int index; - for (int j = 0; j < ckeys.length; j++) { - index = isColumn ? ckeys[j].lastIndexOf(":") : -1; //StringUtil.split返回数组中,子项不会有null - origin = index < 0 ? ckeys[j] : ckeys[j].substring(0, index); - alias = index < 0 ? null : ckeys[j].substring(index + 1); + /** + * 解析函数参数或者字段,此函数对于解析字段 和 函数内参数通用 + * + * @param param + * @param isColumn true:不是函数参数。false:是函数参数 + * @return + */ + private String[] parseArgsSplitWithComma(String param, boolean isColumn) { + // 以"," 分割参数 + String quote = getQuote(); + String tableAlias = getAliasWithQuote(); + String ckeys[] = StringUtil.split(param); // 以","分割参数 + if (ckeys != null && ckeys.length > 0) { + String origin; + String alias; + int index; + for (int i = 0; i < ckeys.length; i++) { + // 如果参数包含 "'" ,解析字符串 + if (ckeys[i].contains("'")) { + int count = 0; + for (int j = 0; j < ckeys[i].length(); j++) { + if (ckeys[i].charAt(j) == '\'') count++; + } + // 排除字符串中参数中包含 ' 的情况和不以' 开头和结尾的情况,同时排除 cast('s' as ...) 以空格分隔的参数中包含字符串的情况 + if (count != 2 || !(ckeys[i].startsWith("'") && ckeys[i].endsWith("'"))) { + throw new IllegalArgumentException("字符串 " + ckeys[i] + " 不合法!" + + "预编译模式下 @column:\"column0,column1:alias;function0(arg0,arg1,...);function1(...):alias...\"" + + " 中字符串参数不合法,必须以 ' 开头, ' 结尾,字符串中不能包含 ' "); + } + //sql 注入判断 判断 + origin = (ckeys[i].substring(1, ckeys[i].length() - 1)); + if (origin.contains("--") || PATTERN_STRING.matcher(origin).matches() == true) { + throw new IllegalArgumentException("字符 " + ckeys[i] + " 不合法!" + + "预编译模式下 @column:\"column0,column1:alias;function0(arg0,arg1,...);function1(...):alias...\"" + + " 中所有字符串 arg 都必须不符合正则表达式 " + PATTERN_STRING + " 且不包含连续减号 -- !"); + } - distinct = j <= 0 && origin.startsWith(PREFFIX_DISTINCT); - if (distinct) { - origin = origin.substring(PREFFIX_DISTINCT.length()); - } + // 1.字符串不是字段也没有别名,所以不解析别名 2. 是字符串,进行预编译,使用getValue() ,对字符串进行截取 + ckeys[i] = getValue(ckeys[i].substring(1, ckeys[i].length() - 1)).toString(); - if (isPrepared()) { - if (isColumn) { - if (StringUtil.isName(origin) == false || (alias != null && StringUtil.isName(alias) == false)) { - throw new IllegalArgumentException("字符 " + ckeys[j] + " 不合法!" - + "预编译模式下 @column:value 中 value里面用 , 分割的每一项" - + " column:alias 中 column 必须是1个单词!如果有alias,则alias也必须为1个单词!" - + "DISTINCT 必须全大写,且后面必须有且只有 1 个空格!其它情况不允许空格!"); - } + } else { + // 参数不包含",",即不是字符串 + // 解析参数:1. 字段 ,2. 是以空格分隔的参数 eg: cast(now() as date) + index = ckeys[i].lastIndexOf(":"); //StringUtil.split返回数组中,子项不会有null + origin = index < 0 ? ckeys[i] : ckeys[i].substring(0, index); //获取 : 之前的 + alias = index < 0 ? null : ckeys[i].substring(index + 1); + if (isPrepared()) { + if (isColumn) { + if (StringUtil.isName(origin) == false || (alias != null && StringUtil.isName(alias) == false)) { + throw new IllegalArgumentException("字符 " + ckeys[i] + " 不合法!" + + "预编译模式下 @column:value 中 value里面用 , 分割的每一项" + + " column:alias 中 column 必须是1个单词!如果有alias,则alias也必须为1个单词!" + + "关键字必须全大写,且以空格分隔的参数,空格必须只有 1 个!其它情况不允许空格!"); } - else { - // if ((StringUtil.isName(origin) == false || origin.startsWith("_"))) { - if (origin.startsWith("_") || origin.contains("--") || PATTERN_FUNCTION.matcher(origin).matches() == false) { - throw new IllegalArgumentException("字符 " + ckeys[j] + " 不合法!" - + "预编译模式下 @column:\"column0,column1:alias;function0(arg0,arg1,...);function1(...):alias...\"" - + " 中所有 arg 都必须是1个不以 _ 开头的单词 或者符合正则表达式 " + PATTERN_FUNCTION + " 且不包含连续减号 -- !DISTINCT 必须全大写,且后面必须有且只有 1 个空格!其它情况不允许空格!"); - } + } else { + if (origin.startsWith("_") || origin.contains("--") || PATTERN_FUNCTION.matcher(origin).matches() == false) { + throw new IllegalArgumentException("字符 " + ckeys[i] + " 不合法!" + + "预编译模式下 @column:\"column0,column1:alias;function0(arg0,arg1,...);function1(...):alias...\"" + + " 中所有 arg 都必须是1个不以 _ 开头的单词 或者符合正则表达式 " + PATTERN_FUNCTION + " 且不包含连续减号 -- !DISTINCT 必须全大写,且后面必须有且只有 1 个空格!其它情况不允许空格!"); } } + } + // 以空格分割参数 + String mkes[] = StringUtil.split(ckeys[i], " ", true); - //JOIN 副表不再在外层加副表名前缀 userId AS `Commet.userId`, 而是直接 userId AS `userId` - boolean isName = false; - if (StringUtil.isNumer(origin)) { + boolean isName = false; + //如果参数中含有空格(少数情况) 比如 fun(arg1 arg2 arg3 ,arg4) 中的 arg1 arg2 arg3,比如 DISTINCT id + if (mkes != null && mkes.length >= 2) { + ckeys[i] = praseArgsSplitWithSpace(mkes); + } else { + // 如果参数没有空格 + if ("".equals(FunctionsAndRaws.RAW_MAP.get(origin))) { + // do nothing , 比如 toDate(now()) , + } else if (StringUtil.isNumer(origin)) { //do nothing - } - else if (StringUtil.isName(origin)) { + } else if (StringUtil.isName(origin)) { origin = quote + origin + quote; isName = true; - } - else { + } else { origin = getValue(origin).toString(); } - if (isName && isKeyPrefix()) { - ckeys[j] = tableAlias + "." + origin; - // if (isColumn) { - // ckeys[j] += " AS " + quote + (isMain() ? "" : tableAlias + ".") + (StringUtil.isEmpty(alias, true) ? origin : alias) + quote; - // } + ckeys[i] = tableAlias + "." + origin; if (isColumn && StringUtil.isEmpty(alias, true) == false) { - ckeys[j] += " AS " + quote + alias + quote; + ckeys[i] += " AS " + quote + alias + quote; } } else { - ckeys[j] = origin + (StringUtil.isEmpty(alias, true) ? "" : " AS " + quote + alias + quote); - } - - if (distinct) { - ckeys[j] = PREFFIX_DISTINCT + ckeys[j]; + ckeys[i] = origin + (StringUtil.isEmpty(alias, true) ? "" : " AS " + quote + alias + quote); } } - // } - - } - if (isColumn) { - keys[i] = StringUtil.getString(ckeys); } - else { - String suffix = expression.substring(end + 1, expression.length()); //:contactCount - int index = suffix.lastIndexOf(":"); - String alias = index < 0 ? "" : suffix.substring(index + 1); //contactCount - suffix = index < 0 ? suffix : suffix.substring(0, index); + } + } + return ckeys; + } - if (alias.isEmpty() == false && StringUtil.isName(alias) == false) { - throw new IllegalArgumentException("字符串 " + alias + " 不合法!" - + "预编译模式下 @column:value 中 value里面用 ; 分割的每一项" - + " function(arg0,arg1,...):alias 中 alias 必须是1个单词!并且不要有多余的空格!"); - } - if (suffix.isEmpty() == false && (((String) suffix).contains("--") || ((String) suffix).contains("/*") || PATTERN_RANGE.matcher((String) suffix).matches() == false)) { - throw new UnsupportedOperationException("字符串 " + suffix + " 不合法!" - + "预编译模式下 @column:\"column?value;function(arg0,arg1,...)?value...\"" - + " 中 ?value 必须符合正则表达式 " + PATTERN_RANGE + " 且不包含连续减号 -- 或注释符 /* !不允许多余的空格!"); + /** + * 只解析以空格分隔的参数 + * + * @param mkes + * @return + */ + private String praseArgsSplitWithSpace(String mkes[]) { + String quote = getQuote(); + String tableAlias = getAliasWithQuote(); + boolean isName = false; + // 包含空格的参数 肯定不包含别名 不用处理别名 + if (mkes != null && mkes.length > 0) { + for (int j = 0; j < mkes.length; j++) { + // now()/AS/ DISTINCT/VALUE 等等放在RAW_MAP中 + if ("".equals(FunctionsAndRaws.RAW_MAP.get(mkes[j]))) { + continue; + } else if (StringUtil.isNumer(mkes[j])) { + // do nothing + } else { + //这里为什么还要做一次判断 是因为解析窗口函数调用的时候会判断一次 + if (isPrepared()) { + if (mkes[j].startsWith("_") || mkes[j].contains("--") || PATTERN_FUNCTION.matcher(mkes[j]).matches() == false) { + throw new IllegalArgumentException("字符 " + mkes[j] + " 不合法!" + + "预编译模式下 @column:\"column0,column1:alias;function0(arg0,arg1,...);function1(...):alias...\"" + + " 中所有 arg 都必须是1个不以 _ 开头的单词 或者符合正则表达式 " + PATTERN_FUNCTION + " 且不包含连续减号 -- !DISTINCT 必须全大写,且后面必须有且只有 1 个空格!其它情况不允许空格!"); + } } - - String origin = method + "(" + StringUtil.getString(ckeys) + ")" + suffix; - // if (isKeyPrefix()) { - // keys[i] = origin + " AS " + quote + (isMain() ? "" : tableAlias + ".") + (StringUtil.isEmpty(alias, true) ? method : alias) + quote; - // } - // else { - keys[i] = origin + (StringUtil.isEmpty(alias, true) ? "" : " AS " + quote + alias + quote); - // } + mkes[j] = quote + mkes[j] + quote; + isName = true; + } + if (isName && isKeyPrefix()) { + mkes[j] = tableAlias + "." + mkes[j]; } - } - - String c = StringUtil.getString(keys); - c = c + (StringUtil.isEmpty(joinColumn, true) ? "" : ", " + joinColumn);//不能在这里改,后续还要用到: - return isMain() && isDistinct() ? PREFFIX_DISTINCT + c : c; - default: - throw new UnsupportedOperationException( - "服务器内部错误:getColumnString 不支持 " + RequestMethod.getName(getMethod()) - + " 等 [GET,GETS,HEAD,HEADS,POST] 外的ReuqestMethod!" - ); } + // 返回重新以" "拼接后的参数 + return StringUtil.join(mkes, " "); } diff --git a/APIJSONORM/src/main/java/apijson/orm/FunctionsAndRaws.java b/APIJSONORM/src/main/java/apijson/orm/FunctionsAndRaws.java new file mode 100644 index 000000000..e0b945340 --- /dev/null +++ b/APIJSONORM/src/main/java/apijson/orm/FunctionsAndRaws.java @@ -0,0 +1,519 @@ +package apijson.orm; + +import java.util.LinkedHashMap; +import java.util.Map; + + +public class FunctionsAndRaws { + // 自定义原始 SQL 片段 Map:当 substring 为 null 时忽略;当 substring 为 "" 时整个 value 是 raw SQL;其它情况则只是 substring 这段为 raw SQL + public static final Map RAW_MAP; + // 允许调用的 SQL 函数:当 substring 为 null 时忽略;当 substring 为 "" 时整个 value 是 raw SQL;其它情况则只是 substring 这段为 raw SQL + public static final Map SQL_FUNCTION_MAP; + static { + RAW_MAP = new LinkedHashMap<>(); // 保证顺序,避免配置冲突等意外情况 + + // mysql关键字 + RAW_MAP.put("AS",""); + RAW_MAP.put("VALUE",""); + RAW_MAP.put("DISTINCT",""); + + //时间 + RAW_MAP.put("DATE",""); + RAW_MAP.put("now()",""); + RAW_MAP.put("DATETIME",""); + RAW_MAP.put("DateTime",""); + RAW_MAP.put("SECOND",""); + RAW_MAP.put("MINUTE",""); + RAW_MAP.put("HOUR",""); + RAW_MAP.put("DAY",""); + RAW_MAP.put("WEEK",""); + RAW_MAP.put("MONTH",""); + RAW_MAP.put("QUARTER",""); + RAW_MAP.put("YEAR",""); + RAW_MAP.put("json",""); + RAW_MAP.put("unit",""); + + //MYSQL 数据类型 BINARY,CHAR,DATETIME,TIME,DECIMAL,SIGNED,UNSIGNED + RAW_MAP.put("BINARY",""); + RAW_MAP.put("SIGNED",""); + RAW_MAP.put("DECIMAL",""); + RAW_MAP.put("BINARY",""); + RAW_MAP.put("UNSIGNED",""); + RAW_MAP.put("CHAR",""); + RAW_MAP.put("TIME",""); + + //窗口函数关键字 + RAW_MAP.put("OVER",""); + RAW_MAP.put("INTERVAL",""); + RAW_MAP.put("ORDER",""); + RAW_MAP.put("BY",""); + RAW_MAP.put("PARTITION",""); //往前 + RAW_MAP.put("DESC",""); + RAW_MAP.put("ASC",""); + RAW_MAP.put("FOLLOWING","");//往后 + RAW_MAP.put("BETWEEN",""); + RAW_MAP.put("AND",""); + RAW_MAP.put("ROWS",""); + + + SQL_FUNCTION_MAP = new LinkedHashMap<>(); // 保证顺序,避免配置冲突等意外情况 + + + //窗口函数 + SQL_FUNCTION_MAP.put("rank", "");//得到数据项在分组中的排名,排名相等的时候会留下空位 + SQL_FUNCTION_MAP.put("dense_rank", ""); //得到数据项在分组中的排名,排名相等的时候不会留下空位 + SQL_FUNCTION_MAP.put("row_number", "");//按照分组中的顺序生成序列,不存在重复的序列 + SQL_FUNCTION_MAP.put("ntile", "");//用于将分组数据按照顺序切分成N片,返回当前切片值,不支持ROWS_BETWEE + SQL_FUNCTION_MAP.put("first_value", "");//取分组排序后,截止到当前行,分组内第一个值 + SQL_FUNCTION_MAP.put("last_value", "");//取分组排序后,截止到当前行,分组内的最后一个值 + SQL_FUNCTION_MAP.put("lag", "");//统计窗口内往上第n行值。第一个参数为列名,第二个参数为往上第n行(可选,默认为1),第三个参数为默认值(当往上第n行为NULL时候,取默认值,如不指定,则为NULL) + SQL_FUNCTION_MAP.put("lead", "");//统计窗口内往下第n行值。第一个参数为列名,第二个参数为往下第n行(可选,默认为1),第三个参数为默认值(当往下第n行为NULL时候,取默认值,如不指定,则为NULL) + SQL_FUNCTION_MAP.put("cume_dist", "");//)返回(小于等于当前行值的行数)/(当前分组内的总行数) + SQL_FUNCTION_MAP.put("percent_rank", "");//返回(组内当前行的rank值-1)/(分组内做总行数-1) + + // MySQL 字符串函数 + SQL_FUNCTION_MAP.put("ascii", ""); // ASCII(s) 返回字符串 s 的第一个字符的 ASCII 码。 + SQL_FUNCTION_MAP.put("char_length", ""); // CHAR_LENGTH(s) 返回字符串 s 的字符数 + SQL_FUNCTION_MAP.put("character_length", ""); // CHARACTER_LENGTH(s) 返回字符串 s 的字符数 + SQL_FUNCTION_MAP.put("concat", ""); // CONCAT(s1, s2...sn) 字符串 s1,s2 等多个字符串合并为一个字符串 + SQL_FUNCTION_MAP.put("concat_ws", ""); // CONCAT_WS(x, s1, s2...sn) 同 CONCAT(s1, s2 ...) 函数,但是每个字符串之间要加上 x,x 可以是分隔符 + SQL_FUNCTION_MAP.put("field", ""); // FIELD(s, s1, s2...) 返回第一个字符串 s 在字符串列表 (s1, s2...)中的位置 + SQL_FUNCTION_MAP.put("find_in_set", ""); // FIND_IN_SET(s1, s2) 返回在字符串s2中与s1匹配的字符串的位置 + SQL_FUNCTION_MAP.put("format", ""); // FORMAT(x, n) 函数可以将数字 x 进行格式化 "#,###.##", 将 x 保留到小数点后 n 位,最后一位四舍五入。 + SQL_FUNCTION_MAP.put("insert", ""); // INSERT(s1, x, len, s2) 字符串 s2 替换 s1 的 x 位置开始长度为 len 的字符串 + SQL_FUNCTION_MAP.put("locate", ""); // LOCATE(s1, s) 从字符串 s 中获取 s1 的开始位置 + SQL_FUNCTION_MAP.put("lcase", ""); // LCASE(s) 将字符串 s 的所有字母变成小写字母 + SQL_FUNCTION_MAP.put("left", ""); // LEFT(s, n) 返回字符串 s 的前 n 个字符 + SQL_FUNCTION_MAP.put("length", ""); // LENGTH(s) 返回字符串 s 的字符数 + SQL_FUNCTION_MAP.put("lower", ""); // LOWER(s) 将字符串 s 的所有字母变成小写字母 + SQL_FUNCTION_MAP.put("lpad", ""); // LPAD(s1, len, s2) 在字符串 s1 的开始处填充字符串 s2,使字符串长度达到 len + SQL_FUNCTION_MAP.put("ltrim", ""); // LTRIM(s) 去掉字符串 s 开始处的空格 + SQL_FUNCTION_MAP.put("mid", ""); // MID(s, n, len) 从字符串 s 的 n 位置截取长度为 len 的子字符串,同 SUBSTRING(s, n, len) + SQL_FUNCTION_MAP.put("position", ""); // POSITION(s, s1); 从字符串 s 中获取 s1 的开始位置 + SQL_FUNCTION_MAP.put("repeat", ""); // REPEAT(s, n) 将字符串 s 重复 n 次 + SQL_FUNCTION_MAP.put("replace", ""); // REPLACE(s, s1, s2) 将字符串 s2 替代字符串 s 中的字符串 s1 + SQL_FUNCTION_MAP.put("reverse", ""); // REVERSE(s); // ) 将字符串s的顺序反过来 + SQL_FUNCTION_MAP.put("right", ""); // RIGHT(s, n) 返回字符串 s 的后 n 个字符 + SQL_FUNCTION_MAP.put("rpad", ""); // RPAD(s1, len, s2) 在字符串 s1 的结尾处添加字符串 s2,使字符串的长度达到 len + SQL_FUNCTION_MAP.put("rtrim", ""); // RTRIM", ""); // ) 去掉字符串 s 结尾处的空格 + SQL_FUNCTION_MAP.put("space", ""); // SPACE(n) 返回 n 个空格 + SQL_FUNCTION_MAP.put("strcmp", ""); // STRCMP(s1, s2) 比较字符串 s1 和 s2,如果 s1 与 s2 相等返回 0 ,如果 s1>s2 返回 1,如果 s1d2 之间相隔的天数 + SQL_FUNCTION_MAP.put("date_add", ""); // DATE_ADD(d,INTERVAL expr type) 计算起始日期 d 加上一个时间段后的日期 + SQL_FUNCTION_MAP.put("date_format", ""); // DATE_FORMAT(d,f) 按表达式 f的要求显示日期 d + SQL_FUNCTION_MAP.put("date_sub", ""); // DATE_SUB(date,INTERVAL expr type) 函数从日期减去指定的时间间隔。 + SQL_FUNCTION_MAP.put("day", ""); // DAY(d) 返回日期值 d 的日期部分 + SQL_FUNCTION_MAP.put("dayname", ""); // DAYNAME(d) 返回日期 d 是星期几,如 Monday,Tuesday + SQL_FUNCTION_MAP.put("dayofmonth", ""); // DAYOFMONTH(d) 计算日期 d 是本月的第几天 + SQL_FUNCTION_MAP.put("dayofweek", ""); // DAYOFWEEK(d) 日期 d 今天是星期几,1 星期日,2 星期一,以此类推 + SQL_FUNCTION_MAP.put("dayofyear", ""); // DAYOFYEAR(d) 计算日期 d 是本年的第几天 + SQL_FUNCTION_MAP.put("extract", ""); // EXTRACT(type FROM d) 从日期 d 中获取指定的值,type 指定返回的值。 + SQL_FUNCTION_MAP.put("from_days", ""); // FROM_DAYS(n) 计算从 0000 年 1 月 1 日开始 n 天后的日期 + SQL_FUNCTION_MAP.put("hour", ""); // 'HOUR(t) 返回 t 中的小时值 + SQL_FUNCTION_MAP.put("last_day", ""); // LAST_DAY(d) 返回给给定日期的那一月份的最后一天 + SQL_FUNCTION_MAP.put("localtime", ""); // LOCALTIME() 返回当前日期和时间 + SQL_FUNCTION_MAP.put("localtimestamp", ""); // LOCALTIMESTAMP() 返回当前日期和时间 + SQL_FUNCTION_MAP.put("makedate", ""); // MAKEDATE(year, day-of-year) 基于给定参数年份 year 和所在年中的天数序号 day-of-year 返回一个日期 + SQL_FUNCTION_MAP.put("maketime", ""); // MAKETIME(hour, minute, second) 组合时间,参数分别为小时、分钟、秒 + SQL_FUNCTION_MAP.put("microsecond", ""); // MICROSECOND(date) 返回日期参数所对应的微秒数 + SQL_FUNCTION_MAP.put("minute", ""); // MINUTE(t) 返回 t 中的分钟值 + SQL_FUNCTION_MAP.put("monthname", ""); // MONTHNAME(d) 返回日期当中的月份名称,如 November + SQL_FUNCTION_MAP.put("month", ""); // MONTH(d) 返回日期d中的月份值,1 到 12 + SQL_FUNCTION_MAP.put("now", ""); // NOW() 返回当前日期和时间 + SQL_FUNCTION_MAP.put("period_add", ""); // PERIOD_ADD(period, number) 为 年-月 组合日期添加一个时段 + SQL_FUNCTION_MAP.put("period_diff", ""); // PERIOD_DIFF(period1, period2) 返回两个时段之间的月份差值 + SQL_FUNCTION_MAP.put("quarter", ""); // QUARTER(d) 返回日期d是第几季节,返回 1 到 4 + SQL_FUNCTION_MAP.put("second", ""); // SECOND(t) 返回 t 中的秒钟值 + SQL_FUNCTION_MAP.put("sec_to_time", ""); // SEC_TO_TIME", ""); // ) 将以秒为单位的时间 s 转换为时分秒的格式 + SQL_FUNCTION_MAP.put("str_to_date", ""); // STR_TO_DATE", ""); // tring, format_mask) 将字符串转变为日期 + SQL_FUNCTION_MAP.put("subdate", ""); // SUBDATE(d,n) 日期 d 减去 n 天后的日期 + SQL_FUNCTION_MAP.put("subtime", ""); // SUBTIME(t,n) 时间 t 减去 n 秒的时间 + SQL_FUNCTION_MAP.put("sysdate", ""); // SYSDATE() 返回当前日期和时间 + SQL_FUNCTION_MAP.put("time", ""); // TIME(expression) 提取传入表达式的时间部分 + SQL_FUNCTION_MAP.put("time_format", ""); // TIME_FORMAT(t,f) 按表达式 f 的要求显示时间 t + SQL_FUNCTION_MAP.put("time_to_sec", ""); // TIME_TO_SEC(t) 将时间 t 转换为秒 + SQL_FUNCTION_MAP.put("timediff", ""); // TIMEDIFF(time1, time2) 计算时间差值 + SQL_FUNCTION_MAP.put("timestamp", ""); // TIMESTAMP(expression, interval) 单个参数时,函数返回日期或日期时间表达式;有2个参数时,将参数加和 + SQL_FUNCTION_MAP.put("to_days", ""); // TO_DAYS(d) 计算日期 d 距离 0000 年 1 月 1 日的天数 + SQL_FUNCTION_MAP.put("week", ""); // WEEK(d) 计算日期 d 是本年的第几个星期,范围是 0 到 53 + SQL_FUNCTION_MAP.put("weekday", ""); // WEEKDAY(d) 日期 d 是星期几,0 表示星期一,1 表示星期二 + SQL_FUNCTION_MAP.put("weekofyear", ""); // WEEKOFYEAR(d) 计算日期 d 是本年的第几个星期,范围是 0 到 53 + SQL_FUNCTION_MAP.put("year", ""); // YEAR(d) 返回年份 + SQL_FUNCTION_MAP.put("yearweek", ""); // YEARWEEK(date, mode) 返回年份及第几周(0到53),mode 中 0 表示周天,1表示周一,以此类推 + SQL_FUNCTION_MAP.put("unix_timestamp", ""); // UNIX_TIMESTAMP(date) 获取UNIX时间戳函数,返回一个以 UNIX 时间戳为基础的无符号整数 + SQL_FUNCTION_MAP.put("from_unixtime", ""); // FROM_UNIXTIME(date) 将 UNIX 时间戳转换为时间格式,与UNIX_TIMESTAMP互为反函数 + + // MYSQL JSON 函数 + SQL_FUNCTION_MAP.put("json_append", ""); // JSON_APPEND(json_doc, path, val[, path, val] ...)) 插入JSON数组 + SQL_FUNCTION_MAP.put("json_array", ""); // JSON_ARRAY(val1, val2...) 创建JSON数组 + SQL_FUNCTION_MAP.put("json_array_append", ""); // JSON_ARRAY_APPEND(json_doc, val) 将数据附加到JSON文档 + SQL_FUNCTION_MAP.put("json_array_insert", ""); // JSON_ARRAY_INSERT(json_doc, val) 插入JSON数组 + SQL_FUNCTION_MAP.put("json_contains", ""); // JSON_CONTAINS(json_doc, val) JSON文档是否在路径中包含特定对象 + SQL_FUNCTION_MAP.put("json_contains_path", ""); // JSON_CONTAINS_PATH(json_doc, path) JSON文档是否在路径中包含任何数据 + SQL_FUNCTION_MAP.put("json_depth", ""); // JSON_DEPTH(json_doc) JSON文档的最大深度 + SQL_FUNCTION_MAP.put("json_extract", ""); // JSON_EXTRACT(json_doc, path) 从JSON文档返回数据 + SQL_FUNCTION_MAP.put("json_insert", ""); // JSON_INSERT(json_doc, val) 将数据插入JSON文档 + SQL_FUNCTION_MAP.put("json_keys", ""); // JSON_KEYS(json_doc[, path]) JSON文档中的键数组 + SQL_FUNCTION_MAP.put("json_length", ""); // JSON_LENGTH(json_doc) JSON文档中的元素数 + SQL_FUNCTION_MAP.put("json_merge", ""); // JSON_MERGE(json_doc1, json_doc2) (已弃用) 合并JSON文档,保留重复的键。JSON_MERGE_PRESERVE()的已弃用同义词 + SQL_FUNCTION_MAP.put("json_merge_patch", ""); // JSON_MERGE_PATCH(json_doc1, json_doc2) 合并JSON文档,替换重复键的值 + SQL_FUNCTION_MAP.put("json_merge_preserve", ""); // JSON_MERGE_PRESERVE(json_doc1, json_doc2) 合并JSON文档,保留重复的键 + SQL_FUNCTION_MAP.put("json_object", ""); // JSON_OBJECT(key1, val1, key2, val2...) 创建JSON对象 + SQL_FUNCTION_MAP.put("json_overlaps", ""); // JSON_OVERLAPS(json_doc1, json_doc2) (引入8.0.17) 比较两个JSON文档,如果它们具有相同的键值对或数组元素,则返回TRUE(1),否则返回FALSE(0) + SQL_FUNCTION_MAP.put("json_pretty", ""); // JSON_PRETTY(json_doc) 以易于阅读的格式打印JSON文档 + SQL_FUNCTION_MAP.put("json_quote", ""); // JSON_QUOTE(json_doc1) 引用JSON文档 + SQL_FUNCTION_MAP.put("json_remove", ""); // JSON_REMOVE(json_doc1, path) 从JSON文档中删除数据 + SQL_FUNCTION_MAP.put("json_replace", ""); // JSON_REPLACE(json_doc1, val1, val2) 替换JSON文档中的值 + SQL_FUNCTION_MAP.put("json_schema_valid", ""); // JSON_SCHEMA_VALID(json_doc) (引入8.0.17) 根据JSON模式验证JSON文档;如果文档针对架构进行验证,则返回TRUE / 1;否则,则返回FALSE / 0 + SQL_FUNCTION_MAP.put("json_schema_validation_report", ""); // JSON_SCHEMA_VALIDATION_REPORT(json_doc, mode) (引入8.0.17) 根据JSON模式验证JSON文档;以JSON格式返回有关验证结果的报告,包括成功或失败以及失败原因 + SQL_FUNCTION_MAP.put("json_search", ""); // JSON_SEARCH(json_doc, val) JSON文档中值的路径 + SQL_FUNCTION_MAP.put("json_set", ""); // JSON_SET(json_doc, val) 将数据插入JSON文档 + // SQL_FUNCTION_MAP.put("json_storage_free", ""); // JSON_STORAGE_FREE() 部分更新后,JSON列值的二进制表示形式中的可用空间 + // SQL_FUNCTION_MAP.put("json_storage_size", ""); // JSON_STORAGE_SIZE() 用于存储JSON文档的二进制表示的空间 + SQL_FUNCTION_MAP.put("json_table", ""); // JSON_TABLE() 从JSON表达式返回数据作为关系表 + SQL_FUNCTION_MAP.put("json_type", ""); // JSON_TYPE(json_doc) JSON值类型 + SQL_FUNCTION_MAP.put("json_unquote", ""); // JSON_UNQUOTE(json_doc) 取消引用JSON值 + SQL_FUNCTION_MAP.put("json_valid", ""); // JSON_VALID(json_doc) JSON值是否有效 + SQL_FUNCTION_MAP.put("json_arrayagg", ""); // JSON_ARRAYAGG(key) 将每个表达式转换为 JSON 值,然后返回一个包含这些 JSON 值的 JSON 数组 + SQL_FUNCTION_MAP.put("json_objectagg", ""); // JSON_OBJECTAGG(key, val)) 将每个表达式转换为 JSON 值,然后返回一个包含这些 JSON 值的 JSON 对象 + + // MySQL 高级函数 + // SQL_FUNCTION_MAP.put("bin", ""); // BIN(x) 返回 x 的二进制编码 + // SQL_FUNCTION_MAP.put("binary", ""); // BINARY(s) 将字符串 s 转换为二进制字符串 + SQL_FUNCTION_MAP.put("case", ""); // CASE 表示函数开始,END 表示函数结束。如果 condition1 成立,则返回 result1, 如果 condition2 成立,则返回 result2,当全部不成立则返回 result,而当有一个成立之后,后面的就不执行了。 + SQL_FUNCTION_MAP.put("cast", ""); // CAST(x AS type) 转换数据类型 + SQL_FUNCTION_MAP.put("coalesce", ""); // COALESCE(expr1, expr2, ...., expr_n) 返回参数中的第一个非空表达式(从左向右) + // SQL_FUNCTION_MAP.put("conv", ""); // CONV(x,f1,f2) 返回 f1 进制数变成 f2 进制数 + // SQL_FUNCTION_MAP.put("convert", ""); // CONVERT(s, cs) 函数将字符串 s 的字符集变成 cs + SQL_FUNCTION_MAP.put("if", ""); // IF(expr,v1,v2) 如果表达式 expr 成立,返回结果 v1;否则,返回结果 v2。 + SQL_FUNCTION_MAP.put("ifnull", ""); // IFNULL(v1,v2) 如果 v1 的值不为 NULL,则返回 v1,否则返回 v2。 + SQL_FUNCTION_MAP.put("isnull", ""); // ISNULL(expression) 判断表达式是否为 NULL + SQL_FUNCTION_MAP.put("nullif", ""); // NULLIF(expr1, expr2) 比较两个字符串,如果字符串 expr1 与 expr2 相等 返回 NULL,否则返回 expr1 + SQL_FUNCTION_MAP.put("group_concat", ""); // GROUP_CONCAT([DISTINCT], s1, s2...) + + //clickhouse 字符串函数 注释的函数表示返回的格式暂时不支持,如:返回数组 ,同时包含因版本不同 clickhosue不支持的函数,版本 + SQL_FUNCTION_MAP.put("empty", ""); // empty(s) 对于空字符串s返回1,对于非空字符串返回0 + SQL_FUNCTION_MAP.put("notEmpty", ""); //notEmpty(s) 对于空字符串返回0,对于非空字符串返回1。 + SQL_FUNCTION_MAP.put("lengthUTF8", ""); //假定字符串以UTF-8编码组成的文本,返回此字符串的Unicode字符长度。如果传入的字符串不是UTF-8编码,则函数可能返回一个预期外的值 + SQL_FUNCTION_MAP.put("lcase", ""); //将字符串中的ASCII转换为小写 + SQL_FUNCTION_MAP.put("ucase", ""); //将字符串中的ASCII转换为大写。 + SQL_FUNCTION_MAP.put("lowerUTF8", ""); //将字符串转换为小写,函数假设字符串是以UTF-8编码文本的字符集。 + SQL_FUNCTION_MAP.put("upperUTF8", ""); //将字符串转换为大写,函数假设字符串是以UTF-8编码文本的字符集。 + SQL_FUNCTION_MAP.put("isValidUTF8", ""); // 检查字符串是否为有效的UTF-8编码,是则返回1,否则返回0。 + SQL_FUNCTION_MAP.put("toValidUTF8", "");//用�(U+FFFD)字符替换无效的UTF-8字符。所有连续的无效字符都会被替换为一个替换字符。 + SQL_FUNCTION_MAP.put("reverseUTF8", "");//以Unicode字符为单位反转UTF-8编码的字符串。 + SQL_FUNCTION_MAP.put("concatAssumeInjective", ""); // concatAssumeInjective(s1, s2, …) 与concat相同,区别在于,你需要保证concat(s1, s2, s3) -> s4是单射的,它将用于GROUP BY的优化。 + SQL_FUNCTION_MAP.put("substringUTF8", ""); // substringUTF8(s,offset,length)¶ 与’substring’相同,但其操作单位为Unicode字符,函数假设字符串是以UTF-8进行编码的文本。如果不是则可能返回一个预期外的结果(不会抛出异常)。 + SQL_FUNCTION_MAP.put("appendTrailingCharIfAbsent", ""); // appendTrailingCharIfAbsent(s,c) 如果’s’字符串非空并且末尾不包含’c’字符,则将’c’字符附加到末尾 + SQL_FUNCTION_MAP.put("convertCharset", ""); // convertCharset(s,from,to) 返回从’from’中的编码转换为’to’中的编码的字符串’s’。 + SQL_FUNCTION_MAP.put("base64Encode", ""); // base64Encode(s) 将字符串’s’编码成base64 + SQL_FUNCTION_MAP.put("base64Decode", ""); //base64Decode(s) 使用base64将字符串解码成原始字符串。如果失败则抛出异常。 + SQL_FUNCTION_MAP.put("tryBase64Decode", ""); //tryBase64Decode(s) 使用base64将字符串解码成原始字符串。但如果出现错误,将返回空字符串。 + SQL_FUNCTION_MAP.put("endsWith", ""); //endsWith(s,后缀) 返回是否以指定的后缀结尾。如果字符串以指定的后缀结束,则返回1,否则返回0。 + SQL_FUNCTION_MAP.put("startsWith", ""); //startsWith(s,前缀) 返回是否以指定的前缀开头。如果字符串以指定的前缀开头,则返回1,否则返回0。 + SQL_FUNCTION_MAP.put("trimLeft", ""); //trimLeft(s)返回一个字符串,用于删除左侧的空白字符。 + SQL_FUNCTION_MAP.put("trimRight", ""); //trimRight(s) 返回一个字符串,用于删除右侧的空白字符。 + SQL_FUNCTION_MAP.put("trimBoth", ""); //trimBoth(s),用于删除任一侧的空白字符 + SQL_FUNCTION_MAP.put("extractAllGroups", ""); //extractAllGroups(text, regexp) 从正则表达式匹配的非重叠子字符串中提取所有组 + // SQL_FUNCTION_MAP.put("leftPad", ""); //leftPad('string', 'length'[, 'pad_string']) 用空格或指定的字符串从左边填充当前字符串(如果需要,可以多次),直到得到的字符串达到给定的长度 + // SQL_FUNCTION_MAP.put("leftPadUTF8", ""); //leftPadUTF8('string','length'[, 'pad_string']) 用空格或指定的字符串从左边填充当前字符串(如果需要,可以多次),直到得到的字符串达到给定的长度 + // SQL_FUNCTION_MAP.put("rightPad", ""); // rightPad('string', 'length'[, 'pad_string']) 用空格或指定的字符串(如果需要,可以多次)从右边填充当前字符串,直到得到的字符串达到给定的长度 + // SQL_FUNCTION_MAP.put("rightPadUTF8", "");// rightPadUTF8('string','length'[, 'pad_string']) 用空格或指定的字符串(如果需要,可以多次)从右边填充当前字符串,直到得到的字符串达到给定的长度。 + SQL_FUNCTION_MAP.put("normalizeQuery", ""); //normalizeQuery(x) 用占位符替换文字、文字序列和复杂的别名。 + SQL_FUNCTION_MAP.put("normalizedQueryHash", ""); //normalizedQueryHash(x) 为类似查询返回相同的64位散列值,但不包含文字值。有助于对查询日志进行分析 + SQL_FUNCTION_MAP.put("positionUTF8", ""); // positionUTF8(s, needle[, start_pos]) 返回在字符串中找到的子字符串的位置(以Unicode点表示),从1开始。 + SQL_FUNCTION_MAP.put("multiSearchFirstIndex", ""); //multiSearchFirstIndex(s, [needle1, needle2, …, needlen]) 返回字符串s中最左边的needlei的索引i(从1开始),否则返回0 + SQL_FUNCTION_MAP.put("multiSearchAny", ""); // multiSearchAny(s, [needle1, needle2, …, needlen])如果至少有一个字符串needlei匹配字符串s,则返回1,否则返回0。 + SQL_FUNCTION_MAP.put("match", ""); //match(s, pattern) 检查字符串是否与模式正则表达式匹配。re2正则表达式。re2正则表达式的语法比Perl正则表达式的语法更有局限性。 + SQL_FUNCTION_MAP.put("multiMatchAny", ""); //multiMatchAny(s, [pattern1, pattern2, …, patternn]) 与match相同,但是如果没有匹配的正则表达式返回0,如果有匹配的模式返回1 + SQL_FUNCTION_MAP.put("multiMatchAnyIndex", ""); //multiMatchAnyIndex(s, [pattern1, pattern2, …, patternn]) 与multiMatchAny相同,但返回与干堆匹配的任何索引 + SQL_FUNCTION_MAP.put("extract", ""); // extract(s, pattern) 使用正则表达式提取字符串的片段 + SQL_FUNCTION_MAP.put("extractAll", ""); //extractAll(s, pattern) 使用正则表达式提取字符串的所有片段 + SQL_FUNCTION_MAP.put("like", ""); //like(s, pattern) 检查字符串是否与简单正则表达式匹配 + SQL_FUNCTION_MAP.put("notLike", "");// 和‘like’是一样的,但是是否定的 + SQL_FUNCTION_MAP.put("countSubstrings", ""); //countSubstrings(s, needle[, start_pos])返回子字符串出现的次数 + SQL_FUNCTION_MAP.put("countMatches", ""); //返回干s中的正则表达式匹配数。countMatches(s, pattern) + SQL_FUNCTION_MAP.put("replaceOne", ""); //replaceOne(s, pattern, replacement)将' s '中的' pattern '子串的第一个出现替换为' replacement '子串。 + + SQL_FUNCTION_MAP.put("replaceAll", ""); //replaceAll(s, pattern, replacement)/用' replacement '子串替换' s '中所有出现的' pattern '子串 + SQL_FUNCTION_MAP.put("replaceRegexpOne", ""); //replaceRegexpOne(s, pattern, replacement)使用' pattern '正则表达式进行替换 + SQL_FUNCTION_MAP.put("replaceRegexpAll", ""); //replaceRegexpAll(s, pattern, replacement) + SQL_FUNCTION_MAP.put("regexpQuoteMeta", ""); //regexpQuoteMeta(s)该函数在字符串中某些预定义字符之前添加一个反斜杠 + + //clickhouse日期函数 + SQL_FUNCTION_MAP.put("toYear", ""); //将Date或DateTime转换为包含年份编号(AD)的UInt16类型的数字。 + SQL_FUNCTION_MAP.put("toQuarter", ""); //将Date或DateTime转换为包含季度编号的UInt8类型的数字。 + SQL_FUNCTION_MAP.put("toMonth", ""); //Date或DateTime转换为包含月份编号(1-12)的UInt8类型的数字。 + SQL_FUNCTION_MAP.put("toDayOfYear", ""); //将Date或DateTime转换为包含一年中的某一天的编号的UInt16(1-366)类型的数字。 + SQL_FUNCTION_MAP.put("toDayOfMonth", "");//将Date或DateTime转换为包含一月中的某一天的编号的UInt8(1-31)类型的数字。 + SQL_FUNCTION_MAP.put("toDayOfWeek", ""); //将Date或DateTime转换为包含一周中的某一天的编号的UInt8(周一是1, 周日是7)类型的数字。 + SQL_FUNCTION_MAP.put("toHour", ""); //将DateTime转换为包含24小时制(0-23)小时数的UInt8数字。 + SQL_FUNCTION_MAP.put("toMinute", ""); //将DateTime转换为包含一小时中分钟数(0-59)的UInt8数字。 + SQL_FUNCTION_MAP.put("toSecond", ""); //将DateTime转换为包含一分钟中秒数(0-59)的UInt8数字。 + SQL_FUNCTION_MAP.put("toUnixTimestamp", ""); // 对于DateTime参数:将值转换为UInt32类型的数字-Unix时间戳 + SQL_FUNCTION_MAP.put("toStartOfYear", ""); //将Date或DateTime向前取整到本年的第一天。 + SQL_FUNCTION_MAP.put("toStartOfISOYear", ""); // 将Date或DateTime向前取整到ISO本年的第一天。 + SQL_FUNCTION_MAP.put("toStartOfQuarter", "");//将Date或DateTime向前取整到本季度的第一天。 + SQL_FUNCTION_MAP.put("toStartOfMonth", ""); //将Date或DateTime向前取整到本月的第一天。 + SQL_FUNCTION_MAP.put("toMonday", ""); //将Date或DateTime向前取整到本周的星期 + SQL_FUNCTION_MAP.put("toStartOfWeek", ""); //按mode将Date或DateTime向前取整到最近的星期日或星期一。 + SQL_FUNCTION_MAP.put("toStartOfDay", ""); //将DateTime向前取整到今天的开始。 + SQL_FUNCTION_MAP.put("toStartOfHour", ""); //将DateTime向前取整到当前小时的开始。 + SQL_FUNCTION_MAP.put("toStartOfMinute", ""); //将DateTime向前取整到当前分钟的开始。 + SQL_FUNCTION_MAP.put("toStartOfSecond", ""); //将DateTime向前取整到当前秒数的开始。 + SQL_FUNCTION_MAP.put("toStartOfFiveMinute", "");//将DateTime以五分钟为单位向前取整到最接近的时间点。 + SQL_FUNCTION_MAP.put("toStartOfTenMinutes", ""); //将DateTime以十分钟为单位向前取整到最接近的时间点。 + SQL_FUNCTION_MAP.put("toStartOfFifteenMinutes", ""); //将DateTime以十五分钟为单位向前取整到最接近的时间点。 + SQL_FUNCTION_MAP.put("toStartOfInterval", ""); // + SQL_FUNCTION_MAP.put("toTime", ""); //将DateTime中的日期转换为一个固定的日期,同时保留时间部分。 + SQL_FUNCTION_MAP.put("toISOYear", ""); //将Date或DateTime转换为包含ISO年份的UInt16类型的编号。 + SQL_FUNCTION_MAP.put("toISOWeek", ""); // + SQL_FUNCTION_MAP.put("toWeek", "");// 返回Date或DateTime的周数。 + SQL_FUNCTION_MAP.put("toYearWeek", ""); //返回年和周的日期 + SQL_FUNCTION_MAP.put("date_trunc", ""); //截断日期和时间数据到日期的指定部分 + SQL_FUNCTION_MAP.put("date_diff", ""); //回两个日期或带有时间值的日期之间的差值。 + + SQL_FUNCTION_MAP.put("yesterday", ""); //不接受任何参数并在请求执行时的某一刻返回昨天的日期(Date)。 + SQL_FUNCTION_MAP.put("today", ""); //不接受任何参数并在请求执行时的某一刻返回当前日期(Date)。 + SQL_FUNCTION_MAP.put("timeSlot", ""); //将时间向前取整半小时。 + SQL_FUNCTION_MAP.put("toYYYYMM", ""); // + SQL_FUNCTION_MAP.put("toYYYYMMDD", "");// + SQL_FUNCTION_MAP.put("toYYYYMMDDhhmmss", ""); // + SQL_FUNCTION_MAP.put("addYears", ""); // Function adds a Date/DateTime interval to a Date/DateTime and then return the Date/DateTime + SQL_FUNCTION_MAP.put("addMonths", ""); //同上 + SQL_FUNCTION_MAP.put("addWeeks", ""); //同上 + SQL_FUNCTION_MAP.put("addDays", ""); //同上 + SQL_FUNCTION_MAP.put("addHours", ""); //同上 + SQL_FUNCTION_MAP.put("addMinutes", "");//同上 + SQL_FUNCTION_MAP.put("addSeconds", ""); //同上 + SQL_FUNCTION_MAP.put("addQuarters", ""); //同上 + SQL_FUNCTION_MAP.put("subtractYears", ""); //Function subtract a Date/DateTime interval to a Date/DateTime and then return the Date/DateTime + SQL_FUNCTION_MAP.put("subtractMonths", ""); //同上 + SQL_FUNCTION_MAP.put("subtractWeeks", ""); //同上 + SQL_FUNCTION_MAP.put("subtractDays", ""); //同上 + SQL_FUNCTION_MAP.put("subtractours", "");//同上 + SQL_FUNCTION_MAP.put("subtractMinutes", ""); //同上 + SQL_FUNCTION_MAP.put("subtractSeconds", ""); //同上 + SQL_FUNCTION_MAP.put("subtractQuarters", ""); //同上 + SQL_FUNCTION_MAP.put("formatDateTime", ""); //函数根据给定的格式字符串来格式化时间 + SQL_FUNCTION_MAP.put("timestamp_add", ""); //使用提供的日期或日期时间值添加指定的时间值。 + SQL_FUNCTION_MAP.put("timestamp_sub", ""); //从提供的日期或带时间的日期中减去时间间隔。 + + //clickhouse json函数 + SQL_FUNCTION_MAP.put("visitParamHas", ""); //visitParamHas(params, name)检查是否存在«name»名称的字段 + SQL_FUNCTION_MAP.put("visitParamExtractUInt", ""); //visitParamExtractUInt(params, name)将名为«name»的字段的值解析成UInt64。 + SQL_FUNCTION_MAP.put("visitParamExtractInt", ""); //与visitParamExtractUInt相同,但返回Int64。 + SQL_FUNCTION_MAP.put("visitParamExtractFloat", ""); //与visitParamExtractUInt相同,但返回Float64。 + SQL_FUNCTION_MAP.put("visitParamExtractBool", "");//解析true/false值。其结果是UInt8类型的。 + SQL_FUNCTION_MAP.put("visitParamExtractRaw", ""); //返回字段的值,包含空格符。 + SQL_FUNCTION_MAP.put("visitParamExtractString", ""); //使用双引号解析字符串。这个值没有进行转义。如果转义失败,它将返回一个空白字符串。 + SQL_FUNCTION_MAP.put("JSONHas", ""); //如果JSON中存在该值,则返回1。 + SQL_FUNCTION_MAP.put("JSONLength", ""); //返回JSON数组或JSON对象的长度。 + SQL_FUNCTION_MAP.put("JSONType", ""); //返回JSON值的类型。 + SQL_FUNCTION_MAP.put("JSONExtractUInt", ""); //解析JSON并提取值。这些函数类似于visitParam*函数。 + SQL_FUNCTION_MAP.put("JSONExtractInt", ""); // + SQL_FUNCTION_MAP.put("JSONExtractFloat", ""); // + SQL_FUNCTION_MAP.put("JSONExtractBool", ""); // + SQL_FUNCTION_MAP.put("JSONExtractString", ""); //解析JSON并提取字符串。此函数类似于visitParamExtractString函数。 + SQL_FUNCTION_MAP.put("JSONExtract", "");//解析JSON并提取给定ClickHouse数据类型的值。 + SQL_FUNCTION_MAP.put("JSONExtractKeysAndValues", ""); //从JSON中解析键值对,其中值是给定的ClickHouse数据类型 + SQL_FUNCTION_MAP.put("JSONExtractRaw", ""); //返回JSON的部分。 + SQL_FUNCTION_MAP.put("toJSONString", ""); // + + //clickhouse 类型转换函数 + SQL_FUNCTION_MAP.put("toInt8", ""); //toInt8(expr) 转换一个输入值为Int类型 + SQL_FUNCTION_MAP.put("toInt16", ""); + SQL_FUNCTION_MAP.put("toInt32", ""); + SQL_FUNCTION_MAP.put("toInt64", ""); + SQL_FUNCTION_MAP.put("toInt8OrZero", ""); //toInt(8|16|32|64)OrZero 这个函数需要一个字符类型的入参,然后尝试把它转为Int (8 | 16 | 32 | 64),如果转换失败直接返回0。 + SQL_FUNCTION_MAP.put("toInt16OrZero", ""); + SQL_FUNCTION_MAP.put("toInt32OrZero", ""); + SQL_FUNCTION_MAP.put("toInt64OrZero", ""); + SQL_FUNCTION_MAP.put("toInt8OrNull", "");//toInt(8|16|32|64)OrNull 这个函数需要一个字符类型的入参,然后尝试把它转为Int (8 | 16 | 32 | 64),如果转换失败直接返回NULL + SQL_FUNCTION_MAP.put("toInt16OrNull", ""); + SQL_FUNCTION_MAP.put("toInt32OrNull", ""); + SQL_FUNCTION_MAP.put("toInt64OrNull", ""); + SQL_FUNCTION_MAP.put("toUInt8", ""); //toInt8(expr) 转换一个输入值为Int类型 + SQL_FUNCTION_MAP.put("toUInt16", ""); + SQL_FUNCTION_MAP.put("toUInt32", ""); + SQL_FUNCTION_MAP.put("toUInt64", ""); + SQL_FUNCTION_MAP.put("toUInt8OrZero", ""); //toInt(8|16|32|64)OrZero 这个函数需要一个字符类型的入参,然后尝试把它转为Int (8 | 16 | 32 | 64),如果转换失败直接返回0。 + SQL_FUNCTION_MAP.put("toUInt16OrZero", ""); + SQL_FUNCTION_MAP.put("toUInt32OrZero", ""); + SQL_FUNCTION_MAP.put("toUInt64OrZero", ""); + SQL_FUNCTION_MAP.put("toUInt8OrNull", "");//toInt(8|16|32|64)OrNull 这个函数需要一个字符类型的入参,然后尝试把它转为Int (8 | 16 | 32 | 64),如果转换失败直接返回NULL + SQL_FUNCTION_MAP.put("toUInt16OrNull", ""); + SQL_FUNCTION_MAP.put("toUInt32OrNull", ""); + SQL_FUNCTION_MAP.put("toUInt64OrNull", ""); + + SQL_FUNCTION_MAP.put("toFloat32", ""); + SQL_FUNCTION_MAP.put("toFloat64", ""); + SQL_FUNCTION_MAP.put("toFloat32OrZero", ""); + SQL_FUNCTION_MAP.put("toFloat64OrZero", ""); + SQL_FUNCTION_MAP.put("toFloat32OrNull", ""); + SQL_FUNCTION_MAP.put("toFloat64OrNull", ""); + + SQL_FUNCTION_MAP.put("toDate", ""); // + SQL_FUNCTION_MAP.put("toDateOrZero", ""); //toInt16(expr) + SQL_FUNCTION_MAP.put("toDateOrNull", ""); //toInt32(expr) + SQL_FUNCTION_MAP.put("toDateTimeOrZero", ""); //toInt64(expr) + SQL_FUNCTION_MAP.put("toDateTimeOrNull", ""); //toInt(8|16|32|64)OrZero 这个函数需要一个字符类型的入参,然后尝试把它转为Int (8 | 16 | 32 | 64),如果转换失败直接返回0。 + + SQL_FUNCTION_MAP.put("toDecimal32", ""); + SQL_FUNCTION_MAP.put("toFixedString", ""); // 将String类型的参数转换为FixedString(N)类型的值 + SQL_FUNCTION_MAP.put("toStringCutToZero", ""); // 接受String或FixedString参数,返回String,其内容在找到的第一个零字节处被截断。 + SQL_FUNCTION_MAP.put("toDecimal256", ""); + SQL_FUNCTION_MAP.put("toDecimal32OrNull", ""); + SQL_FUNCTION_MAP.put("toDecimal64OrNull", ""); + SQL_FUNCTION_MAP.put("toDecimal128OrNull", ""); + SQL_FUNCTION_MAP.put("toDecimal256OrNull", ""); + SQL_FUNCTION_MAP.put("toDecimal32OrZero", ""); + SQL_FUNCTION_MAP.put("toDecimal64OrZero", ""); + SQL_FUNCTION_MAP.put("toDecimal128OrZero", ""); + SQL_FUNCTION_MAP.put("toDecimal256OrZero", ""); + + + SQL_FUNCTION_MAP.put("toIntervalSecond", ""); //把一个数值类型的值转换为Interval类型的数据。 + SQL_FUNCTION_MAP.put("toIntervalMinute", ""); + SQL_FUNCTION_MAP.put("toIntervalHour", ""); + SQL_FUNCTION_MAP.put("toIntervalDay", ""); + SQL_FUNCTION_MAP.put("toIntervalWeek", ""); + SQL_FUNCTION_MAP.put("toIntervalMonth", ""); + SQL_FUNCTION_MAP.put("toIntervalQuarter", ""); + SQL_FUNCTION_MAP.put("toIntervalYear", ""); + SQL_FUNCTION_MAP.put("parseDateTimeBestEffort", ""); //把String类型的时间日期转换为DateTime数据类型。 + SQL_FUNCTION_MAP.put("parseDateTimeBestEffortOrNull", ""); + SQL_FUNCTION_MAP.put("parseDateTimeBestEffortOrZero", ""); + SQL_FUNCTION_MAP.put("toLowCardinality", ""); + + + + ////clickhouse hash函数 + SQL_FUNCTION_MAP.put("halfMD5", ""); //计算字符串的MD5。然后获取结果的前8个字节并将它们作为UInt64(大端)返回 + SQL_FUNCTION_MAP.put("MD5", ""); //计算字符串的MD5并将结果放入FixedString(16)中返回 + + //clickhouse ip地址函数 + SQL_FUNCTION_MAP.put("IPv4NumToString", ""); //接受一个UInt32(大端)表示的IPv4的地址,返回相应IPv4的字符串表现形式,格式为A.B.C.D(以点分割的十进制数字)。 + SQL_FUNCTION_MAP.put("IPv4StringToNum", ""); //与IPv4NumToString函数相反。如果IPv4地址格式无效,则返回0。 + SQL_FUNCTION_MAP.put("IPv6NumToString", ""); //接受FixedString(16)类型的二进制格式的IPv6地址。以文本格式返回此地址的字符串。 + SQL_FUNCTION_MAP.put("IPv6StringToNum", ""); //与IPv6NumToString的相反。如果IPv6地址格式无效,则返回空字节字符串。 + SQL_FUNCTION_MAP.put("IPv4ToIPv6", ""); // 接受一个UInt32类型的IPv4地址,返回FixedString(16)类型的IPv6地址 + SQL_FUNCTION_MAP.put("cutIPv6", ""); //接受一个FixedString(16)类型的IPv6地址,返回一个String,这个String中包含了删除指定位之后的地址的文本格 + SQL_FUNCTION_MAP.put("toIPv4", ""); //IPv4StringToNum()的别名, + SQL_FUNCTION_MAP.put("toIPv6", ""); //IPv6StringToNum()的别名 + SQL_FUNCTION_MAP.put("isIPAddressInRange", ""); //确定一个IP地址是否包含在以CIDR符号表示的网络中 + + //clickhouse Nullable处理函数 + SQL_FUNCTION_MAP.put("isNull", ""); //检查参数是否为NULL。 + SQL_FUNCTION_MAP.put("isNotNull", ""); //检查参数是否不为 NULL. + SQL_FUNCTION_MAP.put("ifNull", ""); //如果第一个参数为«NULL»,则返回第二个参数的值。 + SQL_FUNCTION_MAP.put("assumeNotNull", ""); //将可为空类型的值转换为非Nullable类型的值。 + SQL_FUNCTION_MAP.put("toNullable", ""); //将参数的类型转换为Nullable。 + + //clickhouse UUID函数 + SQL_FUNCTION_MAP.put("generateUUIDv4", ""); // 生成一个UUID + SQL_FUNCTION_MAP.put("toUUID", ""); //toUUID(x) 将String类型的值转换为UUID类型的值。 + + //clickhouse 系统函数 + SQL_FUNCTION_MAP.put("hostName", ""); //hostName()回一个字符串,其中包含执行此函数的主机的名称。 + SQL_FUNCTION_MAP.put("getMacro", ""); //从服务器配置的宏部分获取指定值。 + SQL_FUNCTION_MAP.put("FQDN", "");//返回完全限定的域名。 + SQL_FUNCTION_MAP.put("basename", ""); //提取字符串最后一个斜杠或反斜杠之后的尾随部分 + SQL_FUNCTION_MAP.put("currentUser", ""); //返回当前用户的登录。在分布式查询的情况下,将返回用户的登录,即发起的查询 + SQL_FUNCTION_MAP.put("version", ""); //以字符串形式返回服务器版本。 + SQL_FUNCTION_MAP.put("uptime", "");//以秒为单位返回服务器的正常运行时间。 + + //clickhouse 数学函数 + SQL_FUNCTION_MAP.put("least", ""); //返回a和b中最小的值。 + SQL_FUNCTION_MAP.put("greatest", ""); //返回a和b的最大值。 + SQL_FUNCTION_MAP.put("plus", ""); //plus(a, b), a + b operator¶计算数值的总和。 + SQL_FUNCTION_MAP.put("minus", ""); //minus(a, b), a - b operator 计算数值之间的差,结果总是有符号的。 + SQL_FUNCTION_MAP.put("multiply", "");//multiply(a, b), a * b operator 计算数值的乘积 + SQL_FUNCTION_MAP.put("divide", ""); //divide(a, b), a / b operator 计算数值的商。结果类型始终是浮点类型 + SQL_FUNCTION_MAP.put("intDiv", ""); //intDiv(a,b)计算数值的商,向下舍入取整(按绝对值)。 + SQL_FUNCTION_MAP.put("intDivOrZero", ""); // intDivOrZero(a,b)与’intDiv’的不同之处在于它在除以零或将最小负数除以-1时返回零。 + SQL_FUNCTION_MAP.put("modulo", ""); //modulo(a, b), a % b operator 计算除法后的余数。 + SQL_FUNCTION_MAP.put("moduloOrZero", ""); //和modulo不同之处在于,除以0时结果返回0 + SQL_FUNCTION_MAP.put("negate", ""); //通过改变数值的符号位对数值取反,结果总是有符号 + SQL_FUNCTION_MAP.put("gcd", ""); //gcd(a,b) 返回数值的最大公约数。 + SQL_FUNCTION_MAP.put("lcm", ""); //lcm(a,b) 返回数值的最小公倍数 + SQL_FUNCTION_MAP.put("e", ""); //e() 返回一个接近数学常量e的Float64数字。 + SQL_FUNCTION_MAP.put("pi", ""); //pi() 返回一个接近数学常量π的Float64数字。 + SQL_FUNCTION_MAP.put("exp2", ""); //exp2(x)¶接受一个数值类型的参数并返回它的2的x次幂。 + SQL_FUNCTION_MAP.put("exp10", ""); //exp10(x)¶接受一个数值类型的参数并返回它的10的x次幂。 + SQL_FUNCTION_MAP.put("cbrt", ""); //cbrt(x) 接受一个数值类型的参数并返回它的立方根。 + SQL_FUNCTION_MAP.put("lgamma", ""); //lgamma(x) 返回x的绝对值的自然对数的伽玛函数。 + SQL_FUNCTION_MAP.put("tgamma", ""); //tgamma(x)¶返回x的伽玛函数。 + SQL_FUNCTION_MAP.put("intExp2", ""); //intExp2 接受一个数值类型的参数并返回它的2的x次幂(UInt64) + SQL_FUNCTION_MAP.put("intExp10", ""); //intExp10 接受一个数值类型的参数并返回它的10的x次幂(UInt64)。 + SQL_FUNCTION_MAP.put("cosh", ""); // cosh(x) + SQL_FUNCTION_MAP.put("cosh", ""); //cosh(x) + SQL_FUNCTION_MAP.put("sinh", ""); //sinh(x) + SQL_FUNCTION_MAP.put("asinh", ""); //asinh(x) + SQL_FUNCTION_MAP.put("atanh", ""); //atanh(x) + SQL_FUNCTION_MAP.put("atan2", ""); //atan2(y, x) + SQL_FUNCTION_MAP.put("hypot", ""); //hypot(x, y) + SQL_FUNCTION_MAP.put("log1p", ""); //log1p(x) + SQL_FUNCTION_MAP.put("trunc", ""); //和truncate一样 + SQL_FUNCTION_MAP.put("roundToExp2", ""); //接受一个数字。如果数字小于1,它返回0。 + SQL_FUNCTION_MAP.put("roundDuration", ""); //接受一个数字。如果数字小于1,它返回0。 + SQL_FUNCTION_MAP.put("roundAge", ""); // 接受一个数字。如果数字小于18,它返回0。 + SQL_FUNCTION_MAP.put("roundDown", ""); //接受一个数字并将其舍入到指定数组中的一个元素 + SQL_FUNCTION_MAP.put("bitAnd", ""); //bitAnd(a,b) + SQL_FUNCTION_MAP.put("bitOr", ""); //bitOr(a,b) + } +} From 509f21305b3d6f726efdeb5634c9cda22f749214 Mon Sep 17 00:00:00 2001 From: fineday009 Date: Mon, 6 Sep 2021 12:04:19 +0800 Subject: [PATCH 0453/1181] commit test --- Document.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Document.md b/Document.md index d1fd5dbce..e1f10aab2 100644 --- a/Document.md +++ b/Document.md @@ -1,5 +1,5 @@ # APIJSON通用文档 -后端开发者可以先看这个详细的 [图文入门教程](https://vincentcheng.github.io/apijson-doc/zh)(如果和本文档有出入,以本文档为准,例如正则匹配 key? 已废弃,全面用 key~ 替代) +后端开发者可以先看这个详细的 [图文入门教程](https://vincentcheng.github.io/apijson-doc/zh)(如果和本文档有出入,以本文档为准,例如正则匹配 key? 已废弃,全面用 key~ 替代。) * ### [1.示例](#1) * ### [2.对比传统方式](#2) From 175a65ccf2ae1658579a79e1bd489fc52bd5b665 Mon Sep 17 00:00:00 2001 From: kenlig <28685375+kenlig@users.noreply.github.com> Date: Mon, 6 Sep 2021 17:09:08 +0800 Subject: [PATCH 0454/1181] Update README.md --- README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.md b/README.md index 746389473..b478ba435 100644 --- a/README.md +++ b/README.md @@ -504,6 +504,9 @@ https://www.bilibili.com/video/BV1GM4y1N7XJ APIJSON 后端教程(6):uliweb_apijson https://www.bilibili.com/video/BV1yb4y1S79v/ + +APIJSON 后端教程(7):问题答疑 +https://www.bilibili.com/video/BV1dQ4y1h7Df APIJSON配套文档: https://github.com/kenlig/apijsondocs From d982e58040c7a6254db4df1418bef5d9dae6931f Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Tue, 7 Sep 2021 15:35:20 +0800 Subject: [PATCH 0455/1181] =?UTF-8?q?=E7=94=A8=E6=88=B7=E7=99=BB=E8=AE=B0?= =?UTF-8?q?=E6=96=B0=E5=A2=9E=20=E8=85=BE=E8=AE=AF=E7=A7=91=E6=8A=80?= =?UTF-8?q?=E6=9C=89=E9=99=90=E5=85=AC=E5=8F=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index b478ba435..6250fc7c4 100644 --- a/README.md +++ b/README.md @@ -283,6 +283,8 @@ https://github.com/Tencent/APIJSON/blob/master/CONTRIBUTING.md

+* [腾讯科技有限公司](https://www.tencent.com) + 生态周边项目的作者们(2 个腾讯工程师、1 个字节跳动工程师 等):
https://github.com/search?o=desc&q=apijson&s=stars&type=Repositories
https://search.gitee.com/?skin=rec&type=repository&q=apijson&sort=stars_count
From 2aedc9dac16944ca919ae2275626d67127ac1def Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Tue, 7 Sep 2021 15:36:13 +0800 Subject: [PATCH 0456/1181] =?UTF-8?q?=E7=94=A8=E6=88=B7=E7=99=BB=E8=AE=B0?= =?UTF-8?q?=E6=96=B0=E5=A2=9E=20=E8=85=BE=E8=AE=AF=E7=A7=91=E6=8A=80?= =?UTF-8?q?=E6=9C=89=E9=99=90=E5=85=AC=E5=8F=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 6250fc7c4..d595e8339 100644 --- a/README.md +++ b/README.md @@ -283,7 +283,7 @@ https://github.com/Tencent/APIJSON/blob/master/CONTRIBUTING.md

-* [腾讯科技有限公司](https://www.tencent.com) + * [腾讯科技有限公司](https://www.tencent.com) 生态周边项目的作者们(2 个腾讯工程师、1 个字节跳动工程师 等):
https://github.com/search?o=desc&q=apijson&s=stars&type=Repositories
From baf8176aa2caf1fe1fb61dff73932f98158a8d88 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Tue, 7 Sep 2021 15:38:31 +0800 Subject: [PATCH 0457/1181] =?UTF-8?q?=E4=BD=BF=E7=94=A8=E7=99=BB=E8=AE=B0?= =?UTF-8?q?=20=E6=96=B0=E5=A2=9E=20=E8=85=BE=E8=AE=AF=E7=A7=91=E6=8A=80?= =?UTF-8?q?=E6=9C=89=E9=99=90=E5=85=AC=E5=8F=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index d595e8339..a521615f8 100644 --- a/README.md +++ b/README.md @@ -238,6 +238,8 @@ https://github.com/Tencent/APIJSON/issues/187
+ + * [腾讯科技有限公司](https://www.tencent.com) ### 贡献者们 @@ -283,8 +285,6 @@ https://github.com/Tencent/APIJSON/blob/master/CONTRIBUTING.md

- * [腾讯科技有限公司](https://www.tencent.com) - 生态周边项目的作者们(2 个腾讯工程师、1 个字节跳动工程师 等):
https://github.com/search?o=desc&q=apijson&s=stars&type=Repositories
https://search.gitee.com/?skin=rec&type=repository&q=apijson&sort=stars_count
From 07bc89925d0e14447d417c08ff7149c8c9892d58 Mon Sep 17 00:00:00 2001 From: chenyanlann <32511cyl@gmail.com> Date: Tue, 7 Sep 2021 21:32:29 +0800 Subject: [PATCH 0458/1181] =?UTF-8?q?Modified:=E4=BF=AE=E5=A4=8Dput?= =?UTF-8?q?=E8=AF=B7=E6=B1=82key=E7=9A=84bug?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/main/java/apijson/orm/AbstractObjectParser.java | 4 ++-- APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractObjectParser.java b/APIJSONORM/src/main/java/apijson/orm/AbstractObjectParser.java index 673348935..a8b4535b2 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractObjectParser.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractObjectParser.java @@ -261,8 +261,8 @@ else if ((method == POST || method == PUT) && value instanceof JSONArray && JSONRequest.isTableArray(key)) { // JSONArray,批量新增或修改,往下一级提取 onTableArrayParse(key, (JSONArray) value); } - else if (method == PUT && value instanceof JSONArray - && (whereList == null || whereList.contains(key) == false)) { // PUT JSONArray + else if (method == PUT && value instanceof JSONArray && (whereList == null || whereList.contains(key) == false) && (StringUtil.isName(key) || ((key.endsWith("+") || key.endsWith("-")) && StringUtil.isName(key.substring(0, key.length()-1))))) + { // PUT JSONArray onPUTArrayParse(key, (JSONArray) value); } else { // JSONArray或其它Object,直接填充 diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java index e3f71ccbb..fbcc5507f 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java @@ -3127,7 +3127,7 @@ else if (w.startsWith("!")) { } //解决AccessVerifier新增userId没有作为条件,而是作为内容,导致PUT,DELETE出错 - if (isWhere) { + if (isWhere || (StringUtil.isName(key) == false)) { tableWhere.put(key, value); if (whereList == null || whereList.contains(key) == false) { andList.add(key); From 4beced4624b67a633096cd44d6fc1bffbf4f1dc8 Mon Sep 17 00:00:00 2001 From: chenyanlann <62465397+chenyanlann@users.noreply.github.com> Date: Wed, 8 Sep 2021 19:23:48 +0800 Subject: [PATCH 0459/1181] Update AbstractObjectParser.java MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 简化字符串判断 --- APIJSONORM/src/main/java/apijson/orm/AbstractObjectParser.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractObjectParser.java b/APIJSONORM/src/main/java/apijson/orm/AbstractObjectParser.java index a8b4535b2..979bce409 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractObjectParser.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractObjectParser.java @@ -261,7 +261,7 @@ else if ((method == POST || method == PUT) && value instanceof JSONArray && JSONRequest.isTableArray(key)) { // JSONArray,批量新增或修改,往下一级提取 onTableArrayParse(key, (JSONArray) value); } - else if (method == PUT && value instanceof JSONArray && (whereList == null || whereList.contains(key) == false) && (StringUtil.isName(key) || ((key.endsWith("+") || key.endsWith("-")) && StringUtil.isName(key.substring(0, key.length()-1))))) + else if (method == PUT && value instanceof JSONArray && (whereList == null || whereList.contains(key) == false) && StringUtil.isName(key.replaceFirst("[+-]$", ""))) { // PUT JSONArray onPUTArrayParse(key, (JSONArray) value); } From 59525679b8b9b6754a3d0d9e8f2aab2f32991371 Mon Sep 17 00:00:00 2001 From: qiujunlin <1757591067@qq.com> Date: Fri, 10 Sep 2021 11:05:56 +0800 Subject: [PATCH 0460/1181] add delete and update --- .../src/main/java/apijson/orm/AbstractSQLConfig.java | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java index fbcc5507f..47d07b456 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java @@ -2587,7 +2587,7 @@ public String getSetString(RequestMethod method, Map content, bo if (setString.isEmpty()) { throw new IllegalArgumentException("PUT 请求必须在Table内设置要修改的 key:value !"); } - return " SET " + setString; + return (isClickHouse()?" ":" SET ") + setString; } /**SET key = concat(key, 'value') @@ -2665,9 +2665,15 @@ public static String getSQL(AbstractSQLConfig config) throws Exception { case POST: return "INSERT INTO " + tablePath + config.getColumnString() + " VALUES" + config.getValuesString(); case PUT: - return "UPDATE " + tablePath + config.getSetString() + config.getWhereString(true) + (config.isMySQL()||config.isClickHouse() ? config.getLimitString() : ""); + if(config.isClickHouse()){ + return "ALTER TABLE " + tablePath + " UPDATE"+ config.getSetString()+ config.getWhereString(true); + } + return "UPDATE " + tablePath + config.getSetString() + config.getWhereString(true) + (config.isMySQL() ? config.getLimitString() : ""); case DELETE: - return "DELETE FROM " + tablePath + config.getWhereString(true) + (config.isMySQL()||config.isClickHouse() ? config.getLimitString() : ""); // PostgreSQL 不允许 LIMIT + if(config.isClickHouse()){ + return "ALTER TABLE " + tablePath + " DELETE" + config.getWhereString(true); + } + return "DELETE FROM " + tablePath + config.getWhereString(true) + (config.isMySQL() ? config.getLimitString() : ""); // PostgreSQL 不允许 LIMIT default: String explain = (config.isExplain() ? (config.isSQLServer() || config.isOracle() ? "SET STATISTICS PROFILE ON " : "EXPLAIN ") : ""); if (config.isTest() && RequestMethod.isGetMethod(config.getMethod(), true)) { From e2cfc82db8ae919b50f7de2e3c10db0546470571 Mon Sep 17 00:00:00 2001 From: qiujunlin <1757591067@qq.com> Date: Mon, 13 Sep 2021 14:03:10 +0800 Subject: [PATCH 0461/1181] update a problem about oracle --- APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java index 47d07b456..82bdbf5b4 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java @@ -2685,7 +2685,7 @@ public static String getSQL(AbstractSQLConfig config) throws Exception { String column = config.getColumnString(); if (config.isOracle()) { //When config's database is oracle,Using subquery since Oracle12 below does not support OFFSET FETCH paging syntax. - return explain + "SELECT * FROM (SELECT"+ (config.getCache() == JSONRequest.CACHE_RAM ? "SQL_NO_CACHE " : "") + column + " FROM " + getConditionString(column, tablePath, config) + ") " + config.getLimitString(); + return explain + "SELECT * FROM (SELECT "+ (config.getCache() == JSONRequest.CACHE_RAM ? "SQL_NO_CACHE " : "") + column + " FROM " + getConditionString(column, tablePath, config) + ") " + config.getLimitString(); } return explain + "SELECT " + (config.getCache() == JSONRequest.CACHE_RAM ? "SQL_NO_CACHE " : "") + column + " FROM " + getConditionString(column, tablePath, config) + config.getLimitString(); From 60a305c9aad2dc4f1620c8b084c4ac398fb1c951 Mon Sep 17 00:00:00 2001 From: lixin Date: Thu, 16 Sep 2021 12:58:45 +0800 Subject: [PATCH 0462/1181] =?UTF-8?q?1.=E6=A0=B9=E6=8D=AE=E6=96=B9?= =?UTF-8?q?=E6=B3=95=E4=B8=8D=E5=90=8C=E6=8B=BC=E6=8E=A5=E8=81=9A=E5=90=88?= =?UTF-8?q?=E8=AF=AD=E5=8F=A5=202.=E4=BF=AE=E6=94=B9Oracle=E5=88=86?= =?UTF-8?q?=E7=BB=84=E7=BB=9F=E8=AE=A1=E8=AF=AD=E5=8F=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/apijson/orm/AbstractSQLConfig.java | 38 +++++++++++-------- 1 file changed, 23 insertions(+), 15 deletions(-) diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java index 82bdbf5b4..856c9e8d7 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java @@ -21,12 +21,7 @@ import static apijson.JSONObject.KEY_ROLE; import static apijson.JSONObject.KEY_SCHEMA; import static apijson.JSONObject.KEY_USER_ID; -import static apijson.RequestMethod.DELETE; -import static apijson.RequestMethod.GET; -import static apijson.RequestMethod.GETS; -import static apijson.RequestMethod.HEADS; -import static apijson.RequestMethod.POST; -import static apijson.RequestMethod.PUT; +import static apijson.RequestMethod.*; import static apijson.SQL.AND; import static apijson.SQL.NOT; import static apijson.SQL.OR; @@ -890,7 +885,7 @@ public String getOrderString(boolean hasPrefix) { // return (hasPrefix ? " ORDER BY " : "") + StringUtil.concat(order, joinOrder, ", "); // } - if (getCount() > 0 && (isOracle() || isSQLServer() || isDb2())) { // Oracle, SQL Server, DB2 的 OFFSET 必须加 ORDER BY + if (getCount() > 0 && (isSQLServer() || isDb2())) { // Oracle, SQL Server, DB2 的 OFFSET 必须加 ORDER BY.去掉Oracle,Oracle里面没有offset关键字 // String[] ss = StringUtil.split(order); if (StringUtil.isEmpty(order, true)) { //SQL Server 子查询内必须指定 OFFSET 才能用 ORDER BY @@ -2685,6 +2680,11 @@ public static String getSQL(AbstractSQLConfig config) throws Exception { String column = config.getColumnString(); if (config.isOracle()) { //When config's database is oracle,Using subquery since Oracle12 below does not support OFFSET FETCH paging syntax. + //针对oracle分组后条数的统计 + if ((config.getMethod() == HEAD || config.getMethod() == HEADS) + && StringUtil.isNotEmpty(config.getGroup(),true)){ + return explain + "SELECT count(*) FROM (SELECT "+ (config.getCache() == JSONRequest.CACHE_RAM ? "SQL_NO_CACHE " : "") + column + " FROM " + getConditionString(column, tablePath, config) + ") " + config.getLimitString(); + } return explain + "SELECT * FROM (SELECT "+ (config.getCache() == JSONRequest.CACHE_RAM ? "SQL_NO_CACHE " : "") + column + " FROM " + getConditionString(column, tablePath, config) + ") " + config.getLimitString(); } @@ -2693,10 +2693,9 @@ public static String getSQL(AbstractSQLConfig config) throws Exception { } /**获取条件SQL字符串 - * @param page * @param column * @param table - * @param where + * @param config * @return * @throws Exception */ @@ -2708,11 +2707,21 @@ private static String getConditionString(String column, String table, AbstractSQ table = config.getSubqueryString(from) + " AS " + config.getAliasWithQuote() + " "; } - String condition = table + config.getJoinString() + where + ( - RequestMethod.isGetMethod(config.getMethod(), true) == false ? - "" : config.getGroupString(true) + config.getHavingString(true) + config.getOrderString(true) - ) - ; //+ config.getLimitString(); + //根据方法不同,聚合语句不同。GROUP BY 和 HAVING 可以加在 HEAD 上, HAVING 可以加在 PUT, DELETE 上,GET 全加,POST 全都不加 + String aggregation = ""; + if (RequestMethod.isGetMethod(config.getMethod(), true)){ + aggregation = config.getGroupString(true) + config.getHavingString(true) + + config.getOrderString(true); + } + if (RequestMethod.isHeadMethod(config.getMethod(), true)){ + aggregation = config.getGroupString(true) + config.getHavingString(true) ; + } + if (config.getMethod() == PUT || config.getMethod() == DELETE){ + aggregation = config.getHavingString(true) ; + } + + String condition = table + config.getJoinString() + where + aggregation; + ; //+ config.getLimitString(); //no need to optimize // if (config.getPage() <= 0 || ID.equals(column.trim())) { @@ -2749,7 +2758,6 @@ private static String getConditionString(String column, String table, AbstractSQ // return table + " AS t0 INNER JOIN (SELECT id FROM " + condition + ") AS t1 ON t0.id = t1.id"; } - private boolean keyPrefix; @Override public boolean isKeyPrefix() { From 7a7eed635520556eca3172eaca6127f79aed31c3 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Fri, 17 Sep 2021 17:39:49 +0800 Subject: [PATCH 0463/1181] =?UTF-8?q?=E7=94=9F=E6=80=81=E5=91=A8=E8=BE=B9?= =?UTF-8?q?=E9=A1=B9=E7=9B=AE=E6=96=B0=E5=A2=9E=20apijson-practice?= =?UTF-8?q?=EF=BC=8C=E6=84=9F=E8=B0=A2=E8=B4=A1=E7=8C=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit https://github.com/vcoolwind/apijson-practice --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index a521615f8..f63560f83 100644 --- a/README.md +++ b/README.md @@ -435,6 +435,8 @@ https://github.com/Tencent/APIJSON/blob/master/CONTRIBUTING.md [FfApiJson](https://gitee.com/own_3_0/ff-api-json) 用 JSON 格式直接生成 SQL,借鉴 APIJSON 支持多数据源 +[apijson-practice](https://github.com/vcoolwind/apijson-practice) 实践一下apijson,对做管理平台还是能有不少提效的 + [APIJSON-ToDo-Demo](https://github.com/jerrylususu/apijson_todo_demo) 一个简单的 todo 示例项目,精简数据,简化上手流程,带自定义鉴权逻辑 [apijson-learn](https://github.com/rainboy-learn/apijson-learn) APIJSON 学习笔记和源码解析 From ac9dc853f3886678c7bffc69f108176a6ccde01e Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Sun, 19 Sep 2021 22:42:58 +0800 Subject: [PATCH 0464/1181] =?UTF-8?q?=E6=8A=BD=E5=8F=96=E6=A0=B9=E6=8D=AE?= =?UTF-8?q?=20tag=20=E8=87=AA=E5=8A=A8=E5=8C=85=E8=A3=85=E8=AF=B7=E6=B1=82?= =?UTF-8?q?=E7=BB=93=E6=9E=84=E7=9A=84=E6=96=B9=E6=B3=95=E4=B8=BA=20wrapRe?= =?UTF-8?q?quest?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../main/java/apijson/orm/AbstractParser.java | 55 +++++++++++++------ 1 file changed, 39 insertions(+), 16 deletions(-) diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java b/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java index 7051b3e06..f5ba02636 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java @@ -30,6 +30,7 @@ import com.alibaba.fastjson.JSONObject; import apijson.JSON; +import apijson.JSONRequest; import apijson.JSONResponse; import apijson.Log; import apijson.NotNull; @@ -513,31 +514,53 @@ public JSONObject parseCorrectRequest(RequestMethod method, String tag, int vers + "非开放请求必须是后端 Request 表中校验规则允许的操作!\n " + error + "\n如果需要则在 Request 表中新增配置!"); } - JSONObject target = object; - if (object.containsKey(tag) == false) { //tag 是 Table 名或 Table[] + //获取指定的JSON结构 >>>>>>>>>>>>>> + JSONObject target = wrapRequest(object, tag, false); + + //JSONObject clone 浅拷贝没用,Structure.parse 会导致 structure 里面被清空,第二次从缓存里取到的就是 {} + return getVerifier().verifyRequest(method, name, target, request, maxUpdateCount, getGlobleDatabase(), getGlobleSchema(), creator); + } - boolean isArrayKey = tag.endsWith(":[]"); // JSONRequest.isArrayKey(tag); - String key = isArrayKey ? tag.substring(0, tag.length() - 3) : tag; - if (apijson.JSONObject.isTableKey(key)) { - if (isArrayKey) { //自动为 tag = Comment:[] 的 { ... } 新增键值对 "Comment[]":[] 为 { "Comment[]":[], ... } - target.put(key + "[]", new JSONArray()); - } - else { //自动为 tag = Comment 的 { ... } 包一层为 { "Comment": { ... } } - target = new JSONObject(true); - target.put(tag, object); + /**自动根据 tag 是否为 TableKey 及是否被包含在 object 内来决定是否包装一层,改为 { tag: object, "tag": tag } + * @param object + * @param tag + * @return + */ + public static JSONObject wrapRequest(JSONObject object, String tag, boolean putTag) { + if (object == null || object.containsKey(tag)) { //tag 是 Table 名或 Table[] + if (putTag) { + if (object == null) { + object = new JSONObject(true); } + object.put(JSONRequest.KEY_TAG, tag); } + return object; } - //获取指定的JSON结构 >>>>>>>>>>>>>> - + boolean isDiffArrayKey = tag.endsWith(":[]"); + boolean isArrayKey = isDiffArrayKey || JSONRequest.isArrayKey(tag); + String key = isArrayKey ? tag.substring(0, tag.length() - (isDiffArrayKey ? 3 : 2)) : tag; - //JSONObject clone 浅拷贝没用,Structure.parse 会导致 structure 里面被清空,第二次从缓存里取到的就是 {} - return getVerifier().verifyRequest(method, name, target, request, maxUpdateCount, getGlobleDatabase(), getGlobleSchema(), creator); + JSONObject target = object; + if (apijson.JSONObject.isTableKey(key)) { + if (isDiffArrayKey) { //自动为 tag = Comment:[] 的 { ... } 新增键值对 "Comment[]":[] 为 { "Comment[]":[], ... } + target.put(key + "[]", new JSONArray()); + } + else { //自动为 tag = Comment 的 { ... } 包一层为 { "Comment": { ... } } + target = new JSONObject(true); + target.put(tag, object); + } + } + + if (putTag) { + target.put(JSONRequest.KEY_TAG, tag); + } + + return target; } - + /**新建带状态内容的JSONObject * @param code * @param msg From 77c375b20de5dd4f5486fde26f1b8e773e562f1c Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Thu, 23 Sep 2021 18:18:47 +0800 Subject: [PATCH 0465/1181] =?UTF-8?q?=E5=AE=8C=E5=96=84=E8=AF=B7=E6=B1=82?= =?UTF-8?q?=E5=8F=82=E6=95=B0=E5=9C=A8=E5=90=84=E7=A7=8D=20method=20?= =?UTF-8?q?=E5=8F=8A=20tag=20=E4=B8=8B=E7=9A=84=E8=87=AA=E5=8A=A8=E8=A1=A5?= =?UTF-8?q?=E5=85=A8=EF=BC=9B=E5=85=81=E8=AE=B8=20GETS=20=E9=80=9A?= =?UTF-8?q?=E8=BF=87=20key[]:{}=20=E6=9D=A5=E6=9F=A5=E5=A4=9A=E6=9D=A1?= =?UTF-8?q?=E8=AE=B0=E5=BD=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../main/java/apijson/orm/AbstractParser.java | 47 +++++++++++++++---- 1 file changed, 38 insertions(+), 9 deletions(-) diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java b/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java index f5ba02636..6965f2802 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java @@ -515,7 +515,7 @@ public JSONObject parseCorrectRequest(RequestMethod method, String tag, int vers } //获取指定的JSON结构 >>>>>>>>>>>>>> - JSONObject target = wrapRequest(object, tag, false); + JSONObject target = wrapRequest(method, tag, object, true); //JSONObject clone 浅拷贝没用,Structure.parse 会导致 structure 里面被清空,第二次从缓存里取到的就是 {} return getVerifier().verifyRequest(method, name, target, request, maxUpdateCount, getGlobleDatabase(), getGlobleSchema(), creator); @@ -527,7 +527,9 @@ public JSONObject parseCorrectRequest(RequestMethod method, String tag, int vers * @param tag * @return */ - public static JSONObject wrapRequest(JSONObject object, String tag, boolean putTag) { + public static JSONObject wrapRequest(RequestMethod method, String tag, JSONObject object, boolean isStructure) { + boolean putTag = ! isStructure; + if (object == null || object.containsKey(tag)) { //tag 是 Table 名或 Table[] if (putTag) { if (object == null) { @@ -544,12 +546,39 @@ public static JSONObject wrapRequest(JSONObject object, String tag, boolean putT JSONObject target = object; if (apijson.JSONObject.isTableKey(key)) { - if (isDiffArrayKey) { //自动为 tag = Comment:[] 的 { ... } 新增键值对 "Comment[]":[] 为 { "Comment[]":[], ... } - target.put(key + "[]", new JSONArray()); + if (isDiffArrayKey) { //自动为 tag = Comment:[] 的 { ... } 新增键值对为 { "Comment[]":[], "TYPE": { "Comment[]": "OBJECT[]" } ... } + if (isStructure && (method == RequestMethod.POST || method == RequestMethod.PUT)) { + String arrKey = key + "[]"; + + if (target.containsKey(arrKey) == false) { + target.put(arrKey, new JSONArray()); + } + + try { + JSONObject type = target.getJSONObject(Operation.TYPE.name()); + if (type == null || (type.containsKey(arrKey) == false)) { + if (type == null) { + type = new JSONObject(true); + } + + type.put(arrKey, "OBJECT[]"); + target.put(Operation.TYPE.name(), type); + } + } + catch (Throwable e) { + Log.w(TAG, "wrapRequest try { JSONObject type = target.getJSONObject(Operation.TYPE.name()); } catch (Exception e) = " + e.getMessage()); + } + } } else { //自动为 tag = Comment 的 { ... } 包一层为 { "Comment": { ... } } - target = new JSONObject(true); - target.put(tag, object); + if (isArrayKey == false || RequestMethod.isGetMethod(method, true)) { + target = new JSONObject(true); + target.put(tag, object); + } + else if (target.containsKey(key) == false) { + target = new JSONObject(true); + target.put(key, object); + } } } @@ -958,10 +987,10 @@ public JSONArray onArrayParse(JSONObject request, String parentPath, String name } //不能允许GETS,否则会被通过"[]":{"@role":"ADMIN"},"Table":{},"tag":"Table"绕过权限并能批量查询 - if (isSubquery == false && RequestMethod.isGetMethod(requestMethod, false) == false) { - throw new UnsupportedOperationException("key[]:{}只支持GET方法!不允许传 " + name + ":{} !"); + if (isSubquery == false && RequestMethod.isGetMethod(requestMethod, true) == false) { + throw new UnsupportedOperationException("key[]:{} 只支持 GET, GETS 方法!其它方法不允许传 " + name + ":{} 等这种 key[]:{} 格式!"); } - if (request == null || request.isEmpty()) {//jsonKey-jsonValue条件 + if (request == null || request.isEmpty()) { // jsonKey-jsonValue 条件 return null; } String path = getAbsPath(parentPath, name); From bf1c7966bf97e5dab4150db0c05790f012790621 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Thu, 23 Sep 2021 21:51:11 +0800 Subject: [PATCH 0466/1181] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E9=80=9A=E7=94=A8?= =?UTF-8?q?=E6=96=87=E6=A1=A3=E7=9A=84=E6=9C=AC=E8=BA=AB=E8=AF=B4=E6=98=8E?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Document.md | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/Document.md b/Document.md index e1f10aab2..e01c28658 100644 --- a/Document.md +++ b/Document.md @@ -1,5 +1,10 @@ -# APIJSON通用文档 -后端开发者可以先看这个详细的 [图文入门教程](https://vincentcheng.github.io/apijson-doc/zh)(如果和本文档有出入,以本文档为准,例如正则匹配 key? 已废弃,全面用 key~ 替代。) +# APIJSON 通用文档 +本文是通用文档,只和 APIJSON 协议有关,和 C#, Go, Java, JavaScript, Python, PHP 等开发语言无关。
+具体开发语言相关的 配置、运行、部署 等文档见各个相关项目的文档,可以在首页点击对应语言的入口来查看。
+https://github.com/Tencent/APIJSON +![image](https://user-images.githubusercontent.com/5738175/134518921-0747a035-6f79-4583-9f5e-fba9481c0a87.png) + +后端开发者可以先看这个详细的 [图文入门教程](https://vincentcheng.github.io/apijson-doc/zh)(非官方,和本文档有出入的地方以本文档为准,例如正则匹配 key? 已废弃,全面用 key~ 替代。) * ### [1.示例](#1) * ### [2.对比传统方式](#2) From 0c08c22232e7d59e735eee77f48fc810ba81ba6c Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Thu, 23 Sep 2021 21:53:57 +0800 Subject: [PATCH 0467/1181] =?UTF-8?q?=E6=96=B0=E5=A2=9E=20apijson-go=20?= =?UTF-8?q?=E7=9A=84=E9=93=BE=E6=8E=A5=EF=BC=8C=E6=84=9F=E8=B0=A2=E4=BD=9C?= =?UTF-8?q?=E8=80=85=E7=9A=84=E8=B4=A1=E7=8C=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit https://gitee.com/tiangao/apijson-go --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index f63560f83..0bfb971a6 100644 --- a/README.md +++ b/README.md @@ -20,6 +20,7 @@ This source code is licensed under the Apache License Version 2.0

+       From 326c4d24c84dbf74e5bd42d2dc581bbb540394f6 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Thu, 23 Sep 2021 21:55:40 +0800 Subject: [PATCH 0468/1181] Update Document.md --- Document.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Document.md b/Document.md index e01c28658..e125516e5 100644 --- a/Document.md +++ b/Document.md @@ -2,7 +2,8 @@ 本文是通用文档,只和 APIJSON 协议有关,和 C#, Go, Java, JavaScript, Python, PHP 等开发语言无关。
具体开发语言相关的 配置、运行、部署 等文档见各个相关项目的文档,可以在首页点击对应语言的入口来查看。
https://github.com/Tencent/APIJSON -![image](https://user-images.githubusercontent.com/5738175/134518921-0747a035-6f79-4583-9f5e-fba9481c0a87.png) +![image](https://user-images.githubusercontent.com/5738175/134520081-a63d3817-321c-4e7b-9e03-73c6827a19c1.png) + 后端开发者可以先看这个详细的 [图文入门教程](https://vincentcheng.github.io/apijson-doc/zh)(非官方,和本文档有出入的地方以本文档为准,例如正则匹配 key? 已废弃,全面用 key~ 替代。) From 02a287a98f2bc80499d8d7498a2eafd7c7a3d2c0 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Thu, 23 Sep 2021 21:56:56 +0800 Subject: [PATCH 0469/1181] Update Document.md --- Document.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Document.md b/Document.md index e125516e5..f9969e3e1 100644 --- a/Document.md +++ b/Document.md @@ -5,7 +5,7 @@ https://github.com/Tencent/APIJSON ![image](https://user-images.githubusercontent.com/5738175/134520081-a63d3817-321c-4e7b-9e03-73c6827a19c1.png) -后端开发者可以先看这个详细的 [图文入门教程](https://vincentcheng.github.io/apijson-doc/zh)(非官方,和本文档有出入的地方以本文档为准,例如正则匹配 key? 已废弃,全面用 key~ 替代。) +后端开发者可以先看这个详细的 [图文入门教程](https://vincentcheng.github.io/apijson-doc/zh)(非官方,和本文档有出入的点以本文档为准,例如正则匹配 key? 已废弃,用 key~ 替代。) * ### [1.示例](#1) * ### [2.对比传统方式](#2) From 51b4d5ed278b5f0487b22b2fedbcec0ada1f7b4d Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Thu, 23 Sep 2021 23:01:33 +0800 Subject: [PATCH 0470/1181] Update Document.md --- Document.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Document.md b/Document.md index f9969e3e1..bd641f91e 100644 --- a/Document.md +++ b/Document.md @@ -5,7 +5,7 @@ https://github.com/Tencent/APIJSON ![image](https://user-images.githubusercontent.com/5738175/134520081-a63d3817-321c-4e7b-9e03-73c6827a19c1.png) -后端开发者可以先看这个详细的 [图文入门教程](https://vincentcheng.github.io/apijson-doc/zh)(非官方,和本文档有出入的点以本文档为准,例如正则匹配 key? 已废弃,用 key~ 替代。) +后端开发者可以先看 [图文入门教程1](https://vincentcheng.github.io/apijson-doc/zh) 或 [图文入门教程2](https://hanxu2018.github.io/APIJSON-DOC/) (都非官方,和本文档有出入的点以本文档为准,例如正则匹配 key? 已废弃,用 key~ 替代。) * ### [1.示例](#1) * ### [2.对比传统方式](#2) From 061507810b28d7d2a58fdf1ff82a926044d3b6c1 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Fri, 24 Sep 2021 03:29:49 +0800 Subject: [PATCH 0471/1181] =?UTF-8?q?FunctionsAndRaws=20=E4=BB=A3=E7=A0=81?= =?UTF-8?q?=E7=A7=BB=E5=9B=9E=20AbstractSQLConfig=20=E5=85=BC=E5=AE=B9?= =?UTF-8?q?=E7=8E=B0=E6=9C=89=E7=94=A8=E6=88=B7=E4=BB=A3=E7=A0=81=EF=BC=9B?= =?UTF-8?q?=E5=AE=8C=E6=88=90=20PostgreSQL=20=E7=9A=84=E7=AA=97=E5=8F=A3?= =?UTF-8?q?=E5=87=BD=E6=95=B0=EF=BC=9B=E8=A7=A3=E5=86=B3=20PUT=20=20"blanc?= =?UTF-8?q?e+":=201=20=E6=9C=AA=E5=8A=A0=E5=88=B0=20update=20set=20?= =?UTF-8?q?=E5=AF=BC=E8=87=B4=E6=8A=A5=E9=94=99=EF=BC=9B=E8=A7=A3=E5=86=B3?= =?UTF-8?q?=20@column=20=E5=9C=A8=20OVER,=20MATCH=20=E7=AD=89=E5=87=BD?= =?UTF-8?q?=E6=95=B0=E5=86=85=E9=83=A8=E5=88=86=E5=AD=97=E6=AE=B5=E8=A7=A3?= =?UTF-8?q?=E6=9E=90=E5=87=BA=E9=94=99=E4=BB=A5=E5=8F=8A=E5=8F=AF=E8=83=BD?= =?UTF-8?q?=E7=9A=84=20SQL=20=E6=B3=A8=E5=85=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../apijson/orm/AbstractObjectParser.java | 6 +- .../java/apijson/orm/AbstractSQLConfig.java | 737 ++++++++++++++++-- .../java/apijson/orm/AbstractVerifier.java | 2 +- .../java/apijson/orm/FunctionsAndRaws.java | 519 ------------ 4 files changed, 660 insertions(+), 604 deletions(-) delete mode 100644 APIJSONORM/src/main/java/apijson/orm/FunctionsAndRaws.java diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractObjectParser.java b/APIJSONORM/src/main/java/apijson/orm/AbstractObjectParser.java index 979bce409..421c2d306 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractObjectParser.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractObjectParser.java @@ -224,6 +224,8 @@ public AbstractObjectParser parse(String name, boolean isReuse) throws Exception whereList = new ArrayList(Arrays.asList(combine != null ? combine : new String[]{})); whereList.add(apijson.JSONRequest.KEY_ID); whereList.add(apijson.JSONRequest.KEY_ID_IN); +// whereList.add(apijson.JSONRequest.KEY_USER_ID); +// whereList.add(apijson.JSONRequest.KEY_USER_ID_IN); } //条件>>>>>>>>>>>>>>>>>>> @@ -261,8 +263,8 @@ else if ((method == POST || method == PUT) && value instanceof JSONArray && JSONRequest.isTableArray(key)) { // JSONArray,批量新增或修改,往下一级提取 onTableArrayParse(key, (JSONArray) value); } - else if (method == PUT && value instanceof JSONArray && (whereList == null || whereList.contains(key) == false) && StringUtil.isName(key.replaceFirst("[+-]$", ""))) - { // PUT JSONArray + else if (method == PUT && value instanceof JSONArray && (whereList == null || whereList.contains(key) == false) + && StringUtil.isName(key.replaceFirst("[+-]$", ""))) { // PUT JSONArray onPUTArrayParse(key, (JSONArray) value); } else { // JSONArray或其它Object,直接填充 diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java index 45cbff2d7..108ce9deb 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java @@ -21,7 +21,13 @@ import static apijson.JSONObject.KEY_ROLE; import static apijson.JSONObject.KEY_SCHEMA; import static apijson.JSONObject.KEY_USER_ID; -import static apijson.RequestMethod.*; +import static apijson.RequestMethod.DELETE; +import static apijson.RequestMethod.GET; +import static apijson.RequestMethod.GETS; +import static apijson.RequestMethod.HEAD; +import static apijson.RequestMethod.HEADS; +import static apijson.RequestMethod.POST; +import static apijson.RequestMethod.PUT; import static apijson.SQL.AND; import static apijson.SQL.NOT; import static apijson.SQL.OR; @@ -88,9 +94,14 @@ public abstract class AbstractSQLConfig implements SQLConfig { public static final List CONFIG_TABLE_LIST; public static final List DATABASE_LIST; + // 自定义原始 SQL 片段 Map:当 substring 为 null 时忽略;当 substring 为 "" 时整个 value 是 raw SQL;其它情况则只是 substring 这段为 raw SQL + public static final Map RAW_MAP; + // 允许调用的 SQL 函数:当 substring 为 null 时忽略;当 substring 为 "" 时整个 value 是 raw SQL;其它情况则只是 substring 这段为 raw SQL + public static final Map SQL_FUNCTION_MAP; + static { // 凡是 SQL 边界符、分隔符、注释符 都不允许,例如 ' " ` ( ) ; # -- ,以免拼接 SQL 时被注入意外可执行指令 PATTERN_RANGE = Pattern.compile("^[0-9%,!=\\<\\>/\\.\\+\\-\\*\\^]+$"); // ^[a-zA-Z0-9_*%!=<>(),"]+$ 导致 exists(select*from(Comment)) 通过! - PATTERN_FUNCTION = Pattern.compile("^[A-Za-z0-9%,:_@&~!=\\<\\>\\|\\[\\]\\{\\} /\\.\\+\\-\\*\\^\\?\\(\\)\\$]+$"); //TODO 改成更好的正则,校验前面为单词,中间为操作符,后面为值 + PATTERN_FUNCTION = Pattern.compile("^[A-Za-z0-9%,:_@&~`!=\\<\\>\\|\\[\\]\\{\\} /\\.\\+\\-\\*\\^\\?\\(\\)\\$]+$"); //TODO 改成更好的正则,校验前面为单词,中间为操作符,后面为值 PATTERN_STRING = Pattern.compile("^[,#;\"`]+$"); TABLE_KEY_MAP = new HashMap(); @@ -118,6 +129,531 @@ public abstract class AbstractSQLConfig implements SQLConfig { DATABASE_LIST.add(DATABASE_ORACLE); DATABASE_LIST.add(DATABASE_DB2); DATABASE_LIST.add(DATABASE_CLICKHOUSE); + + + RAW_MAP = new LinkedHashMap<>(); // 保证顺序,避免配置冲突等意外情况 + + // mysql关键字 + RAW_MAP.put("AS",""); + RAW_MAP.put("VALUE",""); + RAW_MAP.put("DISTINCT",""); + + //时间 + RAW_MAP.put("DATE",""); + RAW_MAP.put("now()",""); + RAW_MAP.put("DATETIME",""); + RAW_MAP.put("DateTime",""); + RAW_MAP.put("SECOND",""); + RAW_MAP.put("MINUTE",""); + RAW_MAP.put("HOUR",""); + RAW_MAP.put("DAY",""); + RAW_MAP.put("WEEK",""); + RAW_MAP.put("MONTH",""); + RAW_MAP.put("QUARTER",""); + RAW_MAP.put("YEAR",""); + RAW_MAP.put("json",""); + RAW_MAP.put("unit",""); + + //MYSQL 数据类型 BINARY,CHAR,DATETIME,TIME,DECIMAL,SIGNED,UNSIGNED + RAW_MAP.put("BINARY",""); + RAW_MAP.put("SIGNED",""); + RAW_MAP.put("DECIMAL",""); + RAW_MAP.put("BINARY",""); + RAW_MAP.put("UNSIGNED",""); + RAW_MAP.put("CHAR",""); + RAW_MAP.put("TIME",""); + + //窗口函数关键字 + RAW_MAP.put("OVER", ""); + RAW_MAP.put("INTERVAL", ""); + RAW_MAP.put("GROUP BY", ""); //往前 + RAW_MAP.put("GROUP", ""); //往前 + RAW_MAP.put("ORDER BY", ""); //往前 + RAW_MAP.put("ORDER", ""); + RAW_MAP.put("PARTITION BY", ""); //往前 + RAW_MAP.put("PARTITION", ""); //往前 + RAW_MAP.put("BY", ""); + RAW_MAP.put("DESC", ""); + RAW_MAP.put("ASC", ""); + RAW_MAP.put("FOLLOWING", "");//往后 + RAW_MAP.put("BETWEEN", ""); + RAW_MAP.put("AND", ""); + RAW_MAP.put("ROWS", ""); + + RAW_MAP.put("AGAINST", ""); + RAW_MAP.put("IN NATURAL LANGUAGE MODE", ""); + RAW_MAP.put("IN BOOLEAN MODE", ""); + RAW_MAP.put("IN", ""); + RAW_MAP.put("BOOLEAN", ""); + RAW_MAP.put("NATURAL", ""); + RAW_MAP.put("LANGUAGE", ""); + RAW_MAP.put("MODE", ""); + + + SQL_FUNCTION_MAP = new LinkedHashMap<>(); // 保证顺序,避免配置冲突等意外情况 + + + //窗口函数 + SQL_FUNCTION_MAP.put("rank", "");//得到数据项在分组中的排名,排名相等的时候会留下空位 + SQL_FUNCTION_MAP.put("dense_rank", ""); //得到数据项在分组中的排名,排名相等的时候不会留下空位 + SQL_FUNCTION_MAP.put("row_number", "");//按照分组中的顺序生成序列,不存在重复的序列 + SQL_FUNCTION_MAP.put("ntile", "");//用于将分组数据按照顺序切分成N片,返回当前切片值,不支持ROWS_BETWEE + SQL_FUNCTION_MAP.put("first_value", "");//取分组排序后,截止到当前行,分组内第一个值 + SQL_FUNCTION_MAP.put("last_value", "");//取分组排序后,截止到当前行,分组内的最后一个值 + SQL_FUNCTION_MAP.put("lag", "");//统计窗口内往上第n行值。第一个参数为列名,第二个参数为往上第n行(可选,默认为1),第三个参数为默认值(当往上第n行为NULL时候,取默认值,如不指定,则为NULL) + SQL_FUNCTION_MAP.put("lead", "");//统计窗口内往下第n行值。第一个参数为列名,第二个参数为往下第n行(可选,默认为1),第三个参数为默认值(当往下第n行为NULL时候,取默认值,如不指定,则为NULL) + SQL_FUNCTION_MAP.put("cume_dist", "");//)返回(小于等于当前行值的行数)/(当前分组内的总行数) + SQL_FUNCTION_MAP.put("percent_rank", "");//返回(组内当前行的rank值-1)/(分组内做总行数-1) + + // MySQL 字符串函数 + SQL_FUNCTION_MAP.put("ascii", ""); // ASCII(s) 返回字符串 s 的第一个字符的 ASCII 码。 + SQL_FUNCTION_MAP.put("char_length", ""); // CHAR_LENGTH(s) 返回字符串 s 的字符数 + SQL_FUNCTION_MAP.put("character_length", ""); // CHARACTER_LENGTH(s) 返回字符串 s 的字符数 + SQL_FUNCTION_MAP.put("concat", ""); // CONCAT(s1, s2...sn) 字符串 s1,s2 等多个字符串合并为一个字符串 + SQL_FUNCTION_MAP.put("concat_ws", ""); // CONCAT_WS(x, s1, s2...sn) 同 CONCAT(s1, s2 ...) 函数,但是每个字符串之间要加上 x,x 可以是分隔符 + SQL_FUNCTION_MAP.put("field", ""); // FIELD(s, s1, s2...) 返回第一个字符串 s 在字符串列表 (s1, s2...)中的位置 + SQL_FUNCTION_MAP.put("find_in_set", ""); // FIND_IN_SET(s1, s2) 返回在字符串s2中与s1匹配的字符串的位置 + SQL_FUNCTION_MAP.put("format", ""); // FORMAT(x, n) 函数可以将数字 x 进行格式化 "#,###.##", 将 x 保留到小数点后 n 位,最后一位四舍五入。 + SQL_FUNCTION_MAP.put("insert", ""); // INSERT(s1, x, len, s2) 字符串 s2 替换 s1 的 x 位置开始长度为 len 的字符串 + SQL_FUNCTION_MAP.put("locate", ""); // LOCATE(s1, s) 从字符串 s 中获取 s1 的开始位置 + SQL_FUNCTION_MAP.put("lcase", ""); // LCASE(s) 将字符串 s 的所有字母变成小写字母 + SQL_FUNCTION_MAP.put("left", ""); // LEFT(s, n) 返回字符串 s 的前 n 个字符 + SQL_FUNCTION_MAP.put("length", ""); // LENGTH(s) 返回字符串 s 的字符数 + SQL_FUNCTION_MAP.put("lower", ""); // LOWER(s) 将字符串 s 的所有字母变成小写字母 + SQL_FUNCTION_MAP.put("lpad", ""); // LPAD(s1, len, s2) 在字符串 s1 的开始处填充字符串 s2,使字符串长度达到 len + SQL_FUNCTION_MAP.put("ltrim", ""); // LTRIM(s) 去掉字符串 s 开始处的空格 + SQL_FUNCTION_MAP.put("mid", ""); // MID(s, n, len) 从字符串 s 的 n 位置截取长度为 len 的子字符串,同 SUBSTRING(s, n, len) + SQL_FUNCTION_MAP.put("position", ""); // POSITION(s, s1); 从字符串 s 中获取 s1 的开始位置 + SQL_FUNCTION_MAP.put("repeat", ""); // REPEAT(s, n) 将字符串 s 重复 n 次 + SQL_FUNCTION_MAP.put("replace", ""); // REPLACE(s, s1, s2) 将字符串 s2 替代字符串 s 中的字符串 s1 + SQL_FUNCTION_MAP.put("reverse", ""); // REVERSE(s); // ) 将字符串s的顺序反过来 + SQL_FUNCTION_MAP.put("right", ""); // RIGHT(s, n) 返回字符串 s 的后 n 个字符 + SQL_FUNCTION_MAP.put("rpad", ""); // RPAD(s1, len, s2) 在字符串 s1 的结尾处添加字符串 s2,使字符串的长度达到 len + SQL_FUNCTION_MAP.put("rtrim", ""); // RTRIM", ""); // ) 去掉字符串 s 结尾处的空格 + SQL_FUNCTION_MAP.put("space", ""); // SPACE(n) 返回 n 个空格 + SQL_FUNCTION_MAP.put("strcmp", ""); // STRCMP(s1, s2) 比较字符串 s1 和 s2,如果 s1 与 s2 相等返回 0 ,如果 s1>s2 返回 1,如果 s1d2 之间相隔的天数 + SQL_FUNCTION_MAP.put("date_add", ""); // DATE_ADD(d,INTERVAL expr type) 计算起始日期 d 加上一个时间段后的日期 + SQL_FUNCTION_MAP.put("date_format", ""); // DATE_FORMAT(d,f) 按表达式 f的要求显示日期 d + SQL_FUNCTION_MAP.put("date_sub", ""); // DATE_SUB(date,INTERVAL expr type) 函数从日期减去指定的时间间隔。 + SQL_FUNCTION_MAP.put("day", ""); // DAY(d) 返回日期值 d 的日期部分 + SQL_FUNCTION_MAP.put("dayname", ""); // DAYNAME(d) 返回日期 d 是星期几,如 Monday,Tuesday + SQL_FUNCTION_MAP.put("dayofmonth", ""); // DAYOFMONTH(d) 计算日期 d 是本月的第几天 + SQL_FUNCTION_MAP.put("dayofweek", ""); // DAYOFWEEK(d) 日期 d 今天是星期几,1 星期日,2 星期一,以此类推 + SQL_FUNCTION_MAP.put("dayofyear", ""); // DAYOFYEAR(d) 计算日期 d 是本年的第几天 + SQL_FUNCTION_MAP.put("extract", ""); // EXTRACT(type FROM d) 从日期 d 中获取指定的值,type 指定返回的值。 + SQL_FUNCTION_MAP.put("from_days", ""); // FROM_DAYS(n) 计算从 0000 年 1 月 1 日开始 n 天后的日期 + SQL_FUNCTION_MAP.put("hour", ""); // 'HOUR(t) 返回 t 中的小时值 + SQL_FUNCTION_MAP.put("last_day", ""); // LAST_DAY(d) 返回给给定日期的那一月份的最后一天 + SQL_FUNCTION_MAP.put("localtime", ""); // LOCALTIME() 返回当前日期和时间 + SQL_FUNCTION_MAP.put("localtimestamp", ""); // LOCALTIMESTAMP() 返回当前日期和时间 + SQL_FUNCTION_MAP.put("makedate", ""); // MAKEDATE(year, day-of-year) 基于给定参数年份 year 和所在年中的天数序号 day-of-year 返回一个日期 + SQL_FUNCTION_MAP.put("maketime", ""); // MAKETIME(hour, minute, second) 组合时间,参数分别为小时、分钟、秒 + SQL_FUNCTION_MAP.put("microsecond", ""); // MICROSECOND(date) 返回日期参数所对应的微秒数 + SQL_FUNCTION_MAP.put("minute", ""); // MINUTE(t) 返回 t 中的分钟值 + SQL_FUNCTION_MAP.put("monthname", ""); // MONTHNAME(d) 返回日期当中的月份名称,如 November + SQL_FUNCTION_MAP.put("month", ""); // MONTH(d) 返回日期d中的月份值,1 到 12 + SQL_FUNCTION_MAP.put("now", ""); // NOW() 返回当前日期和时间 + SQL_FUNCTION_MAP.put("period_add", ""); // PERIOD_ADD(period, number) 为 年-月 组合日期添加一个时段 + SQL_FUNCTION_MAP.put("period_diff", ""); // PERIOD_DIFF(period1, period2) 返回两个时段之间的月份差值 + SQL_FUNCTION_MAP.put("quarter", ""); // QUARTER(d) 返回日期d是第几季节,返回 1 到 4 + SQL_FUNCTION_MAP.put("second", ""); // SECOND(t) 返回 t 中的秒钟值 + SQL_FUNCTION_MAP.put("sec_to_time", ""); // SEC_TO_TIME", ""); // ) 将以秒为单位的时间 s 转换为时分秒的格式 + SQL_FUNCTION_MAP.put("str_to_date", ""); // STR_TO_DATE", ""); // tring, format_mask) 将字符串转变为日期 + SQL_FUNCTION_MAP.put("subdate", ""); // SUBDATE(d,n) 日期 d 减去 n 天后的日期 + SQL_FUNCTION_MAP.put("subtime", ""); // SUBTIME(t,n) 时间 t 减去 n 秒的时间 + SQL_FUNCTION_MAP.put("sysdate", ""); // SYSDATE() 返回当前日期和时间 + SQL_FUNCTION_MAP.put("time", ""); // TIME(expression) 提取传入表达式的时间部分 + SQL_FUNCTION_MAP.put("time_format", ""); // TIME_FORMAT(t,f) 按表达式 f 的要求显示时间 t + SQL_FUNCTION_MAP.put("time_to_sec", ""); // TIME_TO_SEC(t) 将时间 t 转换为秒 + SQL_FUNCTION_MAP.put("timediff", ""); // TIMEDIFF(time1, time2) 计算时间差值 + SQL_FUNCTION_MAP.put("timestamp", ""); // TIMESTAMP(expression, interval) 单个参数时,函数返回日期或日期时间表达式;有2个参数时,将参数加和 + SQL_FUNCTION_MAP.put("to_days", ""); // TO_DAYS(d) 计算日期 d 距离 0000 年 1 月 1 日的天数 + SQL_FUNCTION_MAP.put("week", ""); // WEEK(d) 计算日期 d 是本年的第几个星期,范围是 0 到 53 + SQL_FUNCTION_MAP.put("weekday", ""); // WEEKDAY(d) 日期 d 是星期几,0 表示星期一,1 表示星期二 + SQL_FUNCTION_MAP.put("weekofyear", ""); // WEEKOFYEAR(d) 计算日期 d 是本年的第几个星期,范围是 0 到 53 + SQL_FUNCTION_MAP.put("year", ""); // YEAR(d) 返回年份 + SQL_FUNCTION_MAP.put("yearweek", ""); // YEARWEEK(date, mode) 返回年份及第几周(0到53),mode 中 0 表示周天,1表示周一,以此类推 + SQL_FUNCTION_MAP.put("unix_timestamp", ""); // UNIX_TIMESTAMP(date) 获取UNIX时间戳函数,返回一个以 UNIX 时间戳为基础的无符号整数 + SQL_FUNCTION_MAP.put("from_unixtime", ""); // FROM_UNIXTIME(date) 将 UNIX 时间戳转换为时间格式,与UNIX_TIMESTAMP互为反函数 + + // MYSQL JSON 函数 + SQL_FUNCTION_MAP.put("json_append", ""); // JSON_APPEND(json_doc, path, val[, path, val] ...)) 插入JSON数组 + SQL_FUNCTION_MAP.put("json_array", ""); // JSON_ARRAY(val1, val2...) 创建JSON数组 + SQL_FUNCTION_MAP.put("json_array_append", ""); // JSON_ARRAY_APPEND(json_doc, val) 将数据附加到JSON文档 + SQL_FUNCTION_MAP.put("json_array_insert", ""); // JSON_ARRAY_INSERT(json_doc, val) 插入JSON数组 + SQL_FUNCTION_MAP.put("json_contains", ""); // JSON_CONTAINS(json_doc, val) JSON文档是否在路径中包含特定对象 + SQL_FUNCTION_MAP.put("json_contains_path", ""); // JSON_CONTAINS_PATH(json_doc, path) JSON文档是否在路径中包含任何数据 + SQL_FUNCTION_MAP.put("json_depth", ""); // JSON_DEPTH(json_doc) JSON文档的最大深度 + SQL_FUNCTION_MAP.put("json_extract", ""); // JSON_EXTRACT(json_doc, path) 从JSON文档返回数据 + SQL_FUNCTION_MAP.put("json_insert", ""); // JSON_INSERT(json_doc, val) 将数据插入JSON文档 + SQL_FUNCTION_MAP.put("json_keys", ""); // JSON_KEYS(json_doc[, path]) JSON文档中的键数组 + SQL_FUNCTION_MAP.put("json_length", ""); // JSON_LENGTH(json_doc) JSON文档中的元素数 + SQL_FUNCTION_MAP.put("json_merge", ""); // JSON_MERGE(json_doc1, json_doc2) (已弃用) 合并JSON文档,保留重复的键。JSON_MERGE_PRESERVE()的已弃用同义词 + SQL_FUNCTION_MAP.put("json_merge_patch", ""); // JSON_MERGE_PATCH(json_doc1, json_doc2) 合并JSON文档,替换重复键的值 + SQL_FUNCTION_MAP.put("json_merge_preserve", ""); // JSON_MERGE_PRESERVE(json_doc1, json_doc2) 合并JSON文档,保留重复的键 + SQL_FUNCTION_MAP.put("json_object", ""); // JSON_OBJECT(key1, val1, key2, val2...) 创建JSON对象 + SQL_FUNCTION_MAP.put("json_overlaps", ""); // JSON_OVERLAPS(json_doc1, json_doc2) (引入8.0.17) 比较两个JSON文档,如果它们具有相同的键值对或数组元素,则返回TRUE(1),否则返回FALSE(0) + SQL_FUNCTION_MAP.put("json_pretty", ""); // JSON_PRETTY(json_doc) 以易于阅读的格式打印JSON文档 + SQL_FUNCTION_MAP.put("json_quote", ""); // JSON_QUOTE(json_doc1) 引用JSON文档 + SQL_FUNCTION_MAP.put("json_remove", ""); // JSON_REMOVE(json_doc1, path) 从JSON文档中删除数据 + SQL_FUNCTION_MAP.put("json_replace", ""); // JSON_REPLACE(json_doc1, val1, val2) 替换JSON文档中的值 + SQL_FUNCTION_MAP.put("json_schema_valid", ""); // JSON_SCHEMA_VALID(json_doc) (引入8.0.17) 根据JSON模式验证JSON文档;如果文档针对架构进行验证,则返回TRUE / 1;否则,则返回FALSE / 0 + SQL_FUNCTION_MAP.put("json_schema_validation_report", ""); // JSON_SCHEMA_VALIDATION_REPORT(json_doc, mode) (引入8.0.17) 根据JSON模式验证JSON文档;以JSON格式返回有关验证结果的报告,包括成功或失败以及失败原因 + SQL_FUNCTION_MAP.put("json_search", ""); // JSON_SEARCH(json_doc, val) JSON文档中值的路径 + SQL_FUNCTION_MAP.put("json_set", ""); // JSON_SET(json_doc, val) 将数据插入JSON文档 + // SQL_FUNCTION_MAP.put("json_storage_free", ""); // JSON_STORAGE_FREE() 部分更新后,JSON列值的二进制表示形式中的可用空间 + // SQL_FUNCTION_MAP.put("json_storage_size", ""); // JSON_STORAGE_SIZE() 用于存储JSON文档的二进制表示的空间 + SQL_FUNCTION_MAP.put("json_table", ""); // JSON_TABLE() 从JSON表达式返回数据作为关系表 + SQL_FUNCTION_MAP.put("json_type", ""); // JSON_TYPE(json_doc) JSON值类型 + SQL_FUNCTION_MAP.put("json_unquote", ""); // JSON_UNQUOTE(json_doc) 取消引用JSON值 + SQL_FUNCTION_MAP.put("json_valid", ""); // JSON_VALID(json_doc) JSON值是否有效 + SQL_FUNCTION_MAP.put("json_arrayagg", ""); // JSON_ARRAYAGG(key) 将每个表达式转换为 JSON 值,然后返回一个包含这些 JSON 值的 JSON 数组 + SQL_FUNCTION_MAP.put("json_objectagg", ""); // JSON_OBJECTAGG(key, val)) 将每个表达式转换为 JSON 值,然后返回一个包含这些 JSON 值的 JSON 对象 + + // MySQL 高级函数 + // SQL_FUNCTION_MAP.put("bin", ""); // BIN(x) 返回 x 的二进制编码 + // SQL_FUNCTION_MAP.put("binary", ""); // BINARY(s) 将字符串 s 转换为二进制字符串 + SQL_FUNCTION_MAP.put("case", ""); // CASE 表示函数开始,END 表示函数结束。如果 condition1 成立,则返回 result1, 如果 condition2 成立,则返回 result2,当全部不成立则返回 result,而当有一个成立之后,后面的就不执行了。 + SQL_FUNCTION_MAP.put("cast", ""); // CAST(x AS type) 转换数据类型 + SQL_FUNCTION_MAP.put("coalesce", ""); // COALESCE(expr1, expr2, ...., expr_n) 返回参数中的第一个非空表达式(从左向右) + // SQL_FUNCTION_MAP.put("conv", ""); // CONV(x,f1,f2) 返回 f1 进制数变成 f2 进制数 + // SQL_FUNCTION_MAP.put("convert", ""); // CONVERT(s, cs) 函数将字符串 s 的字符集变成 cs + SQL_FUNCTION_MAP.put("if", ""); // IF(expr,v1,v2) 如果表达式 expr 成立,返回结果 v1;否则,返回结果 v2。 + SQL_FUNCTION_MAP.put("ifnull", ""); // IFNULL(v1,v2) 如果 v1 的值不为 NULL,则返回 v1,否则返回 v2。 + SQL_FUNCTION_MAP.put("isnull", ""); // ISNULL(expression) 判断表达式是否为 NULL + SQL_FUNCTION_MAP.put("nullif", ""); // NULLIF(expr1, expr2) 比较两个字符串,如果字符串 expr1 与 expr2 相等 返回 NULL,否则返回 expr1 + SQL_FUNCTION_MAP.put("group_concat", ""); // GROUP_CONCAT([DISTINCT], s1, s2...) 聚合拼接字符串 + SQL_FUNCTION_MAP.put("match", ""); // MATCH (name,tag) AGAINST ('a b' IN NATURAL LANGUAGE MODE) 全文检索 + + + + + + //clickhouse 字符串函数 注释的函数表示返回的格式暂时不支持,如:返回数组 ,同时包含因版本不同 clickhosue不支持的函数,版本 + SQL_FUNCTION_MAP.put("empty", ""); // empty(s) 对于空字符串s返回1,对于非空字符串返回0 + SQL_FUNCTION_MAP.put("notEmpty", ""); //notEmpty(s) 对于空字符串返回0,对于非空字符串返回1。 + SQL_FUNCTION_MAP.put("lengthUTF8", ""); //假定字符串以UTF-8编码组成的文本,返回此字符串的Unicode字符长度。如果传入的字符串不是UTF-8编码,则函数可能返回一个预期外的值 + SQL_FUNCTION_MAP.put("lcase", ""); //将字符串中的ASCII转换为小写 + SQL_FUNCTION_MAP.put("ucase", ""); //将字符串中的ASCII转换为大写。 + SQL_FUNCTION_MAP.put("lowerUTF8", ""); //将字符串转换为小写,函数假设字符串是以UTF-8编码文本的字符集。 + SQL_FUNCTION_MAP.put("upperUTF8", ""); //将字符串转换为大写,函数假设字符串是以UTF-8编码文本的字符集。 + SQL_FUNCTION_MAP.put("isValidUTF8", ""); // 检查字符串是否为有效的UTF-8编码,是则返回1,否则返回0。 + SQL_FUNCTION_MAP.put("toValidUTF8", "");//用�(U+FFFD)字符替换无效的UTF-8字符。所有连续的无效字符都会被替换为一个替换字符。 + SQL_FUNCTION_MAP.put("reverseUTF8", "");//以Unicode字符为单位反转UTF-8编码的字符串。 + SQL_FUNCTION_MAP.put("concatAssumeInjective", ""); // concatAssumeInjective(s1, s2, …) 与concat相同,区别在于,你需要保证concat(s1, s2, s3) -> s4是单射的,它将用于GROUP BY的优化。 + SQL_FUNCTION_MAP.put("substringUTF8", ""); // substringUTF8(s,offset,length)¶ 与’substring’相同,但其操作单位为Unicode字符,函数假设字符串是以UTF-8进行编码的文本。如果不是则可能返回一个预期外的结果(不会抛出异常)。 + SQL_FUNCTION_MAP.put("appendTrailingCharIfAbsent", ""); // appendTrailingCharIfAbsent(s,c) 如果’s’字符串非空并且末尾不包含’c’字符,则将’c’字符附加到末尾 + SQL_FUNCTION_MAP.put("convertCharset", ""); // convertCharset(s,from,to) 返回从’from’中的编码转换为’to’中的编码的字符串’s’。 + SQL_FUNCTION_MAP.put("base64Encode", ""); // base64Encode(s) 将字符串’s’编码成base64 + SQL_FUNCTION_MAP.put("base64Decode", ""); //base64Decode(s) 使用base64将字符串解码成原始字符串。如果失败则抛出异常。 + SQL_FUNCTION_MAP.put("tryBase64Decode", ""); //tryBase64Decode(s) 使用base64将字符串解码成原始字符串。但如果出现错误,将返回空字符串。 + SQL_FUNCTION_MAP.put("endsWith", ""); //endsWith(s,后缀) 返回是否以指定的后缀结尾。如果字符串以指定的后缀结束,则返回1,否则返回0。 + SQL_FUNCTION_MAP.put("startsWith", ""); //startsWith(s,前缀) 返回是否以指定的前缀开头。如果字符串以指定的前缀开头,则返回1,否则返回0。 + SQL_FUNCTION_MAP.put("trimLeft", ""); //trimLeft(s)返回一个字符串,用于删除左侧的空白字符。 + SQL_FUNCTION_MAP.put("trimRight", ""); //trimRight(s) 返回一个字符串,用于删除右侧的空白字符。 + SQL_FUNCTION_MAP.put("trimBoth", ""); //trimBoth(s),用于删除任一侧的空白字符 + SQL_FUNCTION_MAP.put("extractAllGroups", ""); //extractAllGroups(text, regexp) 从正则表达式匹配的非重叠子字符串中提取所有组 + // SQL_FUNCTION_MAP.put("leftPad", ""); //leftPad('string', 'length'[, 'pad_string']) 用空格或指定的字符串从左边填充当前字符串(如果需要,可以多次),直到得到的字符串达到给定的长度 + // SQL_FUNCTION_MAP.put("leftPadUTF8", ""); //leftPadUTF8('string','length'[, 'pad_string']) 用空格或指定的字符串从左边填充当前字符串(如果需要,可以多次),直到得到的字符串达到给定的长度 + // SQL_FUNCTION_MAP.put("rightPad", ""); // rightPad('string', 'length'[, 'pad_string']) 用空格或指定的字符串(如果需要,可以多次)从右边填充当前字符串,直到得到的字符串达到给定的长度 + // SQL_FUNCTION_MAP.put("rightPadUTF8", "");// rightPadUTF8('string','length'[, 'pad_string']) 用空格或指定的字符串(如果需要,可以多次)从右边填充当前字符串,直到得到的字符串达到给定的长度。 + SQL_FUNCTION_MAP.put("normalizeQuery", ""); //normalizeQuery(x) 用占位符替换文字、文字序列和复杂的别名。 + SQL_FUNCTION_MAP.put("normalizedQueryHash", ""); //normalizedQueryHash(x) 为类似查询返回相同的64位散列值,但不包含文字值。有助于对查询日志进行分析 + SQL_FUNCTION_MAP.put("positionUTF8", ""); // positionUTF8(s, needle[, start_pos]) 返回在字符串中找到的子字符串的位置(以Unicode点表示),从1开始。 + SQL_FUNCTION_MAP.put("multiSearchFirstIndex", ""); //multiSearchFirstIndex(s, [needle1, needle2, …, needlen]) 返回字符串s中最左边的needlei的索引i(从1开始),否则返回0 + SQL_FUNCTION_MAP.put("multiSearchAny", ""); // multiSearchAny(s, [needle1, needle2, …, needlen])如果至少有一个字符串needlei匹配字符串s,则返回1,否则返回0。 + SQL_FUNCTION_MAP.put("match", ""); //match(s, pattern) 检查字符串是否与模式正则表达式匹配。re2正则表达式。re2正则表达式的语法比Perl正则表达式的语法更有局限性。 + SQL_FUNCTION_MAP.put("multiMatchAny", ""); //multiMatchAny(s, [pattern1, pattern2, …, patternn]) 与match相同,但是如果没有匹配的正则表达式返回0,如果有匹配的模式返回1 + SQL_FUNCTION_MAP.put("multiMatchAnyIndex", ""); //multiMatchAnyIndex(s, [pattern1, pattern2, …, patternn]) 与multiMatchAny相同,但返回与干堆匹配的任何索引 + SQL_FUNCTION_MAP.put("extract", ""); // extract(s, pattern) 使用正则表达式提取字符串的片段 + SQL_FUNCTION_MAP.put("extractAll", ""); //extractAll(s, pattern) 使用正则表达式提取字符串的所有片段 + SQL_FUNCTION_MAP.put("like", ""); //like(s, pattern) 检查字符串是否与简单正则表达式匹配 + SQL_FUNCTION_MAP.put("notLike", "");// 和‘like’是一样的,但是是否定的 + SQL_FUNCTION_MAP.put("countSubstrings", ""); //countSubstrings(s, needle[, start_pos])返回子字符串出现的次数 + SQL_FUNCTION_MAP.put("countMatches", ""); //返回干s中的正则表达式匹配数。countMatches(s, pattern) + SQL_FUNCTION_MAP.put("replaceOne", ""); //replaceOne(s, pattern, replacement)将' s '中的' pattern '子串的第一个出现替换为' replacement '子串。 + + SQL_FUNCTION_MAP.put("replaceAll", ""); //replaceAll(s, pattern, replacement)/用' replacement '子串替换' s '中所有出现的' pattern '子串 + SQL_FUNCTION_MAP.put("replaceRegexpOne", ""); //replaceRegexpOne(s, pattern, replacement)使用' pattern '正则表达式进行替换 + SQL_FUNCTION_MAP.put("replaceRegexpAll", ""); //replaceRegexpAll(s, pattern, replacement) + SQL_FUNCTION_MAP.put("regexpQuoteMeta", ""); //regexpQuoteMeta(s)该函数在字符串中某些预定义字符之前添加一个反斜杠 + + //clickhouse日期函数 + SQL_FUNCTION_MAP.put("toYear", ""); //将Date或DateTime转换为包含年份编号(AD)的UInt16类型的数字。 + SQL_FUNCTION_MAP.put("toQuarter", ""); //将Date或DateTime转换为包含季度编号的UInt8类型的数字。 + SQL_FUNCTION_MAP.put("toMonth", ""); //Date或DateTime转换为包含月份编号(1-12)的UInt8类型的数字。 + SQL_FUNCTION_MAP.put("toDayOfYear", ""); //将Date或DateTime转换为包含一年中的某一天的编号的UInt16(1-366)类型的数字。 + SQL_FUNCTION_MAP.put("toDayOfMonth", "");//将Date或DateTime转换为包含一月中的某一天的编号的UInt8(1-31)类型的数字。 + SQL_FUNCTION_MAP.put("toDayOfWeek", ""); //将Date或DateTime转换为包含一周中的某一天的编号的UInt8(周一是1, 周日是7)类型的数字。 + SQL_FUNCTION_MAP.put("toHour", ""); //将DateTime转换为包含24小时制(0-23)小时数的UInt8数字。 + SQL_FUNCTION_MAP.put("toMinute", ""); //将DateTime转换为包含一小时中分钟数(0-59)的UInt8数字。 + SQL_FUNCTION_MAP.put("toSecond", ""); //将DateTime转换为包含一分钟中秒数(0-59)的UInt8数字。 + SQL_FUNCTION_MAP.put("toUnixTimestamp", ""); // 对于DateTime参数:将值转换为UInt32类型的数字-Unix时间戳 + SQL_FUNCTION_MAP.put("toStartOfYear", ""); //将Date或DateTime向前取整到本年的第一天。 + SQL_FUNCTION_MAP.put("toStartOfISOYear", ""); // 将Date或DateTime向前取整到ISO本年的第一天。 + SQL_FUNCTION_MAP.put("toStartOfQuarter", "");//将Date或DateTime向前取整到本季度的第一天。 + SQL_FUNCTION_MAP.put("toStartOfMonth", ""); //将Date或DateTime向前取整到本月的第一天。 + SQL_FUNCTION_MAP.put("toMonday", ""); //将Date或DateTime向前取整到本周的星期 + SQL_FUNCTION_MAP.put("toStartOfWeek", ""); //按mode将Date或DateTime向前取整到最近的星期日或星期一。 + SQL_FUNCTION_MAP.put("toStartOfDay", ""); //将DateTime向前取整到今天的开始。 + SQL_FUNCTION_MAP.put("toStartOfHour", ""); //将DateTime向前取整到当前小时的开始。 + SQL_FUNCTION_MAP.put("toStartOfMinute", ""); //将DateTime向前取整到当前分钟的开始。 + SQL_FUNCTION_MAP.put("toStartOfSecond", ""); //将DateTime向前取整到当前秒数的开始。 + SQL_FUNCTION_MAP.put("toStartOfFiveMinute", "");//将DateTime以五分钟为单位向前取整到最接近的时间点。 + SQL_FUNCTION_MAP.put("toStartOfTenMinutes", ""); //将DateTime以十分钟为单位向前取整到最接近的时间点。 + SQL_FUNCTION_MAP.put("toStartOfFifteenMinutes", ""); //将DateTime以十五分钟为单位向前取整到最接近的时间点。 + SQL_FUNCTION_MAP.put("toStartOfInterval", ""); // + SQL_FUNCTION_MAP.put("toTime", ""); //将DateTime中的日期转换为一个固定的日期,同时保留时间部分。 + SQL_FUNCTION_MAP.put("toISOYear", ""); //将Date或DateTime转换为包含ISO年份的UInt16类型的编号。 + SQL_FUNCTION_MAP.put("toISOWeek", ""); // + SQL_FUNCTION_MAP.put("toWeek", "");// 返回Date或DateTime的周数。 + SQL_FUNCTION_MAP.put("toYearWeek", ""); //返回年和周的日期 + SQL_FUNCTION_MAP.put("date_trunc", ""); //截断日期和时间数据到日期的指定部分 + SQL_FUNCTION_MAP.put("date_diff", ""); //回两个日期或带有时间值的日期之间的差值。 + + SQL_FUNCTION_MAP.put("yesterday", ""); //不接受任何参数并在请求执行时的某一刻返回昨天的日期(Date)。 + SQL_FUNCTION_MAP.put("today", ""); //不接受任何参数并在请求执行时的某一刻返回当前日期(Date)。 + SQL_FUNCTION_MAP.put("timeSlot", ""); //将时间向前取整半小时。 + SQL_FUNCTION_MAP.put("toYYYYMM", ""); // + SQL_FUNCTION_MAP.put("toYYYYMMDD", "");// + SQL_FUNCTION_MAP.put("toYYYYMMDDhhmmss", ""); // + SQL_FUNCTION_MAP.put("addYears", ""); // Function adds a Date/DateTime interval to a Date/DateTime and then return the Date/DateTime + SQL_FUNCTION_MAP.put("addMonths", ""); //同上 + SQL_FUNCTION_MAP.put("addWeeks", ""); //同上 + SQL_FUNCTION_MAP.put("addDays", ""); //同上 + SQL_FUNCTION_MAP.put("addHours", ""); //同上 + SQL_FUNCTION_MAP.put("addMinutes", "");//同上 + SQL_FUNCTION_MAP.put("addSeconds", ""); //同上 + SQL_FUNCTION_MAP.put("addQuarters", ""); //同上 + SQL_FUNCTION_MAP.put("subtractYears", ""); //Function subtract a Date/DateTime interval to a Date/DateTime and then return the Date/DateTime + SQL_FUNCTION_MAP.put("subtractMonths", ""); //同上 + SQL_FUNCTION_MAP.put("subtractWeeks", ""); //同上 + SQL_FUNCTION_MAP.put("subtractDays", ""); //同上 + SQL_FUNCTION_MAP.put("subtractours", "");//同上 + SQL_FUNCTION_MAP.put("subtractMinutes", ""); //同上 + SQL_FUNCTION_MAP.put("subtractSeconds", ""); //同上 + SQL_FUNCTION_MAP.put("subtractQuarters", ""); //同上 + SQL_FUNCTION_MAP.put("formatDateTime", ""); //函数根据给定的格式字符串来格式化时间 + SQL_FUNCTION_MAP.put("timestamp_add", ""); //使用提供的日期或日期时间值添加指定的时间值。 + SQL_FUNCTION_MAP.put("timestamp_sub", ""); //从提供的日期或带时间的日期中减去时间间隔。 + + //clickhouse json函数 + SQL_FUNCTION_MAP.put("visitParamHas", ""); //visitParamHas(params, name)检查是否存在«name»名称的字段 + SQL_FUNCTION_MAP.put("visitParamExtractUInt", ""); //visitParamExtractUInt(params, name)将名为«name»的字段的值解析成UInt64。 + SQL_FUNCTION_MAP.put("visitParamExtractInt", ""); //与visitParamExtractUInt相同,但返回Int64。 + SQL_FUNCTION_MAP.put("visitParamExtractFloat", ""); //与visitParamExtractUInt相同,但返回Float64。 + SQL_FUNCTION_MAP.put("visitParamExtractBool", "");//解析true/false值。其结果是UInt8类型的。 + SQL_FUNCTION_MAP.put("visitParamExtractRaw", ""); //返回字段的值,包含空格符。 + SQL_FUNCTION_MAP.put("visitParamExtractString", ""); //使用双引号解析字符串。这个值没有进行转义。如果转义失败,它将返回一个空白字符串。 + SQL_FUNCTION_MAP.put("JSONHas", ""); //如果JSON中存在该值,则返回1。 + SQL_FUNCTION_MAP.put("JSONLength", ""); //返回JSON数组或JSON对象的长度。 + SQL_FUNCTION_MAP.put("JSONType", ""); //返回JSON值的类型。 + SQL_FUNCTION_MAP.put("JSONExtractUInt", ""); //解析JSON并提取值。这些函数类似于visitParam*函数。 + SQL_FUNCTION_MAP.put("JSONExtractInt", ""); // + SQL_FUNCTION_MAP.put("JSONExtractFloat", ""); // + SQL_FUNCTION_MAP.put("JSONExtractBool", ""); // + SQL_FUNCTION_MAP.put("JSONExtractString", ""); //解析JSON并提取字符串。此函数类似于visitParamExtractString函数。 + SQL_FUNCTION_MAP.put("JSONExtract", "");//解析JSON并提取给定ClickHouse数据类型的值。 + SQL_FUNCTION_MAP.put("JSONExtractKeysAndValues", ""); //从JSON中解析键值对,其中值是给定的ClickHouse数据类型 + SQL_FUNCTION_MAP.put("JSONExtractRaw", ""); //返回JSON的部分。 + SQL_FUNCTION_MAP.put("toJSONString", ""); // + + //clickhouse 类型转换函数 + SQL_FUNCTION_MAP.put("toInt8", ""); //toInt8(expr) 转换一个输入值为Int类型 + SQL_FUNCTION_MAP.put("toInt16", ""); + SQL_FUNCTION_MAP.put("toInt32", ""); + SQL_FUNCTION_MAP.put("toInt64", ""); + SQL_FUNCTION_MAP.put("toInt8OrZero", ""); //toInt(8|16|32|64)OrZero 这个函数需要一个字符类型的入参,然后尝试把它转为Int (8 | 16 | 32 | 64),如果转换失败直接返回0。 + SQL_FUNCTION_MAP.put("toInt16OrZero", ""); + SQL_FUNCTION_MAP.put("toInt32OrZero", ""); + SQL_FUNCTION_MAP.put("toInt64OrZero", ""); + SQL_FUNCTION_MAP.put("toInt8OrNull", "");//toInt(8|16|32|64)OrNull 这个函数需要一个字符类型的入参,然后尝试把它转为Int (8 | 16 | 32 | 64),如果转换失败直接返回NULL + SQL_FUNCTION_MAP.put("toInt16OrNull", ""); + SQL_FUNCTION_MAP.put("toInt32OrNull", ""); + SQL_FUNCTION_MAP.put("toInt64OrNull", ""); + SQL_FUNCTION_MAP.put("toUInt8", ""); //toInt8(expr) 转换一个输入值为Int类型 + SQL_FUNCTION_MAP.put("toUInt16", ""); + SQL_FUNCTION_MAP.put("toUInt32", ""); + SQL_FUNCTION_MAP.put("toUInt64", ""); + SQL_FUNCTION_MAP.put("toUInt8OrZero", ""); //toInt(8|16|32|64)OrZero 这个函数需要一个字符类型的入参,然后尝试把它转为Int (8 | 16 | 32 | 64),如果转换失败直接返回0。 + SQL_FUNCTION_MAP.put("toUInt16OrZero", ""); + SQL_FUNCTION_MAP.put("toUInt32OrZero", ""); + SQL_FUNCTION_MAP.put("toUInt64OrZero", ""); + SQL_FUNCTION_MAP.put("toUInt8OrNull", "");//toInt(8|16|32|64)OrNull 这个函数需要一个字符类型的入参,然后尝试把它转为Int (8 | 16 | 32 | 64),如果转换失败直接返回NULL + SQL_FUNCTION_MAP.put("toUInt16OrNull", ""); + SQL_FUNCTION_MAP.put("toUInt32OrNull", ""); + SQL_FUNCTION_MAP.put("toUInt64OrNull", ""); + + SQL_FUNCTION_MAP.put("toFloat32", ""); + SQL_FUNCTION_MAP.put("toFloat64", ""); + SQL_FUNCTION_MAP.put("toFloat32OrZero", ""); + SQL_FUNCTION_MAP.put("toFloat64OrZero", ""); + SQL_FUNCTION_MAP.put("toFloat32OrNull", ""); + SQL_FUNCTION_MAP.put("toFloat64OrNull", ""); + + SQL_FUNCTION_MAP.put("toDate", ""); // + SQL_FUNCTION_MAP.put("toDateOrZero", ""); //toInt16(expr) + SQL_FUNCTION_MAP.put("toDateOrNull", ""); //toInt32(expr) + SQL_FUNCTION_MAP.put("toDateTimeOrZero", ""); //toInt64(expr) + SQL_FUNCTION_MAP.put("toDateTimeOrNull", ""); //toInt(8|16|32|64)OrZero 这个函数需要一个字符类型的入参,然后尝试把它转为Int (8 | 16 | 32 | 64),如果转换失败直接返回0。 + + SQL_FUNCTION_MAP.put("toDecimal32", ""); + SQL_FUNCTION_MAP.put("toFixedString", ""); // 将String类型的参数转换为FixedString(N)类型的值 + SQL_FUNCTION_MAP.put("toStringCutToZero", ""); // 接受String或FixedString参数,返回String,其内容在找到的第一个零字节处被截断。 + SQL_FUNCTION_MAP.put("toDecimal256", ""); + SQL_FUNCTION_MAP.put("toDecimal32OrNull", ""); + SQL_FUNCTION_MAP.put("toDecimal64OrNull", ""); + SQL_FUNCTION_MAP.put("toDecimal128OrNull", ""); + SQL_FUNCTION_MAP.put("toDecimal256OrNull", ""); + SQL_FUNCTION_MAP.put("toDecimal32OrZero", ""); + SQL_FUNCTION_MAP.put("toDecimal64OrZero", ""); + SQL_FUNCTION_MAP.put("toDecimal128OrZero", ""); + SQL_FUNCTION_MAP.put("toDecimal256OrZero", ""); + + + SQL_FUNCTION_MAP.put("toIntervalSecond", ""); //把一个数值类型的值转换为Interval类型的数据。 + SQL_FUNCTION_MAP.put("toIntervalMinute", ""); + SQL_FUNCTION_MAP.put("toIntervalHour", ""); + SQL_FUNCTION_MAP.put("toIntervalDay", ""); + SQL_FUNCTION_MAP.put("toIntervalWeek", ""); + SQL_FUNCTION_MAP.put("toIntervalMonth", ""); + SQL_FUNCTION_MAP.put("toIntervalQuarter", ""); + SQL_FUNCTION_MAP.put("toIntervalYear", ""); + SQL_FUNCTION_MAP.put("parseDateTimeBestEffort", ""); //把String类型的时间日期转换为DateTime数据类型。 + SQL_FUNCTION_MAP.put("parseDateTimeBestEffortOrNull", ""); + SQL_FUNCTION_MAP.put("parseDateTimeBestEffortOrZero", ""); + SQL_FUNCTION_MAP.put("toLowCardinality", ""); + + + + ////clickhouse hash函数 + SQL_FUNCTION_MAP.put("halfMD5", ""); //计算字符串的MD5。然后获取结果的前8个字节并将它们作为UInt64(大端)返回 + SQL_FUNCTION_MAP.put("MD5", ""); //计算字符串的MD5并将结果放入FixedString(16)中返回 + + //clickhouse ip地址函数 + SQL_FUNCTION_MAP.put("IPv4NumToString", ""); //接受一个UInt32(大端)表示的IPv4的地址,返回相应IPv4的字符串表现形式,格式为A.B.C.D(以点分割的十进制数字)。 + SQL_FUNCTION_MAP.put("IPv4StringToNum", ""); //与IPv4NumToString函数相反。如果IPv4地址格式无效,则返回0。 + SQL_FUNCTION_MAP.put("IPv6NumToString", ""); //接受FixedString(16)类型的二进制格式的IPv6地址。以文本格式返回此地址的字符串。 + SQL_FUNCTION_MAP.put("IPv6StringToNum", ""); //与IPv6NumToString的相反。如果IPv6地址格式无效,则返回空字节字符串。 + SQL_FUNCTION_MAP.put("IPv4ToIPv6", ""); // 接受一个UInt32类型的IPv4地址,返回FixedString(16)类型的IPv6地址 + SQL_FUNCTION_MAP.put("cutIPv6", ""); //接受一个FixedString(16)类型的IPv6地址,返回一个String,这个String中包含了删除指定位之后的地址的文本格 + SQL_FUNCTION_MAP.put("toIPv4", ""); //IPv4StringToNum()的别名, + SQL_FUNCTION_MAP.put("toIPv6", ""); //IPv6StringToNum()的别名 + SQL_FUNCTION_MAP.put("isIPAddressInRange", ""); //确定一个IP地址是否包含在以CIDR符号表示的网络中 + + //clickhouse Nullable处理函数 + SQL_FUNCTION_MAP.put("isNull", ""); //检查参数是否为NULL。 + SQL_FUNCTION_MAP.put("isNotNull", ""); //检查参数是否不为 NULL. + SQL_FUNCTION_MAP.put("ifNull", ""); //如果第一个参数为«NULL»,则返回第二个参数的值。 + SQL_FUNCTION_MAP.put("assumeNotNull", ""); //将可为空类型的值转换为非Nullable类型的值。 + SQL_FUNCTION_MAP.put("toNullable", ""); //将参数的类型转换为Nullable。 + + //clickhouse UUID函数 + SQL_FUNCTION_MAP.put("generateUUIDv4", ""); // 生成一个UUID + SQL_FUNCTION_MAP.put("toUUID", ""); //toUUID(x) 将String类型的值转换为UUID类型的值。 + + //clickhouse 系统函数 + SQL_FUNCTION_MAP.put("hostName", ""); //hostName()回一个字符串,其中包含执行此函数的主机的名称。 + SQL_FUNCTION_MAP.put("getMacro", ""); //从服务器配置的宏部分获取指定值。 + SQL_FUNCTION_MAP.put("FQDN", "");//返回完全限定的域名。 + SQL_FUNCTION_MAP.put("basename", ""); //提取字符串最后一个斜杠或反斜杠之后的尾随部分 + SQL_FUNCTION_MAP.put("currentUser", ""); //返回当前用户的登录。在分布式查询的情况下,将返回用户的登录,即发起的查询 + SQL_FUNCTION_MAP.put("version", ""); //以字符串形式返回服务器版本。 + SQL_FUNCTION_MAP.put("uptime", "");//以秒为单位返回服务器的正常运行时间。 + + //clickhouse 数学函数 + SQL_FUNCTION_MAP.put("least", ""); //返回a和b中最小的值。 + SQL_FUNCTION_MAP.put("greatest", ""); //返回a和b的最大值。 + SQL_FUNCTION_MAP.put("plus", ""); //plus(a, b), a + b operator¶计算数值的总和。 + SQL_FUNCTION_MAP.put("minus", ""); //minus(a, b), a - b operator 计算数值之间的差,结果总是有符号的。 + SQL_FUNCTION_MAP.put("multiply", "");//multiply(a, b), a * b operator 计算数值的乘积 + SQL_FUNCTION_MAP.put("divide", ""); //divide(a, b), a / b operator 计算数值的商。结果类型始终是浮点类型 + SQL_FUNCTION_MAP.put("intDiv", ""); //intDiv(a,b)计算数值的商,向下舍入取整(按绝对值)。 + SQL_FUNCTION_MAP.put("intDivOrZero", ""); // intDivOrZero(a,b)与’intDiv’的不同之处在于它在除以零或将最小负数除以-1时返回零。 + SQL_FUNCTION_MAP.put("modulo", ""); //modulo(a, b), a % b operator 计算除法后的余数。 + SQL_FUNCTION_MAP.put("moduloOrZero", ""); //和modulo不同之处在于,除以0时结果返回0 + SQL_FUNCTION_MAP.put("negate", ""); //通过改变数值的符号位对数值取反,结果总是有符号 + SQL_FUNCTION_MAP.put("gcd", ""); //gcd(a,b) 返回数值的最大公约数。 + SQL_FUNCTION_MAP.put("lcm", ""); //lcm(a,b) 返回数值的最小公倍数 + SQL_FUNCTION_MAP.put("e", ""); //e() 返回一个接近数学常量e的Float64数字。 + SQL_FUNCTION_MAP.put("pi", ""); //pi() 返回一个接近数学常量π的Float64数字。 + SQL_FUNCTION_MAP.put("exp2", ""); //exp2(x)¶接受一个数值类型的参数并返回它的2的x次幂。 + SQL_FUNCTION_MAP.put("exp10", ""); //exp10(x)¶接受一个数值类型的参数并返回它的10的x次幂。 + SQL_FUNCTION_MAP.put("cbrt", ""); //cbrt(x) 接受一个数值类型的参数并返回它的立方根。 + SQL_FUNCTION_MAP.put("lgamma", ""); //lgamma(x) 返回x的绝对值的自然对数的伽玛函数。 + SQL_FUNCTION_MAP.put("tgamma", ""); //tgamma(x)¶返回x的伽玛函数。 + SQL_FUNCTION_MAP.put("intExp2", ""); //intExp2 接受一个数值类型的参数并返回它的2的x次幂(UInt64) + SQL_FUNCTION_MAP.put("intExp10", ""); //intExp10 接受一个数值类型的参数并返回它的10的x次幂(UInt64)。 + SQL_FUNCTION_MAP.put("cosh", ""); // cosh(x) + SQL_FUNCTION_MAP.put("cosh", ""); //cosh(x) + SQL_FUNCTION_MAP.put("sinh", ""); //sinh(x) + SQL_FUNCTION_MAP.put("asinh", ""); //asinh(x) + SQL_FUNCTION_MAP.put("atanh", ""); //atanh(x) + SQL_FUNCTION_MAP.put("atan2", ""); //atan2(y, x) + SQL_FUNCTION_MAP.put("hypot", ""); //hypot(x, y) + SQL_FUNCTION_MAP.put("log1p", ""); //log1p(x) + SQL_FUNCTION_MAP.put("trunc", ""); //和truncate一样 + SQL_FUNCTION_MAP.put("roundToExp2", ""); //接受一个数字。如果数字小于1,它返回0。 + SQL_FUNCTION_MAP.put("roundDuration", ""); //接受一个数字。如果数字小于1,它返回0。 + SQL_FUNCTION_MAP.put("roundAge", ""); // 接受一个数字。如果数字小于18,它返回0。 + SQL_FUNCTION_MAP.put("roundDown", ""); //接受一个数字并将其舍入到指定数组中的一个元素 + SQL_FUNCTION_MAP.put("bitAnd", ""); //bitAnd(a,b) + SQL_FUNCTION_MAP.put("bitOr", ""); //bitOr(a,b) } @@ -597,14 +1133,14 @@ public String getHavingString(boolean hasPrefix) { method = expression.substring(0, start); if (method.isEmpty() == false) { - if (FunctionsAndRaws.SQL_FUNCTION_MAP == null || FunctionsAndRaws.SQL_FUNCTION_MAP.isEmpty()) { + if (SQL_FUNCTION_MAP == null || SQL_FUNCTION_MAP.isEmpty()) { if (StringUtil.isName(method) == false) { throw new IllegalArgumentException("字符 " + method + " 不合法!" + "预编译模式下 @having:\"column?value;function(arg0,arg1,...)?value...\"" + " 中 function 必须符合小写英文单词的 SQL 函数名格式!"); } } - else if (FunctionsAndRaws.SQL_FUNCTION_MAP.containsKey(method) == false) { + else if (SQL_FUNCTION_MAP.containsKey(method) == false) { throw new IllegalArgumentException("字符 " + method + " 不合法!" + "预编译模式下 @column:\"column0,column1:alias;function0(arg0,arg1,...);function1(...):alias...\"" + " 中 function 必须符合小写英文单词的 SQL 函数名格式!且必须是后端允许调用的 SQL 函数!"); @@ -796,14 +1332,14 @@ public String getRawSQL(String key, Object value) throws Exception { + "对应的 " + key + ":value 中 value 类型只能为 String!"); } - String rawSQL = containRaw ? FunctionsAndRaws.RAW_MAP.get(value) : null; + String rawSQL = containRaw ? RAW_MAP.get(value) : null; if (containRaw) { if (rawSQL == null) { throw new UnsupportedOperationException("@raw:value 的 value 中 " + key + " 不合法!" + "对应的 " + key + ":value 中 value 值 " + value + " 未在后端 RAW_MAP 中配置 !"); } - if ("".equals(rawSQL)) { + if (rawSQL.isEmpty()) { return (String) value; } } @@ -865,7 +1401,7 @@ public String getColumnString(boolean inSQLJoin) throws Exception { for (String c : column) { if (containRaw) { // 由于 HashMap 对 key 做了 hash 处理,所以 get 比 containsValue 更快 - if ("".equals(FunctionsAndRaws.RAW_MAP.get(c)) || FunctionsAndRaws.RAW_MAP.containsValue(c)) { // newSQLConfig 提前处理好的 + if ("".equals(RAW_MAP.get(c)) || RAW_MAP.containsValue(c)) { // newSQLConfig 提前处理好的 //排除@raw中的值,以避免使用date_format(date,'%Y-%m-%d %H:%i:%s') 时,冒号的解析出错 //column.remove(c); continue; @@ -967,17 +1503,12 @@ public String getColumnString(boolean inSQLJoin) throws Exception { List raw = getRaw(); boolean containRaw = raw != null && raw.contains(KEY_COLUMN); - String expression; - String method = null; - //...;fun0(arg0,arg1,...):fun0;fun1(arg0,arg1,...):fun1;... for (int i = 0; i < keys.length; i++) { - - //fun(arg0,arg1,...) - expression = keys[i]; + String expression = keys[i]; //fun(arg0,arg1,...) if (containRaw) { // 由于 HashMap 对 key 做了 hash 处理,所以 get 比 containsValue 更快 - if ("".equals(FunctionsAndRaws.RAW_MAP.get(expression)) || FunctionsAndRaws.RAW_MAP.containsValue(expression)) { // newSQLConfig 提前处理好的 + if ("".equals(RAW_MAP.get(expression)) || RAW_MAP.containsValue(expression)) { // newSQLConfig 提前处理好的 continue; } @@ -996,12 +1527,12 @@ public String getColumnString(boolean inSQLJoin) throws Exception { throw new UnsupportedOperationException("@column:value 的 value 中字符串 " + expression + " 不合法!" + "不允许传超过 100 个字符的函数或表达式!请用 @raw 简化传参!"); } - keys[i] = getColumnPrase(expression); + keys[i] = getColumnPrase(expression, containRaw); } String c = StringUtil.getString(keys); c = c + (StringUtil.isEmpty(joinColumn, true) ? "" : ", " + joinColumn);//不能在这里改,后续还要用到: - return c; + return isMain() && isDistinct() ? PREFFIX_DISTINCT + c : c; default: throw new UnsupportedOperationException( "服务器内部错误:getColumnString 不支持 " + RequestMethod.getName(getMethod()) @@ -1016,16 +1547,30 @@ public String getColumnString(boolean inSQLJoin) throws Exception { * @param expression * @return */ - public String getColumnPrase(String expression) { + public String getColumnPrase(String expression, boolean containRaw) { String quote = getQuote(); int start = expression.indexOf('('); if (start < 0) { //没有函数 ,可能是字段,也可能是 DISTINCT xx - String cks[] = parseArgsSplitWithComma(expression, true); + String[] cks = parseArgsSplitWithComma(expression, true, containRaw); expression = StringUtil.getString(cks); - } else { + } else { // FIXME 用括号断开? 如果少的话,用关键词加括号断开,例如 )OVER( 和 )AGAINST( + // 窗口函数 rank() OVER (PARTITION BY id ORDER BY userId ASC) + // 全文索引 math(name,tag) AGAINST ('a b +c -d' IN NATURALE LANGUAGE MODE) // IN BOOLEAN MODE + //有函数,但不是窗口函数 - if (expression.indexOf("OVER") < 0) { + int overIndex = expression.indexOf(") OVER ("); + int againstIndex = expression.indexOf(") AGAINST ("); + boolean containOver = overIndex > 0 && overIndex < expression.length() - ") OVER (".length(); + boolean containAgainst = againstIndex > 0 && againstIndex < expression.length() - ") AGAINST (".length(); + + if (containOver && containAgainst) { + throw new IllegalArgumentException("字符 " + expression + " 不合法!" + + "预编译模式下 @column:\"column0,column1:alias;function0(arg0,arg1,...);function1(...):alias...\"" + + " 中 function 必须符合小写英文单词的 SQL 函数名格式!不能同时存在窗口函数关键词 OVER 和全文索引关键词 AGAINST!"); + } + + if (containOver == false && containAgainst == false) { int end = expression.lastIndexOf(")"); if (start >= end) { throw new IllegalArgumentException("字符 " + expression + " 不合法!" @@ -1033,13 +1578,13 @@ public String getColumnPrase(String expression) { } String fun = expression.substring(0, start); if (fun.isEmpty() == false) { - if (FunctionsAndRaws.SQL_FUNCTION_MAP == null || FunctionsAndRaws.SQL_FUNCTION_MAP.isEmpty()) { + if (SQL_FUNCTION_MAP == null || SQL_FUNCTION_MAP.isEmpty()) { if (StringUtil.isName(fun) == false) { throw new IllegalArgumentException("字符 " + fun + " 不合法!" + "预编译模式下 @column:\"column0,column1:alias;function0(arg0,arg1,...);function1(...):alias...\"" + " 中 function 必须符合小写英文单词的 SQL 函数名格式!"); } - } else if (FunctionsAndRaws.SQL_FUNCTION_MAP.containsKey(fun) == false) { + } else if (SQL_FUNCTION_MAP.containsKey(fun) == false) { throw new IllegalArgumentException("字符 " + fun + " 不合法!" + "预编译模式下 @column:\"column0,column1:alias;function0(arg0,arg1,...);function1(...):alias...\"" + " 中 function 必须符合小写英文单词的 SQL 函数名格式!且必须是后端允许调用的 SQL 函数!"); @@ -1047,8 +1592,13 @@ public String getColumnPrase(String expression) { } String s = expression.substring(start + 1, end); + boolean distinct = s.startsWith(PREFFIX_DISTINCT); + if (distinct) { + s = s.substring(PREFFIX_DISTINCT.length()); + } + // 解析函数内的参数 - String ckeys[] = parseArgsSplitWithComma(s, false); + String ckeys[] = parseArgsSplitWithComma(s, false, containRaw); String suffix = expression.substring(end + 1, expression.length()); //:contactCount int index = suffix.lastIndexOf(":"); @@ -1066,14 +1616,14 @@ public String getColumnPrase(String expression) { + " 中 ?value 必须符合正则表达式 " + PATTERN_RANGE + " 且不包含连续减号 -- 或注释符 /* !不允许多余的空格!"); } - String origin = fun + "(" + StringUtil.getString(ckeys) + ")" + suffix; + String origin = fun + "(" + (distinct ? PREFFIX_DISTINCT : "") + StringUtil.getString(ckeys) + ")" + suffix; expression = origin + (StringUtil.isEmpty(alias, true) ? "" : " AS " + quote + alias + quote); } else { //是窗口函数 fun(arg0,agr1) OVER (agr0 agr1 ...) - int overindex = expression.indexOf("OVER"); // OVER 的位置 - String s1 = expression.substring(0, overindex); // OVER 前半部分 - String s2 = expression.substring(overindex); // OVER 后半部分 + int keyIndex = containOver ? overIndex : againstIndex; + String s1 = expression.substring(0, keyIndex + 1); // OVER 前半部分 + String s2 = expression.substring(keyIndex + 1); // OVER 后半部分 int index1 = s1.indexOf("("); // 函数 "(" 的起始位置 String fun = s1.substring(0, index1); // 函数名称 @@ -1084,13 +1634,13 @@ public String getColumnPrase(String expression) { + "@column:value 中 value 里的 SQL函数必须为 function(arg0,arg1,...) 这种格式!"); } if (fun.isEmpty() == false) { - if (FunctionsAndRaws.SQL_FUNCTION_MAP == null || FunctionsAndRaws.SQL_FUNCTION_MAP.isEmpty()) { + if (SQL_FUNCTION_MAP == null || SQL_FUNCTION_MAP.isEmpty()) { if (StringUtil.isName(fun) == false) { throw new IllegalArgumentException("字符 " + fun + " 不合法!" + "预编译模式下 @column:\"column0,column1:alias;function0(arg0,arg1,...);function1(...):alias...\"" + " 中 function 必须符合小写英文单词的 SQL 函数名格式!"); } - } else if (FunctionsAndRaws.SQL_FUNCTION_MAP.containsKey(fun) == false) { + } else if (SQL_FUNCTION_MAP.containsKey(fun) == false) { throw new IllegalArgumentException("字符 " + fun + " 不合法!" + "预编译模式下 @column:\"column0,column1:alias;function0(arg0,arg1,...);function1(...):alias...\"" + " 中 function 必须符合小写英文单词的 SQL 函数名格式!且必须是后端允许调用的 SQL 函数!"); @@ -1098,18 +1648,18 @@ public String getColumnPrase(String expression) { } // 获取前半部分函数的参数解析 fun(arg0,agr1) - String agrsString1[] = parseArgsSplitWithComma(s1.substring(index1 + 1, s1.lastIndexOf(")")), false); + String agrsString1[] = parseArgsSplitWithComma(s1.substring(index1 + 1, s1.lastIndexOf(")")), false, containRaw); int index2 = s2.indexOf("("); // 后半部分 “(”的起始位置 String argString2 = s2.substring(index2 + 1, end); // 后半部分的参数 // 别名 - String alias = s2.lastIndexOf(":") < 0 ? null : s2.substring(s2.lastIndexOf(":") + 1); + String alias = s2.lastIndexOf(":") < s2.lastIndexOf(")") ? null : s2.substring(s2.lastIndexOf(":") + 1); // 获取后半部分的参数解析 (agr0 agr1 ...) - String argsString2[] = parseArgsSplitWithComma(argString2,false); - expression = fun + "(" + StringUtil.getString(agrsString1) + ")" + " OVER " + "(" + StringUtil.getString(argsString2) + ")" + (StringUtil.isEmpty(alias, true) ? "" : " AS " + quote + alias + quote); } + String argsString2[] = parseArgsSplitWithComma(argString2, false, containRaw); + expression = fun + "(" + StringUtil.getString(agrsString1) + (containOver ? ") OVER (" : ") AGAINST (") + StringUtil.getString(argsString2) + ")" + (StringUtil.isEmpty(alias, true) ? "" : " AS " + quote + alias + quote); } } + return expression; - } /** @@ -1119,7 +1669,7 @@ public String getColumnPrase(String expression) { * @param isColumn true:不是函数参数。false:是函数参数 * @return */ - private String[] parseArgsSplitWithComma(String param, boolean isColumn) { + private String[] parseArgsSplitWithComma(String param, boolean isColumn, boolean containRaw) { // 以"," 分割参数 String quote = getQuote(); String tableAlias = getAliasWithQuote(); @@ -1129,62 +1679,68 @@ private String[] parseArgsSplitWithComma(String param, boolean isColumn) { String alias; int index; for (int i = 0; i < ckeys.length; i++) { + String ck = ckeys[i]; + // 如果参数包含 "'" ,解析字符串 - if (ckeys[i].contains("'")) { + if (ck.contains("'")) { int count = 0; - for (int j = 0; j < ckeys[i].length(); j++) { - if (ckeys[i].charAt(j) == '\'') count++; + for (int j = 0; j < ck.length(); j++) { + if (ck.charAt(j) == '\'') count++; } + // FIXME 把 `column` 和 '2 values with [ / : ] ..' 按引号位置分割才能满足全文索引、窗口函数的需要 // 排除字符串中参数中包含 ' 的情况和不以' 开头和结尾的情况,同时排除 cast('s' as ...) 以空格分隔的参数中包含字符串的情况 - if (count != 2 || !(ckeys[i].startsWith("'") && ckeys[i].endsWith("'"))) { - throw new IllegalArgumentException("字符串 " + ckeys[i] + " 不合法!" + if (count != 2 || !(ck.startsWith("'") && ck.endsWith("'"))) { + throw new IllegalArgumentException("字符串 " + ck + " 不合法!" + "预编译模式下 @column:\"column0,column1:alias;function0(arg0,arg1,...);function1(...):alias...\"" + " 中字符串参数不合法,必须以 ' 开头, ' 结尾,字符串中不能包含 ' "); } //sql 注入判断 判断 - origin = (ckeys[i].substring(1, ckeys[i].length() - 1)); + origin = (ck.substring(1, ck.length() - 1)); if (origin.contains("--") || PATTERN_STRING.matcher(origin).matches() == true) { - throw new IllegalArgumentException("字符 " + ckeys[i] + " 不合法!" + throw new IllegalArgumentException("字符 " + ck + " 不合法!" + "预编译模式下 @column:\"column0,column1:alias;function0(arg0,arg1,...);function1(...):alias...\"" + " 中所有字符串 arg 都必须不符合正则表达式 " + PATTERN_STRING + " 且不包含连续减号 -- !"); } // 1.字符串不是字段也没有别名,所以不解析别名 2. 是字符串,进行预编译,使用getValue() ,对字符串进行截取 - ckeys[i] = getValue(ckeys[i].substring(1, ckeys[i].length() - 1)).toString(); - - } else { + ckeys[i] = getValue(ck.substring(1, ck.length() - 1)).toString(); + } + else { // 参数不包含",",即不是字符串 // 解析参数:1. 字段 ,2. 是以空格分隔的参数 eg: cast(now() as date) - index = ckeys[i].lastIndexOf(":"); //StringUtil.split返回数组中,子项不会有null - origin = index < 0 ? ckeys[i] : ckeys[i].substring(0, index); //获取 : 之前的 - alias = index < 0 ? null : ckeys[i].substring(index + 1); + index = isColumn ? ck.lastIndexOf(":") : -1; //StringUtil.split返回数组中,子项不会有null + origin = index < 0 ? ck : ck.substring(0, index); //获取 : 之前的 + alias = index < 0 ? null : ck.substring(index + 1); if (isPrepared()) { if (isColumn) { if (StringUtil.isName(origin) == false || (alias != null && StringUtil.isName(alias) == false)) { - throw new IllegalArgumentException("字符 " + ckeys[i] + " 不合法!" + throw new IllegalArgumentException("字符 " + ck + " 不合法!" + "预编译模式下 @column:value 中 value里面用 , 分割的每一项" + " column:alias 中 column 必须是1个单词!如果有alias,则alias也必须为1个单词!" + "关键字必须全大写,且以空格分隔的参数,空格必须只有 1 个!其它情况不允许空格!"); } } else { if (origin.startsWith("_") || origin.contains("--") || PATTERN_FUNCTION.matcher(origin).matches() == false) { - throw new IllegalArgumentException("字符 " + ckeys[i] + " 不合法!" + throw new IllegalArgumentException("字符 " + ck + " 不合法!" + "预编译模式下 @column:\"column0,column1:alias;function0(arg0,arg1,...);function1(...):alias...\"" + " 中所有 arg 都必须是1个不以 _ 开头的单词 或者符合正则表达式 " + PATTERN_FUNCTION + " 且不包含连续减号 -- !DISTINCT 必须全大写,且后面必须有且只有 1 个空格!其它情况不允许空格!"); } } } // 以空格分割参数 - String mkes[] = StringUtil.split(ckeys[i], " ", true); + String[] mkes = containRaw ? StringUtil.split(ck, " ", true) : new String[]{ ck }; - boolean isName = false; //如果参数中含有空格(少数情况) 比如 fun(arg1 arg2 arg3 ,arg4) 中的 arg1 arg2 arg3,比如 DISTINCT id if (mkes != null && mkes.length >= 2) { ckeys[i] = praseArgsSplitWithSpace(mkes); } else { - // 如果参数没有空格 - if ("".equals(FunctionsAndRaws.RAW_MAP.get(origin))) { - // do nothing , 比如 toDate(now()) , + boolean isName = false; + + String mk = RAW_MAP.get(origin); + if (mk != null) { // newSQLConfig 提前处理好的 + if (mk.length() > 0) { + origin = mk; + } } else if (StringUtil.isNumer(origin)) { //do nothing } else if (StringUtil.isName(origin)) { @@ -1193,14 +1749,16 @@ private String[] parseArgsSplitWithComma(String param, boolean isColumn) { } else { origin = getValue(origin).toString(); } + if (isName && isKeyPrefix()) { - ckeys[i] = tableAlias + "." + origin; - if (isColumn && StringUtil.isEmpty(alias, true) == false) { - ckeys[i] += " AS " + quote + alias + quote; - } - } else { - ckeys[i] = origin + (StringUtil.isEmpty(alias, true) ? "" : " AS " + quote + alias + quote); + origin = tableAlias + "." + origin; } + + if (isColumn && StringUtil.isEmpty(alias, true) == false) { + origin += " AS " + quote + alias + quote; + } + + ckeys[i] = origin; } } @@ -1219,30 +1777,45 @@ private String[] parseArgsSplitWithComma(String param, boolean isColumn) { private String praseArgsSplitWithSpace(String mkes[]) { String quote = getQuote(); String tableAlias = getAliasWithQuote(); - boolean isName = false; + // 包含空格的参数 肯定不包含别名 不用处理别名 if (mkes != null && mkes.length > 0) { for (int j = 0; j < mkes.length; j++) { // now()/AS/ DISTINCT/VALUE 等等放在RAW_MAP中 - if ("".equals(FunctionsAndRaws.RAW_MAP.get(mkes[j]))) { + String origin = mkes[j]; + + String mk = RAW_MAP.get(origin); + if (mk != null) { // newSQLConfig 提前处理好的 + if (mk.length() > 0) { + mkes[j] = mk; + } continue; - } else if (StringUtil.isNumer(mkes[j])) { - // do nothing - } else { - //这里为什么还要做一次判断 是因为解析窗口函数调用的时候会判断一次 - if (isPrepared()) { - if (mkes[j].startsWith("_") || mkes[j].contains("--") || PATTERN_FUNCTION.matcher(mkes[j]).matches() == false) { - throw new IllegalArgumentException("字符 " + mkes[j] + " 不合法!" - + "预编译模式下 @column:\"column0,column1:alias;function0(arg0,arg1,...);function1(...):alias...\"" - + " 中所有 arg 都必须是1个不以 _ 开头的单词 或者符合正则表达式 " + PATTERN_FUNCTION + " 且不包含连续减号 -- !DISTINCT 必须全大写,且后面必须有且只有 1 个空格!其它情况不允许空格!"); - } + } + + //这里为什么还要做一次判断 是因为解析窗口函数调用的时候会判断一次 + if (isPrepared()) { + if (origin.startsWith("_") || origin.contains("--") || PATTERN_FUNCTION.matcher(origin).matches() == false) { + throw new IllegalArgumentException("字符 " + origin + " 不合法!" + + "预编译模式下 @column:\"column0,column1:alias;function0(arg0,arg1,...);function1(...):alias...\"" + + " 中所有 arg 都必须是1个不以 _ 开头的单词 或者符合正则表达式 " + PATTERN_FUNCTION + " 且不包含连续减号 -- !DISTINCT 必须全大写,且后面必须有且只有 1 个空格!其它情况不允许空格!"); } - mkes[j] = quote + mkes[j] + quote; + } + + boolean isName = false; + if (StringUtil.isNumer(origin)) { + //do nothing + } else if (StringUtil.isName(origin)) { + origin = quote + origin + quote; isName = true; + } else { + origin = getValue(origin).toString(); } + if (isName && isKeyPrefix()) { - mkes[j] = tableAlias + "." + mkes[j]; + origin = tableAlias + "." + origin; } + + mkes[j] = origin; } } // 返回重新以" "拼接后的参数 @@ -2583,7 +3156,7 @@ public static String getSQL(AbstractSQLConfig config) throws Exception { return "INSERT INTO " + tablePath + config.getColumnString() + " VALUES" + config.getValuesString(); case PUT: if(config.isClickHouse()){ - return "ALTER TABLE " + tablePath + " UPDATE"+ config.getSetString()+ config.getWhereString(true); + return "ALTER TABLE " + tablePath + " UPDATE" + config.getSetString() + config.getWhereString(true); } return "UPDATE " + tablePath + config.getSetString() + config.getWhereString(true) + (config.isMySQL() ? config.getLimitString() : ""); case DELETE: @@ -2593,7 +3166,7 @@ public static String getSQL(AbstractSQLConfig config) throws Exception { return "DELETE FROM " + tablePath + config.getWhereString(true) + (config.isMySQL() ? config.getLimitString() : ""); // PostgreSQL 不允许 LIMIT default: String explain = (config.isExplain() ? (config.isSQLServer() || config.isOracle() ? "SET STATISTICS PROFILE ON " : "EXPLAIN ") : ""); - if (config.isTest() && RequestMethod.isGetMethod(config.getMethod(), true)) { + if (config.isTest() && RequestMethod.isGetMethod(config.getMethod(), true)) { // FIXME 为啥是 code 而不是 count ? String q = config.getQuote(); // 生成 SELECT ( (24 >=0 AND 24 <3) ) AS `code` LIMIT 1 OFFSET 0 return explain + "SELECT " + config.getWhereString(false) + " AS " + q + JSONResponse.KEY_CODE + q + config.getLimitString(); } @@ -2605,9 +3178,9 @@ public static String getSQL(AbstractSQLConfig config) throws Exception { //针对oracle分组后条数的统计 if ((config.getMethod() == HEAD || config.getMethod() == HEADS) && StringUtil.isNotEmpty(config.getGroup(),true)){ - return explain + "SELECT count(*) FROM (SELECT "+ (config.getCache() == JSONRequest.CACHE_RAM ? "SQL_NO_CACHE " : "") + column + " FROM " + getConditionString(column, tablePath, config) + ") " + config.getLimitString(); + return explain + "SELECT count(*) FROM (SELECT " + (config.getCache() == JSONRequest.CACHE_RAM ? "SQL_NO_CACHE " : "") + column + " FROM " + getConditionString(column, tablePath, config) + ") " + config.getLimitString(); } - return explain + "SELECT * FROM (SELECT "+ (config.getCache() == JSONRequest.CACHE_RAM ? "SQL_NO_CACHE " : "") + column + " FROM " + getConditionString(column, tablePath, config) + ") " + config.getLimitString(); + return explain + "SELECT * FROM (SELECT " + (config.getCache() == JSONRequest.CACHE_RAM ? "SQL_NO_CACHE " : "") + column + " FROM " + getConditionString(column, tablePath, config) + ") " + config.getLimitString(); } return explain + "SELECT " + (config.getCache() == JSONRequest.CACHE_RAM ? "SQL_NO_CACHE " : "") + column + " FROM " + getConditionString(column, tablePath, config) + config.getLimitString(); @@ -2826,9 +3399,9 @@ public static SQLConfig newSQLConfig(RequestMethod method, String table, String } - String idKey = callback.getIdKey(database, schema, table); + String idKey = callback.getIdKey(datasource, database, schema, table); String idInKey = idKey + "{}"; - String userIdKey = callback.getUserIdKey(database, schema, table); + String userIdKey = callback.getUserIdKey(datasource, database, schema, table); String userIdInKey = userIdKey + "{}"; //对id和id{}处理,这两个一定会作为条件 @@ -3063,7 +3636,7 @@ else if (w.startsWith("!")) { } //解决AccessVerifier新增userId没有作为条件,而是作为内容,导致PUT,DELETE出错 - if (isWhere || (StringUtil.isName(key) == false)) { + if (isWhere || (StringUtil.isName(key.replaceFirst("[+-]$", "")) == false)) { tableWhere.put(key, value); if (whereList == null || whereList.contains(key) == false) { andList.add(key); diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractVerifier.java b/APIJSONORM/src/main/java/apijson/orm/AbstractVerifier.java index 448b54155..97ff9cd38 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractVerifier.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractVerifier.java @@ -970,7 +970,7 @@ public static JSONObject parse(@NotNull final RequestMethod method, String name, if (StringUtil.isEmpty(ds, false)) { ds = datasource; } - String idKey = idCallback == null ? null : idCallback.getIdKey(db, sh, name); + String idKey = idCallback == null ? null : idCallback.getIdKey(db, sh, ds, name); String finalIdKey = StringUtil.isEmpty(idKey, false) ? apijson.JSONObject.KEY_ID : idKey; //TODO放在operate前?考虑性能、operate修改后再验证的值是否和原来一样 diff --git a/APIJSONORM/src/main/java/apijson/orm/FunctionsAndRaws.java b/APIJSONORM/src/main/java/apijson/orm/FunctionsAndRaws.java deleted file mode 100644 index e0b945340..000000000 --- a/APIJSONORM/src/main/java/apijson/orm/FunctionsAndRaws.java +++ /dev/null @@ -1,519 +0,0 @@ -package apijson.orm; - -import java.util.LinkedHashMap; -import java.util.Map; - - -public class FunctionsAndRaws { - // 自定义原始 SQL 片段 Map:当 substring 为 null 时忽略;当 substring 为 "" 时整个 value 是 raw SQL;其它情况则只是 substring 这段为 raw SQL - public static final Map RAW_MAP; - // 允许调用的 SQL 函数:当 substring 为 null 时忽略;当 substring 为 "" 时整个 value 是 raw SQL;其它情况则只是 substring 这段为 raw SQL - public static final Map SQL_FUNCTION_MAP; - static { - RAW_MAP = new LinkedHashMap<>(); // 保证顺序,避免配置冲突等意外情况 - - // mysql关键字 - RAW_MAP.put("AS",""); - RAW_MAP.put("VALUE",""); - RAW_MAP.put("DISTINCT",""); - - //时间 - RAW_MAP.put("DATE",""); - RAW_MAP.put("now()",""); - RAW_MAP.put("DATETIME",""); - RAW_MAP.put("DateTime",""); - RAW_MAP.put("SECOND",""); - RAW_MAP.put("MINUTE",""); - RAW_MAP.put("HOUR",""); - RAW_MAP.put("DAY",""); - RAW_MAP.put("WEEK",""); - RAW_MAP.put("MONTH",""); - RAW_MAP.put("QUARTER",""); - RAW_MAP.put("YEAR",""); - RAW_MAP.put("json",""); - RAW_MAP.put("unit",""); - - //MYSQL 数据类型 BINARY,CHAR,DATETIME,TIME,DECIMAL,SIGNED,UNSIGNED - RAW_MAP.put("BINARY",""); - RAW_MAP.put("SIGNED",""); - RAW_MAP.put("DECIMAL",""); - RAW_MAP.put("BINARY",""); - RAW_MAP.put("UNSIGNED",""); - RAW_MAP.put("CHAR",""); - RAW_MAP.put("TIME",""); - - //窗口函数关键字 - RAW_MAP.put("OVER",""); - RAW_MAP.put("INTERVAL",""); - RAW_MAP.put("ORDER",""); - RAW_MAP.put("BY",""); - RAW_MAP.put("PARTITION",""); //往前 - RAW_MAP.put("DESC",""); - RAW_MAP.put("ASC",""); - RAW_MAP.put("FOLLOWING","");//往后 - RAW_MAP.put("BETWEEN",""); - RAW_MAP.put("AND",""); - RAW_MAP.put("ROWS",""); - - - SQL_FUNCTION_MAP = new LinkedHashMap<>(); // 保证顺序,避免配置冲突等意外情况 - - - //窗口函数 - SQL_FUNCTION_MAP.put("rank", "");//得到数据项在分组中的排名,排名相等的时候会留下空位 - SQL_FUNCTION_MAP.put("dense_rank", ""); //得到数据项在分组中的排名,排名相等的时候不会留下空位 - SQL_FUNCTION_MAP.put("row_number", "");//按照分组中的顺序生成序列,不存在重复的序列 - SQL_FUNCTION_MAP.put("ntile", "");//用于将分组数据按照顺序切分成N片,返回当前切片值,不支持ROWS_BETWEE - SQL_FUNCTION_MAP.put("first_value", "");//取分组排序后,截止到当前行,分组内第一个值 - SQL_FUNCTION_MAP.put("last_value", "");//取分组排序后,截止到当前行,分组内的最后一个值 - SQL_FUNCTION_MAP.put("lag", "");//统计窗口内往上第n行值。第一个参数为列名,第二个参数为往上第n行(可选,默认为1),第三个参数为默认值(当往上第n行为NULL时候,取默认值,如不指定,则为NULL) - SQL_FUNCTION_MAP.put("lead", "");//统计窗口内往下第n行值。第一个参数为列名,第二个参数为往下第n行(可选,默认为1),第三个参数为默认值(当往下第n行为NULL时候,取默认值,如不指定,则为NULL) - SQL_FUNCTION_MAP.put("cume_dist", "");//)返回(小于等于当前行值的行数)/(当前分组内的总行数) - SQL_FUNCTION_MAP.put("percent_rank", "");//返回(组内当前行的rank值-1)/(分组内做总行数-1) - - // MySQL 字符串函数 - SQL_FUNCTION_MAP.put("ascii", ""); // ASCII(s) 返回字符串 s 的第一个字符的 ASCII 码。 - SQL_FUNCTION_MAP.put("char_length", ""); // CHAR_LENGTH(s) 返回字符串 s 的字符数 - SQL_FUNCTION_MAP.put("character_length", ""); // CHARACTER_LENGTH(s) 返回字符串 s 的字符数 - SQL_FUNCTION_MAP.put("concat", ""); // CONCAT(s1, s2...sn) 字符串 s1,s2 等多个字符串合并为一个字符串 - SQL_FUNCTION_MAP.put("concat_ws", ""); // CONCAT_WS(x, s1, s2...sn) 同 CONCAT(s1, s2 ...) 函数,但是每个字符串之间要加上 x,x 可以是分隔符 - SQL_FUNCTION_MAP.put("field", ""); // FIELD(s, s1, s2...) 返回第一个字符串 s 在字符串列表 (s1, s2...)中的位置 - SQL_FUNCTION_MAP.put("find_in_set", ""); // FIND_IN_SET(s1, s2) 返回在字符串s2中与s1匹配的字符串的位置 - SQL_FUNCTION_MAP.put("format", ""); // FORMAT(x, n) 函数可以将数字 x 进行格式化 "#,###.##", 将 x 保留到小数点后 n 位,最后一位四舍五入。 - SQL_FUNCTION_MAP.put("insert", ""); // INSERT(s1, x, len, s2) 字符串 s2 替换 s1 的 x 位置开始长度为 len 的字符串 - SQL_FUNCTION_MAP.put("locate", ""); // LOCATE(s1, s) 从字符串 s 中获取 s1 的开始位置 - SQL_FUNCTION_MAP.put("lcase", ""); // LCASE(s) 将字符串 s 的所有字母变成小写字母 - SQL_FUNCTION_MAP.put("left", ""); // LEFT(s, n) 返回字符串 s 的前 n 个字符 - SQL_FUNCTION_MAP.put("length", ""); // LENGTH(s) 返回字符串 s 的字符数 - SQL_FUNCTION_MAP.put("lower", ""); // LOWER(s) 将字符串 s 的所有字母变成小写字母 - SQL_FUNCTION_MAP.put("lpad", ""); // LPAD(s1, len, s2) 在字符串 s1 的开始处填充字符串 s2,使字符串长度达到 len - SQL_FUNCTION_MAP.put("ltrim", ""); // LTRIM(s) 去掉字符串 s 开始处的空格 - SQL_FUNCTION_MAP.put("mid", ""); // MID(s, n, len) 从字符串 s 的 n 位置截取长度为 len 的子字符串,同 SUBSTRING(s, n, len) - SQL_FUNCTION_MAP.put("position", ""); // POSITION(s, s1); 从字符串 s 中获取 s1 的开始位置 - SQL_FUNCTION_MAP.put("repeat", ""); // REPEAT(s, n) 将字符串 s 重复 n 次 - SQL_FUNCTION_MAP.put("replace", ""); // REPLACE(s, s1, s2) 将字符串 s2 替代字符串 s 中的字符串 s1 - SQL_FUNCTION_MAP.put("reverse", ""); // REVERSE(s); // ) 将字符串s的顺序反过来 - SQL_FUNCTION_MAP.put("right", ""); // RIGHT(s, n) 返回字符串 s 的后 n 个字符 - SQL_FUNCTION_MAP.put("rpad", ""); // RPAD(s1, len, s2) 在字符串 s1 的结尾处添加字符串 s2,使字符串的长度达到 len - SQL_FUNCTION_MAP.put("rtrim", ""); // RTRIM", ""); // ) 去掉字符串 s 结尾处的空格 - SQL_FUNCTION_MAP.put("space", ""); // SPACE(n) 返回 n 个空格 - SQL_FUNCTION_MAP.put("strcmp", ""); // STRCMP(s1, s2) 比较字符串 s1 和 s2,如果 s1 与 s2 相等返回 0 ,如果 s1>s2 返回 1,如果 s1d2 之间相隔的天数 - SQL_FUNCTION_MAP.put("date_add", ""); // DATE_ADD(d,INTERVAL expr type) 计算起始日期 d 加上一个时间段后的日期 - SQL_FUNCTION_MAP.put("date_format", ""); // DATE_FORMAT(d,f) 按表达式 f的要求显示日期 d - SQL_FUNCTION_MAP.put("date_sub", ""); // DATE_SUB(date,INTERVAL expr type) 函数从日期减去指定的时间间隔。 - SQL_FUNCTION_MAP.put("day", ""); // DAY(d) 返回日期值 d 的日期部分 - SQL_FUNCTION_MAP.put("dayname", ""); // DAYNAME(d) 返回日期 d 是星期几,如 Monday,Tuesday - SQL_FUNCTION_MAP.put("dayofmonth", ""); // DAYOFMONTH(d) 计算日期 d 是本月的第几天 - SQL_FUNCTION_MAP.put("dayofweek", ""); // DAYOFWEEK(d) 日期 d 今天是星期几,1 星期日,2 星期一,以此类推 - SQL_FUNCTION_MAP.put("dayofyear", ""); // DAYOFYEAR(d) 计算日期 d 是本年的第几天 - SQL_FUNCTION_MAP.put("extract", ""); // EXTRACT(type FROM d) 从日期 d 中获取指定的值,type 指定返回的值。 - SQL_FUNCTION_MAP.put("from_days", ""); // FROM_DAYS(n) 计算从 0000 年 1 月 1 日开始 n 天后的日期 - SQL_FUNCTION_MAP.put("hour", ""); // 'HOUR(t) 返回 t 中的小时值 - SQL_FUNCTION_MAP.put("last_day", ""); // LAST_DAY(d) 返回给给定日期的那一月份的最后一天 - SQL_FUNCTION_MAP.put("localtime", ""); // LOCALTIME() 返回当前日期和时间 - SQL_FUNCTION_MAP.put("localtimestamp", ""); // LOCALTIMESTAMP() 返回当前日期和时间 - SQL_FUNCTION_MAP.put("makedate", ""); // MAKEDATE(year, day-of-year) 基于给定参数年份 year 和所在年中的天数序号 day-of-year 返回一个日期 - SQL_FUNCTION_MAP.put("maketime", ""); // MAKETIME(hour, minute, second) 组合时间,参数分别为小时、分钟、秒 - SQL_FUNCTION_MAP.put("microsecond", ""); // MICROSECOND(date) 返回日期参数所对应的微秒数 - SQL_FUNCTION_MAP.put("minute", ""); // MINUTE(t) 返回 t 中的分钟值 - SQL_FUNCTION_MAP.put("monthname", ""); // MONTHNAME(d) 返回日期当中的月份名称,如 November - SQL_FUNCTION_MAP.put("month", ""); // MONTH(d) 返回日期d中的月份值,1 到 12 - SQL_FUNCTION_MAP.put("now", ""); // NOW() 返回当前日期和时间 - SQL_FUNCTION_MAP.put("period_add", ""); // PERIOD_ADD(period, number) 为 年-月 组合日期添加一个时段 - SQL_FUNCTION_MAP.put("period_diff", ""); // PERIOD_DIFF(period1, period2) 返回两个时段之间的月份差值 - SQL_FUNCTION_MAP.put("quarter", ""); // QUARTER(d) 返回日期d是第几季节,返回 1 到 4 - SQL_FUNCTION_MAP.put("second", ""); // SECOND(t) 返回 t 中的秒钟值 - SQL_FUNCTION_MAP.put("sec_to_time", ""); // SEC_TO_TIME", ""); // ) 将以秒为单位的时间 s 转换为时分秒的格式 - SQL_FUNCTION_MAP.put("str_to_date", ""); // STR_TO_DATE", ""); // tring, format_mask) 将字符串转变为日期 - SQL_FUNCTION_MAP.put("subdate", ""); // SUBDATE(d,n) 日期 d 减去 n 天后的日期 - SQL_FUNCTION_MAP.put("subtime", ""); // SUBTIME(t,n) 时间 t 减去 n 秒的时间 - SQL_FUNCTION_MAP.put("sysdate", ""); // SYSDATE() 返回当前日期和时间 - SQL_FUNCTION_MAP.put("time", ""); // TIME(expression) 提取传入表达式的时间部分 - SQL_FUNCTION_MAP.put("time_format", ""); // TIME_FORMAT(t,f) 按表达式 f 的要求显示时间 t - SQL_FUNCTION_MAP.put("time_to_sec", ""); // TIME_TO_SEC(t) 将时间 t 转换为秒 - SQL_FUNCTION_MAP.put("timediff", ""); // TIMEDIFF(time1, time2) 计算时间差值 - SQL_FUNCTION_MAP.put("timestamp", ""); // TIMESTAMP(expression, interval) 单个参数时,函数返回日期或日期时间表达式;有2个参数时,将参数加和 - SQL_FUNCTION_MAP.put("to_days", ""); // TO_DAYS(d) 计算日期 d 距离 0000 年 1 月 1 日的天数 - SQL_FUNCTION_MAP.put("week", ""); // WEEK(d) 计算日期 d 是本年的第几个星期,范围是 0 到 53 - SQL_FUNCTION_MAP.put("weekday", ""); // WEEKDAY(d) 日期 d 是星期几,0 表示星期一,1 表示星期二 - SQL_FUNCTION_MAP.put("weekofyear", ""); // WEEKOFYEAR(d) 计算日期 d 是本年的第几个星期,范围是 0 到 53 - SQL_FUNCTION_MAP.put("year", ""); // YEAR(d) 返回年份 - SQL_FUNCTION_MAP.put("yearweek", ""); // YEARWEEK(date, mode) 返回年份及第几周(0到53),mode 中 0 表示周天,1表示周一,以此类推 - SQL_FUNCTION_MAP.put("unix_timestamp", ""); // UNIX_TIMESTAMP(date) 获取UNIX时间戳函数,返回一个以 UNIX 时间戳为基础的无符号整数 - SQL_FUNCTION_MAP.put("from_unixtime", ""); // FROM_UNIXTIME(date) 将 UNIX 时间戳转换为时间格式,与UNIX_TIMESTAMP互为反函数 - - // MYSQL JSON 函数 - SQL_FUNCTION_MAP.put("json_append", ""); // JSON_APPEND(json_doc, path, val[, path, val] ...)) 插入JSON数组 - SQL_FUNCTION_MAP.put("json_array", ""); // JSON_ARRAY(val1, val2...) 创建JSON数组 - SQL_FUNCTION_MAP.put("json_array_append", ""); // JSON_ARRAY_APPEND(json_doc, val) 将数据附加到JSON文档 - SQL_FUNCTION_MAP.put("json_array_insert", ""); // JSON_ARRAY_INSERT(json_doc, val) 插入JSON数组 - SQL_FUNCTION_MAP.put("json_contains", ""); // JSON_CONTAINS(json_doc, val) JSON文档是否在路径中包含特定对象 - SQL_FUNCTION_MAP.put("json_contains_path", ""); // JSON_CONTAINS_PATH(json_doc, path) JSON文档是否在路径中包含任何数据 - SQL_FUNCTION_MAP.put("json_depth", ""); // JSON_DEPTH(json_doc) JSON文档的最大深度 - SQL_FUNCTION_MAP.put("json_extract", ""); // JSON_EXTRACT(json_doc, path) 从JSON文档返回数据 - SQL_FUNCTION_MAP.put("json_insert", ""); // JSON_INSERT(json_doc, val) 将数据插入JSON文档 - SQL_FUNCTION_MAP.put("json_keys", ""); // JSON_KEYS(json_doc[, path]) JSON文档中的键数组 - SQL_FUNCTION_MAP.put("json_length", ""); // JSON_LENGTH(json_doc) JSON文档中的元素数 - SQL_FUNCTION_MAP.put("json_merge", ""); // JSON_MERGE(json_doc1, json_doc2) (已弃用) 合并JSON文档,保留重复的键。JSON_MERGE_PRESERVE()的已弃用同义词 - SQL_FUNCTION_MAP.put("json_merge_patch", ""); // JSON_MERGE_PATCH(json_doc1, json_doc2) 合并JSON文档,替换重复键的值 - SQL_FUNCTION_MAP.put("json_merge_preserve", ""); // JSON_MERGE_PRESERVE(json_doc1, json_doc2) 合并JSON文档,保留重复的键 - SQL_FUNCTION_MAP.put("json_object", ""); // JSON_OBJECT(key1, val1, key2, val2...) 创建JSON对象 - SQL_FUNCTION_MAP.put("json_overlaps", ""); // JSON_OVERLAPS(json_doc1, json_doc2) (引入8.0.17) 比较两个JSON文档,如果它们具有相同的键值对或数组元素,则返回TRUE(1),否则返回FALSE(0) - SQL_FUNCTION_MAP.put("json_pretty", ""); // JSON_PRETTY(json_doc) 以易于阅读的格式打印JSON文档 - SQL_FUNCTION_MAP.put("json_quote", ""); // JSON_QUOTE(json_doc1) 引用JSON文档 - SQL_FUNCTION_MAP.put("json_remove", ""); // JSON_REMOVE(json_doc1, path) 从JSON文档中删除数据 - SQL_FUNCTION_MAP.put("json_replace", ""); // JSON_REPLACE(json_doc1, val1, val2) 替换JSON文档中的值 - SQL_FUNCTION_MAP.put("json_schema_valid", ""); // JSON_SCHEMA_VALID(json_doc) (引入8.0.17) 根据JSON模式验证JSON文档;如果文档针对架构进行验证,则返回TRUE / 1;否则,则返回FALSE / 0 - SQL_FUNCTION_MAP.put("json_schema_validation_report", ""); // JSON_SCHEMA_VALIDATION_REPORT(json_doc, mode) (引入8.0.17) 根据JSON模式验证JSON文档;以JSON格式返回有关验证结果的报告,包括成功或失败以及失败原因 - SQL_FUNCTION_MAP.put("json_search", ""); // JSON_SEARCH(json_doc, val) JSON文档中值的路径 - SQL_FUNCTION_MAP.put("json_set", ""); // JSON_SET(json_doc, val) 将数据插入JSON文档 - // SQL_FUNCTION_MAP.put("json_storage_free", ""); // JSON_STORAGE_FREE() 部分更新后,JSON列值的二进制表示形式中的可用空间 - // SQL_FUNCTION_MAP.put("json_storage_size", ""); // JSON_STORAGE_SIZE() 用于存储JSON文档的二进制表示的空间 - SQL_FUNCTION_MAP.put("json_table", ""); // JSON_TABLE() 从JSON表达式返回数据作为关系表 - SQL_FUNCTION_MAP.put("json_type", ""); // JSON_TYPE(json_doc) JSON值类型 - SQL_FUNCTION_MAP.put("json_unquote", ""); // JSON_UNQUOTE(json_doc) 取消引用JSON值 - SQL_FUNCTION_MAP.put("json_valid", ""); // JSON_VALID(json_doc) JSON值是否有效 - SQL_FUNCTION_MAP.put("json_arrayagg", ""); // JSON_ARRAYAGG(key) 将每个表达式转换为 JSON 值,然后返回一个包含这些 JSON 值的 JSON 数组 - SQL_FUNCTION_MAP.put("json_objectagg", ""); // JSON_OBJECTAGG(key, val)) 将每个表达式转换为 JSON 值,然后返回一个包含这些 JSON 值的 JSON 对象 - - // MySQL 高级函数 - // SQL_FUNCTION_MAP.put("bin", ""); // BIN(x) 返回 x 的二进制编码 - // SQL_FUNCTION_MAP.put("binary", ""); // BINARY(s) 将字符串 s 转换为二进制字符串 - SQL_FUNCTION_MAP.put("case", ""); // CASE 表示函数开始,END 表示函数结束。如果 condition1 成立,则返回 result1, 如果 condition2 成立,则返回 result2,当全部不成立则返回 result,而当有一个成立之后,后面的就不执行了。 - SQL_FUNCTION_MAP.put("cast", ""); // CAST(x AS type) 转换数据类型 - SQL_FUNCTION_MAP.put("coalesce", ""); // COALESCE(expr1, expr2, ...., expr_n) 返回参数中的第一个非空表达式(从左向右) - // SQL_FUNCTION_MAP.put("conv", ""); // CONV(x,f1,f2) 返回 f1 进制数变成 f2 进制数 - // SQL_FUNCTION_MAP.put("convert", ""); // CONVERT(s, cs) 函数将字符串 s 的字符集变成 cs - SQL_FUNCTION_MAP.put("if", ""); // IF(expr,v1,v2) 如果表达式 expr 成立,返回结果 v1;否则,返回结果 v2。 - SQL_FUNCTION_MAP.put("ifnull", ""); // IFNULL(v1,v2) 如果 v1 的值不为 NULL,则返回 v1,否则返回 v2。 - SQL_FUNCTION_MAP.put("isnull", ""); // ISNULL(expression) 判断表达式是否为 NULL - SQL_FUNCTION_MAP.put("nullif", ""); // NULLIF(expr1, expr2) 比较两个字符串,如果字符串 expr1 与 expr2 相等 返回 NULL,否则返回 expr1 - SQL_FUNCTION_MAP.put("group_concat", ""); // GROUP_CONCAT([DISTINCT], s1, s2...) - - //clickhouse 字符串函数 注释的函数表示返回的格式暂时不支持,如:返回数组 ,同时包含因版本不同 clickhosue不支持的函数,版本 - SQL_FUNCTION_MAP.put("empty", ""); // empty(s) 对于空字符串s返回1,对于非空字符串返回0 - SQL_FUNCTION_MAP.put("notEmpty", ""); //notEmpty(s) 对于空字符串返回0,对于非空字符串返回1。 - SQL_FUNCTION_MAP.put("lengthUTF8", ""); //假定字符串以UTF-8编码组成的文本,返回此字符串的Unicode字符长度。如果传入的字符串不是UTF-8编码,则函数可能返回一个预期外的值 - SQL_FUNCTION_MAP.put("lcase", ""); //将字符串中的ASCII转换为小写 - SQL_FUNCTION_MAP.put("ucase", ""); //将字符串中的ASCII转换为大写。 - SQL_FUNCTION_MAP.put("lowerUTF8", ""); //将字符串转换为小写,函数假设字符串是以UTF-8编码文本的字符集。 - SQL_FUNCTION_MAP.put("upperUTF8", ""); //将字符串转换为大写,函数假设字符串是以UTF-8编码文本的字符集。 - SQL_FUNCTION_MAP.put("isValidUTF8", ""); // 检查字符串是否为有效的UTF-8编码,是则返回1,否则返回0。 - SQL_FUNCTION_MAP.put("toValidUTF8", "");//用�(U+FFFD)字符替换无效的UTF-8字符。所有连续的无效字符都会被替换为一个替换字符。 - SQL_FUNCTION_MAP.put("reverseUTF8", "");//以Unicode字符为单位反转UTF-8编码的字符串。 - SQL_FUNCTION_MAP.put("concatAssumeInjective", ""); // concatAssumeInjective(s1, s2, …) 与concat相同,区别在于,你需要保证concat(s1, s2, s3) -> s4是单射的,它将用于GROUP BY的优化。 - SQL_FUNCTION_MAP.put("substringUTF8", ""); // substringUTF8(s,offset,length)¶ 与’substring’相同,但其操作单位为Unicode字符,函数假设字符串是以UTF-8进行编码的文本。如果不是则可能返回一个预期外的结果(不会抛出异常)。 - SQL_FUNCTION_MAP.put("appendTrailingCharIfAbsent", ""); // appendTrailingCharIfAbsent(s,c) 如果’s’字符串非空并且末尾不包含’c’字符,则将’c’字符附加到末尾 - SQL_FUNCTION_MAP.put("convertCharset", ""); // convertCharset(s,from,to) 返回从’from’中的编码转换为’to’中的编码的字符串’s’。 - SQL_FUNCTION_MAP.put("base64Encode", ""); // base64Encode(s) 将字符串’s’编码成base64 - SQL_FUNCTION_MAP.put("base64Decode", ""); //base64Decode(s) 使用base64将字符串解码成原始字符串。如果失败则抛出异常。 - SQL_FUNCTION_MAP.put("tryBase64Decode", ""); //tryBase64Decode(s) 使用base64将字符串解码成原始字符串。但如果出现错误,将返回空字符串。 - SQL_FUNCTION_MAP.put("endsWith", ""); //endsWith(s,后缀) 返回是否以指定的后缀结尾。如果字符串以指定的后缀结束,则返回1,否则返回0。 - SQL_FUNCTION_MAP.put("startsWith", ""); //startsWith(s,前缀) 返回是否以指定的前缀开头。如果字符串以指定的前缀开头,则返回1,否则返回0。 - SQL_FUNCTION_MAP.put("trimLeft", ""); //trimLeft(s)返回一个字符串,用于删除左侧的空白字符。 - SQL_FUNCTION_MAP.put("trimRight", ""); //trimRight(s) 返回一个字符串,用于删除右侧的空白字符。 - SQL_FUNCTION_MAP.put("trimBoth", ""); //trimBoth(s),用于删除任一侧的空白字符 - SQL_FUNCTION_MAP.put("extractAllGroups", ""); //extractAllGroups(text, regexp) 从正则表达式匹配的非重叠子字符串中提取所有组 - // SQL_FUNCTION_MAP.put("leftPad", ""); //leftPad('string', 'length'[, 'pad_string']) 用空格或指定的字符串从左边填充当前字符串(如果需要,可以多次),直到得到的字符串达到给定的长度 - // SQL_FUNCTION_MAP.put("leftPadUTF8", ""); //leftPadUTF8('string','length'[, 'pad_string']) 用空格或指定的字符串从左边填充当前字符串(如果需要,可以多次),直到得到的字符串达到给定的长度 - // SQL_FUNCTION_MAP.put("rightPad", ""); // rightPad('string', 'length'[, 'pad_string']) 用空格或指定的字符串(如果需要,可以多次)从右边填充当前字符串,直到得到的字符串达到给定的长度 - // SQL_FUNCTION_MAP.put("rightPadUTF8", "");// rightPadUTF8('string','length'[, 'pad_string']) 用空格或指定的字符串(如果需要,可以多次)从右边填充当前字符串,直到得到的字符串达到给定的长度。 - SQL_FUNCTION_MAP.put("normalizeQuery", ""); //normalizeQuery(x) 用占位符替换文字、文字序列和复杂的别名。 - SQL_FUNCTION_MAP.put("normalizedQueryHash", ""); //normalizedQueryHash(x) 为类似查询返回相同的64位散列值,但不包含文字值。有助于对查询日志进行分析 - SQL_FUNCTION_MAP.put("positionUTF8", ""); // positionUTF8(s, needle[, start_pos]) 返回在字符串中找到的子字符串的位置(以Unicode点表示),从1开始。 - SQL_FUNCTION_MAP.put("multiSearchFirstIndex", ""); //multiSearchFirstIndex(s, [needle1, needle2, …, needlen]) 返回字符串s中最左边的needlei的索引i(从1开始),否则返回0 - SQL_FUNCTION_MAP.put("multiSearchAny", ""); // multiSearchAny(s, [needle1, needle2, …, needlen])如果至少有一个字符串needlei匹配字符串s,则返回1,否则返回0。 - SQL_FUNCTION_MAP.put("match", ""); //match(s, pattern) 检查字符串是否与模式正则表达式匹配。re2正则表达式。re2正则表达式的语法比Perl正则表达式的语法更有局限性。 - SQL_FUNCTION_MAP.put("multiMatchAny", ""); //multiMatchAny(s, [pattern1, pattern2, …, patternn]) 与match相同,但是如果没有匹配的正则表达式返回0,如果有匹配的模式返回1 - SQL_FUNCTION_MAP.put("multiMatchAnyIndex", ""); //multiMatchAnyIndex(s, [pattern1, pattern2, …, patternn]) 与multiMatchAny相同,但返回与干堆匹配的任何索引 - SQL_FUNCTION_MAP.put("extract", ""); // extract(s, pattern) 使用正则表达式提取字符串的片段 - SQL_FUNCTION_MAP.put("extractAll", ""); //extractAll(s, pattern) 使用正则表达式提取字符串的所有片段 - SQL_FUNCTION_MAP.put("like", ""); //like(s, pattern) 检查字符串是否与简单正则表达式匹配 - SQL_FUNCTION_MAP.put("notLike", "");// 和‘like’是一样的,但是是否定的 - SQL_FUNCTION_MAP.put("countSubstrings", ""); //countSubstrings(s, needle[, start_pos])返回子字符串出现的次数 - SQL_FUNCTION_MAP.put("countMatches", ""); //返回干s中的正则表达式匹配数。countMatches(s, pattern) - SQL_FUNCTION_MAP.put("replaceOne", ""); //replaceOne(s, pattern, replacement)将' s '中的' pattern '子串的第一个出现替换为' replacement '子串。 - - SQL_FUNCTION_MAP.put("replaceAll", ""); //replaceAll(s, pattern, replacement)/用' replacement '子串替换' s '中所有出现的' pattern '子串 - SQL_FUNCTION_MAP.put("replaceRegexpOne", ""); //replaceRegexpOne(s, pattern, replacement)使用' pattern '正则表达式进行替换 - SQL_FUNCTION_MAP.put("replaceRegexpAll", ""); //replaceRegexpAll(s, pattern, replacement) - SQL_FUNCTION_MAP.put("regexpQuoteMeta", ""); //regexpQuoteMeta(s)该函数在字符串中某些预定义字符之前添加一个反斜杠 - - //clickhouse日期函数 - SQL_FUNCTION_MAP.put("toYear", ""); //将Date或DateTime转换为包含年份编号(AD)的UInt16类型的数字。 - SQL_FUNCTION_MAP.put("toQuarter", ""); //将Date或DateTime转换为包含季度编号的UInt8类型的数字。 - SQL_FUNCTION_MAP.put("toMonth", ""); //Date或DateTime转换为包含月份编号(1-12)的UInt8类型的数字。 - SQL_FUNCTION_MAP.put("toDayOfYear", ""); //将Date或DateTime转换为包含一年中的某一天的编号的UInt16(1-366)类型的数字。 - SQL_FUNCTION_MAP.put("toDayOfMonth", "");//将Date或DateTime转换为包含一月中的某一天的编号的UInt8(1-31)类型的数字。 - SQL_FUNCTION_MAP.put("toDayOfWeek", ""); //将Date或DateTime转换为包含一周中的某一天的编号的UInt8(周一是1, 周日是7)类型的数字。 - SQL_FUNCTION_MAP.put("toHour", ""); //将DateTime转换为包含24小时制(0-23)小时数的UInt8数字。 - SQL_FUNCTION_MAP.put("toMinute", ""); //将DateTime转换为包含一小时中分钟数(0-59)的UInt8数字。 - SQL_FUNCTION_MAP.put("toSecond", ""); //将DateTime转换为包含一分钟中秒数(0-59)的UInt8数字。 - SQL_FUNCTION_MAP.put("toUnixTimestamp", ""); // 对于DateTime参数:将值转换为UInt32类型的数字-Unix时间戳 - SQL_FUNCTION_MAP.put("toStartOfYear", ""); //将Date或DateTime向前取整到本年的第一天。 - SQL_FUNCTION_MAP.put("toStartOfISOYear", ""); // 将Date或DateTime向前取整到ISO本年的第一天。 - SQL_FUNCTION_MAP.put("toStartOfQuarter", "");//将Date或DateTime向前取整到本季度的第一天。 - SQL_FUNCTION_MAP.put("toStartOfMonth", ""); //将Date或DateTime向前取整到本月的第一天。 - SQL_FUNCTION_MAP.put("toMonday", ""); //将Date或DateTime向前取整到本周的星期 - SQL_FUNCTION_MAP.put("toStartOfWeek", ""); //按mode将Date或DateTime向前取整到最近的星期日或星期一。 - SQL_FUNCTION_MAP.put("toStartOfDay", ""); //将DateTime向前取整到今天的开始。 - SQL_FUNCTION_MAP.put("toStartOfHour", ""); //将DateTime向前取整到当前小时的开始。 - SQL_FUNCTION_MAP.put("toStartOfMinute", ""); //将DateTime向前取整到当前分钟的开始。 - SQL_FUNCTION_MAP.put("toStartOfSecond", ""); //将DateTime向前取整到当前秒数的开始。 - SQL_FUNCTION_MAP.put("toStartOfFiveMinute", "");//将DateTime以五分钟为单位向前取整到最接近的时间点。 - SQL_FUNCTION_MAP.put("toStartOfTenMinutes", ""); //将DateTime以十分钟为单位向前取整到最接近的时间点。 - SQL_FUNCTION_MAP.put("toStartOfFifteenMinutes", ""); //将DateTime以十五分钟为单位向前取整到最接近的时间点。 - SQL_FUNCTION_MAP.put("toStartOfInterval", ""); // - SQL_FUNCTION_MAP.put("toTime", ""); //将DateTime中的日期转换为一个固定的日期,同时保留时间部分。 - SQL_FUNCTION_MAP.put("toISOYear", ""); //将Date或DateTime转换为包含ISO年份的UInt16类型的编号。 - SQL_FUNCTION_MAP.put("toISOWeek", ""); // - SQL_FUNCTION_MAP.put("toWeek", "");// 返回Date或DateTime的周数。 - SQL_FUNCTION_MAP.put("toYearWeek", ""); //返回年和周的日期 - SQL_FUNCTION_MAP.put("date_trunc", ""); //截断日期和时间数据到日期的指定部分 - SQL_FUNCTION_MAP.put("date_diff", ""); //回两个日期或带有时间值的日期之间的差值。 - - SQL_FUNCTION_MAP.put("yesterday", ""); //不接受任何参数并在请求执行时的某一刻返回昨天的日期(Date)。 - SQL_FUNCTION_MAP.put("today", ""); //不接受任何参数并在请求执行时的某一刻返回当前日期(Date)。 - SQL_FUNCTION_MAP.put("timeSlot", ""); //将时间向前取整半小时。 - SQL_FUNCTION_MAP.put("toYYYYMM", ""); // - SQL_FUNCTION_MAP.put("toYYYYMMDD", "");// - SQL_FUNCTION_MAP.put("toYYYYMMDDhhmmss", ""); // - SQL_FUNCTION_MAP.put("addYears", ""); // Function adds a Date/DateTime interval to a Date/DateTime and then return the Date/DateTime - SQL_FUNCTION_MAP.put("addMonths", ""); //同上 - SQL_FUNCTION_MAP.put("addWeeks", ""); //同上 - SQL_FUNCTION_MAP.put("addDays", ""); //同上 - SQL_FUNCTION_MAP.put("addHours", ""); //同上 - SQL_FUNCTION_MAP.put("addMinutes", "");//同上 - SQL_FUNCTION_MAP.put("addSeconds", ""); //同上 - SQL_FUNCTION_MAP.put("addQuarters", ""); //同上 - SQL_FUNCTION_MAP.put("subtractYears", ""); //Function subtract a Date/DateTime interval to a Date/DateTime and then return the Date/DateTime - SQL_FUNCTION_MAP.put("subtractMonths", ""); //同上 - SQL_FUNCTION_MAP.put("subtractWeeks", ""); //同上 - SQL_FUNCTION_MAP.put("subtractDays", ""); //同上 - SQL_FUNCTION_MAP.put("subtractours", "");//同上 - SQL_FUNCTION_MAP.put("subtractMinutes", ""); //同上 - SQL_FUNCTION_MAP.put("subtractSeconds", ""); //同上 - SQL_FUNCTION_MAP.put("subtractQuarters", ""); //同上 - SQL_FUNCTION_MAP.put("formatDateTime", ""); //函数根据给定的格式字符串来格式化时间 - SQL_FUNCTION_MAP.put("timestamp_add", ""); //使用提供的日期或日期时间值添加指定的时间值。 - SQL_FUNCTION_MAP.put("timestamp_sub", ""); //从提供的日期或带时间的日期中减去时间间隔。 - - //clickhouse json函数 - SQL_FUNCTION_MAP.put("visitParamHas", ""); //visitParamHas(params, name)检查是否存在«name»名称的字段 - SQL_FUNCTION_MAP.put("visitParamExtractUInt", ""); //visitParamExtractUInt(params, name)将名为«name»的字段的值解析成UInt64。 - SQL_FUNCTION_MAP.put("visitParamExtractInt", ""); //与visitParamExtractUInt相同,但返回Int64。 - SQL_FUNCTION_MAP.put("visitParamExtractFloat", ""); //与visitParamExtractUInt相同,但返回Float64。 - SQL_FUNCTION_MAP.put("visitParamExtractBool", "");//解析true/false值。其结果是UInt8类型的。 - SQL_FUNCTION_MAP.put("visitParamExtractRaw", ""); //返回字段的值,包含空格符。 - SQL_FUNCTION_MAP.put("visitParamExtractString", ""); //使用双引号解析字符串。这个值没有进行转义。如果转义失败,它将返回一个空白字符串。 - SQL_FUNCTION_MAP.put("JSONHas", ""); //如果JSON中存在该值,则返回1。 - SQL_FUNCTION_MAP.put("JSONLength", ""); //返回JSON数组或JSON对象的长度。 - SQL_FUNCTION_MAP.put("JSONType", ""); //返回JSON值的类型。 - SQL_FUNCTION_MAP.put("JSONExtractUInt", ""); //解析JSON并提取值。这些函数类似于visitParam*函数。 - SQL_FUNCTION_MAP.put("JSONExtractInt", ""); // - SQL_FUNCTION_MAP.put("JSONExtractFloat", ""); // - SQL_FUNCTION_MAP.put("JSONExtractBool", ""); // - SQL_FUNCTION_MAP.put("JSONExtractString", ""); //解析JSON并提取字符串。此函数类似于visitParamExtractString函数。 - SQL_FUNCTION_MAP.put("JSONExtract", "");//解析JSON并提取给定ClickHouse数据类型的值。 - SQL_FUNCTION_MAP.put("JSONExtractKeysAndValues", ""); //从JSON中解析键值对,其中值是给定的ClickHouse数据类型 - SQL_FUNCTION_MAP.put("JSONExtractRaw", ""); //返回JSON的部分。 - SQL_FUNCTION_MAP.put("toJSONString", ""); // - - //clickhouse 类型转换函数 - SQL_FUNCTION_MAP.put("toInt8", ""); //toInt8(expr) 转换一个输入值为Int类型 - SQL_FUNCTION_MAP.put("toInt16", ""); - SQL_FUNCTION_MAP.put("toInt32", ""); - SQL_FUNCTION_MAP.put("toInt64", ""); - SQL_FUNCTION_MAP.put("toInt8OrZero", ""); //toInt(8|16|32|64)OrZero 这个函数需要一个字符类型的入参,然后尝试把它转为Int (8 | 16 | 32 | 64),如果转换失败直接返回0。 - SQL_FUNCTION_MAP.put("toInt16OrZero", ""); - SQL_FUNCTION_MAP.put("toInt32OrZero", ""); - SQL_FUNCTION_MAP.put("toInt64OrZero", ""); - SQL_FUNCTION_MAP.put("toInt8OrNull", "");//toInt(8|16|32|64)OrNull 这个函数需要一个字符类型的入参,然后尝试把它转为Int (8 | 16 | 32 | 64),如果转换失败直接返回NULL - SQL_FUNCTION_MAP.put("toInt16OrNull", ""); - SQL_FUNCTION_MAP.put("toInt32OrNull", ""); - SQL_FUNCTION_MAP.put("toInt64OrNull", ""); - SQL_FUNCTION_MAP.put("toUInt8", ""); //toInt8(expr) 转换一个输入值为Int类型 - SQL_FUNCTION_MAP.put("toUInt16", ""); - SQL_FUNCTION_MAP.put("toUInt32", ""); - SQL_FUNCTION_MAP.put("toUInt64", ""); - SQL_FUNCTION_MAP.put("toUInt8OrZero", ""); //toInt(8|16|32|64)OrZero 这个函数需要一个字符类型的入参,然后尝试把它转为Int (8 | 16 | 32 | 64),如果转换失败直接返回0。 - SQL_FUNCTION_MAP.put("toUInt16OrZero", ""); - SQL_FUNCTION_MAP.put("toUInt32OrZero", ""); - SQL_FUNCTION_MAP.put("toUInt64OrZero", ""); - SQL_FUNCTION_MAP.put("toUInt8OrNull", "");//toInt(8|16|32|64)OrNull 这个函数需要一个字符类型的入参,然后尝试把它转为Int (8 | 16 | 32 | 64),如果转换失败直接返回NULL - SQL_FUNCTION_MAP.put("toUInt16OrNull", ""); - SQL_FUNCTION_MAP.put("toUInt32OrNull", ""); - SQL_FUNCTION_MAP.put("toUInt64OrNull", ""); - - SQL_FUNCTION_MAP.put("toFloat32", ""); - SQL_FUNCTION_MAP.put("toFloat64", ""); - SQL_FUNCTION_MAP.put("toFloat32OrZero", ""); - SQL_FUNCTION_MAP.put("toFloat64OrZero", ""); - SQL_FUNCTION_MAP.put("toFloat32OrNull", ""); - SQL_FUNCTION_MAP.put("toFloat64OrNull", ""); - - SQL_FUNCTION_MAP.put("toDate", ""); // - SQL_FUNCTION_MAP.put("toDateOrZero", ""); //toInt16(expr) - SQL_FUNCTION_MAP.put("toDateOrNull", ""); //toInt32(expr) - SQL_FUNCTION_MAP.put("toDateTimeOrZero", ""); //toInt64(expr) - SQL_FUNCTION_MAP.put("toDateTimeOrNull", ""); //toInt(8|16|32|64)OrZero 这个函数需要一个字符类型的入参,然后尝试把它转为Int (8 | 16 | 32 | 64),如果转换失败直接返回0。 - - SQL_FUNCTION_MAP.put("toDecimal32", ""); - SQL_FUNCTION_MAP.put("toFixedString", ""); // 将String类型的参数转换为FixedString(N)类型的值 - SQL_FUNCTION_MAP.put("toStringCutToZero", ""); // 接受String或FixedString参数,返回String,其内容在找到的第一个零字节处被截断。 - SQL_FUNCTION_MAP.put("toDecimal256", ""); - SQL_FUNCTION_MAP.put("toDecimal32OrNull", ""); - SQL_FUNCTION_MAP.put("toDecimal64OrNull", ""); - SQL_FUNCTION_MAP.put("toDecimal128OrNull", ""); - SQL_FUNCTION_MAP.put("toDecimal256OrNull", ""); - SQL_FUNCTION_MAP.put("toDecimal32OrZero", ""); - SQL_FUNCTION_MAP.put("toDecimal64OrZero", ""); - SQL_FUNCTION_MAP.put("toDecimal128OrZero", ""); - SQL_FUNCTION_MAP.put("toDecimal256OrZero", ""); - - - SQL_FUNCTION_MAP.put("toIntervalSecond", ""); //把一个数值类型的值转换为Interval类型的数据。 - SQL_FUNCTION_MAP.put("toIntervalMinute", ""); - SQL_FUNCTION_MAP.put("toIntervalHour", ""); - SQL_FUNCTION_MAP.put("toIntervalDay", ""); - SQL_FUNCTION_MAP.put("toIntervalWeek", ""); - SQL_FUNCTION_MAP.put("toIntervalMonth", ""); - SQL_FUNCTION_MAP.put("toIntervalQuarter", ""); - SQL_FUNCTION_MAP.put("toIntervalYear", ""); - SQL_FUNCTION_MAP.put("parseDateTimeBestEffort", ""); //把String类型的时间日期转换为DateTime数据类型。 - SQL_FUNCTION_MAP.put("parseDateTimeBestEffortOrNull", ""); - SQL_FUNCTION_MAP.put("parseDateTimeBestEffortOrZero", ""); - SQL_FUNCTION_MAP.put("toLowCardinality", ""); - - - - ////clickhouse hash函数 - SQL_FUNCTION_MAP.put("halfMD5", ""); //计算字符串的MD5。然后获取结果的前8个字节并将它们作为UInt64(大端)返回 - SQL_FUNCTION_MAP.put("MD5", ""); //计算字符串的MD5并将结果放入FixedString(16)中返回 - - //clickhouse ip地址函数 - SQL_FUNCTION_MAP.put("IPv4NumToString", ""); //接受一个UInt32(大端)表示的IPv4的地址,返回相应IPv4的字符串表现形式,格式为A.B.C.D(以点分割的十进制数字)。 - SQL_FUNCTION_MAP.put("IPv4StringToNum", ""); //与IPv4NumToString函数相反。如果IPv4地址格式无效,则返回0。 - SQL_FUNCTION_MAP.put("IPv6NumToString", ""); //接受FixedString(16)类型的二进制格式的IPv6地址。以文本格式返回此地址的字符串。 - SQL_FUNCTION_MAP.put("IPv6StringToNum", ""); //与IPv6NumToString的相反。如果IPv6地址格式无效,则返回空字节字符串。 - SQL_FUNCTION_MAP.put("IPv4ToIPv6", ""); // 接受一个UInt32类型的IPv4地址,返回FixedString(16)类型的IPv6地址 - SQL_FUNCTION_MAP.put("cutIPv6", ""); //接受一个FixedString(16)类型的IPv6地址,返回一个String,这个String中包含了删除指定位之后的地址的文本格 - SQL_FUNCTION_MAP.put("toIPv4", ""); //IPv4StringToNum()的别名, - SQL_FUNCTION_MAP.put("toIPv6", ""); //IPv6StringToNum()的别名 - SQL_FUNCTION_MAP.put("isIPAddressInRange", ""); //确定一个IP地址是否包含在以CIDR符号表示的网络中 - - //clickhouse Nullable处理函数 - SQL_FUNCTION_MAP.put("isNull", ""); //检查参数是否为NULL。 - SQL_FUNCTION_MAP.put("isNotNull", ""); //检查参数是否不为 NULL. - SQL_FUNCTION_MAP.put("ifNull", ""); //如果第一个参数为«NULL»,则返回第二个参数的值。 - SQL_FUNCTION_MAP.put("assumeNotNull", ""); //将可为空类型的值转换为非Nullable类型的值。 - SQL_FUNCTION_MAP.put("toNullable", ""); //将参数的类型转换为Nullable。 - - //clickhouse UUID函数 - SQL_FUNCTION_MAP.put("generateUUIDv4", ""); // 生成一个UUID - SQL_FUNCTION_MAP.put("toUUID", ""); //toUUID(x) 将String类型的值转换为UUID类型的值。 - - //clickhouse 系统函数 - SQL_FUNCTION_MAP.put("hostName", ""); //hostName()回一个字符串,其中包含执行此函数的主机的名称。 - SQL_FUNCTION_MAP.put("getMacro", ""); //从服务器配置的宏部分获取指定值。 - SQL_FUNCTION_MAP.put("FQDN", "");//返回完全限定的域名。 - SQL_FUNCTION_MAP.put("basename", ""); //提取字符串最后一个斜杠或反斜杠之后的尾随部分 - SQL_FUNCTION_MAP.put("currentUser", ""); //返回当前用户的登录。在分布式查询的情况下,将返回用户的登录,即发起的查询 - SQL_FUNCTION_MAP.put("version", ""); //以字符串形式返回服务器版本。 - SQL_FUNCTION_MAP.put("uptime", "");//以秒为单位返回服务器的正常运行时间。 - - //clickhouse 数学函数 - SQL_FUNCTION_MAP.put("least", ""); //返回a和b中最小的值。 - SQL_FUNCTION_MAP.put("greatest", ""); //返回a和b的最大值。 - SQL_FUNCTION_MAP.put("plus", ""); //plus(a, b), a + b operator¶计算数值的总和。 - SQL_FUNCTION_MAP.put("minus", ""); //minus(a, b), a - b operator 计算数值之间的差,结果总是有符号的。 - SQL_FUNCTION_MAP.put("multiply", "");//multiply(a, b), a * b operator 计算数值的乘积 - SQL_FUNCTION_MAP.put("divide", ""); //divide(a, b), a / b operator 计算数值的商。结果类型始终是浮点类型 - SQL_FUNCTION_MAP.put("intDiv", ""); //intDiv(a,b)计算数值的商,向下舍入取整(按绝对值)。 - SQL_FUNCTION_MAP.put("intDivOrZero", ""); // intDivOrZero(a,b)与’intDiv’的不同之处在于它在除以零或将最小负数除以-1时返回零。 - SQL_FUNCTION_MAP.put("modulo", ""); //modulo(a, b), a % b operator 计算除法后的余数。 - SQL_FUNCTION_MAP.put("moduloOrZero", ""); //和modulo不同之处在于,除以0时结果返回0 - SQL_FUNCTION_MAP.put("negate", ""); //通过改变数值的符号位对数值取反,结果总是有符号 - SQL_FUNCTION_MAP.put("gcd", ""); //gcd(a,b) 返回数值的最大公约数。 - SQL_FUNCTION_MAP.put("lcm", ""); //lcm(a,b) 返回数值的最小公倍数 - SQL_FUNCTION_MAP.put("e", ""); //e() 返回一个接近数学常量e的Float64数字。 - SQL_FUNCTION_MAP.put("pi", ""); //pi() 返回一个接近数学常量π的Float64数字。 - SQL_FUNCTION_MAP.put("exp2", ""); //exp2(x)¶接受一个数值类型的参数并返回它的2的x次幂。 - SQL_FUNCTION_MAP.put("exp10", ""); //exp10(x)¶接受一个数值类型的参数并返回它的10的x次幂。 - SQL_FUNCTION_MAP.put("cbrt", ""); //cbrt(x) 接受一个数值类型的参数并返回它的立方根。 - SQL_FUNCTION_MAP.put("lgamma", ""); //lgamma(x) 返回x的绝对值的自然对数的伽玛函数。 - SQL_FUNCTION_MAP.put("tgamma", ""); //tgamma(x)¶返回x的伽玛函数。 - SQL_FUNCTION_MAP.put("intExp2", ""); //intExp2 接受一个数值类型的参数并返回它的2的x次幂(UInt64) - SQL_FUNCTION_MAP.put("intExp10", ""); //intExp10 接受一个数值类型的参数并返回它的10的x次幂(UInt64)。 - SQL_FUNCTION_MAP.put("cosh", ""); // cosh(x) - SQL_FUNCTION_MAP.put("cosh", ""); //cosh(x) - SQL_FUNCTION_MAP.put("sinh", ""); //sinh(x) - SQL_FUNCTION_MAP.put("asinh", ""); //asinh(x) - SQL_FUNCTION_MAP.put("atanh", ""); //atanh(x) - SQL_FUNCTION_MAP.put("atan2", ""); //atan2(y, x) - SQL_FUNCTION_MAP.put("hypot", ""); //hypot(x, y) - SQL_FUNCTION_MAP.put("log1p", ""); //log1p(x) - SQL_FUNCTION_MAP.put("trunc", ""); //和truncate一样 - SQL_FUNCTION_MAP.put("roundToExp2", ""); //接受一个数字。如果数字小于1,它返回0。 - SQL_FUNCTION_MAP.put("roundDuration", ""); //接受一个数字。如果数字小于1,它返回0。 - SQL_FUNCTION_MAP.put("roundAge", ""); // 接受一个数字。如果数字小于18,它返回0。 - SQL_FUNCTION_MAP.put("roundDown", ""); //接受一个数字并将其舍入到指定数组中的一个元素 - SQL_FUNCTION_MAP.put("bitAnd", ""); //bitAnd(a,b) - SQL_FUNCTION_MAP.put("bitOr", ""); //bitOr(a,b) - } -} From d013830bbeee4233b32c8066d7bd82aef6ce9c47 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Fri, 24 Sep 2021 03:32:48 +0800 Subject: [PATCH 0472/1181] =?UTF-8?q?=E6=A0=BC=E5=BC=8F=E5=8C=96=E4=BB=A3?= =?UTF-8?q?=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../apijson/orm/AbstractObjectParser.java | 18 +- .../main/java/apijson/orm/AbstractParser.java | 68 +- .../java/apijson/orm/AbstractSQLConfig.java | 1116 ++++++++--------- .../java/apijson/orm/AbstractSQLExecutor.java | 10 +- 4 files changed, 606 insertions(+), 606 deletions(-) diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractObjectParser.java b/APIJSONORM/src/main/java/apijson/orm/AbstractObjectParser.java index 421c2d306..d1ed399e4 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractObjectParser.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractObjectParser.java @@ -87,10 +87,10 @@ public AbstractObjectParser(@NotNull JSONObject request, String parentPath, SQLC this.type = arrayConfig == null ? 0 : arrayConfig.getType(); this.joinList = arrayConfig == null ? null : arrayConfig.getJoinList(); - + this.isTable = isTable; // apijson.JSONObject.isTableKey(table); this.isArrayMainTable = isArrayMainTable; // isSubquery == false && this.isTable && this.type == SQLConfig.TYPE_ITEM_CHILD_0 && RequestMethod.isGetMethod(method, true); -// this.isReuse = isReuse; // isArrayMainTable && arrayConfig != null && arrayConfig.getPosition() > 0; + // this.isReuse = isReuse; // isArrayMainTable && arrayConfig != null && arrayConfig.getPosition() > 0; this.objectCount = 0; this.arrayCount = 0; @@ -182,11 +182,11 @@ public AbstractObjectParser parse(String name, boolean isReuse) throws Exception apijson.orm.Entry tentry = Pair.parseEntry(name, true); this.table = tentry.getKey(); this.alias = tentry.getValue(); - + Log.d(TAG, "AbstractObjectParser parentPath = " + parentPath + "; name = " + name + "; table = " + table + "; alias = " + alias); Log.d(TAG, "AbstractObjectParser type = " + type + "; isTable = " + isTable + "; isArrayMainTable = " + isArrayMainTable); Log.d(TAG, "AbstractObjectParser isEmpty = " + request.isEmpty() + "; tri = " + tri + "; drop = " + drop); - + breakParse = false; response = new JSONObject(true);//must init @@ -224,8 +224,8 @@ public AbstractObjectParser parse(String name, boolean isReuse) throws Exception whereList = new ArrayList(Arrays.asList(combine != null ? combine : new String[]{})); whereList.add(apijson.JSONRequest.KEY_ID); whereList.add(apijson.JSONRequest.KEY_ID_IN); -// whereList.add(apijson.JSONRequest.KEY_USER_ID); -// whereList.add(apijson.JSONRequest.KEY_USER_ID_IN); + // whereList.add(apijson.JSONRequest.KEY_USER_ID); + // whereList.add(apijson.JSONRequest.KEY_USER_ID_IN); } //条件>>>>>>>>>>>>>>>>>>> @@ -292,7 +292,7 @@ else if (method == PUT && value instanceof JSONArray && (whereList == null || wh if (parser.getGlobleDatasource() != null && sqlRequest.get(JSONRequest.KEY_DATASOURCE) == null) { sqlRequest.put(JSONRequest.KEY_DATASOURCE, parser.getGlobleDatasource()); } - + if (isSubquery == false) { //解决 SQL 语法报错,子查询不能 EXPLAIN if (parser.getGlobleExplain() != null && sqlRequest.get(JSONRequest.KEY_EXPLAIN) == null) { sqlRequest.put(JSONRequest.KEY_EXPLAIN, parser.getGlobleExplain()); @@ -818,10 +818,10 @@ public void onChildResponse() throws Exception { if (child == null || (child instanceof JSONObject && ((JSONObject) child).isEmpty()) || (child instanceof JSONArray && ((JSONArray) child).isEmpty()) - ) { + ) { continue; } - + response.put(entry.getKey(), child ); index ++; } diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java b/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java index 6965f2802..eb4556af2 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java @@ -209,7 +209,7 @@ public AbstractParser setGlobleDatasource(String globleDatasource) { this.globleDatasource = globleDatasource; return this; } - + protected Boolean globleExplain; public AbstractParser setGlobleExplain(Boolean globleExplain) { this.globleExplain = globleExplain; @@ -516,7 +516,7 @@ public JSONObject parseCorrectRequest(RequestMethod method, String tag, int vers //获取指定的JSON结构 >>>>>>>>>>>>>> JSONObject target = wrapRequest(method, tag, object, true); - + //JSONObject clone 浅拷贝没用,Structure.parse 会导致 structure 里面被清空,第二次从缓存里取到的就是 {} return getVerifier().verifyRequest(method, name, target, request, maxUpdateCount, getGlobleDatabase(), getGlobleSchema(), creator); } @@ -529,7 +529,7 @@ public JSONObject parseCorrectRequest(RequestMethod method, String tag, int vers */ public static JSONObject wrapRequest(RequestMethod method, String tag, JSONObject object, boolean isStructure) { boolean putTag = ! isStructure; - + if (object == null || object.containsKey(tag)) { //tag 是 Table 名或 Table[] if (putTag) { if (object == null) { @@ -549,18 +549,18 @@ public static JSONObject wrapRequest(RequestMethod method, String tag, JSONObjec if (isDiffArrayKey) { //自动为 tag = Comment:[] 的 { ... } 新增键值对为 { "Comment[]":[], "TYPE": { "Comment[]": "OBJECT[]" } ... } if (isStructure && (method == RequestMethod.POST || method == RequestMethod.PUT)) { String arrKey = key + "[]"; - + if (target.containsKey(arrKey) == false) { target.put(arrKey, new JSONArray()); } - + try { JSONObject type = target.getJSONObject(Operation.TYPE.name()); if (type == null || (type.containsKey(arrKey) == false)) { if (type == null) { type = new JSONObject(true); } - + type.put(arrKey, "OBJECT[]"); target.put(Operation.TYPE.name(), type); } @@ -581,15 +581,15 @@ else if (target.containsKey(key) == false) { } } } - + if (putTag) { target.put(JSONRequest.KEY_TAG, tag); } - + return target; } - + /**新建带状态内容的JSONObject * @param code * @param msg @@ -835,7 +835,7 @@ public JSONObject getStructure(@NotNull String table, String method, String tag, protected Map arrayObjectParserCacheMap = new HashMap<>(); - + // protected SQLConfig itemConfig; /**获取单个对象,该对象处于parentObject内 * @param parentPath parentObject的路径 @@ -871,7 +871,7 @@ public JSONObject onObjectParse(final JSONObject request } } } - + apijson.orm.Entry entry = Pair.parseEntry(name, true); String table = entry.getKey(); //Comment // String alias = entry.getValue(); //to @@ -884,15 +884,15 @@ public JSONObject onObjectParse(final JSONObject request if (isReuse) { // 数组主表使用专门的缓存数据 op = arrayObjectParserCacheMap.get(parentPath.substring(0, parentPath.lastIndexOf("[]") + 2)); } - + if (op == null) { op = createObjectParser(request, parentPath, arrayConfig, isSubquery, isTable, isArrayMainTable); } op = op.parse(name, isReuse); - + JSONObject response = null; if (op != null) {//SQL查询结果为空时,functionMap和customMap没有意义 - + if (arrayConfig == null) { //Common response = op.setSQLConfig().executeSQL().response(); } @@ -901,11 +901,11 @@ public JSONObject onObjectParse(final JSONObject request //total 这里不能用arrayConfig.getType(),因为在createObjectParser.onChildParse传到onObjectParse时已被改掉 if (type == SQLConfig.TYPE_ITEM_CHILD_0 && query != JSONRequest.QUERY_TABLE && position == 0) { - + RequestMethod method = op.getMethod(); JSONObject rp = op.setMethod(RequestMethod.HEAD).setSQLConfig().executeSQL().getSqlReponse(); op.setMethod(method); - + if (rp != null) { int index = parentPath.lastIndexOf("]/"); if (index >= 0) { @@ -934,7 +934,7 @@ public JSONObject onObjectParse(final JSONObject request pagination.put(JSONResponse.KEY_MORE, page < max); pagination.put(JSONResponse.KEY_FIRST, page == 0); pagination.put(JSONResponse.KEY_LAST, page == max); - + putQueryResult(pathPrefix + JSONResponse.KEY_INFO, pagination); if (total <= count*page) { @@ -963,9 +963,9 @@ public JSONObject onObjectParse(final JSONObject request arrayObjectParserCacheMap.put(parentPath.substring(0, parentPath.lastIndexOf("[]") + 2), op); } } -// else { -// op.recycle(); -// } + // else { + // op.recycle(); + // } op = null; } @@ -1196,13 +1196,13 @@ else if (join != null){ throw new IllegalArgumentException(JSONRequest.KEY_JOIN + ":value 中 value 的 Table 值 " + table + " 不合法!" + "必须为 &/Table0/key0, 1) { // 把 key 强制放最前,AbstractSQLExcecutor 中 config.putWhere 也是放尽可能最前 JSONObject newTableObj = new JSONObject(tableObj.size(), true); newTableObj.put(key, tableObj.remove(key)); newTableObj.putAll(tableObj); - + tableObj = newTableObj; request.put(tableKey, tableObj); } // 保证和 SQLExcecutor 缓存的 Config 里 where 顺序一致,生成的 SQL 也就一致 >>>>>>>>> - + Join j = new Join(); j.setPath(path); @@ -1285,7 +1285,7 @@ else if (join != null){ j.setKeyAndType(key); j.setRequest(getJoinObject(table, tableObj, key)); j.setOuter((JSONObject) e.getValue()); - + if (StringUtil.isName(j.getKey()) == false) { throw new IllegalArgumentException(JSONRequest.KEY_JOIN + ":value 中 value 的 key@ 中 key 值 " + j.getKey() + " 不合法!必须满足英文单词变量名格式!"); } @@ -1601,7 +1601,7 @@ public static JSONObject getJSONObject(JSONObject object, String key) { public static final String KEY_CONFIG = "config"; public static final String KEY_SQL = "sql"; - + protected Map> arrayMainCacheMap = new HashMap<>(); public void putArrayMainCache(String arrayPath, List mainTableDataList) { arrayMainCacheMap.put(arrayPath, mainTableDataList); @@ -1613,8 +1613,8 @@ public JSONObject getArrayMainCacheItem(String arrayPath, int position) { List list = getArrayMainCache(arrayPath); return list == null || position >= list.size() ? null : list.get(position); } - - + + /**执行 SQL 并返回 JSONObject * @param config @@ -1636,7 +1636,7 @@ public JSONObject executeSQL(SQLConfig config, boolean isSubquery) throws Except try { JSONObject result; - + boolean explain = config.isExplain(); if (explain) { //如果先执行 explain,则 execute 会死循环,所以只能先执行非 explain diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java index 108ce9deb..a9f99b7df 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java @@ -94,11 +94,11 @@ public abstract class AbstractSQLConfig implements SQLConfig { public static final List CONFIG_TABLE_LIST; public static final List DATABASE_LIST; - // 自定义原始 SQL 片段 Map:当 substring 为 null 时忽略;当 substring 为 "" 时整个 value 是 raw SQL;其它情况则只是 substring 这段为 raw SQL - public static final Map RAW_MAP; - // 允许调用的 SQL 函数:当 substring 为 null 时忽略;当 substring 为 "" 时整个 value 是 raw SQL;其它情况则只是 substring 这段为 raw SQL - public static final Map SQL_FUNCTION_MAP; - + // 自定义原始 SQL 片段 Map:当 substring 为 null 时忽略;当 substring 为 "" 时整个 value 是 raw SQL;其它情况则只是 substring 这段为 raw SQL + public static final Map RAW_MAP; + // 允许调用的 SQL 函数:当 substring 为 null 时忽略;当 substring 为 "" 时整个 value 是 raw SQL;其它情况则只是 substring 这段为 raw SQL + public static final Map SQL_FUNCTION_MAP; + static { // 凡是 SQL 边界符、分隔符、注释符 都不允许,例如 ' " ` ( ) ; # -- ,以免拼接 SQL 时被注入意外可执行指令 PATTERN_RANGE = Pattern.compile("^[0-9%,!=\\<\\>/\\.\\+\\-\\*\\^]+$"); // ^[a-zA-Z0-9_*%!=<>(),"]+$ 导致 exists(select*from(Comment)) 通过! PATTERN_FUNCTION = Pattern.compile("^[A-Za-z0-9%,:_@&~`!=\\<\\>\\|\\[\\]\\{\\} /\\.\\+\\-\\*\\^\\?\\(\\)\\$]+$"); //TODO 改成更好的正则,校验前面为单词,中间为操作符,后面为值 @@ -129,531 +129,531 @@ public abstract class AbstractSQLConfig implements SQLConfig { DATABASE_LIST.add(DATABASE_ORACLE); DATABASE_LIST.add(DATABASE_DB2); DATABASE_LIST.add(DATABASE_CLICKHOUSE); - - - RAW_MAP = new LinkedHashMap<>(); // 保证顺序,避免配置冲突等意外情况 - - // mysql关键字 - RAW_MAP.put("AS",""); - RAW_MAP.put("VALUE",""); - RAW_MAP.put("DISTINCT",""); - - //时间 - RAW_MAP.put("DATE",""); - RAW_MAP.put("now()",""); - RAW_MAP.put("DATETIME",""); - RAW_MAP.put("DateTime",""); - RAW_MAP.put("SECOND",""); - RAW_MAP.put("MINUTE",""); - RAW_MAP.put("HOUR",""); - RAW_MAP.put("DAY",""); - RAW_MAP.put("WEEK",""); - RAW_MAP.put("MONTH",""); - RAW_MAP.put("QUARTER",""); - RAW_MAP.put("YEAR",""); - RAW_MAP.put("json",""); - RAW_MAP.put("unit",""); - - //MYSQL 数据类型 BINARY,CHAR,DATETIME,TIME,DECIMAL,SIGNED,UNSIGNED - RAW_MAP.put("BINARY",""); - RAW_MAP.put("SIGNED",""); - RAW_MAP.put("DECIMAL",""); - RAW_MAP.put("BINARY",""); - RAW_MAP.put("UNSIGNED",""); - RAW_MAP.put("CHAR",""); - RAW_MAP.put("TIME",""); - - //窗口函数关键字 - RAW_MAP.put("OVER", ""); - RAW_MAP.put("INTERVAL", ""); - RAW_MAP.put("GROUP BY", ""); //往前 - RAW_MAP.put("GROUP", ""); //往前 - RAW_MAP.put("ORDER BY", ""); //往前 - RAW_MAP.put("ORDER", ""); - RAW_MAP.put("PARTITION BY", ""); //往前 - RAW_MAP.put("PARTITION", ""); //往前 - RAW_MAP.put("BY", ""); - RAW_MAP.put("DESC", ""); - RAW_MAP.put("ASC", ""); - RAW_MAP.put("FOLLOWING", "");//往后 - RAW_MAP.put("BETWEEN", ""); - RAW_MAP.put("AND", ""); - RAW_MAP.put("ROWS", ""); - - RAW_MAP.put("AGAINST", ""); - RAW_MAP.put("IN NATURAL LANGUAGE MODE", ""); - RAW_MAP.put("IN BOOLEAN MODE", ""); - RAW_MAP.put("IN", ""); - RAW_MAP.put("BOOLEAN", ""); - RAW_MAP.put("NATURAL", ""); - RAW_MAP.put("LANGUAGE", ""); - RAW_MAP.put("MODE", ""); - - - SQL_FUNCTION_MAP = new LinkedHashMap<>(); // 保证顺序,避免配置冲突等意外情况 - - - //窗口函数 - SQL_FUNCTION_MAP.put("rank", "");//得到数据项在分组中的排名,排名相等的时候会留下空位 - SQL_FUNCTION_MAP.put("dense_rank", ""); //得到数据项在分组中的排名,排名相等的时候不会留下空位 - SQL_FUNCTION_MAP.put("row_number", "");//按照分组中的顺序生成序列,不存在重复的序列 - SQL_FUNCTION_MAP.put("ntile", "");//用于将分组数据按照顺序切分成N片,返回当前切片值,不支持ROWS_BETWEE - SQL_FUNCTION_MAP.put("first_value", "");//取分组排序后,截止到当前行,分组内第一个值 - SQL_FUNCTION_MAP.put("last_value", "");//取分组排序后,截止到当前行,分组内的最后一个值 - SQL_FUNCTION_MAP.put("lag", "");//统计窗口内往上第n行值。第一个参数为列名,第二个参数为往上第n行(可选,默认为1),第三个参数为默认值(当往上第n行为NULL时候,取默认值,如不指定,则为NULL) - SQL_FUNCTION_MAP.put("lead", "");//统计窗口内往下第n行值。第一个参数为列名,第二个参数为往下第n行(可选,默认为1),第三个参数为默认值(当往下第n行为NULL时候,取默认值,如不指定,则为NULL) - SQL_FUNCTION_MAP.put("cume_dist", "");//)返回(小于等于当前行值的行数)/(当前分组内的总行数) - SQL_FUNCTION_MAP.put("percent_rank", "");//返回(组内当前行的rank值-1)/(分组内做总行数-1) - - // MySQL 字符串函数 - SQL_FUNCTION_MAP.put("ascii", ""); // ASCII(s) 返回字符串 s 的第一个字符的 ASCII 码。 - SQL_FUNCTION_MAP.put("char_length", ""); // CHAR_LENGTH(s) 返回字符串 s 的字符数 - SQL_FUNCTION_MAP.put("character_length", ""); // CHARACTER_LENGTH(s) 返回字符串 s 的字符数 - SQL_FUNCTION_MAP.put("concat", ""); // CONCAT(s1, s2...sn) 字符串 s1,s2 等多个字符串合并为一个字符串 - SQL_FUNCTION_MAP.put("concat_ws", ""); // CONCAT_WS(x, s1, s2...sn) 同 CONCAT(s1, s2 ...) 函数,但是每个字符串之间要加上 x,x 可以是分隔符 - SQL_FUNCTION_MAP.put("field", ""); // FIELD(s, s1, s2...) 返回第一个字符串 s 在字符串列表 (s1, s2...)中的位置 - SQL_FUNCTION_MAP.put("find_in_set", ""); // FIND_IN_SET(s1, s2) 返回在字符串s2中与s1匹配的字符串的位置 - SQL_FUNCTION_MAP.put("format", ""); // FORMAT(x, n) 函数可以将数字 x 进行格式化 "#,###.##", 将 x 保留到小数点后 n 位,最后一位四舍五入。 - SQL_FUNCTION_MAP.put("insert", ""); // INSERT(s1, x, len, s2) 字符串 s2 替换 s1 的 x 位置开始长度为 len 的字符串 - SQL_FUNCTION_MAP.put("locate", ""); // LOCATE(s1, s) 从字符串 s 中获取 s1 的开始位置 - SQL_FUNCTION_MAP.put("lcase", ""); // LCASE(s) 将字符串 s 的所有字母变成小写字母 - SQL_FUNCTION_MAP.put("left", ""); // LEFT(s, n) 返回字符串 s 的前 n 个字符 - SQL_FUNCTION_MAP.put("length", ""); // LENGTH(s) 返回字符串 s 的字符数 - SQL_FUNCTION_MAP.put("lower", ""); // LOWER(s) 将字符串 s 的所有字母变成小写字母 - SQL_FUNCTION_MAP.put("lpad", ""); // LPAD(s1, len, s2) 在字符串 s1 的开始处填充字符串 s2,使字符串长度达到 len - SQL_FUNCTION_MAP.put("ltrim", ""); // LTRIM(s) 去掉字符串 s 开始处的空格 - SQL_FUNCTION_MAP.put("mid", ""); // MID(s, n, len) 从字符串 s 的 n 位置截取长度为 len 的子字符串,同 SUBSTRING(s, n, len) - SQL_FUNCTION_MAP.put("position", ""); // POSITION(s, s1); 从字符串 s 中获取 s1 的开始位置 - SQL_FUNCTION_MAP.put("repeat", ""); // REPEAT(s, n) 将字符串 s 重复 n 次 - SQL_FUNCTION_MAP.put("replace", ""); // REPLACE(s, s1, s2) 将字符串 s2 替代字符串 s 中的字符串 s1 - SQL_FUNCTION_MAP.put("reverse", ""); // REVERSE(s); // ) 将字符串s的顺序反过来 - SQL_FUNCTION_MAP.put("right", ""); // RIGHT(s, n) 返回字符串 s 的后 n 个字符 - SQL_FUNCTION_MAP.put("rpad", ""); // RPAD(s1, len, s2) 在字符串 s1 的结尾处添加字符串 s2,使字符串的长度达到 len - SQL_FUNCTION_MAP.put("rtrim", ""); // RTRIM", ""); // ) 去掉字符串 s 结尾处的空格 - SQL_FUNCTION_MAP.put("space", ""); // SPACE(n) 返回 n 个空格 - SQL_FUNCTION_MAP.put("strcmp", ""); // STRCMP(s1, s2) 比较字符串 s1 和 s2,如果 s1 与 s2 相等返回 0 ,如果 s1>s2 返回 1,如果 s1d2 之间相隔的天数 - SQL_FUNCTION_MAP.put("date_add", ""); // DATE_ADD(d,INTERVAL expr type) 计算起始日期 d 加上一个时间段后的日期 - SQL_FUNCTION_MAP.put("date_format", ""); // DATE_FORMAT(d,f) 按表达式 f的要求显示日期 d - SQL_FUNCTION_MAP.put("date_sub", ""); // DATE_SUB(date,INTERVAL expr type) 函数从日期减去指定的时间间隔。 - SQL_FUNCTION_MAP.put("day", ""); // DAY(d) 返回日期值 d 的日期部分 - SQL_FUNCTION_MAP.put("dayname", ""); // DAYNAME(d) 返回日期 d 是星期几,如 Monday,Tuesday - SQL_FUNCTION_MAP.put("dayofmonth", ""); // DAYOFMONTH(d) 计算日期 d 是本月的第几天 - SQL_FUNCTION_MAP.put("dayofweek", ""); // DAYOFWEEK(d) 日期 d 今天是星期几,1 星期日,2 星期一,以此类推 - SQL_FUNCTION_MAP.put("dayofyear", ""); // DAYOFYEAR(d) 计算日期 d 是本年的第几天 - SQL_FUNCTION_MAP.put("extract", ""); // EXTRACT(type FROM d) 从日期 d 中获取指定的值,type 指定返回的值。 - SQL_FUNCTION_MAP.put("from_days", ""); // FROM_DAYS(n) 计算从 0000 年 1 月 1 日开始 n 天后的日期 - SQL_FUNCTION_MAP.put("hour", ""); // 'HOUR(t) 返回 t 中的小时值 - SQL_FUNCTION_MAP.put("last_day", ""); // LAST_DAY(d) 返回给给定日期的那一月份的最后一天 - SQL_FUNCTION_MAP.put("localtime", ""); // LOCALTIME() 返回当前日期和时间 - SQL_FUNCTION_MAP.put("localtimestamp", ""); // LOCALTIMESTAMP() 返回当前日期和时间 - SQL_FUNCTION_MAP.put("makedate", ""); // MAKEDATE(year, day-of-year) 基于给定参数年份 year 和所在年中的天数序号 day-of-year 返回一个日期 - SQL_FUNCTION_MAP.put("maketime", ""); // MAKETIME(hour, minute, second) 组合时间,参数分别为小时、分钟、秒 - SQL_FUNCTION_MAP.put("microsecond", ""); // MICROSECOND(date) 返回日期参数所对应的微秒数 - SQL_FUNCTION_MAP.put("minute", ""); // MINUTE(t) 返回 t 中的分钟值 - SQL_FUNCTION_MAP.put("monthname", ""); // MONTHNAME(d) 返回日期当中的月份名称,如 November - SQL_FUNCTION_MAP.put("month", ""); // MONTH(d) 返回日期d中的月份值,1 到 12 - SQL_FUNCTION_MAP.put("now", ""); // NOW() 返回当前日期和时间 - SQL_FUNCTION_MAP.put("period_add", ""); // PERIOD_ADD(period, number) 为 年-月 组合日期添加一个时段 - SQL_FUNCTION_MAP.put("period_diff", ""); // PERIOD_DIFF(period1, period2) 返回两个时段之间的月份差值 - SQL_FUNCTION_MAP.put("quarter", ""); // QUARTER(d) 返回日期d是第几季节,返回 1 到 4 - SQL_FUNCTION_MAP.put("second", ""); // SECOND(t) 返回 t 中的秒钟值 - SQL_FUNCTION_MAP.put("sec_to_time", ""); // SEC_TO_TIME", ""); // ) 将以秒为单位的时间 s 转换为时分秒的格式 - SQL_FUNCTION_MAP.put("str_to_date", ""); // STR_TO_DATE", ""); // tring, format_mask) 将字符串转变为日期 - SQL_FUNCTION_MAP.put("subdate", ""); // SUBDATE(d,n) 日期 d 减去 n 天后的日期 - SQL_FUNCTION_MAP.put("subtime", ""); // SUBTIME(t,n) 时间 t 减去 n 秒的时间 - SQL_FUNCTION_MAP.put("sysdate", ""); // SYSDATE() 返回当前日期和时间 - SQL_FUNCTION_MAP.put("time", ""); // TIME(expression) 提取传入表达式的时间部分 - SQL_FUNCTION_MAP.put("time_format", ""); // TIME_FORMAT(t,f) 按表达式 f 的要求显示时间 t - SQL_FUNCTION_MAP.put("time_to_sec", ""); // TIME_TO_SEC(t) 将时间 t 转换为秒 - SQL_FUNCTION_MAP.put("timediff", ""); // TIMEDIFF(time1, time2) 计算时间差值 - SQL_FUNCTION_MAP.put("timestamp", ""); // TIMESTAMP(expression, interval) 单个参数时,函数返回日期或日期时间表达式;有2个参数时,将参数加和 - SQL_FUNCTION_MAP.put("to_days", ""); // TO_DAYS(d) 计算日期 d 距离 0000 年 1 月 1 日的天数 - SQL_FUNCTION_MAP.put("week", ""); // WEEK(d) 计算日期 d 是本年的第几个星期,范围是 0 到 53 - SQL_FUNCTION_MAP.put("weekday", ""); // WEEKDAY(d) 日期 d 是星期几,0 表示星期一,1 表示星期二 - SQL_FUNCTION_MAP.put("weekofyear", ""); // WEEKOFYEAR(d) 计算日期 d 是本年的第几个星期,范围是 0 到 53 - SQL_FUNCTION_MAP.put("year", ""); // YEAR(d) 返回年份 - SQL_FUNCTION_MAP.put("yearweek", ""); // YEARWEEK(date, mode) 返回年份及第几周(0到53),mode 中 0 表示周天,1表示周一,以此类推 - SQL_FUNCTION_MAP.put("unix_timestamp", ""); // UNIX_TIMESTAMP(date) 获取UNIX时间戳函数,返回一个以 UNIX 时间戳为基础的无符号整数 - SQL_FUNCTION_MAP.put("from_unixtime", ""); // FROM_UNIXTIME(date) 将 UNIX 时间戳转换为时间格式,与UNIX_TIMESTAMP互为反函数 - - // MYSQL JSON 函数 - SQL_FUNCTION_MAP.put("json_append", ""); // JSON_APPEND(json_doc, path, val[, path, val] ...)) 插入JSON数组 - SQL_FUNCTION_MAP.put("json_array", ""); // JSON_ARRAY(val1, val2...) 创建JSON数组 - SQL_FUNCTION_MAP.put("json_array_append", ""); // JSON_ARRAY_APPEND(json_doc, val) 将数据附加到JSON文档 - SQL_FUNCTION_MAP.put("json_array_insert", ""); // JSON_ARRAY_INSERT(json_doc, val) 插入JSON数组 - SQL_FUNCTION_MAP.put("json_contains", ""); // JSON_CONTAINS(json_doc, val) JSON文档是否在路径中包含特定对象 - SQL_FUNCTION_MAP.put("json_contains_path", ""); // JSON_CONTAINS_PATH(json_doc, path) JSON文档是否在路径中包含任何数据 - SQL_FUNCTION_MAP.put("json_depth", ""); // JSON_DEPTH(json_doc) JSON文档的最大深度 - SQL_FUNCTION_MAP.put("json_extract", ""); // JSON_EXTRACT(json_doc, path) 从JSON文档返回数据 - SQL_FUNCTION_MAP.put("json_insert", ""); // JSON_INSERT(json_doc, val) 将数据插入JSON文档 - SQL_FUNCTION_MAP.put("json_keys", ""); // JSON_KEYS(json_doc[, path]) JSON文档中的键数组 - SQL_FUNCTION_MAP.put("json_length", ""); // JSON_LENGTH(json_doc) JSON文档中的元素数 - SQL_FUNCTION_MAP.put("json_merge", ""); // JSON_MERGE(json_doc1, json_doc2) (已弃用) 合并JSON文档,保留重复的键。JSON_MERGE_PRESERVE()的已弃用同义词 - SQL_FUNCTION_MAP.put("json_merge_patch", ""); // JSON_MERGE_PATCH(json_doc1, json_doc2) 合并JSON文档,替换重复键的值 - SQL_FUNCTION_MAP.put("json_merge_preserve", ""); // JSON_MERGE_PRESERVE(json_doc1, json_doc2) 合并JSON文档,保留重复的键 - SQL_FUNCTION_MAP.put("json_object", ""); // JSON_OBJECT(key1, val1, key2, val2...) 创建JSON对象 - SQL_FUNCTION_MAP.put("json_overlaps", ""); // JSON_OVERLAPS(json_doc1, json_doc2) (引入8.0.17) 比较两个JSON文档,如果它们具有相同的键值对或数组元素,则返回TRUE(1),否则返回FALSE(0) - SQL_FUNCTION_MAP.put("json_pretty", ""); // JSON_PRETTY(json_doc) 以易于阅读的格式打印JSON文档 - SQL_FUNCTION_MAP.put("json_quote", ""); // JSON_QUOTE(json_doc1) 引用JSON文档 - SQL_FUNCTION_MAP.put("json_remove", ""); // JSON_REMOVE(json_doc1, path) 从JSON文档中删除数据 - SQL_FUNCTION_MAP.put("json_replace", ""); // JSON_REPLACE(json_doc1, val1, val2) 替换JSON文档中的值 - SQL_FUNCTION_MAP.put("json_schema_valid", ""); // JSON_SCHEMA_VALID(json_doc) (引入8.0.17) 根据JSON模式验证JSON文档;如果文档针对架构进行验证,则返回TRUE / 1;否则,则返回FALSE / 0 - SQL_FUNCTION_MAP.put("json_schema_validation_report", ""); // JSON_SCHEMA_VALIDATION_REPORT(json_doc, mode) (引入8.0.17) 根据JSON模式验证JSON文档;以JSON格式返回有关验证结果的报告,包括成功或失败以及失败原因 - SQL_FUNCTION_MAP.put("json_search", ""); // JSON_SEARCH(json_doc, val) JSON文档中值的路径 - SQL_FUNCTION_MAP.put("json_set", ""); // JSON_SET(json_doc, val) 将数据插入JSON文档 - // SQL_FUNCTION_MAP.put("json_storage_free", ""); // JSON_STORAGE_FREE() 部分更新后,JSON列值的二进制表示形式中的可用空间 - // SQL_FUNCTION_MAP.put("json_storage_size", ""); // JSON_STORAGE_SIZE() 用于存储JSON文档的二进制表示的空间 - SQL_FUNCTION_MAP.put("json_table", ""); // JSON_TABLE() 从JSON表达式返回数据作为关系表 - SQL_FUNCTION_MAP.put("json_type", ""); // JSON_TYPE(json_doc) JSON值类型 - SQL_FUNCTION_MAP.put("json_unquote", ""); // JSON_UNQUOTE(json_doc) 取消引用JSON值 - SQL_FUNCTION_MAP.put("json_valid", ""); // JSON_VALID(json_doc) JSON值是否有效 - SQL_FUNCTION_MAP.put("json_arrayagg", ""); // JSON_ARRAYAGG(key) 将每个表达式转换为 JSON 值,然后返回一个包含这些 JSON 值的 JSON 数组 - SQL_FUNCTION_MAP.put("json_objectagg", ""); // JSON_OBJECTAGG(key, val)) 将每个表达式转换为 JSON 值,然后返回一个包含这些 JSON 值的 JSON 对象 - - // MySQL 高级函数 - // SQL_FUNCTION_MAP.put("bin", ""); // BIN(x) 返回 x 的二进制编码 - // SQL_FUNCTION_MAP.put("binary", ""); // BINARY(s) 将字符串 s 转换为二进制字符串 - SQL_FUNCTION_MAP.put("case", ""); // CASE 表示函数开始,END 表示函数结束。如果 condition1 成立,则返回 result1, 如果 condition2 成立,则返回 result2,当全部不成立则返回 result,而当有一个成立之后,后面的就不执行了。 - SQL_FUNCTION_MAP.put("cast", ""); // CAST(x AS type) 转换数据类型 - SQL_FUNCTION_MAP.put("coalesce", ""); // COALESCE(expr1, expr2, ...., expr_n) 返回参数中的第一个非空表达式(从左向右) - // SQL_FUNCTION_MAP.put("conv", ""); // CONV(x,f1,f2) 返回 f1 进制数变成 f2 进制数 - // SQL_FUNCTION_MAP.put("convert", ""); // CONVERT(s, cs) 函数将字符串 s 的字符集变成 cs - SQL_FUNCTION_MAP.put("if", ""); // IF(expr,v1,v2) 如果表达式 expr 成立,返回结果 v1;否则,返回结果 v2。 - SQL_FUNCTION_MAP.put("ifnull", ""); // IFNULL(v1,v2) 如果 v1 的值不为 NULL,则返回 v1,否则返回 v2。 - SQL_FUNCTION_MAP.put("isnull", ""); // ISNULL(expression) 判断表达式是否为 NULL - SQL_FUNCTION_MAP.put("nullif", ""); // NULLIF(expr1, expr2) 比较两个字符串,如果字符串 expr1 与 expr2 相等 返回 NULL,否则返回 expr1 - SQL_FUNCTION_MAP.put("group_concat", ""); // GROUP_CONCAT([DISTINCT], s1, s2...) 聚合拼接字符串 - SQL_FUNCTION_MAP.put("match", ""); // MATCH (name,tag) AGAINST ('a b' IN NATURAL LANGUAGE MODE) 全文检索 - - - - - - //clickhouse 字符串函数 注释的函数表示返回的格式暂时不支持,如:返回数组 ,同时包含因版本不同 clickhosue不支持的函数,版本 - SQL_FUNCTION_MAP.put("empty", ""); // empty(s) 对于空字符串s返回1,对于非空字符串返回0 - SQL_FUNCTION_MAP.put("notEmpty", ""); //notEmpty(s) 对于空字符串返回0,对于非空字符串返回1。 - SQL_FUNCTION_MAP.put("lengthUTF8", ""); //假定字符串以UTF-8编码组成的文本,返回此字符串的Unicode字符长度。如果传入的字符串不是UTF-8编码,则函数可能返回一个预期外的值 - SQL_FUNCTION_MAP.put("lcase", ""); //将字符串中的ASCII转换为小写 - SQL_FUNCTION_MAP.put("ucase", ""); //将字符串中的ASCII转换为大写。 - SQL_FUNCTION_MAP.put("lowerUTF8", ""); //将字符串转换为小写,函数假设字符串是以UTF-8编码文本的字符集。 - SQL_FUNCTION_MAP.put("upperUTF8", ""); //将字符串转换为大写,函数假设字符串是以UTF-8编码文本的字符集。 - SQL_FUNCTION_MAP.put("isValidUTF8", ""); // 检查字符串是否为有效的UTF-8编码,是则返回1,否则返回0。 - SQL_FUNCTION_MAP.put("toValidUTF8", "");//用�(U+FFFD)字符替换无效的UTF-8字符。所有连续的无效字符都会被替换为一个替换字符。 - SQL_FUNCTION_MAP.put("reverseUTF8", "");//以Unicode字符为单位反转UTF-8编码的字符串。 - SQL_FUNCTION_MAP.put("concatAssumeInjective", ""); // concatAssumeInjective(s1, s2, …) 与concat相同,区别在于,你需要保证concat(s1, s2, s3) -> s4是单射的,它将用于GROUP BY的优化。 - SQL_FUNCTION_MAP.put("substringUTF8", ""); // substringUTF8(s,offset,length)¶ 与’substring’相同,但其操作单位为Unicode字符,函数假设字符串是以UTF-8进行编码的文本。如果不是则可能返回一个预期外的结果(不会抛出异常)。 - SQL_FUNCTION_MAP.put("appendTrailingCharIfAbsent", ""); // appendTrailingCharIfAbsent(s,c) 如果’s’字符串非空并且末尾不包含’c’字符,则将’c’字符附加到末尾 - SQL_FUNCTION_MAP.put("convertCharset", ""); // convertCharset(s,from,to) 返回从’from’中的编码转换为’to’中的编码的字符串’s’。 - SQL_FUNCTION_MAP.put("base64Encode", ""); // base64Encode(s) 将字符串’s’编码成base64 - SQL_FUNCTION_MAP.put("base64Decode", ""); //base64Decode(s) 使用base64将字符串解码成原始字符串。如果失败则抛出异常。 - SQL_FUNCTION_MAP.put("tryBase64Decode", ""); //tryBase64Decode(s) 使用base64将字符串解码成原始字符串。但如果出现错误,将返回空字符串。 - SQL_FUNCTION_MAP.put("endsWith", ""); //endsWith(s,后缀) 返回是否以指定的后缀结尾。如果字符串以指定的后缀结束,则返回1,否则返回0。 - SQL_FUNCTION_MAP.put("startsWith", ""); //startsWith(s,前缀) 返回是否以指定的前缀开头。如果字符串以指定的前缀开头,则返回1,否则返回0。 - SQL_FUNCTION_MAP.put("trimLeft", ""); //trimLeft(s)返回一个字符串,用于删除左侧的空白字符。 - SQL_FUNCTION_MAP.put("trimRight", ""); //trimRight(s) 返回一个字符串,用于删除右侧的空白字符。 - SQL_FUNCTION_MAP.put("trimBoth", ""); //trimBoth(s),用于删除任一侧的空白字符 - SQL_FUNCTION_MAP.put("extractAllGroups", ""); //extractAllGroups(text, regexp) 从正则表达式匹配的非重叠子字符串中提取所有组 - // SQL_FUNCTION_MAP.put("leftPad", ""); //leftPad('string', 'length'[, 'pad_string']) 用空格或指定的字符串从左边填充当前字符串(如果需要,可以多次),直到得到的字符串达到给定的长度 - // SQL_FUNCTION_MAP.put("leftPadUTF8", ""); //leftPadUTF8('string','length'[, 'pad_string']) 用空格或指定的字符串从左边填充当前字符串(如果需要,可以多次),直到得到的字符串达到给定的长度 - // SQL_FUNCTION_MAP.put("rightPad", ""); // rightPad('string', 'length'[, 'pad_string']) 用空格或指定的字符串(如果需要,可以多次)从右边填充当前字符串,直到得到的字符串达到给定的长度 - // SQL_FUNCTION_MAP.put("rightPadUTF8", "");// rightPadUTF8('string','length'[, 'pad_string']) 用空格或指定的字符串(如果需要,可以多次)从右边填充当前字符串,直到得到的字符串达到给定的长度。 - SQL_FUNCTION_MAP.put("normalizeQuery", ""); //normalizeQuery(x) 用占位符替换文字、文字序列和复杂的别名。 - SQL_FUNCTION_MAP.put("normalizedQueryHash", ""); //normalizedQueryHash(x) 为类似查询返回相同的64位散列值,但不包含文字值。有助于对查询日志进行分析 - SQL_FUNCTION_MAP.put("positionUTF8", ""); // positionUTF8(s, needle[, start_pos]) 返回在字符串中找到的子字符串的位置(以Unicode点表示),从1开始。 - SQL_FUNCTION_MAP.put("multiSearchFirstIndex", ""); //multiSearchFirstIndex(s, [needle1, needle2, …, needlen]) 返回字符串s中最左边的needlei的索引i(从1开始),否则返回0 - SQL_FUNCTION_MAP.put("multiSearchAny", ""); // multiSearchAny(s, [needle1, needle2, …, needlen])如果至少有一个字符串needlei匹配字符串s,则返回1,否则返回0。 - SQL_FUNCTION_MAP.put("match", ""); //match(s, pattern) 检查字符串是否与模式正则表达式匹配。re2正则表达式。re2正则表达式的语法比Perl正则表达式的语法更有局限性。 - SQL_FUNCTION_MAP.put("multiMatchAny", ""); //multiMatchAny(s, [pattern1, pattern2, …, patternn]) 与match相同,但是如果没有匹配的正则表达式返回0,如果有匹配的模式返回1 - SQL_FUNCTION_MAP.put("multiMatchAnyIndex", ""); //multiMatchAnyIndex(s, [pattern1, pattern2, …, patternn]) 与multiMatchAny相同,但返回与干堆匹配的任何索引 - SQL_FUNCTION_MAP.put("extract", ""); // extract(s, pattern) 使用正则表达式提取字符串的片段 - SQL_FUNCTION_MAP.put("extractAll", ""); //extractAll(s, pattern) 使用正则表达式提取字符串的所有片段 - SQL_FUNCTION_MAP.put("like", ""); //like(s, pattern) 检查字符串是否与简单正则表达式匹配 - SQL_FUNCTION_MAP.put("notLike", "");// 和‘like’是一样的,但是是否定的 - SQL_FUNCTION_MAP.put("countSubstrings", ""); //countSubstrings(s, needle[, start_pos])返回子字符串出现的次数 - SQL_FUNCTION_MAP.put("countMatches", ""); //返回干s中的正则表达式匹配数。countMatches(s, pattern) - SQL_FUNCTION_MAP.put("replaceOne", ""); //replaceOne(s, pattern, replacement)将' s '中的' pattern '子串的第一个出现替换为' replacement '子串。 - - SQL_FUNCTION_MAP.put("replaceAll", ""); //replaceAll(s, pattern, replacement)/用' replacement '子串替换' s '中所有出现的' pattern '子串 - SQL_FUNCTION_MAP.put("replaceRegexpOne", ""); //replaceRegexpOne(s, pattern, replacement)使用' pattern '正则表达式进行替换 - SQL_FUNCTION_MAP.put("replaceRegexpAll", ""); //replaceRegexpAll(s, pattern, replacement) - SQL_FUNCTION_MAP.put("regexpQuoteMeta", ""); //regexpQuoteMeta(s)该函数在字符串中某些预定义字符之前添加一个反斜杠 - - //clickhouse日期函数 - SQL_FUNCTION_MAP.put("toYear", ""); //将Date或DateTime转换为包含年份编号(AD)的UInt16类型的数字。 - SQL_FUNCTION_MAP.put("toQuarter", ""); //将Date或DateTime转换为包含季度编号的UInt8类型的数字。 - SQL_FUNCTION_MAP.put("toMonth", ""); //Date或DateTime转换为包含月份编号(1-12)的UInt8类型的数字。 - SQL_FUNCTION_MAP.put("toDayOfYear", ""); //将Date或DateTime转换为包含一年中的某一天的编号的UInt16(1-366)类型的数字。 - SQL_FUNCTION_MAP.put("toDayOfMonth", "");//将Date或DateTime转换为包含一月中的某一天的编号的UInt8(1-31)类型的数字。 - SQL_FUNCTION_MAP.put("toDayOfWeek", ""); //将Date或DateTime转换为包含一周中的某一天的编号的UInt8(周一是1, 周日是7)类型的数字。 - SQL_FUNCTION_MAP.put("toHour", ""); //将DateTime转换为包含24小时制(0-23)小时数的UInt8数字。 - SQL_FUNCTION_MAP.put("toMinute", ""); //将DateTime转换为包含一小时中分钟数(0-59)的UInt8数字。 - SQL_FUNCTION_MAP.put("toSecond", ""); //将DateTime转换为包含一分钟中秒数(0-59)的UInt8数字。 - SQL_FUNCTION_MAP.put("toUnixTimestamp", ""); // 对于DateTime参数:将值转换为UInt32类型的数字-Unix时间戳 - SQL_FUNCTION_MAP.put("toStartOfYear", ""); //将Date或DateTime向前取整到本年的第一天。 - SQL_FUNCTION_MAP.put("toStartOfISOYear", ""); // 将Date或DateTime向前取整到ISO本年的第一天。 - SQL_FUNCTION_MAP.put("toStartOfQuarter", "");//将Date或DateTime向前取整到本季度的第一天。 - SQL_FUNCTION_MAP.put("toStartOfMonth", ""); //将Date或DateTime向前取整到本月的第一天。 - SQL_FUNCTION_MAP.put("toMonday", ""); //将Date或DateTime向前取整到本周的星期 - SQL_FUNCTION_MAP.put("toStartOfWeek", ""); //按mode将Date或DateTime向前取整到最近的星期日或星期一。 - SQL_FUNCTION_MAP.put("toStartOfDay", ""); //将DateTime向前取整到今天的开始。 - SQL_FUNCTION_MAP.put("toStartOfHour", ""); //将DateTime向前取整到当前小时的开始。 - SQL_FUNCTION_MAP.put("toStartOfMinute", ""); //将DateTime向前取整到当前分钟的开始。 - SQL_FUNCTION_MAP.put("toStartOfSecond", ""); //将DateTime向前取整到当前秒数的开始。 - SQL_FUNCTION_MAP.put("toStartOfFiveMinute", "");//将DateTime以五分钟为单位向前取整到最接近的时间点。 - SQL_FUNCTION_MAP.put("toStartOfTenMinutes", ""); //将DateTime以十分钟为单位向前取整到最接近的时间点。 - SQL_FUNCTION_MAP.put("toStartOfFifteenMinutes", ""); //将DateTime以十五分钟为单位向前取整到最接近的时间点。 - SQL_FUNCTION_MAP.put("toStartOfInterval", ""); // - SQL_FUNCTION_MAP.put("toTime", ""); //将DateTime中的日期转换为一个固定的日期,同时保留时间部分。 - SQL_FUNCTION_MAP.put("toISOYear", ""); //将Date或DateTime转换为包含ISO年份的UInt16类型的编号。 - SQL_FUNCTION_MAP.put("toISOWeek", ""); // - SQL_FUNCTION_MAP.put("toWeek", "");// 返回Date或DateTime的周数。 - SQL_FUNCTION_MAP.put("toYearWeek", ""); //返回年和周的日期 - SQL_FUNCTION_MAP.put("date_trunc", ""); //截断日期和时间数据到日期的指定部分 - SQL_FUNCTION_MAP.put("date_diff", ""); //回两个日期或带有时间值的日期之间的差值。 - - SQL_FUNCTION_MAP.put("yesterday", ""); //不接受任何参数并在请求执行时的某一刻返回昨天的日期(Date)。 - SQL_FUNCTION_MAP.put("today", ""); //不接受任何参数并在请求执行时的某一刻返回当前日期(Date)。 - SQL_FUNCTION_MAP.put("timeSlot", ""); //将时间向前取整半小时。 - SQL_FUNCTION_MAP.put("toYYYYMM", ""); // - SQL_FUNCTION_MAP.put("toYYYYMMDD", "");// - SQL_FUNCTION_MAP.put("toYYYYMMDDhhmmss", ""); // - SQL_FUNCTION_MAP.put("addYears", ""); // Function adds a Date/DateTime interval to a Date/DateTime and then return the Date/DateTime - SQL_FUNCTION_MAP.put("addMonths", ""); //同上 - SQL_FUNCTION_MAP.put("addWeeks", ""); //同上 - SQL_FUNCTION_MAP.put("addDays", ""); //同上 - SQL_FUNCTION_MAP.put("addHours", ""); //同上 - SQL_FUNCTION_MAP.put("addMinutes", "");//同上 - SQL_FUNCTION_MAP.put("addSeconds", ""); //同上 - SQL_FUNCTION_MAP.put("addQuarters", ""); //同上 - SQL_FUNCTION_MAP.put("subtractYears", ""); //Function subtract a Date/DateTime interval to a Date/DateTime and then return the Date/DateTime - SQL_FUNCTION_MAP.put("subtractMonths", ""); //同上 - SQL_FUNCTION_MAP.put("subtractWeeks", ""); //同上 - SQL_FUNCTION_MAP.put("subtractDays", ""); //同上 - SQL_FUNCTION_MAP.put("subtractours", "");//同上 - SQL_FUNCTION_MAP.put("subtractMinutes", ""); //同上 - SQL_FUNCTION_MAP.put("subtractSeconds", ""); //同上 - SQL_FUNCTION_MAP.put("subtractQuarters", ""); //同上 - SQL_FUNCTION_MAP.put("formatDateTime", ""); //函数根据给定的格式字符串来格式化时间 - SQL_FUNCTION_MAP.put("timestamp_add", ""); //使用提供的日期或日期时间值添加指定的时间值。 - SQL_FUNCTION_MAP.put("timestamp_sub", ""); //从提供的日期或带时间的日期中减去时间间隔。 - - //clickhouse json函数 - SQL_FUNCTION_MAP.put("visitParamHas", ""); //visitParamHas(params, name)检查是否存在«name»名称的字段 - SQL_FUNCTION_MAP.put("visitParamExtractUInt", ""); //visitParamExtractUInt(params, name)将名为«name»的字段的值解析成UInt64。 - SQL_FUNCTION_MAP.put("visitParamExtractInt", ""); //与visitParamExtractUInt相同,但返回Int64。 - SQL_FUNCTION_MAP.put("visitParamExtractFloat", ""); //与visitParamExtractUInt相同,但返回Float64。 - SQL_FUNCTION_MAP.put("visitParamExtractBool", "");//解析true/false值。其结果是UInt8类型的。 - SQL_FUNCTION_MAP.put("visitParamExtractRaw", ""); //返回字段的值,包含空格符。 - SQL_FUNCTION_MAP.put("visitParamExtractString", ""); //使用双引号解析字符串。这个值没有进行转义。如果转义失败,它将返回一个空白字符串。 - SQL_FUNCTION_MAP.put("JSONHas", ""); //如果JSON中存在该值,则返回1。 - SQL_FUNCTION_MAP.put("JSONLength", ""); //返回JSON数组或JSON对象的长度。 - SQL_FUNCTION_MAP.put("JSONType", ""); //返回JSON值的类型。 - SQL_FUNCTION_MAP.put("JSONExtractUInt", ""); //解析JSON并提取值。这些函数类似于visitParam*函数。 - SQL_FUNCTION_MAP.put("JSONExtractInt", ""); // - SQL_FUNCTION_MAP.put("JSONExtractFloat", ""); // - SQL_FUNCTION_MAP.put("JSONExtractBool", ""); // - SQL_FUNCTION_MAP.put("JSONExtractString", ""); //解析JSON并提取字符串。此函数类似于visitParamExtractString函数。 - SQL_FUNCTION_MAP.put("JSONExtract", "");//解析JSON并提取给定ClickHouse数据类型的值。 - SQL_FUNCTION_MAP.put("JSONExtractKeysAndValues", ""); //从JSON中解析键值对,其中值是给定的ClickHouse数据类型 - SQL_FUNCTION_MAP.put("JSONExtractRaw", ""); //返回JSON的部分。 - SQL_FUNCTION_MAP.put("toJSONString", ""); // - - //clickhouse 类型转换函数 - SQL_FUNCTION_MAP.put("toInt8", ""); //toInt8(expr) 转换一个输入值为Int类型 - SQL_FUNCTION_MAP.put("toInt16", ""); - SQL_FUNCTION_MAP.put("toInt32", ""); - SQL_FUNCTION_MAP.put("toInt64", ""); - SQL_FUNCTION_MAP.put("toInt8OrZero", ""); //toInt(8|16|32|64)OrZero 这个函数需要一个字符类型的入参,然后尝试把它转为Int (8 | 16 | 32 | 64),如果转换失败直接返回0。 - SQL_FUNCTION_MAP.put("toInt16OrZero", ""); - SQL_FUNCTION_MAP.put("toInt32OrZero", ""); - SQL_FUNCTION_MAP.put("toInt64OrZero", ""); - SQL_FUNCTION_MAP.put("toInt8OrNull", "");//toInt(8|16|32|64)OrNull 这个函数需要一个字符类型的入参,然后尝试把它转为Int (8 | 16 | 32 | 64),如果转换失败直接返回NULL - SQL_FUNCTION_MAP.put("toInt16OrNull", ""); - SQL_FUNCTION_MAP.put("toInt32OrNull", ""); - SQL_FUNCTION_MAP.put("toInt64OrNull", ""); - SQL_FUNCTION_MAP.put("toUInt8", ""); //toInt8(expr) 转换一个输入值为Int类型 - SQL_FUNCTION_MAP.put("toUInt16", ""); - SQL_FUNCTION_MAP.put("toUInt32", ""); - SQL_FUNCTION_MAP.put("toUInt64", ""); - SQL_FUNCTION_MAP.put("toUInt8OrZero", ""); //toInt(8|16|32|64)OrZero 这个函数需要一个字符类型的入参,然后尝试把它转为Int (8 | 16 | 32 | 64),如果转换失败直接返回0。 - SQL_FUNCTION_MAP.put("toUInt16OrZero", ""); - SQL_FUNCTION_MAP.put("toUInt32OrZero", ""); - SQL_FUNCTION_MAP.put("toUInt64OrZero", ""); - SQL_FUNCTION_MAP.put("toUInt8OrNull", "");//toInt(8|16|32|64)OrNull 这个函数需要一个字符类型的入参,然后尝试把它转为Int (8 | 16 | 32 | 64),如果转换失败直接返回NULL - SQL_FUNCTION_MAP.put("toUInt16OrNull", ""); - SQL_FUNCTION_MAP.put("toUInt32OrNull", ""); - SQL_FUNCTION_MAP.put("toUInt64OrNull", ""); - - SQL_FUNCTION_MAP.put("toFloat32", ""); - SQL_FUNCTION_MAP.put("toFloat64", ""); - SQL_FUNCTION_MAP.put("toFloat32OrZero", ""); - SQL_FUNCTION_MAP.put("toFloat64OrZero", ""); - SQL_FUNCTION_MAP.put("toFloat32OrNull", ""); - SQL_FUNCTION_MAP.put("toFloat64OrNull", ""); - - SQL_FUNCTION_MAP.put("toDate", ""); // - SQL_FUNCTION_MAP.put("toDateOrZero", ""); //toInt16(expr) - SQL_FUNCTION_MAP.put("toDateOrNull", ""); //toInt32(expr) - SQL_FUNCTION_MAP.put("toDateTimeOrZero", ""); //toInt64(expr) - SQL_FUNCTION_MAP.put("toDateTimeOrNull", ""); //toInt(8|16|32|64)OrZero 这个函数需要一个字符类型的入参,然后尝试把它转为Int (8 | 16 | 32 | 64),如果转换失败直接返回0。 - - SQL_FUNCTION_MAP.put("toDecimal32", ""); - SQL_FUNCTION_MAP.put("toFixedString", ""); // 将String类型的参数转换为FixedString(N)类型的值 - SQL_FUNCTION_MAP.put("toStringCutToZero", ""); // 接受String或FixedString参数,返回String,其内容在找到的第一个零字节处被截断。 - SQL_FUNCTION_MAP.put("toDecimal256", ""); - SQL_FUNCTION_MAP.put("toDecimal32OrNull", ""); - SQL_FUNCTION_MAP.put("toDecimal64OrNull", ""); - SQL_FUNCTION_MAP.put("toDecimal128OrNull", ""); - SQL_FUNCTION_MAP.put("toDecimal256OrNull", ""); - SQL_FUNCTION_MAP.put("toDecimal32OrZero", ""); - SQL_FUNCTION_MAP.put("toDecimal64OrZero", ""); - SQL_FUNCTION_MAP.put("toDecimal128OrZero", ""); - SQL_FUNCTION_MAP.put("toDecimal256OrZero", ""); - - - SQL_FUNCTION_MAP.put("toIntervalSecond", ""); //把一个数值类型的值转换为Interval类型的数据。 - SQL_FUNCTION_MAP.put("toIntervalMinute", ""); - SQL_FUNCTION_MAP.put("toIntervalHour", ""); - SQL_FUNCTION_MAP.put("toIntervalDay", ""); - SQL_FUNCTION_MAP.put("toIntervalWeek", ""); - SQL_FUNCTION_MAP.put("toIntervalMonth", ""); - SQL_FUNCTION_MAP.put("toIntervalQuarter", ""); - SQL_FUNCTION_MAP.put("toIntervalYear", ""); - SQL_FUNCTION_MAP.put("parseDateTimeBestEffort", ""); //把String类型的时间日期转换为DateTime数据类型。 - SQL_FUNCTION_MAP.put("parseDateTimeBestEffortOrNull", ""); - SQL_FUNCTION_MAP.put("parseDateTimeBestEffortOrZero", ""); - SQL_FUNCTION_MAP.put("toLowCardinality", ""); - - - - ////clickhouse hash函数 - SQL_FUNCTION_MAP.put("halfMD5", ""); //计算字符串的MD5。然后获取结果的前8个字节并将它们作为UInt64(大端)返回 - SQL_FUNCTION_MAP.put("MD5", ""); //计算字符串的MD5并将结果放入FixedString(16)中返回 - - //clickhouse ip地址函数 - SQL_FUNCTION_MAP.put("IPv4NumToString", ""); //接受一个UInt32(大端)表示的IPv4的地址,返回相应IPv4的字符串表现形式,格式为A.B.C.D(以点分割的十进制数字)。 - SQL_FUNCTION_MAP.put("IPv4StringToNum", ""); //与IPv4NumToString函数相反。如果IPv4地址格式无效,则返回0。 - SQL_FUNCTION_MAP.put("IPv6NumToString", ""); //接受FixedString(16)类型的二进制格式的IPv6地址。以文本格式返回此地址的字符串。 - SQL_FUNCTION_MAP.put("IPv6StringToNum", ""); //与IPv6NumToString的相反。如果IPv6地址格式无效,则返回空字节字符串。 - SQL_FUNCTION_MAP.put("IPv4ToIPv6", ""); // 接受一个UInt32类型的IPv4地址,返回FixedString(16)类型的IPv6地址 - SQL_FUNCTION_MAP.put("cutIPv6", ""); //接受一个FixedString(16)类型的IPv6地址,返回一个String,这个String中包含了删除指定位之后的地址的文本格 - SQL_FUNCTION_MAP.put("toIPv4", ""); //IPv4StringToNum()的别名, - SQL_FUNCTION_MAP.put("toIPv6", ""); //IPv6StringToNum()的别名 - SQL_FUNCTION_MAP.put("isIPAddressInRange", ""); //确定一个IP地址是否包含在以CIDR符号表示的网络中 - - //clickhouse Nullable处理函数 - SQL_FUNCTION_MAP.put("isNull", ""); //检查参数是否为NULL。 - SQL_FUNCTION_MAP.put("isNotNull", ""); //检查参数是否不为 NULL. - SQL_FUNCTION_MAP.put("ifNull", ""); //如果第一个参数为«NULL»,则返回第二个参数的值。 - SQL_FUNCTION_MAP.put("assumeNotNull", ""); //将可为空类型的值转换为非Nullable类型的值。 - SQL_FUNCTION_MAP.put("toNullable", ""); //将参数的类型转换为Nullable。 - - //clickhouse UUID函数 - SQL_FUNCTION_MAP.put("generateUUIDv4", ""); // 生成一个UUID - SQL_FUNCTION_MAP.put("toUUID", ""); //toUUID(x) 将String类型的值转换为UUID类型的值。 - - //clickhouse 系统函数 - SQL_FUNCTION_MAP.put("hostName", ""); //hostName()回一个字符串,其中包含执行此函数的主机的名称。 - SQL_FUNCTION_MAP.put("getMacro", ""); //从服务器配置的宏部分获取指定值。 - SQL_FUNCTION_MAP.put("FQDN", "");//返回完全限定的域名。 - SQL_FUNCTION_MAP.put("basename", ""); //提取字符串最后一个斜杠或反斜杠之后的尾随部分 - SQL_FUNCTION_MAP.put("currentUser", ""); //返回当前用户的登录。在分布式查询的情况下,将返回用户的登录,即发起的查询 - SQL_FUNCTION_MAP.put("version", ""); //以字符串形式返回服务器版本。 - SQL_FUNCTION_MAP.put("uptime", "");//以秒为单位返回服务器的正常运行时间。 - - //clickhouse 数学函数 - SQL_FUNCTION_MAP.put("least", ""); //返回a和b中最小的值。 - SQL_FUNCTION_MAP.put("greatest", ""); //返回a和b的最大值。 - SQL_FUNCTION_MAP.put("plus", ""); //plus(a, b), a + b operator¶计算数值的总和。 - SQL_FUNCTION_MAP.put("minus", ""); //minus(a, b), a - b operator 计算数值之间的差,结果总是有符号的。 - SQL_FUNCTION_MAP.put("multiply", "");//multiply(a, b), a * b operator 计算数值的乘积 - SQL_FUNCTION_MAP.put("divide", ""); //divide(a, b), a / b operator 计算数值的商。结果类型始终是浮点类型 - SQL_FUNCTION_MAP.put("intDiv", ""); //intDiv(a,b)计算数值的商,向下舍入取整(按绝对值)。 - SQL_FUNCTION_MAP.put("intDivOrZero", ""); // intDivOrZero(a,b)与’intDiv’的不同之处在于它在除以零或将最小负数除以-1时返回零。 - SQL_FUNCTION_MAP.put("modulo", ""); //modulo(a, b), a % b operator 计算除法后的余数。 - SQL_FUNCTION_MAP.put("moduloOrZero", ""); //和modulo不同之处在于,除以0时结果返回0 - SQL_FUNCTION_MAP.put("negate", ""); //通过改变数值的符号位对数值取反,结果总是有符号 - SQL_FUNCTION_MAP.put("gcd", ""); //gcd(a,b) 返回数值的最大公约数。 - SQL_FUNCTION_MAP.put("lcm", ""); //lcm(a,b) 返回数值的最小公倍数 - SQL_FUNCTION_MAP.put("e", ""); //e() 返回一个接近数学常量e的Float64数字。 - SQL_FUNCTION_MAP.put("pi", ""); //pi() 返回一个接近数学常量π的Float64数字。 - SQL_FUNCTION_MAP.put("exp2", ""); //exp2(x)¶接受一个数值类型的参数并返回它的2的x次幂。 - SQL_FUNCTION_MAP.put("exp10", ""); //exp10(x)¶接受一个数值类型的参数并返回它的10的x次幂。 - SQL_FUNCTION_MAP.put("cbrt", ""); //cbrt(x) 接受一个数值类型的参数并返回它的立方根。 - SQL_FUNCTION_MAP.put("lgamma", ""); //lgamma(x) 返回x的绝对值的自然对数的伽玛函数。 - SQL_FUNCTION_MAP.put("tgamma", ""); //tgamma(x)¶返回x的伽玛函数。 - SQL_FUNCTION_MAP.put("intExp2", ""); //intExp2 接受一个数值类型的参数并返回它的2的x次幂(UInt64) - SQL_FUNCTION_MAP.put("intExp10", ""); //intExp10 接受一个数值类型的参数并返回它的10的x次幂(UInt64)。 - SQL_FUNCTION_MAP.put("cosh", ""); // cosh(x) - SQL_FUNCTION_MAP.put("cosh", ""); //cosh(x) - SQL_FUNCTION_MAP.put("sinh", ""); //sinh(x) - SQL_FUNCTION_MAP.put("asinh", ""); //asinh(x) - SQL_FUNCTION_MAP.put("atanh", ""); //atanh(x) - SQL_FUNCTION_MAP.put("atan2", ""); //atan2(y, x) - SQL_FUNCTION_MAP.put("hypot", ""); //hypot(x, y) - SQL_FUNCTION_MAP.put("log1p", ""); //log1p(x) - SQL_FUNCTION_MAP.put("trunc", ""); //和truncate一样 - SQL_FUNCTION_MAP.put("roundToExp2", ""); //接受一个数字。如果数字小于1,它返回0。 - SQL_FUNCTION_MAP.put("roundDuration", ""); //接受一个数字。如果数字小于1,它返回0。 - SQL_FUNCTION_MAP.put("roundAge", ""); // 接受一个数字。如果数字小于18,它返回0。 - SQL_FUNCTION_MAP.put("roundDown", ""); //接受一个数字并将其舍入到指定数组中的一个元素 - SQL_FUNCTION_MAP.put("bitAnd", ""); //bitAnd(a,b) - SQL_FUNCTION_MAP.put("bitOr", ""); //bitOr(a,b) + + + RAW_MAP = new LinkedHashMap<>(); // 保证顺序,避免配置冲突等意外情况 + + // mysql关键字 + RAW_MAP.put("AS",""); + RAW_MAP.put("VALUE",""); + RAW_MAP.put("DISTINCT",""); + + //时间 + RAW_MAP.put("DATE",""); + RAW_MAP.put("now()",""); + RAW_MAP.put("DATETIME",""); + RAW_MAP.put("DateTime",""); + RAW_MAP.put("SECOND",""); + RAW_MAP.put("MINUTE",""); + RAW_MAP.put("HOUR",""); + RAW_MAP.put("DAY",""); + RAW_MAP.put("WEEK",""); + RAW_MAP.put("MONTH",""); + RAW_MAP.put("QUARTER",""); + RAW_MAP.put("YEAR",""); + RAW_MAP.put("json",""); + RAW_MAP.put("unit",""); + + //MYSQL 数据类型 BINARY,CHAR,DATETIME,TIME,DECIMAL,SIGNED,UNSIGNED + RAW_MAP.put("BINARY",""); + RAW_MAP.put("SIGNED",""); + RAW_MAP.put("DECIMAL",""); + RAW_MAP.put("BINARY",""); + RAW_MAP.put("UNSIGNED",""); + RAW_MAP.put("CHAR",""); + RAW_MAP.put("TIME",""); + + //窗口函数关键字 + RAW_MAP.put("OVER", ""); + RAW_MAP.put("INTERVAL", ""); + RAW_MAP.put("GROUP BY", ""); //往前 + RAW_MAP.put("GROUP", ""); //往前 + RAW_MAP.put("ORDER BY", ""); //往前 + RAW_MAP.put("ORDER", ""); + RAW_MAP.put("PARTITION BY", ""); //往前 + RAW_MAP.put("PARTITION", ""); //往前 + RAW_MAP.put("BY", ""); + RAW_MAP.put("DESC", ""); + RAW_MAP.put("ASC", ""); + RAW_MAP.put("FOLLOWING", "");//往后 + RAW_MAP.put("BETWEEN", ""); + RAW_MAP.put("AND", ""); + RAW_MAP.put("ROWS", ""); + + RAW_MAP.put("AGAINST", ""); + RAW_MAP.put("IN NATURAL LANGUAGE MODE", ""); + RAW_MAP.put("IN BOOLEAN MODE", ""); + RAW_MAP.put("IN", ""); + RAW_MAP.put("BOOLEAN", ""); + RAW_MAP.put("NATURAL", ""); + RAW_MAP.put("LANGUAGE", ""); + RAW_MAP.put("MODE", ""); + + + SQL_FUNCTION_MAP = new LinkedHashMap<>(); // 保证顺序,避免配置冲突等意外情况 + + + //窗口函数 + SQL_FUNCTION_MAP.put("rank", "");//得到数据项在分组中的排名,排名相等的时候会留下空位 + SQL_FUNCTION_MAP.put("dense_rank", ""); //得到数据项在分组中的排名,排名相等的时候不会留下空位 + SQL_FUNCTION_MAP.put("row_number", "");//按照分组中的顺序生成序列,不存在重复的序列 + SQL_FUNCTION_MAP.put("ntile", "");//用于将分组数据按照顺序切分成N片,返回当前切片值,不支持ROWS_BETWEE + SQL_FUNCTION_MAP.put("first_value", "");//取分组排序后,截止到当前行,分组内第一个值 + SQL_FUNCTION_MAP.put("last_value", "");//取分组排序后,截止到当前行,分组内的最后一个值 + SQL_FUNCTION_MAP.put("lag", "");//统计窗口内往上第n行值。第一个参数为列名,第二个参数为往上第n行(可选,默认为1),第三个参数为默认值(当往上第n行为NULL时候,取默认值,如不指定,则为NULL) + SQL_FUNCTION_MAP.put("lead", "");//统计窗口内往下第n行值。第一个参数为列名,第二个参数为往下第n行(可选,默认为1),第三个参数为默认值(当往下第n行为NULL时候,取默认值,如不指定,则为NULL) + SQL_FUNCTION_MAP.put("cume_dist", "");//)返回(小于等于当前行值的行数)/(当前分组内的总行数) + SQL_FUNCTION_MAP.put("percent_rank", "");//返回(组内当前行的rank值-1)/(分组内做总行数-1) + + // MySQL 字符串函数 + SQL_FUNCTION_MAP.put("ascii", ""); // ASCII(s) 返回字符串 s 的第一个字符的 ASCII 码。 + SQL_FUNCTION_MAP.put("char_length", ""); // CHAR_LENGTH(s) 返回字符串 s 的字符数 + SQL_FUNCTION_MAP.put("character_length", ""); // CHARACTER_LENGTH(s) 返回字符串 s 的字符数 + SQL_FUNCTION_MAP.put("concat", ""); // CONCAT(s1, s2...sn) 字符串 s1,s2 等多个字符串合并为一个字符串 + SQL_FUNCTION_MAP.put("concat_ws", ""); // CONCAT_WS(x, s1, s2...sn) 同 CONCAT(s1, s2 ...) 函数,但是每个字符串之间要加上 x,x 可以是分隔符 + SQL_FUNCTION_MAP.put("field", ""); // FIELD(s, s1, s2...) 返回第一个字符串 s 在字符串列表 (s1, s2...)中的位置 + SQL_FUNCTION_MAP.put("find_in_set", ""); // FIND_IN_SET(s1, s2) 返回在字符串s2中与s1匹配的字符串的位置 + SQL_FUNCTION_MAP.put("format", ""); // FORMAT(x, n) 函数可以将数字 x 进行格式化 "#,###.##", 将 x 保留到小数点后 n 位,最后一位四舍五入。 + SQL_FUNCTION_MAP.put("insert", ""); // INSERT(s1, x, len, s2) 字符串 s2 替换 s1 的 x 位置开始长度为 len 的字符串 + SQL_FUNCTION_MAP.put("locate", ""); // LOCATE(s1, s) 从字符串 s 中获取 s1 的开始位置 + SQL_FUNCTION_MAP.put("lcase", ""); // LCASE(s) 将字符串 s 的所有字母变成小写字母 + SQL_FUNCTION_MAP.put("left", ""); // LEFT(s, n) 返回字符串 s 的前 n 个字符 + SQL_FUNCTION_MAP.put("length", ""); // LENGTH(s) 返回字符串 s 的字符数 + SQL_FUNCTION_MAP.put("lower", ""); // LOWER(s) 将字符串 s 的所有字母变成小写字母 + SQL_FUNCTION_MAP.put("lpad", ""); // LPAD(s1, len, s2) 在字符串 s1 的开始处填充字符串 s2,使字符串长度达到 len + SQL_FUNCTION_MAP.put("ltrim", ""); // LTRIM(s) 去掉字符串 s 开始处的空格 + SQL_FUNCTION_MAP.put("mid", ""); // MID(s, n, len) 从字符串 s 的 n 位置截取长度为 len 的子字符串,同 SUBSTRING(s, n, len) + SQL_FUNCTION_MAP.put("position", ""); // POSITION(s, s1); 从字符串 s 中获取 s1 的开始位置 + SQL_FUNCTION_MAP.put("repeat", ""); // REPEAT(s, n) 将字符串 s 重复 n 次 + SQL_FUNCTION_MAP.put("replace", ""); // REPLACE(s, s1, s2) 将字符串 s2 替代字符串 s 中的字符串 s1 + SQL_FUNCTION_MAP.put("reverse", ""); // REVERSE(s); // ) 将字符串s的顺序反过来 + SQL_FUNCTION_MAP.put("right", ""); // RIGHT(s, n) 返回字符串 s 的后 n 个字符 + SQL_FUNCTION_MAP.put("rpad", ""); // RPAD(s1, len, s2) 在字符串 s1 的结尾处添加字符串 s2,使字符串的长度达到 len + SQL_FUNCTION_MAP.put("rtrim", ""); // RTRIM", ""); // ) 去掉字符串 s 结尾处的空格 + SQL_FUNCTION_MAP.put("space", ""); // SPACE(n) 返回 n 个空格 + SQL_FUNCTION_MAP.put("strcmp", ""); // STRCMP(s1, s2) 比较字符串 s1 和 s2,如果 s1 与 s2 相等返回 0 ,如果 s1>s2 返回 1,如果 s1d2 之间相隔的天数 + SQL_FUNCTION_MAP.put("date_add", ""); // DATE_ADD(d,INTERVAL expr type) 计算起始日期 d 加上一个时间段后的日期 + SQL_FUNCTION_MAP.put("date_format", ""); // DATE_FORMAT(d,f) 按表达式 f的要求显示日期 d + SQL_FUNCTION_MAP.put("date_sub", ""); // DATE_SUB(date,INTERVAL expr type) 函数从日期减去指定的时间间隔。 + SQL_FUNCTION_MAP.put("day", ""); // DAY(d) 返回日期值 d 的日期部分 + SQL_FUNCTION_MAP.put("dayname", ""); // DAYNAME(d) 返回日期 d 是星期几,如 Monday,Tuesday + SQL_FUNCTION_MAP.put("dayofmonth", ""); // DAYOFMONTH(d) 计算日期 d 是本月的第几天 + SQL_FUNCTION_MAP.put("dayofweek", ""); // DAYOFWEEK(d) 日期 d 今天是星期几,1 星期日,2 星期一,以此类推 + SQL_FUNCTION_MAP.put("dayofyear", ""); // DAYOFYEAR(d) 计算日期 d 是本年的第几天 + SQL_FUNCTION_MAP.put("extract", ""); // EXTRACT(type FROM d) 从日期 d 中获取指定的值,type 指定返回的值。 + SQL_FUNCTION_MAP.put("from_days", ""); // FROM_DAYS(n) 计算从 0000 年 1 月 1 日开始 n 天后的日期 + SQL_FUNCTION_MAP.put("hour", ""); // 'HOUR(t) 返回 t 中的小时值 + SQL_FUNCTION_MAP.put("last_day", ""); // LAST_DAY(d) 返回给给定日期的那一月份的最后一天 + SQL_FUNCTION_MAP.put("localtime", ""); // LOCALTIME() 返回当前日期和时间 + SQL_FUNCTION_MAP.put("localtimestamp", ""); // LOCALTIMESTAMP() 返回当前日期和时间 + SQL_FUNCTION_MAP.put("makedate", ""); // MAKEDATE(year, day-of-year) 基于给定参数年份 year 和所在年中的天数序号 day-of-year 返回一个日期 + SQL_FUNCTION_MAP.put("maketime", ""); // MAKETIME(hour, minute, second) 组合时间,参数分别为小时、分钟、秒 + SQL_FUNCTION_MAP.put("microsecond", ""); // MICROSECOND(date) 返回日期参数所对应的微秒数 + SQL_FUNCTION_MAP.put("minute", ""); // MINUTE(t) 返回 t 中的分钟值 + SQL_FUNCTION_MAP.put("monthname", ""); // MONTHNAME(d) 返回日期当中的月份名称,如 November + SQL_FUNCTION_MAP.put("month", ""); // MONTH(d) 返回日期d中的月份值,1 到 12 + SQL_FUNCTION_MAP.put("now", ""); // NOW() 返回当前日期和时间 + SQL_FUNCTION_MAP.put("period_add", ""); // PERIOD_ADD(period, number) 为 年-月 组合日期添加一个时段 + SQL_FUNCTION_MAP.put("period_diff", ""); // PERIOD_DIFF(period1, period2) 返回两个时段之间的月份差值 + SQL_FUNCTION_MAP.put("quarter", ""); // QUARTER(d) 返回日期d是第几季节,返回 1 到 4 + SQL_FUNCTION_MAP.put("second", ""); // SECOND(t) 返回 t 中的秒钟值 + SQL_FUNCTION_MAP.put("sec_to_time", ""); // SEC_TO_TIME", ""); // ) 将以秒为单位的时间 s 转换为时分秒的格式 + SQL_FUNCTION_MAP.put("str_to_date", ""); // STR_TO_DATE", ""); // tring, format_mask) 将字符串转变为日期 + SQL_FUNCTION_MAP.put("subdate", ""); // SUBDATE(d,n) 日期 d 减去 n 天后的日期 + SQL_FUNCTION_MAP.put("subtime", ""); // SUBTIME(t,n) 时间 t 减去 n 秒的时间 + SQL_FUNCTION_MAP.put("sysdate", ""); // SYSDATE() 返回当前日期和时间 + SQL_FUNCTION_MAP.put("time", ""); // TIME(expression) 提取传入表达式的时间部分 + SQL_FUNCTION_MAP.put("time_format", ""); // TIME_FORMAT(t,f) 按表达式 f 的要求显示时间 t + SQL_FUNCTION_MAP.put("time_to_sec", ""); // TIME_TO_SEC(t) 将时间 t 转换为秒 + SQL_FUNCTION_MAP.put("timediff", ""); // TIMEDIFF(time1, time2) 计算时间差值 + SQL_FUNCTION_MAP.put("timestamp", ""); // TIMESTAMP(expression, interval) 单个参数时,函数返回日期或日期时间表达式;有2个参数时,将参数加和 + SQL_FUNCTION_MAP.put("to_days", ""); // TO_DAYS(d) 计算日期 d 距离 0000 年 1 月 1 日的天数 + SQL_FUNCTION_MAP.put("week", ""); // WEEK(d) 计算日期 d 是本年的第几个星期,范围是 0 到 53 + SQL_FUNCTION_MAP.put("weekday", ""); // WEEKDAY(d) 日期 d 是星期几,0 表示星期一,1 表示星期二 + SQL_FUNCTION_MAP.put("weekofyear", ""); // WEEKOFYEAR(d) 计算日期 d 是本年的第几个星期,范围是 0 到 53 + SQL_FUNCTION_MAP.put("year", ""); // YEAR(d) 返回年份 + SQL_FUNCTION_MAP.put("yearweek", ""); // YEARWEEK(date, mode) 返回年份及第几周(0到53),mode 中 0 表示周天,1表示周一,以此类推 + SQL_FUNCTION_MAP.put("unix_timestamp", ""); // UNIX_TIMESTAMP(date) 获取UNIX时间戳函数,返回一个以 UNIX 时间戳为基础的无符号整数 + SQL_FUNCTION_MAP.put("from_unixtime", ""); // FROM_UNIXTIME(date) 将 UNIX 时间戳转换为时间格式,与UNIX_TIMESTAMP互为反函数 + + // MYSQL JSON 函数 + SQL_FUNCTION_MAP.put("json_append", ""); // JSON_APPEND(json_doc, path, val[, path, val] ...)) 插入JSON数组 + SQL_FUNCTION_MAP.put("json_array", ""); // JSON_ARRAY(val1, val2...) 创建JSON数组 + SQL_FUNCTION_MAP.put("json_array_append", ""); // JSON_ARRAY_APPEND(json_doc, val) 将数据附加到JSON文档 + SQL_FUNCTION_MAP.put("json_array_insert", ""); // JSON_ARRAY_INSERT(json_doc, val) 插入JSON数组 + SQL_FUNCTION_MAP.put("json_contains", ""); // JSON_CONTAINS(json_doc, val) JSON文档是否在路径中包含特定对象 + SQL_FUNCTION_MAP.put("json_contains_path", ""); // JSON_CONTAINS_PATH(json_doc, path) JSON文档是否在路径中包含任何数据 + SQL_FUNCTION_MAP.put("json_depth", ""); // JSON_DEPTH(json_doc) JSON文档的最大深度 + SQL_FUNCTION_MAP.put("json_extract", ""); // JSON_EXTRACT(json_doc, path) 从JSON文档返回数据 + SQL_FUNCTION_MAP.put("json_insert", ""); // JSON_INSERT(json_doc, val) 将数据插入JSON文档 + SQL_FUNCTION_MAP.put("json_keys", ""); // JSON_KEYS(json_doc[, path]) JSON文档中的键数组 + SQL_FUNCTION_MAP.put("json_length", ""); // JSON_LENGTH(json_doc) JSON文档中的元素数 + SQL_FUNCTION_MAP.put("json_merge", ""); // JSON_MERGE(json_doc1, json_doc2) (已弃用) 合并JSON文档,保留重复的键。JSON_MERGE_PRESERVE()的已弃用同义词 + SQL_FUNCTION_MAP.put("json_merge_patch", ""); // JSON_MERGE_PATCH(json_doc1, json_doc2) 合并JSON文档,替换重复键的值 + SQL_FUNCTION_MAP.put("json_merge_preserve", ""); // JSON_MERGE_PRESERVE(json_doc1, json_doc2) 合并JSON文档,保留重复的键 + SQL_FUNCTION_MAP.put("json_object", ""); // JSON_OBJECT(key1, val1, key2, val2...) 创建JSON对象 + SQL_FUNCTION_MAP.put("json_overlaps", ""); // JSON_OVERLAPS(json_doc1, json_doc2) (引入8.0.17) 比较两个JSON文档,如果它们具有相同的键值对或数组元素,则返回TRUE(1),否则返回FALSE(0) + SQL_FUNCTION_MAP.put("json_pretty", ""); // JSON_PRETTY(json_doc) 以易于阅读的格式打印JSON文档 + SQL_FUNCTION_MAP.put("json_quote", ""); // JSON_QUOTE(json_doc1) 引用JSON文档 + SQL_FUNCTION_MAP.put("json_remove", ""); // JSON_REMOVE(json_doc1, path) 从JSON文档中删除数据 + SQL_FUNCTION_MAP.put("json_replace", ""); // JSON_REPLACE(json_doc1, val1, val2) 替换JSON文档中的值 + SQL_FUNCTION_MAP.put("json_schema_valid", ""); // JSON_SCHEMA_VALID(json_doc) (引入8.0.17) 根据JSON模式验证JSON文档;如果文档针对架构进行验证,则返回TRUE / 1;否则,则返回FALSE / 0 + SQL_FUNCTION_MAP.put("json_schema_validation_report", ""); // JSON_SCHEMA_VALIDATION_REPORT(json_doc, mode) (引入8.0.17) 根据JSON模式验证JSON文档;以JSON格式返回有关验证结果的报告,包括成功或失败以及失败原因 + SQL_FUNCTION_MAP.put("json_search", ""); // JSON_SEARCH(json_doc, val) JSON文档中值的路径 + SQL_FUNCTION_MAP.put("json_set", ""); // JSON_SET(json_doc, val) 将数据插入JSON文档 + // SQL_FUNCTION_MAP.put("json_storage_free", ""); // JSON_STORAGE_FREE() 部分更新后,JSON列值的二进制表示形式中的可用空间 + // SQL_FUNCTION_MAP.put("json_storage_size", ""); // JSON_STORAGE_SIZE() 用于存储JSON文档的二进制表示的空间 + SQL_FUNCTION_MAP.put("json_table", ""); // JSON_TABLE() 从JSON表达式返回数据作为关系表 + SQL_FUNCTION_MAP.put("json_type", ""); // JSON_TYPE(json_doc) JSON值类型 + SQL_FUNCTION_MAP.put("json_unquote", ""); // JSON_UNQUOTE(json_doc) 取消引用JSON值 + SQL_FUNCTION_MAP.put("json_valid", ""); // JSON_VALID(json_doc) JSON值是否有效 + SQL_FUNCTION_MAP.put("json_arrayagg", ""); // JSON_ARRAYAGG(key) 将每个表达式转换为 JSON 值,然后返回一个包含这些 JSON 值的 JSON 数组 + SQL_FUNCTION_MAP.put("json_objectagg", ""); // JSON_OBJECTAGG(key, val)) 将每个表达式转换为 JSON 值,然后返回一个包含这些 JSON 值的 JSON 对象 + + // MySQL 高级函数 + // SQL_FUNCTION_MAP.put("bin", ""); // BIN(x) 返回 x 的二进制编码 + // SQL_FUNCTION_MAP.put("binary", ""); // BINARY(s) 将字符串 s 转换为二进制字符串 + SQL_FUNCTION_MAP.put("case", ""); // CASE 表示函数开始,END 表示函数结束。如果 condition1 成立,则返回 result1, 如果 condition2 成立,则返回 result2,当全部不成立则返回 result,而当有一个成立之后,后面的就不执行了。 + SQL_FUNCTION_MAP.put("cast", ""); // CAST(x AS type) 转换数据类型 + SQL_FUNCTION_MAP.put("coalesce", ""); // COALESCE(expr1, expr2, ...., expr_n) 返回参数中的第一个非空表达式(从左向右) + // SQL_FUNCTION_MAP.put("conv", ""); // CONV(x,f1,f2) 返回 f1 进制数变成 f2 进制数 + // SQL_FUNCTION_MAP.put("convert", ""); // CONVERT(s, cs) 函数将字符串 s 的字符集变成 cs + SQL_FUNCTION_MAP.put("if", ""); // IF(expr,v1,v2) 如果表达式 expr 成立,返回结果 v1;否则,返回结果 v2。 + SQL_FUNCTION_MAP.put("ifnull", ""); // IFNULL(v1,v2) 如果 v1 的值不为 NULL,则返回 v1,否则返回 v2。 + SQL_FUNCTION_MAP.put("isnull", ""); // ISNULL(expression) 判断表达式是否为 NULL + SQL_FUNCTION_MAP.put("nullif", ""); // NULLIF(expr1, expr2) 比较两个字符串,如果字符串 expr1 与 expr2 相等 返回 NULL,否则返回 expr1 + SQL_FUNCTION_MAP.put("group_concat", ""); // GROUP_CONCAT([DISTINCT], s1, s2...) 聚合拼接字符串 + SQL_FUNCTION_MAP.put("match", ""); // MATCH (name,tag) AGAINST ('a b' IN NATURAL LANGUAGE MODE) 全文检索 + + + + + + //clickhouse 字符串函数 注释的函数表示返回的格式暂时不支持,如:返回数组 ,同时包含因版本不同 clickhosue不支持的函数,版本 + SQL_FUNCTION_MAP.put("empty", ""); // empty(s) 对于空字符串s返回1,对于非空字符串返回0 + SQL_FUNCTION_MAP.put("notEmpty", ""); //notEmpty(s) 对于空字符串返回0,对于非空字符串返回1。 + SQL_FUNCTION_MAP.put("lengthUTF8", ""); //假定字符串以UTF-8编码组成的文本,返回此字符串的Unicode字符长度。如果传入的字符串不是UTF-8编码,则函数可能返回一个预期外的值 + SQL_FUNCTION_MAP.put("lcase", ""); //将字符串中的ASCII转换为小写 + SQL_FUNCTION_MAP.put("ucase", ""); //将字符串中的ASCII转换为大写。 + SQL_FUNCTION_MAP.put("lowerUTF8", ""); //将字符串转换为小写,函数假设字符串是以UTF-8编码文本的字符集。 + SQL_FUNCTION_MAP.put("upperUTF8", ""); //将字符串转换为大写,函数假设字符串是以UTF-8编码文本的字符集。 + SQL_FUNCTION_MAP.put("isValidUTF8", ""); // 检查字符串是否为有效的UTF-8编码,是则返回1,否则返回0。 + SQL_FUNCTION_MAP.put("toValidUTF8", "");//用�(U+FFFD)字符替换无效的UTF-8字符。所有连续的无效字符都会被替换为一个替换字符。 + SQL_FUNCTION_MAP.put("reverseUTF8", "");//以Unicode字符为单位反转UTF-8编码的字符串。 + SQL_FUNCTION_MAP.put("concatAssumeInjective", ""); // concatAssumeInjective(s1, s2, …) 与concat相同,区别在于,你需要保证concat(s1, s2, s3) -> s4是单射的,它将用于GROUP BY的优化。 + SQL_FUNCTION_MAP.put("substringUTF8", ""); // substringUTF8(s,offset,length)¶ 与’substring’相同,但其操作单位为Unicode字符,函数假设字符串是以UTF-8进行编码的文本。如果不是则可能返回一个预期外的结果(不会抛出异常)。 + SQL_FUNCTION_MAP.put("appendTrailingCharIfAbsent", ""); // appendTrailingCharIfAbsent(s,c) 如果’s’字符串非空并且末尾不包含’c’字符,则将’c’字符附加到末尾 + SQL_FUNCTION_MAP.put("convertCharset", ""); // convertCharset(s,from,to) 返回从’from’中的编码转换为’to’中的编码的字符串’s’。 + SQL_FUNCTION_MAP.put("base64Encode", ""); // base64Encode(s) 将字符串’s’编码成base64 + SQL_FUNCTION_MAP.put("base64Decode", ""); //base64Decode(s) 使用base64将字符串解码成原始字符串。如果失败则抛出异常。 + SQL_FUNCTION_MAP.put("tryBase64Decode", ""); //tryBase64Decode(s) 使用base64将字符串解码成原始字符串。但如果出现错误,将返回空字符串。 + SQL_FUNCTION_MAP.put("endsWith", ""); //endsWith(s,后缀) 返回是否以指定的后缀结尾。如果字符串以指定的后缀结束,则返回1,否则返回0。 + SQL_FUNCTION_MAP.put("startsWith", ""); //startsWith(s,前缀) 返回是否以指定的前缀开头。如果字符串以指定的前缀开头,则返回1,否则返回0。 + SQL_FUNCTION_MAP.put("trimLeft", ""); //trimLeft(s)返回一个字符串,用于删除左侧的空白字符。 + SQL_FUNCTION_MAP.put("trimRight", ""); //trimRight(s) 返回一个字符串,用于删除右侧的空白字符。 + SQL_FUNCTION_MAP.put("trimBoth", ""); //trimBoth(s),用于删除任一侧的空白字符 + SQL_FUNCTION_MAP.put("extractAllGroups", ""); //extractAllGroups(text, regexp) 从正则表达式匹配的非重叠子字符串中提取所有组 + // SQL_FUNCTION_MAP.put("leftPad", ""); //leftPad('string', 'length'[, 'pad_string']) 用空格或指定的字符串从左边填充当前字符串(如果需要,可以多次),直到得到的字符串达到给定的长度 + // SQL_FUNCTION_MAP.put("leftPadUTF8", ""); //leftPadUTF8('string','length'[, 'pad_string']) 用空格或指定的字符串从左边填充当前字符串(如果需要,可以多次),直到得到的字符串达到给定的长度 + // SQL_FUNCTION_MAP.put("rightPad", ""); // rightPad('string', 'length'[, 'pad_string']) 用空格或指定的字符串(如果需要,可以多次)从右边填充当前字符串,直到得到的字符串达到给定的长度 + // SQL_FUNCTION_MAP.put("rightPadUTF8", "");// rightPadUTF8('string','length'[, 'pad_string']) 用空格或指定的字符串(如果需要,可以多次)从右边填充当前字符串,直到得到的字符串达到给定的长度。 + SQL_FUNCTION_MAP.put("normalizeQuery", ""); //normalizeQuery(x) 用占位符替换文字、文字序列和复杂的别名。 + SQL_FUNCTION_MAP.put("normalizedQueryHash", ""); //normalizedQueryHash(x) 为类似查询返回相同的64位散列值,但不包含文字值。有助于对查询日志进行分析 + SQL_FUNCTION_MAP.put("positionUTF8", ""); // positionUTF8(s, needle[, start_pos]) 返回在字符串中找到的子字符串的位置(以Unicode点表示),从1开始。 + SQL_FUNCTION_MAP.put("multiSearchFirstIndex", ""); //multiSearchFirstIndex(s, [needle1, needle2, …, needlen]) 返回字符串s中最左边的needlei的索引i(从1开始),否则返回0 + SQL_FUNCTION_MAP.put("multiSearchAny", ""); // multiSearchAny(s, [needle1, needle2, …, needlen])如果至少有一个字符串needlei匹配字符串s,则返回1,否则返回0。 + SQL_FUNCTION_MAP.put("match", ""); //match(s, pattern) 检查字符串是否与模式正则表达式匹配。re2正则表达式。re2正则表达式的语法比Perl正则表达式的语法更有局限性。 + SQL_FUNCTION_MAP.put("multiMatchAny", ""); //multiMatchAny(s, [pattern1, pattern2, …, patternn]) 与match相同,但是如果没有匹配的正则表达式返回0,如果有匹配的模式返回1 + SQL_FUNCTION_MAP.put("multiMatchAnyIndex", ""); //multiMatchAnyIndex(s, [pattern1, pattern2, …, patternn]) 与multiMatchAny相同,但返回与干堆匹配的任何索引 + SQL_FUNCTION_MAP.put("extract", ""); // extract(s, pattern) 使用正则表达式提取字符串的片段 + SQL_FUNCTION_MAP.put("extractAll", ""); //extractAll(s, pattern) 使用正则表达式提取字符串的所有片段 + SQL_FUNCTION_MAP.put("like", ""); //like(s, pattern) 检查字符串是否与简单正则表达式匹配 + SQL_FUNCTION_MAP.put("notLike", "");// 和‘like’是一样的,但是是否定的 + SQL_FUNCTION_MAP.put("countSubstrings", ""); //countSubstrings(s, needle[, start_pos])返回子字符串出现的次数 + SQL_FUNCTION_MAP.put("countMatches", ""); //返回干s中的正则表达式匹配数。countMatches(s, pattern) + SQL_FUNCTION_MAP.put("replaceOne", ""); //replaceOne(s, pattern, replacement)将' s '中的' pattern '子串的第一个出现替换为' replacement '子串。 + + SQL_FUNCTION_MAP.put("replaceAll", ""); //replaceAll(s, pattern, replacement)/用' replacement '子串替换' s '中所有出现的' pattern '子串 + SQL_FUNCTION_MAP.put("replaceRegexpOne", ""); //replaceRegexpOne(s, pattern, replacement)使用' pattern '正则表达式进行替换 + SQL_FUNCTION_MAP.put("replaceRegexpAll", ""); //replaceRegexpAll(s, pattern, replacement) + SQL_FUNCTION_MAP.put("regexpQuoteMeta", ""); //regexpQuoteMeta(s)该函数在字符串中某些预定义字符之前添加一个反斜杠 + + //clickhouse日期函数 + SQL_FUNCTION_MAP.put("toYear", ""); //将Date或DateTime转换为包含年份编号(AD)的UInt16类型的数字。 + SQL_FUNCTION_MAP.put("toQuarter", ""); //将Date或DateTime转换为包含季度编号的UInt8类型的数字。 + SQL_FUNCTION_MAP.put("toMonth", ""); //Date或DateTime转换为包含月份编号(1-12)的UInt8类型的数字。 + SQL_FUNCTION_MAP.put("toDayOfYear", ""); //将Date或DateTime转换为包含一年中的某一天的编号的UInt16(1-366)类型的数字。 + SQL_FUNCTION_MAP.put("toDayOfMonth", "");//将Date或DateTime转换为包含一月中的某一天的编号的UInt8(1-31)类型的数字。 + SQL_FUNCTION_MAP.put("toDayOfWeek", ""); //将Date或DateTime转换为包含一周中的某一天的编号的UInt8(周一是1, 周日是7)类型的数字。 + SQL_FUNCTION_MAP.put("toHour", ""); //将DateTime转换为包含24小时制(0-23)小时数的UInt8数字。 + SQL_FUNCTION_MAP.put("toMinute", ""); //将DateTime转换为包含一小时中分钟数(0-59)的UInt8数字。 + SQL_FUNCTION_MAP.put("toSecond", ""); //将DateTime转换为包含一分钟中秒数(0-59)的UInt8数字。 + SQL_FUNCTION_MAP.put("toUnixTimestamp", ""); // 对于DateTime参数:将值转换为UInt32类型的数字-Unix时间戳 + SQL_FUNCTION_MAP.put("toStartOfYear", ""); //将Date或DateTime向前取整到本年的第一天。 + SQL_FUNCTION_MAP.put("toStartOfISOYear", ""); // 将Date或DateTime向前取整到ISO本年的第一天。 + SQL_FUNCTION_MAP.put("toStartOfQuarter", "");//将Date或DateTime向前取整到本季度的第一天。 + SQL_FUNCTION_MAP.put("toStartOfMonth", ""); //将Date或DateTime向前取整到本月的第一天。 + SQL_FUNCTION_MAP.put("toMonday", ""); //将Date或DateTime向前取整到本周的星期 + SQL_FUNCTION_MAP.put("toStartOfWeek", ""); //按mode将Date或DateTime向前取整到最近的星期日或星期一。 + SQL_FUNCTION_MAP.put("toStartOfDay", ""); //将DateTime向前取整到今天的开始。 + SQL_FUNCTION_MAP.put("toStartOfHour", ""); //将DateTime向前取整到当前小时的开始。 + SQL_FUNCTION_MAP.put("toStartOfMinute", ""); //将DateTime向前取整到当前分钟的开始。 + SQL_FUNCTION_MAP.put("toStartOfSecond", ""); //将DateTime向前取整到当前秒数的开始。 + SQL_FUNCTION_MAP.put("toStartOfFiveMinute", "");//将DateTime以五分钟为单位向前取整到最接近的时间点。 + SQL_FUNCTION_MAP.put("toStartOfTenMinutes", ""); //将DateTime以十分钟为单位向前取整到最接近的时间点。 + SQL_FUNCTION_MAP.put("toStartOfFifteenMinutes", ""); //将DateTime以十五分钟为单位向前取整到最接近的时间点。 + SQL_FUNCTION_MAP.put("toStartOfInterval", ""); // + SQL_FUNCTION_MAP.put("toTime", ""); //将DateTime中的日期转换为一个固定的日期,同时保留时间部分。 + SQL_FUNCTION_MAP.put("toISOYear", ""); //将Date或DateTime转换为包含ISO年份的UInt16类型的编号。 + SQL_FUNCTION_MAP.put("toISOWeek", ""); // + SQL_FUNCTION_MAP.put("toWeek", "");// 返回Date或DateTime的周数。 + SQL_FUNCTION_MAP.put("toYearWeek", ""); //返回年和周的日期 + SQL_FUNCTION_MAP.put("date_trunc", ""); //截断日期和时间数据到日期的指定部分 + SQL_FUNCTION_MAP.put("date_diff", ""); //回两个日期或带有时间值的日期之间的差值。 + + SQL_FUNCTION_MAP.put("yesterday", ""); //不接受任何参数并在请求执行时的某一刻返回昨天的日期(Date)。 + SQL_FUNCTION_MAP.put("today", ""); //不接受任何参数并在请求执行时的某一刻返回当前日期(Date)。 + SQL_FUNCTION_MAP.put("timeSlot", ""); //将时间向前取整半小时。 + SQL_FUNCTION_MAP.put("toYYYYMM", ""); // + SQL_FUNCTION_MAP.put("toYYYYMMDD", "");// + SQL_FUNCTION_MAP.put("toYYYYMMDDhhmmss", ""); // + SQL_FUNCTION_MAP.put("addYears", ""); // Function adds a Date/DateTime interval to a Date/DateTime and then return the Date/DateTime + SQL_FUNCTION_MAP.put("addMonths", ""); //同上 + SQL_FUNCTION_MAP.put("addWeeks", ""); //同上 + SQL_FUNCTION_MAP.put("addDays", ""); //同上 + SQL_FUNCTION_MAP.put("addHours", ""); //同上 + SQL_FUNCTION_MAP.put("addMinutes", "");//同上 + SQL_FUNCTION_MAP.put("addSeconds", ""); //同上 + SQL_FUNCTION_MAP.put("addQuarters", ""); //同上 + SQL_FUNCTION_MAP.put("subtractYears", ""); //Function subtract a Date/DateTime interval to a Date/DateTime and then return the Date/DateTime + SQL_FUNCTION_MAP.put("subtractMonths", ""); //同上 + SQL_FUNCTION_MAP.put("subtractWeeks", ""); //同上 + SQL_FUNCTION_MAP.put("subtractDays", ""); //同上 + SQL_FUNCTION_MAP.put("subtractours", "");//同上 + SQL_FUNCTION_MAP.put("subtractMinutes", ""); //同上 + SQL_FUNCTION_MAP.put("subtractSeconds", ""); //同上 + SQL_FUNCTION_MAP.put("subtractQuarters", ""); //同上 + SQL_FUNCTION_MAP.put("formatDateTime", ""); //函数根据给定的格式字符串来格式化时间 + SQL_FUNCTION_MAP.put("timestamp_add", ""); //使用提供的日期或日期时间值添加指定的时间值。 + SQL_FUNCTION_MAP.put("timestamp_sub", ""); //从提供的日期或带时间的日期中减去时间间隔。 + + //clickhouse json函数 + SQL_FUNCTION_MAP.put("visitParamHas", ""); //visitParamHas(params, name)检查是否存在«name»名称的字段 + SQL_FUNCTION_MAP.put("visitParamExtractUInt", ""); //visitParamExtractUInt(params, name)将名为«name»的字段的值解析成UInt64。 + SQL_FUNCTION_MAP.put("visitParamExtractInt", ""); //与visitParamExtractUInt相同,但返回Int64。 + SQL_FUNCTION_MAP.put("visitParamExtractFloat", ""); //与visitParamExtractUInt相同,但返回Float64。 + SQL_FUNCTION_MAP.put("visitParamExtractBool", "");//解析true/false值。其结果是UInt8类型的。 + SQL_FUNCTION_MAP.put("visitParamExtractRaw", ""); //返回字段的值,包含空格符。 + SQL_FUNCTION_MAP.put("visitParamExtractString", ""); //使用双引号解析字符串。这个值没有进行转义。如果转义失败,它将返回一个空白字符串。 + SQL_FUNCTION_MAP.put("JSONHas", ""); //如果JSON中存在该值,则返回1。 + SQL_FUNCTION_MAP.put("JSONLength", ""); //返回JSON数组或JSON对象的长度。 + SQL_FUNCTION_MAP.put("JSONType", ""); //返回JSON值的类型。 + SQL_FUNCTION_MAP.put("JSONExtractUInt", ""); //解析JSON并提取值。这些函数类似于visitParam*函数。 + SQL_FUNCTION_MAP.put("JSONExtractInt", ""); // + SQL_FUNCTION_MAP.put("JSONExtractFloat", ""); // + SQL_FUNCTION_MAP.put("JSONExtractBool", ""); // + SQL_FUNCTION_MAP.put("JSONExtractString", ""); //解析JSON并提取字符串。此函数类似于visitParamExtractString函数。 + SQL_FUNCTION_MAP.put("JSONExtract", "");//解析JSON并提取给定ClickHouse数据类型的值。 + SQL_FUNCTION_MAP.put("JSONExtractKeysAndValues", ""); //从JSON中解析键值对,其中值是给定的ClickHouse数据类型 + SQL_FUNCTION_MAP.put("JSONExtractRaw", ""); //返回JSON的部分。 + SQL_FUNCTION_MAP.put("toJSONString", ""); // + + //clickhouse 类型转换函数 + SQL_FUNCTION_MAP.put("toInt8", ""); //toInt8(expr) 转换一个输入值为Int类型 + SQL_FUNCTION_MAP.put("toInt16", ""); + SQL_FUNCTION_MAP.put("toInt32", ""); + SQL_FUNCTION_MAP.put("toInt64", ""); + SQL_FUNCTION_MAP.put("toInt8OrZero", ""); //toInt(8|16|32|64)OrZero 这个函数需要一个字符类型的入参,然后尝试把它转为Int (8 | 16 | 32 | 64),如果转换失败直接返回0。 + SQL_FUNCTION_MAP.put("toInt16OrZero", ""); + SQL_FUNCTION_MAP.put("toInt32OrZero", ""); + SQL_FUNCTION_MAP.put("toInt64OrZero", ""); + SQL_FUNCTION_MAP.put("toInt8OrNull", "");//toInt(8|16|32|64)OrNull 这个函数需要一个字符类型的入参,然后尝试把它转为Int (8 | 16 | 32 | 64),如果转换失败直接返回NULL + SQL_FUNCTION_MAP.put("toInt16OrNull", ""); + SQL_FUNCTION_MAP.put("toInt32OrNull", ""); + SQL_FUNCTION_MAP.put("toInt64OrNull", ""); + SQL_FUNCTION_MAP.put("toUInt8", ""); //toInt8(expr) 转换一个输入值为Int类型 + SQL_FUNCTION_MAP.put("toUInt16", ""); + SQL_FUNCTION_MAP.put("toUInt32", ""); + SQL_FUNCTION_MAP.put("toUInt64", ""); + SQL_FUNCTION_MAP.put("toUInt8OrZero", ""); //toInt(8|16|32|64)OrZero 这个函数需要一个字符类型的入参,然后尝试把它转为Int (8 | 16 | 32 | 64),如果转换失败直接返回0。 + SQL_FUNCTION_MAP.put("toUInt16OrZero", ""); + SQL_FUNCTION_MAP.put("toUInt32OrZero", ""); + SQL_FUNCTION_MAP.put("toUInt64OrZero", ""); + SQL_FUNCTION_MAP.put("toUInt8OrNull", "");//toInt(8|16|32|64)OrNull 这个函数需要一个字符类型的入参,然后尝试把它转为Int (8 | 16 | 32 | 64),如果转换失败直接返回NULL + SQL_FUNCTION_MAP.put("toUInt16OrNull", ""); + SQL_FUNCTION_MAP.put("toUInt32OrNull", ""); + SQL_FUNCTION_MAP.put("toUInt64OrNull", ""); + + SQL_FUNCTION_MAP.put("toFloat32", ""); + SQL_FUNCTION_MAP.put("toFloat64", ""); + SQL_FUNCTION_MAP.put("toFloat32OrZero", ""); + SQL_FUNCTION_MAP.put("toFloat64OrZero", ""); + SQL_FUNCTION_MAP.put("toFloat32OrNull", ""); + SQL_FUNCTION_MAP.put("toFloat64OrNull", ""); + + SQL_FUNCTION_MAP.put("toDate", ""); // + SQL_FUNCTION_MAP.put("toDateOrZero", ""); //toInt16(expr) + SQL_FUNCTION_MAP.put("toDateOrNull", ""); //toInt32(expr) + SQL_FUNCTION_MAP.put("toDateTimeOrZero", ""); //toInt64(expr) + SQL_FUNCTION_MAP.put("toDateTimeOrNull", ""); //toInt(8|16|32|64)OrZero 这个函数需要一个字符类型的入参,然后尝试把它转为Int (8 | 16 | 32 | 64),如果转换失败直接返回0。 + + SQL_FUNCTION_MAP.put("toDecimal32", ""); + SQL_FUNCTION_MAP.put("toFixedString", ""); // 将String类型的参数转换为FixedString(N)类型的值 + SQL_FUNCTION_MAP.put("toStringCutToZero", ""); // 接受String或FixedString参数,返回String,其内容在找到的第一个零字节处被截断。 + SQL_FUNCTION_MAP.put("toDecimal256", ""); + SQL_FUNCTION_MAP.put("toDecimal32OrNull", ""); + SQL_FUNCTION_MAP.put("toDecimal64OrNull", ""); + SQL_FUNCTION_MAP.put("toDecimal128OrNull", ""); + SQL_FUNCTION_MAP.put("toDecimal256OrNull", ""); + SQL_FUNCTION_MAP.put("toDecimal32OrZero", ""); + SQL_FUNCTION_MAP.put("toDecimal64OrZero", ""); + SQL_FUNCTION_MAP.put("toDecimal128OrZero", ""); + SQL_FUNCTION_MAP.put("toDecimal256OrZero", ""); + + + SQL_FUNCTION_MAP.put("toIntervalSecond", ""); //把一个数值类型的值转换为Interval类型的数据。 + SQL_FUNCTION_MAP.put("toIntervalMinute", ""); + SQL_FUNCTION_MAP.put("toIntervalHour", ""); + SQL_FUNCTION_MAP.put("toIntervalDay", ""); + SQL_FUNCTION_MAP.put("toIntervalWeek", ""); + SQL_FUNCTION_MAP.put("toIntervalMonth", ""); + SQL_FUNCTION_MAP.put("toIntervalQuarter", ""); + SQL_FUNCTION_MAP.put("toIntervalYear", ""); + SQL_FUNCTION_MAP.put("parseDateTimeBestEffort", ""); //把String类型的时间日期转换为DateTime数据类型。 + SQL_FUNCTION_MAP.put("parseDateTimeBestEffortOrNull", ""); + SQL_FUNCTION_MAP.put("parseDateTimeBestEffortOrZero", ""); + SQL_FUNCTION_MAP.put("toLowCardinality", ""); + + + + ////clickhouse hash函数 + SQL_FUNCTION_MAP.put("halfMD5", ""); //计算字符串的MD5。然后获取结果的前8个字节并将它们作为UInt64(大端)返回 + SQL_FUNCTION_MAP.put("MD5", ""); //计算字符串的MD5并将结果放入FixedString(16)中返回 + + //clickhouse ip地址函数 + SQL_FUNCTION_MAP.put("IPv4NumToString", ""); //接受一个UInt32(大端)表示的IPv4的地址,返回相应IPv4的字符串表现形式,格式为A.B.C.D(以点分割的十进制数字)。 + SQL_FUNCTION_MAP.put("IPv4StringToNum", ""); //与IPv4NumToString函数相反。如果IPv4地址格式无效,则返回0。 + SQL_FUNCTION_MAP.put("IPv6NumToString", ""); //接受FixedString(16)类型的二进制格式的IPv6地址。以文本格式返回此地址的字符串。 + SQL_FUNCTION_MAP.put("IPv6StringToNum", ""); //与IPv6NumToString的相反。如果IPv6地址格式无效,则返回空字节字符串。 + SQL_FUNCTION_MAP.put("IPv4ToIPv6", ""); // 接受一个UInt32类型的IPv4地址,返回FixedString(16)类型的IPv6地址 + SQL_FUNCTION_MAP.put("cutIPv6", ""); //接受一个FixedString(16)类型的IPv6地址,返回一个String,这个String中包含了删除指定位之后的地址的文本格 + SQL_FUNCTION_MAP.put("toIPv4", ""); //IPv4StringToNum()的别名, + SQL_FUNCTION_MAP.put("toIPv6", ""); //IPv6StringToNum()的别名 + SQL_FUNCTION_MAP.put("isIPAddressInRange", ""); //确定一个IP地址是否包含在以CIDR符号表示的网络中 + + //clickhouse Nullable处理函数 + SQL_FUNCTION_MAP.put("isNull", ""); //检查参数是否为NULL。 + SQL_FUNCTION_MAP.put("isNotNull", ""); //检查参数是否不为 NULL. + SQL_FUNCTION_MAP.put("ifNull", ""); //如果第一个参数为«NULL»,则返回第二个参数的值。 + SQL_FUNCTION_MAP.put("assumeNotNull", ""); //将可为空类型的值转换为非Nullable类型的值。 + SQL_FUNCTION_MAP.put("toNullable", ""); //将参数的类型转换为Nullable。 + + //clickhouse UUID函数 + SQL_FUNCTION_MAP.put("generateUUIDv4", ""); // 生成一个UUID + SQL_FUNCTION_MAP.put("toUUID", ""); //toUUID(x) 将String类型的值转换为UUID类型的值。 + + //clickhouse 系统函数 + SQL_FUNCTION_MAP.put("hostName", ""); //hostName()回一个字符串,其中包含执行此函数的主机的名称。 + SQL_FUNCTION_MAP.put("getMacro", ""); //从服务器配置的宏部分获取指定值。 + SQL_FUNCTION_MAP.put("FQDN", "");//返回完全限定的域名。 + SQL_FUNCTION_MAP.put("basename", ""); //提取字符串最后一个斜杠或反斜杠之后的尾随部分 + SQL_FUNCTION_MAP.put("currentUser", ""); //返回当前用户的登录。在分布式查询的情况下,将返回用户的登录,即发起的查询 + SQL_FUNCTION_MAP.put("version", ""); //以字符串形式返回服务器版本。 + SQL_FUNCTION_MAP.put("uptime", "");//以秒为单位返回服务器的正常运行时间。 + + //clickhouse 数学函数 + SQL_FUNCTION_MAP.put("least", ""); //返回a和b中最小的值。 + SQL_FUNCTION_MAP.put("greatest", ""); //返回a和b的最大值。 + SQL_FUNCTION_MAP.put("plus", ""); //plus(a, b), a + b operator¶计算数值的总和。 + SQL_FUNCTION_MAP.put("minus", ""); //minus(a, b), a - b operator 计算数值之间的差,结果总是有符号的。 + SQL_FUNCTION_MAP.put("multiply", "");//multiply(a, b), a * b operator 计算数值的乘积 + SQL_FUNCTION_MAP.put("divide", ""); //divide(a, b), a / b operator 计算数值的商。结果类型始终是浮点类型 + SQL_FUNCTION_MAP.put("intDiv", ""); //intDiv(a,b)计算数值的商,向下舍入取整(按绝对值)。 + SQL_FUNCTION_MAP.put("intDivOrZero", ""); // intDivOrZero(a,b)与’intDiv’的不同之处在于它在除以零或将最小负数除以-1时返回零。 + SQL_FUNCTION_MAP.put("modulo", ""); //modulo(a, b), a % b operator 计算除法后的余数。 + SQL_FUNCTION_MAP.put("moduloOrZero", ""); //和modulo不同之处在于,除以0时结果返回0 + SQL_FUNCTION_MAP.put("negate", ""); //通过改变数值的符号位对数值取反,结果总是有符号 + SQL_FUNCTION_MAP.put("gcd", ""); //gcd(a,b) 返回数值的最大公约数。 + SQL_FUNCTION_MAP.put("lcm", ""); //lcm(a,b) 返回数值的最小公倍数 + SQL_FUNCTION_MAP.put("e", ""); //e() 返回一个接近数学常量e的Float64数字。 + SQL_FUNCTION_MAP.put("pi", ""); //pi() 返回一个接近数学常量π的Float64数字。 + SQL_FUNCTION_MAP.put("exp2", ""); //exp2(x)¶接受一个数值类型的参数并返回它的2的x次幂。 + SQL_FUNCTION_MAP.put("exp10", ""); //exp10(x)¶接受一个数值类型的参数并返回它的10的x次幂。 + SQL_FUNCTION_MAP.put("cbrt", ""); //cbrt(x) 接受一个数值类型的参数并返回它的立方根。 + SQL_FUNCTION_MAP.put("lgamma", ""); //lgamma(x) 返回x的绝对值的自然对数的伽玛函数。 + SQL_FUNCTION_MAP.put("tgamma", ""); //tgamma(x)¶返回x的伽玛函数。 + SQL_FUNCTION_MAP.put("intExp2", ""); //intExp2 接受一个数值类型的参数并返回它的2的x次幂(UInt64) + SQL_FUNCTION_MAP.put("intExp10", ""); //intExp10 接受一个数值类型的参数并返回它的10的x次幂(UInt64)。 + SQL_FUNCTION_MAP.put("cosh", ""); // cosh(x) + SQL_FUNCTION_MAP.put("cosh", ""); //cosh(x) + SQL_FUNCTION_MAP.put("sinh", ""); //sinh(x) + SQL_FUNCTION_MAP.put("asinh", ""); //asinh(x) + SQL_FUNCTION_MAP.put("atanh", ""); //atanh(x) + SQL_FUNCTION_MAP.put("atan2", ""); //atan2(y, x) + SQL_FUNCTION_MAP.put("hypot", ""); //hypot(x, y) + SQL_FUNCTION_MAP.put("log1p", ""); //log1p(x) + SQL_FUNCTION_MAP.put("trunc", ""); //和truncate一样 + SQL_FUNCTION_MAP.put("roundToExp2", ""); //接受一个数字。如果数字小于1,它返回0。 + SQL_FUNCTION_MAP.put("roundDuration", ""); //接受一个数字。如果数字小于1,它返回0。 + SQL_FUNCTION_MAP.put("roundAge", ""); // 接受一个数字。如果数字小于18,它返回0。 + SQL_FUNCTION_MAP.put("roundDown", ""); //接受一个数字并将其舍入到指定数组中的一个元素 + SQL_FUNCTION_MAP.put("bitAnd", ""); //bitAnd(a,b) + SQL_FUNCTION_MAP.put("bitOr", ""); //bitOr(a,b) } @@ -908,7 +908,7 @@ public AbstractSQLConfig setSchema(String schema) { this.schema = schema; return this; } - + @Override public String getDatasource() { return datasource; @@ -918,7 +918,7 @@ public SQLConfig setDatasource(String datasource) { this.datasource = datasource; return this; } - + /**请求传进来的Table名 * @return * @see {@link #getSQLTable()} @@ -1385,15 +1385,15 @@ public String getColumnString() throws Exception { @JSONField(serialize = false) public String getColumnString(boolean inSQLJoin) throws Exception { List column = getColumn(); - + switch (getMethod()) { case HEAD: case HEADS: //StringUtil.isEmpty(column, true) || column.contains(",") 时SQL.count(column)会return "*" if (isPrepared() && column != null) { - + List raw = getRaw(); boolean containRaw = raw != null && raw.contains(KEY_COLUMN); - + String origin; String alias; int index; @@ -1407,7 +1407,7 @@ public String getColumnString(boolean inSQLJoin) throws Exception { continue; } } - + index = c.lastIndexOf(":"); //StringUtil.split返回数组中,子项不会有null origin = index < 0 ? c : c.substring(0, index); alias = index < 0 ? null : c.substring(index + 1); @@ -1423,7 +1423,7 @@ public String getColumnString(boolean inSQLJoin) throws Exception { throw new IllegalArgumentException("HEAD请求: 字符" + origin + " 不合法!预编译模式下 @column:value 中 value里面用 , 分割的每一项" + " column:alias 中 column 必须是1个单词!如果有alias,则alias也必须为1个单词!并且不要有多余的空格!"); } - + if (start > 0 && StringUtil.isName(origin.substring(0, start)) == false) { throw new IllegalArgumentException("HEAD请求: 字符 " + origin.substring(0, start) + " 不合法!预编译模式下 @column:value 中 value里面用 , 分割的每一项" + " column:alias 中 column 必须是1个单词!如果有alias,则alias也必须为1个单词!并且不要有多余的空格!"); @@ -1557,19 +1557,19 @@ public String getColumnPrase(String expression, boolean containRaw) { } else { // FIXME 用括号断开? 如果少的话,用关键词加括号断开,例如 )OVER( 和 )AGAINST( // 窗口函数 rank() OVER (PARTITION BY id ORDER BY userId ASC) // 全文索引 math(name,tag) AGAINST ('a b +c -d' IN NATURALE LANGUAGE MODE) // IN BOOLEAN MODE - + //有函数,但不是窗口函数 int overIndex = expression.indexOf(") OVER ("); int againstIndex = expression.indexOf(") AGAINST ("); boolean containOver = overIndex > 0 && overIndex < expression.length() - ") OVER (".length(); boolean containAgainst = againstIndex > 0 && againstIndex < expression.length() - ") AGAINST (".length(); - + if (containOver && containAgainst) { throw new IllegalArgumentException("字符 " + expression + " 不合法!" + "预编译模式下 @column:\"column0,column1:alias;function0(arg0,arg1,...);function1(...):alias...\"" + " 中 function 必须符合小写英文单词的 SQL 函数名格式!不能同时存在窗口函数关键词 OVER 和全文索引关键词 AGAINST!"); } - + if (containOver == false && containAgainst == false) { int end = expression.lastIndexOf(")"); if (start >= end) { @@ -1596,7 +1596,7 @@ public String getColumnPrase(String expression, boolean containRaw) { if (distinct) { s = s.substring(PREFFIX_DISTINCT.length()); } - + // 解析函数内的参数 String ckeys[] = parseArgsSplitWithComma(s, false, containRaw); @@ -1658,7 +1658,7 @@ public String getColumnPrase(String expression, boolean containRaw) { String argsString2[] = parseArgsSplitWithComma(argString2, false, containRaw); expression = fun + "(" + StringUtil.getString(agrsString1) + (containOver ? ") OVER (" : ") AGAINST (") + StringUtil.getString(argsString2) + ")" + (StringUtil.isEmpty(alias, true) ? "" : " AS " + quote + alias + quote); } } - + return expression; } @@ -1680,7 +1680,7 @@ private String[] parseArgsSplitWithComma(String param, boolean isColumn, boolean int index; for (int i = 0; i < ckeys.length; i++) { String ck = ckeys[i]; - + // 如果参数包含 "'" ,解析字符串 if (ck.contains("'")) { int count = 0; @@ -1749,15 +1749,15 @@ private String[] parseArgsSplitWithComma(String param, boolean isColumn, boolean } else { origin = getValue(origin).toString(); } - + if (isName && isKeyPrefix()) { origin = tableAlias + "." + origin; } - + if (isColumn && StringUtil.isEmpty(alias, true) == false) { origin += " AS " + quote + alias + quote; } - + ckeys[i] = origin; } @@ -1777,13 +1777,13 @@ private String[] parseArgsSplitWithComma(String param, boolean isColumn, boolean private String praseArgsSplitWithSpace(String mkes[]) { String quote = getQuote(); String tableAlias = getAliasWithQuote(); - + // 包含空格的参数 肯定不包含别名 不用处理别名 if (mkes != null && mkes.length > 0) { for (int j = 0; j < mkes.length; j++) { // now()/AS/ DISTINCT/VALUE 等等放在RAW_MAP中 String origin = mkes[j]; - + String mk = RAW_MAP.get(origin); if (mk != null) { // newSQLConfig 提前处理好的 if (mk.length() > 0) { @@ -1800,7 +1800,7 @@ private String praseArgsSplitWithSpace(String mkes[]) { + " 中所有 arg 都必须是1个不以 _ 开头的单词 或者符合正则表达式 " + PATTERN_FUNCTION + " 且不包含连续减号 -- !DISTINCT 必须全大写,且后面必须有且只有 1 个空格!其它情况不允许空格!"); } } - + boolean isName = false; if (StringUtil.isNumer(origin)) { //do nothing @@ -1810,11 +1810,11 @@ private String praseArgsSplitWithSpace(String mkes[]) { } else { origin = getValue(origin).toString(); } - + if (isName && isKeyPrefix()) { origin = tableAlias + "." + origin; } - + mkes[j] = origin; } } @@ -2569,7 +2569,7 @@ public String getSearchString(String key, Object[] values, int type) throws Ille // if (((String) v).contains("%%")) { // 需要通过 %\%% 来模糊搜索 % // throw new IllegalArgumentException(key + "$:value 中 value 值 " + v + " 中包含 %% !不允许有连续的 % !"); // } - + condition += (i <= 0 ? "" : (Logic.isAnd(type) ? AND : OR)) + getLikeString(key, v); } @@ -3182,7 +3182,7 @@ public static String getSQL(AbstractSQLConfig config) throws Exception { } return explain + "SELECT * FROM (SELECT " + (config.getCache() == JSONRequest.CACHE_RAM ? "SQL_NO_CACHE " : "") + column + " FROM " + getConditionString(column, tablePath, config) + ") " + config.getLimitString(); } - + return explain + "SELECT " + (config.getCache() == JSONRequest.CACHE_RAM ? "SQL_NO_CACHE " : "") + column + " FROM " + getConditionString(column, tablePath, config) + config.getLimitString(); } } @@ -3797,7 +3797,7 @@ else if (joinConfig.getDatabase().equals(config.getDatabase()) == false) { if (joinConfig.getSchema() == null) { joinConfig.setSchema(config.getSchema()); //主表 JOIN 副表,默认 schema 一致 } - + if (cacheConfig != null) { cacheConfig.setDatabase(joinConfig.getDatabase()).setSchema(joinConfig.getSchema()); //解决主表 JOIN 副表,引号不一致 } @@ -3967,7 +3967,7 @@ public static interface IdCallback { */ @Deprecated String getIdKey(String database, String schema, String table); - + /**获取主键名 * @param database * @param schema @@ -3984,7 +3984,7 @@ public static interface IdCallback { */ @Deprecated String getUserIdKey(String database, String schema, String table); - + /**获取 User 的主键名 * @param database * @param schema @@ -4024,7 +4024,7 @@ public Object newId(RequestMethod method, String database, String schema, String public String getIdKey(String database, String schema, String table) { return KEY_ID; } - + @Override public String getIdKey(String database, String schema, String datasource, String table) { return getIdKey(database, schema, table); @@ -4034,7 +4034,7 @@ public String getIdKey(String database, String schema, String datasource, String public String getUserIdKey(String database, String schema, String table) { return KEY_USER_ID; } - + @Override public String getUserIdKey(String database, String schema, String datasource, String table) { return getUserIdKey(database, schema, table); diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java index 1a7d0f3f6..760781c3a 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java @@ -158,7 +158,7 @@ public JSONObject execute(@NotNull SQLConfig config, boolean unknowType) throws Log.e(TAG, "execute StringUtil.isEmpty(sql, true) >> return null;"); return null; } - + boolean isExplain = config.isExplain(); boolean isHead = RequestMethod.isHeadMethod(config.getMethod(), true); @@ -216,7 +216,7 @@ public JSONObject execute(@NotNull SQLConfig config, boolean unknowType) throws result.put(config.getIdKey() + "[]", config.getWhere(config.getIdKey() + "{}", true)); } return result; - + case GET: case GETS: case HEAD: @@ -243,7 +243,7 @@ public JSONObject execute(@NotNull SQLConfig config, boolean unknowType) throws } } - + if (isExplain == false && isHead) { if (rs.next() == false) { return AbstractParser.newErrorResult(new SQLException("数据库错误, rs.next() 失败!")); @@ -337,7 +337,7 @@ else if (config.getSQLTable().equalsIgnoreCase(sqlTable) == false) { result.put("list", resultList); return result; } - + if (isHead == false) { // @ APP JOIN 查询副表并缓存到 childMap <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< @@ -370,7 +370,7 @@ else if (config.getSQLTable().equalsIgnoreCase(sqlTable) == false) { result.put(KEY_RAW_LIST, resultList); } } - + long endTime = System.currentTimeMillis(); Log.d(TAG, "\n\n execute endTime = " + endTime + "; duration = " + (endTime - startTime) + "\n return resultList.get(" + position + ");" + "\n>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>\n\n"); From 2107040c96223a7c8ac0e35d895cfcea7537b3ad Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Fri, 24 Sep 2021 03:35:45 +0800 Subject: [PATCH 0473/1181] =?UTF-8?q?=E4=BC=98=E5=8C=96=E5=90=8D=E8=BD=A6?= =?UTF-8?q?=E5=92=8C=E7=A9=BA=E6=A0=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/apijson/orm/AbstractSQLConfig.java | 68 +++++++++---------- 1 file changed, 34 insertions(+), 34 deletions(-) diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java index a9f99b7df..b5a830223 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java @@ -133,35 +133,35 @@ public abstract class AbstractSQLConfig implements SQLConfig { RAW_MAP = new LinkedHashMap<>(); // 保证顺序,避免配置冲突等意外情况 - // mysql关键字 - RAW_MAP.put("AS",""); - RAW_MAP.put("VALUE",""); - RAW_MAP.put("DISTINCT",""); + // MySQL 关键字 + RAW_MAP.put("AS", ""); + RAW_MAP.put("VALUE", ""); + RAW_MAP.put("DISTINCT", ""); //时间 - RAW_MAP.put("DATE",""); - RAW_MAP.put("now()",""); - RAW_MAP.put("DATETIME",""); - RAW_MAP.put("DateTime",""); - RAW_MAP.put("SECOND",""); - RAW_MAP.put("MINUTE",""); - RAW_MAP.put("HOUR",""); - RAW_MAP.put("DAY",""); - RAW_MAP.put("WEEK",""); - RAW_MAP.put("MONTH",""); - RAW_MAP.put("QUARTER",""); - RAW_MAP.put("YEAR",""); - RAW_MAP.put("json",""); - RAW_MAP.put("unit",""); + RAW_MAP.put("DATE", ""); + RAW_MAP.put("now()", ""); + RAW_MAP.put("DATETIME", ""); + RAW_MAP.put("DateTime", ""); + RAW_MAP.put("SECOND", ""); + RAW_MAP.put("MINUTE", ""); + RAW_MAP.put("HOUR", ""); + RAW_MAP.put("DAY", ""); + RAW_MAP.put("WEEK", ""); + RAW_MAP.put("MONTH", ""); + RAW_MAP.put("QUARTER", ""); + RAW_MAP.put("YEAR", ""); + RAW_MAP.put("json", ""); + RAW_MAP.put("unit", ""); //MYSQL 数据类型 BINARY,CHAR,DATETIME,TIME,DECIMAL,SIGNED,UNSIGNED - RAW_MAP.put("BINARY",""); - RAW_MAP.put("SIGNED",""); - RAW_MAP.put("DECIMAL",""); - RAW_MAP.put("BINARY",""); - RAW_MAP.put("UNSIGNED",""); - RAW_MAP.put("CHAR",""); - RAW_MAP.put("TIME",""); + RAW_MAP.put("BINARY", ""); + RAW_MAP.put("SIGNED", ""); + RAW_MAP.put("DECIMAL", ""); + RAW_MAP.put("BINARY", ""); + RAW_MAP.put("UNSIGNED", ""); + RAW_MAP.put("CHAR", ""); + RAW_MAP.put("TIME", ""); //窗口函数关键字 RAW_MAP.put("OVER", ""); @@ -384,7 +384,7 @@ public abstract class AbstractSQLConfig implements SQLConfig { - //clickhouse 字符串函数 注释的函数表示返回的格式暂时不支持,如:返回数组 ,同时包含因版本不同 clickhosue不支持的函数,版本 + //ClickHouse 字符串函数 注释的函数表示返回的格式暂时不支持,如:返回数组 ,同时包含因版本不同 clickhosue不支持的函数,版本 SQL_FUNCTION_MAP.put("empty", ""); // empty(s) 对于空字符串s返回1,对于非空字符串返回0 SQL_FUNCTION_MAP.put("notEmpty", ""); //notEmpty(s) 对于空字符串返回0,对于非空字符串返回1。 SQL_FUNCTION_MAP.put("lengthUTF8", ""); //假定字符串以UTF-8编码组成的文本,返回此字符串的Unicode字符长度。如果传入的字符串不是UTF-8编码,则函数可能返回一个预期外的值 @@ -492,7 +492,7 @@ public abstract class AbstractSQLConfig implements SQLConfig { SQL_FUNCTION_MAP.put("timestamp_add", ""); //使用提供的日期或日期时间值添加指定的时间值。 SQL_FUNCTION_MAP.put("timestamp_sub", ""); //从提供的日期或带时间的日期中减去时间间隔。 - //clickhouse json函数 + //ClickHouse json函数 SQL_FUNCTION_MAP.put("visitParamHas", ""); //visitParamHas(params, name)检查是否存在«name»名称的字段 SQL_FUNCTION_MAP.put("visitParamExtractUInt", ""); //visitParamExtractUInt(params, name)将名为«name»的字段的值解析成UInt64。 SQL_FUNCTION_MAP.put("visitParamExtractInt", ""); //与visitParamExtractUInt相同,但返回Int64。 @@ -513,7 +513,7 @@ public abstract class AbstractSQLConfig implements SQLConfig { SQL_FUNCTION_MAP.put("JSONExtractRaw", ""); //返回JSON的部分。 SQL_FUNCTION_MAP.put("toJSONString", ""); // - //clickhouse 类型转换函数 + //ClickHouse 类型转换函数 SQL_FUNCTION_MAP.put("toInt8", ""); //toInt8(expr) 转换一个输入值为Int类型 SQL_FUNCTION_MAP.put("toInt16", ""); SQL_FUNCTION_MAP.put("toInt32", ""); @@ -581,11 +581,11 @@ public abstract class AbstractSQLConfig implements SQLConfig { - ////clickhouse hash函数 + ////ClickHouse hash函数 SQL_FUNCTION_MAP.put("halfMD5", ""); //计算字符串的MD5。然后获取结果的前8个字节并将它们作为UInt64(大端)返回 SQL_FUNCTION_MAP.put("MD5", ""); //计算字符串的MD5并将结果放入FixedString(16)中返回 - //clickhouse ip地址函数 + //ClickHouse ip地址函数 SQL_FUNCTION_MAP.put("IPv4NumToString", ""); //接受一个UInt32(大端)表示的IPv4的地址,返回相应IPv4的字符串表现形式,格式为A.B.C.D(以点分割的十进制数字)。 SQL_FUNCTION_MAP.put("IPv4StringToNum", ""); //与IPv4NumToString函数相反。如果IPv4地址格式无效,则返回0。 SQL_FUNCTION_MAP.put("IPv6NumToString", ""); //接受FixedString(16)类型的二进制格式的IPv6地址。以文本格式返回此地址的字符串。 @@ -596,18 +596,18 @@ public abstract class AbstractSQLConfig implements SQLConfig { SQL_FUNCTION_MAP.put("toIPv6", ""); //IPv6StringToNum()的别名 SQL_FUNCTION_MAP.put("isIPAddressInRange", ""); //确定一个IP地址是否包含在以CIDR符号表示的网络中 - //clickhouse Nullable处理函数 + //ClickHouse Nullable处理函数 SQL_FUNCTION_MAP.put("isNull", ""); //检查参数是否为NULL。 SQL_FUNCTION_MAP.put("isNotNull", ""); //检查参数是否不为 NULL. SQL_FUNCTION_MAP.put("ifNull", ""); //如果第一个参数为«NULL»,则返回第二个参数的值。 SQL_FUNCTION_MAP.put("assumeNotNull", ""); //将可为空类型的值转换为非Nullable类型的值。 SQL_FUNCTION_MAP.put("toNullable", ""); //将参数的类型转换为Nullable。 - //clickhouse UUID函数 + //ClickHouse UUID函数 SQL_FUNCTION_MAP.put("generateUUIDv4", ""); // 生成一个UUID SQL_FUNCTION_MAP.put("toUUID", ""); //toUUID(x) 将String类型的值转换为UUID类型的值。 - //clickhouse 系统函数 + //ClickHouse 系统函数 SQL_FUNCTION_MAP.put("hostName", ""); //hostName()回一个字符串,其中包含执行此函数的主机的名称。 SQL_FUNCTION_MAP.put("getMacro", ""); //从服务器配置的宏部分获取指定值。 SQL_FUNCTION_MAP.put("FQDN", "");//返回完全限定的域名。 @@ -616,7 +616,7 @@ public abstract class AbstractSQLConfig implements SQLConfig { SQL_FUNCTION_MAP.put("version", ""); //以字符串形式返回服务器版本。 SQL_FUNCTION_MAP.put("uptime", "");//以秒为单位返回服务器的正常运行时间。 - //clickhouse 数学函数 + //ClickHouse 数学函数 SQL_FUNCTION_MAP.put("least", ""); //返回a和b中最小的值。 SQL_FUNCTION_MAP.put("greatest", ""); //返回a和b的最大值。 SQL_FUNCTION_MAP.put("plus", ""); //plus(a, b), a + b operator¶计算数值的总和。 From d46d1f321613d8ee37cd38b84d922ef300328f95 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Fri, 24 Sep 2021 04:06:44 +0800 Subject: [PATCH 0474/1181] =?UTF-8?q?RAW=5FMAP=20=E9=BB=98=E8=AE=A4?= =?UTF-8?q?=E6=94=AF=E6=8C=81=20=E4=B8=8E=E6=88=96=E9=9D=9E=20=E5=92=8C=20?= =?UTF-8?q?IS=20NULL=20=E7=AD=89=E5=85=B3=E9=94=AE=E8=AF=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/main/java/apijson/orm/AbstractSQLConfig.java | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java index b5a830223..396ed23a8 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java @@ -135,6 +135,13 @@ public abstract class AbstractSQLConfig implements SQLConfig { // MySQL 关键字 RAW_MAP.put("AS", ""); + RAW_MAP.put("IS NOT NULL", ""); + RAW_MAP.put("IS NULL", ""); + RAW_MAP.put("IS", ""); + RAW_MAP.put("NULL", ""); + RAW_MAP.put("AND", ""); + RAW_MAP.put("OR", ""); + RAW_MAP.put("NOT", ""); RAW_MAP.put("VALUE", ""); RAW_MAP.put("DISTINCT", ""); From 5c682cbf348d6f7660ece8da6c9556c9a4fed771 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Sat, 25 Sep 2021 00:58:30 +0800 Subject: [PATCH 0475/1181] =?UTF-8?q?=E9=87=8D=E6=9E=84=20enum=20RequestRo?= =?UTF-8?q?le=20=E4=B8=BA=20String=20=E6=96=B9=E4=BE=BF=E7=94=A8=E6=88=B7?= =?UTF-8?q?=E8=87=AA=E5=AE=9A=E4=B9=89=E6=89=A9=E5=B1=95=EF=BC=9B=E5=88=A0?= =?UTF-8?q?=E9=99=A4=E9=83=A8=E5=88=86=E5=B7=B2=E5=BA=9F=E5=BC=83=E7=9A=84?= =?UTF-8?q?=E6=96=B9=E6=B3=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/main/java/apijson/MethodAccess.java | 26 ++-- .../src/main/java/apijson/RequestRole.java | 60 --------- .../main/java/apijson/orm/AbstractParser.java | 11 +- .../java/apijson/orm/AbstractSQLConfig.java | 47 ++----- .../java/apijson/orm/AbstractVerifier.java | 121 +++++++++++------- .../src/main/java/apijson/orm/Parser.java | 3 +- .../src/main/java/apijson/orm/SQLConfig.java | 5 +- .../src/main/java/apijson/orm/Verifier.java | 12 +- .../main/java/apijson/orm/model/Document.java | 4 +- .../java/apijson/orm/model/TestRecord.java | 4 +- 10 files changed, 108 insertions(+), 185 deletions(-) delete mode 100755 APIJSONORM/src/main/java/apijson/RequestRole.java diff --git a/APIJSONORM/src/main/java/apijson/MethodAccess.java b/APIJSONORM/src/main/java/apijson/MethodAccess.java index 3eff1ae3d..31d45843e 100755 --- a/APIJSONORM/src/main/java/apijson/MethodAccess.java +++ b/APIJSONORM/src/main/java/apijson/MethodAccess.java @@ -10,12 +10,12 @@ import java.lang.annotation.Retention; import java.lang.annotation.Target; -import static apijson.RequestRole.ADMIN; -import static apijson.RequestRole.CIRCLE; -import static apijson.RequestRole.CONTACT; -import static apijson.RequestRole.LOGIN; -import static apijson.RequestRole.OWNER; -import static apijson.RequestRole.UNKNOWN; +import static apijson.orm.AbstractVerifier.ADMIN; +import static apijson.orm.AbstractVerifier.CIRCLE; +import static apijson.orm.AbstractVerifier.CONTACT; +import static apijson.orm.AbstractVerifier.LOGIN; +import static apijson.orm.AbstractVerifier.OWNER; +import static apijson.orm.AbstractVerifier.UNKNOWN; import static java.lang.annotation.ElementType.TYPE; import static java.lang.annotation.RetentionPolicy.RUNTIME; @@ -31,36 +31,36 @@ /**@see {@link RequestMethod#GET} * @return 该请求方法允许的角色 default {UNKNOWN, LOGIN, CONTACT, CIRCLE, OWNER, ADMIN}; */ - RequestRole[] GET() default {UNKNOWN, LOGIN, CONTACT, CIRCLE, OWNER, ADMIN}; + String[] GET() default {UNKNOWN, LOGIN, CONTACT, CIRCLE, OWNER, ADMIN}; /**@see {@link RequestMethod#HEAD} * @return 该请求方法允许的角色 default {UNKNOWN, LOGIN, CONTACT, CIRCLE, OWNER, ADMIN}; */ - RequestRole[] HEAD() default {UNKNOWN, LOGIN, CONTACT, CIRCLE, OWNER, ADMIN}; + String[] HEAD() default {UNKNOWN, LOGIN, CONTACT, CIRCLE, OWNER, ADMIN}; /**@see {@link RequestMethod#GETS} * @return 该请求方法允许的角色 default {LOGIN, CONTACT, CIRCLE, OWNER, ADMIN}; */ - RequestRole[] GETS() default {LOGIN, CONTACT, CIRCLE, OWNER, ADMIN}; + String[] GETS() default {LOGIN, CONTACT, CIRCLE, OWNER, ADMIN}; /**@see {@link RequestMethod#HEADS} * @return 该请求方法允许的角色 default {LOGIN, CONTACT, CIRCLE, OWNER, ADMIN}; */ - RequestRole[] HEADS() default {LOGIN, CONTACT, CIRCLE, OWNER, ADMIN}; + String[] HEADS() default {LOGIN, CONTACT, CIRCLE, OWNER, ADMIN}; /**@see {@link RequestMethod#POST} * @return 该请求方法允许的角色 default {LOGIN, ADMIN}; */ - RequestRole[] POST() default {OWNER, ADMIN}; + String[] POST() default {OWNER, ADMIN}; /**@see {@link RequestMethod#PUT} * @return 该请求方法允许的角色 default {OWNER, ADMIN}; */ - RequestRole[] PUT() default {OWNER, ADMIN}; + String[] PUT() default {OWNER, ADMIN}; /**@see {@link RequestMethod#DELETE} * @return 该请求方法允许的角色 default {OWNER, ADMIN}; */ - RequestRole[] DELETE() default {OWNER, ADMIN}; + String[] DELETE() default {OWNER, ADMIN}; } diff --git a/APIJSONORM/src/main/java/apijson/RequestRole.java b/APIJSONORM/src/main/java/apijson/RequestRole.java deleted file mode 100755 index d1d00b18b..000000000 --- a/APIJSONORM/src/main/java/apijson/RequestRole.java +++ /dev/null @@ -1,60 +0,0 @@ -/*Copyright (C) 2020 THL A29 Limited, a Tencent company. All rights reserved. - -This source code is licensed under the Apache License Version 2.0.*/ - - -package apijson; - -/**来访的用户角色 - * @author Lemon - */ -public enum RequestRole { - - /**未登录,不明身份的用户 - */ - UNKNOWN, - - /**已登录的用户 - */ - LOGIN, - - /**联系人,必须已登录 - */ - CONTACT, - - /**圈子成员(CONTACT + OWNER),必须已登录 - */ - CIRCLE, - - /**拥有者,必须已登录 - */ - OWNER, - - /**管理员,必须已登录 - */ - ADMIN; - - //似乎不管怎么做,外部引用后都是空值。并且如果在注解内的位置不是最前的,还会导致被注解的类在其它类中import报错。 - //虽然直接打印显示正常,但被@MethodAccess内RequestRole[] GET()等方法引用后获取的是空值 - // public static final RequestRole[] ALL = {RequestRole.UNKNOWN};//values();//所有 - // public static final RequestRole[] HIGHS;//高级 - // static { - // HIGHS = new RequestRole[] {OWNER, ADMIN}; - // } - - public static final String[] NAMES = { - UNKNOWN.name(), LOGIN.name(), CONTACT.name(), CIRCLE.name(), OWNER.name(), ADMIN.name() - }; - - public static RequestRole get(String name) throws Exception { - if (name == null) { - return null; - } - try { //Enum.valueOf只要找不到对应的值就会抛异常 - return RequestRole.valueOf(name); - } catch (Exception e) { - throw new IllegalArgumentException("角色 " + name + " 不存在!只能是[" + StringUtil.getString(NAMES) + "]中的一种!", e); - } - } - -} diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java b/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java index eb4556af2..cf154a93e 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java @@ -35,7 +35,6 @@ import apijson.Log; import apijson.NotNull; import apijson.RequestMethod; -import apijson.RequestRole; import apijson.StringUtil; import apijson.orm.exception.ConditionErrorException; import apijson.orm.exception.ConflictException; @@ -173,13 +172,13 @@ public AbstractParser setGlobleFormat(Boolean globleFormat) { public Boolean getGlobleFormat() { return globleFormat; } - protected RequestRole globleRole; - public AbstractParser setGlobleRole(RequestRole globleRole) { + protected String globleRole; + public AbstractParser setGlobleRole(String globleRole) { this.globleRole = globleRole; return this; } @Override - public RequestRole getGlobleRole() { + public String getGlobleRole() { return globleRole; } protected String globleDatabase; @@ -361,7 +360,7 @@ public JSONObject parseResponse(JSONObject request) { //必须在parseCorrectRequest后面,因为parseCorrectRequest可能会添加 @role if (isNeedVerifyRole() && globleRole == null) { try { - setGlobleRole(RequestRole.get(requestObject.getString(JSONRequest.KEY_ROLE))); + setGlobleRole(requestObject.getString(JSONRequest.KEY_ROLE)); requestObject.remove(JSONRequest.KEY_ROLE); } catch (Exception e) { return extendErrorResult(requestObject, e); @@ -466,7 +465,7 @@ public void onVerifyRole(@NotNull SQLConfig config) throws Exception { if (globleRole != null) { config.setRole(globleRole); } else { - config.setRole(getVisitor().getId() == null ? RequestRole.UNKNOWN : RequestRole.LOGIN); + config.setRole(getVisitor().getId() == null ? AbstractVerifier.UNKNOWN : AbstractVerifier.LOGIN); } } getVerifier().verifyAccess(config); diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java index 396ed23a8..fc3bb3ba5 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java @@ -54,7 +54,6 @@ import apijson.Log; import apijson.NotNull; import apijson.RequestMethod; -import apijson.RequestRole; import apijson.SQL; import apijson.StringUtil; import apijson.orm.exception.NotExistException; @@ -196,10 +195,10 @@ public abstract class AbstractSQLConfig implements SQLConfig { RAW_MAP.put("LANGUAGE", ""); RAW_MAP.put("MODE", ""); + SQL_FUNCTION_MAP = new LinkedHashMap<>(); // 保证顺序,避免配置冲突等意外情况 - //窗口函数 SQL_FUNCTION_MAP.put("rank", "");//得到数据项在分组中的排名,排名相等的时候会留下空位 SQL_FUNCTION_MAP.put("dense_rank", ""); //得到数据项在分组中的排名,排名相等的时候不会留下空位 @@ -689,7 +688,7 @@ public String getUserIdKey() { /** * TODO 被关联的表通过就忽略关联的表?(这个不行 User:{"sex@":"/Comment/toId"}) */ - private RequestRole role; //发送请求的用户的角色 + private String role; //发送请求的用户的角色 private boolean distinct = false; private String database; //表所在的数据库类型 private String schema; //表所在的数据库名 @@ -789,15 +788,12 @@ public AbstractSQLConfig setId(Object id) { } @Override - public RequestRole getRole() { + public String getRole() { //不能 @NotNull , AbstractParser#getSQLObject 内当getRole() == null时填充默认值 return role; } - public AbstractSQLConfig setRole(String roleName) throws Exception { - return setRole(RequestRole.get(roleName)); - } @Override - public AbstractSQLConfig setRole(RequestRole role) { + public AbstractSQLConfig setRole(String role) { this.role = role; return this; } @@ -3175,7 +3171,7 @@ public static String getSQL(AbstractSQLConfig config) throws Exception { String explain = (config.isExplain() ? (config.isSQLServer() || config.isOracle() ? "SET STATISTICS PROFILE ON " : "EXPLAIN ") : ""); if (config.isTest() && RequestMethod.isGetMethod(config.getMethod(), true)) { // FIXME 为啥是 code 而不是 count ? String q = config.getQuote(); // 生成 SELECT ( (24 >=0 AND 24 <3) ) AS `code` LIMIT 1 OFFSET 0 - return explain + "SELECT " + config.getWhereString(false) + " AS " + q + JSONResponse.KEY_CODE + q + config.getLimitString(); + return explain + "SELECT " + config.getWhereString(false) + " AS " + q + JSONResponse.KEY_COUNT + q + config.getLimitString(); } config.setPreparedValueList(new ArrayList()); @@ -3728,7 +3724,7 @@ else if (whereList != null && whereList.contains(key)) { config.setId(id); //在 tableWhere 第0个 config.setIdIn(idIn); - config.setRole(RequestRole.get(role)); + config.setRole(role); config.setGroup(group); config.setHaving(having); config.setOrder(order); @@ -3966,14 +3962,6 @@ public static interface IdCallback { */ Object newId(RequestMethod method, String database, String schema, String table); - /**已废弃,最早 5.0.0 移除,改用 {@link #getIdKey(String, String, String, String)} - * @param database - * @param schema - * @param table - * @return - */ - @Deprecated - String getIdKey(String database, String schema, String table); /**获取主键名 * @param database @@ -3983,15 +3971,6 @@ public static interface IdCallback { */ String getIdKey(String database, String schema, String datasource, String table); - /**已废弃,最早 5.0.0 移除,改用 {@link #getUserIdKey(String, String, String, String)} - * @param database - * @param schema - * @param table - * @return - */ - @Deprecated - String getUserIdKey(String database, String schema, String table); - /**获取 User 的主键名 * @param database * @param schema @@ -4027,24 +4006,14 @@ public Object newId(RequestMethod method, String database, String schema, String return System.currentTimeMillis(); } - @Override - public String getIdKey(String database, String schema, String table) { - return KEY_ID; - } - @Override public String getIdKey(String database, String schema, String datasource, String table) { - return getIdKey(database, schema, table); - } - - @Override - public String getUserIdKey(String database, String schema, String table) { - return KEY_USER_ID; + return KEY_ID; } @Override public String getUserIdKey(String database, String schema, String datasource, String table) { - return getUserIdKey(database, schema, table); + return KEY_USER_ID; } @Override diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractVerifier.java b/APIJSONORM/src/main/java/apijson/orm/AbstractVerifier.java index 97ff9cd38..5527f17c4 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractVerifier.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractVerifier.java @@ -32,10 +32,10 @@ import java.util.Collection; import java.util.HashMap; import java.util.HashSet; +import java.util.LinkedHashMap; import java.util.LinkedHashSet; import java.util.List; import java.util.Map; -import java.util.Map.Entry; import java.util.Set; import java.util.SortedMap; import java.util.regex.Pattern; @@ -51,7 +51,6 @@ import apijson.MethodAccess; import apijson.NotNull; import apijson.RequestMethod; -import apijson.RequestRole; import apijson.StringUtil; import apijson.orm.AbstractSQLConfig.IdCallback; import apijson.orm.exception.ConflictException; @@ -78,16 +77,42 @@ public abstract class AbstractVerifier implements Verifier, IdCallback { private static final String TAG = "AbstractVerifier"; + /**未登录,不明身份的用户 + */ + public static final String UNKNOWN = "UNKNOWN"; + + /**已登录的用户 + */ + public static final String LOGIN = "LOGIN"; + + /**联系人,必须已登录 + */ + public static final String CONTACT = "CONTACT"; + + /**圈子成员(CONTACT + OWNER),必须已登录 + */ + public static final String CIRCLE = "CIRCLE"; + + /**拥有者,必须已登录 + */ + public static final String OWNER = "OWNER"; + + /**管理员,必须已登录 + */ + public static final String ADMIN = "ADMIN"; + // 共享 STRUCTURE_MAP 则不能 remove 等做任何变更,否则在并发情况下可能会出错,加锁效率又低,所以这里改为忽略对应的 key + public static final Map> ROLE_MAP; + public static final List OPERATION_KEY_LIST; // > // > @NotNull - public static final Map> SYSTEM_ACCESS_MAP; + public static final Map> SYSTEM_ACCESS_MAP; @NotNull - public static final Map> ACCESS_MAP; + public static final Map> ACCESS_MAP; // > // > @@ -98,6 +123,14 @@ public abstract class AbstractVerifier implements Verifier, IdCallback { @NotNull public static final Map COMPILE_MAP; static { + ROLE_MAP = new LinkedHashMap<>(); + ROLE_MAP.put(UNKNOWN, new Entry()); + ROLE_MAP.put(LOGIN, new Entry("userId>", 0)); + ROLE_MAP.put(CONTACT, new Entry("userId{}", "contactIdList")); + ROLE_MAP.put(CIRCLE, new Entry("userId-()", "verifyCircle()")); // "userId{}", "circleIdList")); // 还是 {"userId":"currentUserId", "userId{}": "contactIdList", "@combine": "userId,userId{}" } ? + ROLE_MAP.put(OWNER, new Entry("userId", "userId")); + ROLE_MAP.put(ADMIN, new Entry("userId-()", "verifyAdmin()")); + OPERATION_KEY_LIST = new ArrayList<>(); OPERATION_KEY_LIST.add(TYPE.name()); OPERATION_KEY_LIST.add(VERIFY.name()); @@ -111,7 +144,7 @@ public abstract class AbstractVerifier implements Verifier, IdCallback { OPERATION_KEY_LIST.add(REFUSE.name()); - SYSTEM_ACCESS_MAP = new HashMap>(); + SYSTEM_ACCESS_MAP = new HashMap>(); SYSTEM_ACCESS_MAP.put(Access.class.getSimpleName(), getAccessMap(Access.class.getAnnotation(MethodAccess.class))); SYSTEM_ACCESS_MAP.put(Function.class.getSimpleName(), getAccessMap(Function.class.getAnnotation(MethodAccess.class))); @@ -142,12 +175,12 @@ public abstract class AbstractVerifier implements Verifier, IdCallback { * @param access * @return */ - public static HashMap getAccessMap(MethodAccess access) { + public static HashMap getAccessMap(MethodAccess access) { if (access == null) { return null; } - HashMap map = new HashMap<>(); + HashMap map = new HashMap<>(); map.put(GET, access.GET()); map.put(HEAD, access.HEAD()); map.put(GETS, access.GETS()); @@ -165,22 +198,15 @@ public String getVisitorIdKey(SQLConfig config) { return config.getUserIdKey(); } - @Override - public String getIdKey(String database, String schema, String table) { - return apijson.JSONObject.KEY_ID; - } @Override public String getIdKey(String database, String schema, String datasource, String table) { - return getIdKey(database, schema, table); - } - @Override - public String getUserIdKey(String database, String schema, String table) { - return apijson.JSONObject.KEY_USER_ID; + return apijson.JSONObject.KEY_ID; } @Override public String getUserIdKey(String database, String schema, String datasource, String table) { - return getUserIdKey(database, schema, table); + return apijson.JSONObject.KEY_USER_ID; } + @Override public Object newId(RequestMethod method, String database, String schema, String table) { return System.currentTimeMillis(); @@ -201,7 +227,7 @@ public AbstractVerifier setVisitor(Visitor visitor) { this.visitor = visitor; this.visitorId = visitor == null ? null : visitor.getId(); - //导致内部调用且放行校验(noVerifyLogin, noVerifyRole)也抛异常 + //导致内部调用且放行校验(needVerifyLogin, needVerifyRole)也抛异常 // if (visitorId == null) { // throw new NullPointerException(TAG + ".setVisitor visitorId == null !!! 可能导致权限校验失效,引发安全问题!"); // } @@ -210,16 +236,6 @@ public AbstractVerifier setVisitor(Visitor visitor) { } - /**验证权限是否通过 - * @param config - * @param visitor - * @return - * @throws Exception - */ - @Deprecated - public boolean verify(SQLConfig config) throws Exception { - return verifyAccess(config); - } /**验证权限是否通过 * @param config * @param visitor @@ -231,13 +247,20 @@ public boolean verifyAccess(SQLConfig config) throws Exception { if (table == null) { return true; } - RequestRole role = config.getRole(); + + String role = config.getRole(); if (role == null) { - role = RequestRole.UNKNOWN; - } + role = UNKNOWN; + } + else { + if (ROLE_MAP.containsKey(role) == false) { + Set NAMES = ROLE_MAP.keySet(); + throw new IllegalArgumentException("角色 " + role + " 不存在!只能是[" + StringUtil.getString(NAMES.toArray()) + "]中的一种!"); + } - if (role != RequestRole.UNKNOWN) {//未登录的角色 - verifyLogin(); + if (role.equals(UNKNOWN) == false) { //未登录的角色 + verifyLogin(); + } } RequestMethod method = config.getMethod(); @@ -259,7 +282,7 @@ public boolean verifyAccess(SQLConfig config) throws Exception { //不能在Visitor内null -> [] ! 否则会导致某些查询加上不需要的条件! List list = visitor.getContactIdList() == null ? new ArrayList() : new ArrayList(visitor.getContactIdList()); - if (role == RequestRole.CIRCLE) { + if (role == CIRCLE) { list.add(visitorId); } @@ -287,7 +310,7 @@ public boolean verifyAccess(SQLConfig config) throws Exception { } if (list.contains(Long.valueOf("" + id)) == false) {//Integer等转为Long才能正确判断。强转崩溃 throw new IllegalAccessException(visitorIdkey + " = " + id + " 的 " + table - + " 不允许 " + role.name() + " 用户的 " + method.name() + " 请求!"); + + " 不允许 " + role + " 用户的 " + method.name() + " 请求!"); } } } @@ -307,7 +330,7 @@ public boolean verifyAccess(SQLConfig config) throws Exception { oid = ovl == null || index >= ovl.size() ? null : ovl.get(index); if (oid == null || StringUtil.getString(oid).equals("" + visitorId) == false) { throw new IllegalAccessException(visitorIdkey + " = " + oid + " 的 " + table - + " 不允许 " + role.name() + " 用户的 " + method.name() + " 请求!"); + + " 不允许 " + role + " 用户的 " + method.name() + " 请求!"); } } } @@ -331,13 +354,13 @@ public boolean verifyAccess(SQLConfig config) throws Exception { requestId = config.getWhere(visitorIdkey, true);//JSON里数值不能保证是Long,可能是Integer if (requestId != null && StringUtil.getString(requestId).equals(StringUtil.getString(visitorId)) == false) { throw new IllegalAccessException(visitorIdkey + " = " + requestId + " 的 " + table - + " 不允许 " + role.name() + " 用户的 " + method.name() + " 请求!"); + + " 不允许 " + role + " 用户的 " + method.name() + " 请求!"); } config.putWhere(visitorIdkey, visitorId, true); } break; - case ADMIN://这里不好做,在特定接口内部判。 可以是 /get/admin + 固定秘钥 Parser#noVerify,之后全局跳过验证 + case ADMIN://这里不好做,在特定接口内部判。 可以是 /get/admin + 固定秘钥 Parser#needVerify,之后全局跳过验证 verifyAdmin(); break; default://unknown,verifyRole通过就行 @@ -362,19 +385,20 @@ public boolean verifyAccess(SQLConfig config) throws Exception { * @throws Exception * @see {@link apijson.JSONObject#KEY_ROLE} */ - public void verifyRole(String table, RequestMethod method, RequestRole role) throws Exception { + public void verifyRole(String table, RequestMethod method, String role) throws Exception { Log.d(TAG, "verifyRole table = " + table + "; method = " + method + "; role = " + role); if (table != null) { if (method == null) { method = GET; } if (role == null) { - role = RequestRole.UNKNOWN; + role = UNKNOWN; } - Map map = ACCESS_MAP.get(table); + + Map map = ACCESS_MAP.get(table); if (map == null || Arrays.asList(map.get(method)).contains(role) == false) { - throw new IllegalAccessException(table + " 不允许 " + role.name() + " 用户的 " + method.name() + " 请求!"); + throw new IllegalAccessException(table + " 不允许 " + role + " 用户的 " + method.name() + " 请求!"); } } } @@ -551,7 +575,7 @@ public static JSONObject verifyRequest(@NotNull final RequestMethod method, fina } //已在 Verifier 中处理 - // if (RequestRole.get(request.getString(JSONRequest.KEY_ROLE)) == RequestRole.ADMIN) { + // if (get(request.getString(JSONRequest.KEY_ROLE)) == ADMIN) { // throw new IllegalArgumentException("角色设置错误!不允许在写操作Request中传 " + name + // ":{ " + JSONRequest.KEY_ROLE + ":admin } !"); // } @@ -847,13 +871,13 @@ public static JSONObject parse(@NotNull final RequestMethod method, String name, //解析内容<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< - Set> set = new LinkedHashSet<>(target.entrySet()); + Set> set = new LinkedHashSet<>(target.entrySet()); if (set.isEmpty() == false) { String key; Object tvalue; Object rvalue; - for (Entry entry : set) { + for (Map.Entry entry : set) { key = entry == null ? null : entry.getKey(); if (key == null || OPERATION_KEY_LIST.contains(key)) { continue; @@ -1019,11 +1043,11 @@ private static JSONObject operate(Operation opt, JSONObject targetChild, JSONObj } - Set> set = new LinkedHashSet<>(targetChild.entrySet()); + Set> set = new LinkedHashSet<>(targetChild.entrySet()); String tk; Object tv; - for (Entry e : set) { + for (Map.Entry e : set) { tk = e == null ? null : e.getKey(); if (tk == null || OPERATION_KEY_LIST.contains(tk)) { continue; @@ -1342,7 +1366,8 @@ private static void verifyCondition(@NotNull String funChar, @NotNull JSONObject } finally { executor.close(); } - if (result != null && JSONResponse.isExist(result.getIntValue(JSONResponse.KEY_CODE)) == false) { + + if (result != null && JSONResponse.isExist(result.getIntValue(JSONResponse.KEY_COUNT)) == false) { throw new IllegalArgumentException(rk + ":value 中value不合法!必须匹配 '" + tk + "': '" + tv + "' !"); } } diff --git a/APIJSONORM/src/main/java/apijson/orm/Parser.java b/APIJSONORM/src/main/java/apijson/orm/Parser.java index 6e0af5368..43b76d5e4 100755 --- a/APIJSONORM/src/main/java/apijson/orm/Parser.java +++ b/APIJSONORM/src/main/java/apijson/orm/Parser.java @@ -13,7 +13,6 @@ import apijson.NotNull; import apijson.RequestMethod; -import apijson.RequestRole; /**解析器 * @author Lemon @@ -121,7 +120,7 @@ JSONObject parseCorrectRequest(RequestMethod method, String tag, int version, St Boolean getGlobleFormat(); - RequestRole getGlobleRole(); + String getGlobleRole(); String getGlobleDatabase(); String getGlobleSchema(); String getGlobleDatasource(); diff --git a/APIJSONORM/src/main/java/apijson/orm/SQLConfig.java b/APIJSONORM/src/main/java/apijson/orm/SQLConfig.java index 8710b6e97..298065d31 100755 --- a/APIJSONORM/src/main/java/apijson/orm/SQLConfig.java +++ b/APIJSONORM/src/main/java/apijson/orm/SQLConfig.java @@ -10,7 +10,6 @@ import apijson.NotNull; import apijson.RequestMethod; -import apijson.RequestRole; /**SQL配置 * @author Lemon @@ -113,8 +112,8 @@ public interface SQLConfig { Object getId(); SQLConfig setId(Object id); - RequestRole getRole(); - SQLConfig setRole(RequestRole role); // TODO 提供 String 类型的,方便扩展 + String getRole(); + SQLConfig setRole(String role); public boolean isDistinct(); public SQLConfig setDistinct(boolean distinct); diff --git a/APIJSONORM/src/main/java/apijson/orm/Verifier.java b/APIJSONORM/src/main/java/apijson/orm/Verifier.java index b91549ec0..903b69530 100755 --- a/APIJSONORM/src/main/java/apijson/orm/Verifier.java +++ b/APIJSONORM/src/main/java/apijson/orm/Verifier.java @@ -9,21 +9,13 @@ import apijson.NotNull; import apijson.RequestMethod; -import apijson.RequestRole; /**校验器(权限、请求参数、返回结果等) * @author Lemon */ public interface Verifier { - /**验证权限是否通过,用 verifyAccess 替代,最早 4.5.0 移除 - * @param config - * @param visitor - * @return - * @throws Exception - */ - @Deprecated - boolean verify(SQLConfig config) throws Exception; + /**验证权限是否通过 * @param config * @param visitor @@ -40,7 +32,7 @@ public interface Verifier { * @throws Exception * @see {@link apijson.JSONObject#KEY_ROLE} */ - void verifyRole(String table, RequestMethod method, RequestRole role) throws Exception; + void verifyRole(String table, RequestMethod method, String role) throws Exception; /**登录校验 * @param config diff --git a/APIJSONORM/src/main/java/apijson/orm/model/Document.java b/APIJSONORM/src/main/java/apijson/orm/model/Document.java index 6d5ded353..6f2a8bba2 100755 --- a/APIJSONORM/src/main/java/apijson/orm/model/Document.java +++ b/APIJSONORM/src/main/java/apijson/orm/model/Document.java @@ -5,8 +5,8 @@ package apijson.orm.model; -import static apijson.RequestRole.ADMIN; -import static apijson.RequestRole.LOGIN; +import static apijson.orm.AbstractVerifier.ADMIN; +import static apijson.orm.AbstractVerifier.LOGIN; import java.io.Serializable; import java.sql.Timestamp; diff --git a/APIJSONORM/src/main/java/apijson/orm/model/TestRecord.java b/APIJSONORM/src/main/java/apijson/orm/model/TestRecord.java index b8c519e75..b1ceaa77c 100644 --- a/APIJSONORM/src/main/java/apijson/orm/model/TestRecord.java +++ b/APIJSONORM/src/main/java/apijson/orm/model/TestRecord.java @@ -5,8 +5,8 @@ package apijson.orm.model; -import static apijson.RequestRole.ADMIN; -import static apijson.RequestRole.LOGIN; +import static apijson.orm.AbstractVerifier.ADMIN; +import static apijson.orm.AbstractVerifier.LOGIN; import java.io.Serializable; import java.sql.Timestamp; From 47961e3ee4f9db13717ddf3f0d75e76dbefd4524 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Sun, 26 Sep 2021 23:57:11 +0800 Subject: [PATCH 0476/1181] =?UTF-8?q?Parser=20=E7=A7=BB=E9=99=A4=E6=B2=A1?= =?UTF-8?q?=E5=BF=85=E8=A6=81=E7=9A=84=E6=96=B9=E6=B3=95=20parseCorrectRes?= =?UTF-8?q?ponse?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../apijson/orm/AbstractObjectParser.java | 7 ++-- .../main/java/apijson/orm/AbstractParser.java | 36 +++++-------------- .../src/main/java/apijson/orm/Parser.java | 5 ++- 3 files changed, 15 insertions(+), 33 deletions(-) diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractObjectParser.java b/APIJSONORM/src/main/java/apijson/orm/AbstractObjectParser.java index d1ed399e4..7e38e0393 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractObjectParser.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractObjectParser.java @@ -852,14 +852,17 @@ public JSONObject onSQLExecute() throws Exception { if (list != null) { String arrayPath = parentPath.substring(0, parentPath.lastIndexOf("[]") + 2); + long startTime = System.currentTimeMillis(); for (int i = 1; i < list.size(); i++) { // 从 1 开始,0 已经处理过 - JSONObject obj = parser.parseCorrectResponse(table, list.get(i)); - list.set(i, obj); + JSONObject obj = list.get(i); if (obj != null) { parser.putQueryResult(arrayPath + "/" + i + "/" + name, obj); //解决获取关联数据时requestObject里不存在需要的关联数据 } } + + long endTime = System.currentTimeMillis(); + Log.e(TAG, "onSQLExecute for (int i = 1; i < list.size(); i++) startTime = " + startTime + "; endTime = " + endTime + "; duration = " + (endTime - startTime)); parser.putArrayMainCache(arrayPath, list); } diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java b/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java index cf154a93e..c5ddd5564 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java @@ -719,33 +719,6 @@ public JSONObject parseCorrectRequest() throws Exception { } - //TODO 优化性能! - /**获取正确的返回结果 - * @param method - * @param response - * @return - * @throws Exception - */ - @Override - public JSONObject parseCorrectResponse(String table, JSONObject response) throws Exception { - // Log.d(TAG, "getCorrectResponse method = " + method + "; table = " + table); - // if (response == null || response.isEmpty()) {//避免无效空result:{}添加内容后变有效 - // Log.e(TAG, "getCorrectResponse response == null || response.isEmpty() >> return response;"); - return response; - // } - // - // JSONObject target = apijson.JSONObject.isTableKey(table) == false - // ? new JSONObject() : getStructure(method, "Response", "model", table); - // - // return MethodStructure.parseResponse(method, table, target, response, new OnParseCallback() { - // - // @Override - // protected JSONObject onParseJSONObject(String key, JSONObject tobj, JSONObject robj) throws Exception { - // return getCorrectResponse(method, key, robj); - // } - // }); - } - /**获取Request或Response内指定JSON结构 * @param table * @param method @@ -1075,15 +1048,22 @@ public JSONArray onArrayParse(JSONObject request, String parentPath, String name .setJoinList(onJoinParse(join, request)); JSONObject parent; + + long startTime = System.currentTimeMillis(); //生成size个 for (int i = 0; i < (isSubquery ? 1 : size); i++) { parent = onObjectParse(request, isSubquery ? parentPath : path, isSubquery ? name : "" + i, config.setType(SQLConfig.TYPE_ITEM).setPosition(i), isSubquery); if (parent == null || parent.isEmpty()) { break; } + //key[]:{Table:{}}中key equals Table时 提取Table response.add(getValue(parent, childKeys)); //null有意义 } + + long endTime = System.currentTimeMillis(); + Log.e(TAG, "onArrayParse for for (int i = 0; i < (isSubquery ? 1 : size); i++) startTime = " + startTime + "; endTime = " + endTime + "; duration = " + (endTime - startTime)); + //Table>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> @@ -1665,7 +1645,7 @@ public JSONObject executeSQL(SQLConfig config, boolean isSubquery) throws Except result = getSQLExecutor().execute(config, false); } - return parseCorrectResponse(config.getTable(), result); + return result; } catch (Exception e) { if (Log.DEBUG == false && e instanceof SQLException) { diff --git a/APIJSONORM/src/main/java/apijson/orm/Parser.java b/APIJSONORM/src/main/java/apijson/orm/Parser.java index 43b76d5e4..135dc1c7a 100755 --- a/APIJSONORM/src/main/java/apijson/orm/Parser.java +++ b/APIJSONORM/src/main/java/apijson/orm/Parser.java @@ -64,6 +64,7 @@ public interface Parser { JSONObject parseResponse(String request); JSONObject parseResponse(JSONObject request); + // 没必要性能还差 JSONObject parseCorrectResponse(String table, JSONObject response) throws Exception; JSONObject parseCorrectRequest() throws Exception; @@ -71,12 +72,10 @@ public interface Parser { JSONObject parseCorrectRequest(RequestMethod method, String tag, int version, String name, JSONObject request, int maxUpdateCount, SQLCreator creator) throws Exception; - JSONObject parseCorrectResponse(String table, JSONObject response) throws Exception; - + JSONObject getStructure(String table, String method, String tag, int version) throws Exception; - JSONObject onObjectParse(JSONObject request, String parentPath, String name, SQLConfig arrayConfig, boolean isSubquery) throws Exception; JSONArray onArrayParse(JSONObject request, String parentPath, String name, boolean isSubquery) throws Exception; From ed036ef025d26356d9af0f23d4a5970319748770 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Mon, 27 Sep 2021 00:40:41 +0800 Subject: [PATCH 0477/1181] =?UTF-8?q?=E4=BC=98=E5=8C=96=20Table[]:{=20Tabl?= =?UTF-8?q?e:{}=20}=20=E8=BF=99=E7=A7=8D=E5=8D=95=E8=A1=A8=E6=95=B0?= =?UTF-8?q?=E7=BB=84=E7=9A=84=E6=9F=A5=E8=AF=A2=E6=80=A7=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../apijson/orm/AbstractObjectParser.java | 48 ++++++++++++------ .../main/java/apijson/orm/AbstractParser.java | 49 ++++++++++++++++--- .../src/main/java/apijson/orm/SQLConfig.java | 4 +- 3 files changed, 78 insertions(+), 23 deletions(-) diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractObjectParser.java b/APIJSONORM/src/main/java/apijson/orm/AbstractObjectParser.java index 7e38e0393..7fc29e1fb 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractObjectParser.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractObjectParser.java @@ -58,6 +58,7 @@ public AbstractObjectParser setParser(AbstractParser parser) { protected boolean isSubquery; protected final int type; + protected final String arrayTable; protected final List joinList; protected final boolean isTable; protected final boolean isArrayMainTable; @@ -86,6 +87,7 @@ public AbstractObjectParser(@NotNull JSONObject request, String parentPath, SQLC this.isSubquery = isSubquery; this.type = arrayConfig == null ? 0 : arrayConfig.getType(); + this.arrayTable = arrayConfig == null ? null : arrayConfig.getTable(); this.joinList = arrayConfig == null ? null : arrayConfig.getJoinList(); this.isTable = isTable; // apijson.JSONObject.isTableKey(table); @@ -835,6 +837,7 @@ public Object onReferenceParse(@NotNull String path) { return parser.getValueByPath(path); } + @SuppressWarnings("unchecked") @Override public JSONObject onSQLExecute() throws Exception { int position = getPosition(); @@ -846,30 +849,47 @@ public JSONObject onSQLExecute() throws Exception { else { result = parser.executeSQL(sqlConfig, isSubquery); - if (isArrayMainTable && position == 0 && result != null) { // 提取并缓存数组主表的列表数据 - @SuppressWarnings("unchecked") - List list = (List) result.remove(SQLExecutor.KEY_RAW_LIST); - if (list != null) { + boolean isSimpleArray = false; + List rawList = null; + + if (isArrayMainTable && position == 0 && result != null) { + + isSimpleArray = (functionMap == null || functionMap.isEmpty()) + && (customMap == null || customMap.isEmpty()) + && (table.equals(arrayTable)); + + // 提取并缓存数组主表的列表数据 + rawList = (List) result.remove(SQLExecutor.KEY_RAW_LIST); + if (rawList != null) { String arrayPath = parentPath.substring(0, parentPath.lastIndexOf("[]") + 2); - long startTime = System.currentTimeMillis(); - for (int i = 1; i < list.size(); i++) { // 从 1 开始,0 已经处理过 - JSONObject obj = list.get(i); - if (obj != null) { - parser.putQueryResult(arrayPath + "/" + i + "/" + name, obj); //解决获取关联数据时requestObject里不存在需要的关联数据 + if (isSimpleArray == false) { + long startTime = System.currentTimeMillis(); + + for (int i = 1; i < rawList.size(); i++) { // 从 1 开始,0 已经处理过 + JSONObject obj = rawList.get(i); + + if (obj != null) { + parser.putQueryResult(arrayPath + "/" + i + "/" + name, obj); // 解决获取关联数据时requestObject里不存在需要的关联数据 + } } + + long endTime = System.currentTimeMillis(); // 3ms - 8ms + Log.e(TAG, "\n onSQLExecute <<<<<<<<<<<<<<<<<<<<<<<<<<<<\n for (int i = 1; i < list.size(); i++) startTime = " + startTime + + "; endTime = " + endTime + "; duration = " + (endTime - startTime) + "\n >>>>>>>>>>>>>>>>>>>>>>>>>>>>>\n "); } - - long endTime = System.currentTimeMillis(); - Log.e(TAG, "onSQLExecute for (int i = 1; i < list.size(); i++) startTime = " + startTime + "; endTime = " + endTime + "; duration = " + (endTime - startTime)); - parser.putArrayMainCache(arrayPath, list); + parser.putArrayMainCache(arrayPath, rawList); } } if (isSubquery == false && result != null) { - parser.putQueryResult(path, result);//解决获取关联数据时requestObject里不存在需要的关联数据 + parser.putQueryResult(path, result); // 解决获取关联数据时requestObject里不存在需要的关联数据 + + if (isSimpleArray && rawList != null) { + result.put(SQLExecutor.KEY_RAW_LIST, rawList); + } } } diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java b/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java index c5ddd5564..172e3c83e 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java @@ -1031,10 +1031,14 @@ public JSONArray onArrayParse(JSONObject request, String parentPath, String name int index = isSubquery || name == null ? -1 : name.lastIndexOf("[]"); String childPath = index <= 0 ? null : Pair.parseEntry(name.substring(0, index), true).getKey(); // Table-key1-key2... + String arrTableKey = null; //判断第一个key,即Table是否存在,如果存在就提取 String[] childKeys = StringUtil.split(childPath, "-", false); if (childKeys == null || childKeys.length <= 0 || request.containsKey(childKeys[0]) == false) { childKeys = null; + } + else if (childKeys.length == 1 && JSONRequest.isTableKey(childKeys[0])) { // 可能无需提取,直接返回 rawList 即可 + arrTableKey = childKeys[0]; } @@ -1045,11 +1049,13 @@ public JSONArray onArrayParse(JSONObject request, String parentPath, String name .setCount(size) .setPage(page) .setQuery(query2) + .setTable(arrTableKey) .setJoinList(onJoinParse(join, request)); JSONObject parent; - long startTime = System.currentTimeMillis(); + boolean isExtract = true; + //生成size个 for (int i = 0; i < (isSubquery ? 1 : size); i++) { parent = onObjectParse(request, isSubquery ? parentPath : path, isSubquery ? name : "" + i, config.setType(SQLConfig.TYPE_ITEM).setPosition(i), isSubquery); @@ -1057,13 +1063,32 @@ public JSONArray onArrayParse(JSONObject request, String parentPath, String name break; } + long startTime = System.currentTimeMillis(); + + /* 这里优化了 Table[]: { Table:{} } 这种情况下的性能 + * 如果把 List 改成 JSONArray 来减少以下 addAll 一次复制,则会导致 AbstractSQLExecutor 等其它很多地方 get 要改为 getJSONObject, + * 修改类型会导致不兼容旧版依赖 ORM 的项目,而且整体上性能只有特殊情况下性能提升,其它非特殊情况下因为多出很多 instanceof JSONObject 的判断而降低了性能。 + */ + JSONObject fo = i != 0 || arrTableKey == null ? null : parent.getJSONObject(arrTableKey); + @SuppressWarnings("unchecked") + List list = fo == null ? null : (List) fo.remove(SQLExecutor.KEY_RAW_LIST); + + if (list != null && list.isEmpty() == false) { + isExtract = false; + + list.set(0, fo); // 不知道为啥第 0 项也加了 @RAW@LIST + response.addAll(list); // List cannot match List response = new JSONArray(list); + + long endTime = System.currentTimeMillis(); // 0ms + Log.d(TAG, "\n onArrayParse <<<<<<<<<<<<<<<<<<<<<<<<<<<<\n for (int i = 0; i < (isSubquery ? 1 : size); i++) " + + " startTime = " + startTime + "; endTime = " + endTime + "; duration = " + (endTime - startTime) + "\n >>>>>>>>>>>>>>>>>>>>>>>>>>>>>\n"); + break; + } + //key[]:{Table:{}}中key equals Table时 提取Table response.add(getValue(parent, childKeys)); //null有意义 } - long endTime = System.currentTimeMillis(); - Log.e(TAG, "onArrayParse for for (int i = 0; i < (isSubquery ? 1 : size); i++) startTime = " + startTime + "; endTime = " + endTime + "; duration = " + (endTime - startTime)); - //Table>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> @@ -1082,12 +1107,20 @@ public JSONArray onArrayParse(JSONObject request, String parentPath, String name } } */ - Object fo = childKeys == null || response.isEmpty() ? null : response.get(0); - if (fo instanceof Boolean || fo instanceof Number || fo instanceof String) { //[{}] 和 [[]] 都没意义 - putQueryResult(path, response); + if (isExtract) { + long startTime = System.currentTimeMillis(); + + Object fo = childKeys == null || response.isEmpty() ? null : response.get(0); + if (fo instanceof Boolean || fo instanceof Number || fo instanceof String) { //[{}] 和 [[]] 都没意义 + putQueryResult(path, response); + } + + long endTime = System.currentTimeMillis(); + Log.d(TAG, "\n onArrayParse <<<<<<<<<<<<<<<<<<<<<<<<<<<<\n isExtract >> putQueryResult " + + " startTime = " + startTime + "; endTime = " + endTime + "; duration = " + (endTime - startTime) + "\n >>>>>>>>>>>>>>>>>>>>>>>>>>>>>\n"); } - } finally { + } finally { //后面还可能用到,要还原 request.put(JSONRequest.KEY_QUERY, query); request.put(JSONRequest.KEY_COUNT, count); diff --git a/APIJSONORM/src/main/java/apijson/orm/SQLConfig.java b/APIJSONORM/src/main/java/apijson/orm/SQLConfig.java index 298065d31..ab8e2d254 100755 --- a/APIJSONORM/src/main/java/apijson/orm/SQLConfig.java +++ b/APIJSONORM/src/main/java/apijson/orm/SQLConfig.java @@ -134,6 +134,9 @@ public interface SQLConfig { * @see {@link #getSQLTable()} */ String getTable(); + + SQLConfig setTable(String table); + /**数据库里的真实Table名 * 通过 {@link #TABLE_KEY_MAP} 映射 * @return @@ -145,7 +148,6 @@ public interface SQLConfig { List getRaw(); SQLConfig setRaw(List raw); - SQLConfig setTable(String table); String getGroup(); SQLConfig setGroup(String group); From 8d780ddcb0e1dce8e9c39dd1424e81821ea954ce Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Mon, 27 Sep 2021 15:20:33 +0800 Subject: [PATCH 0478/1181] =?UTF-8?q?=E8=A7=A3=E5=86=B3=E8=A1=A8=E5=AF=B9?= =?UTF-8?q?=E8=B1=A1=E4=B8=AD=E7=9A=84=E5=AD=90=E8=A1=A8=E5=AF=B9=E8=B1=A1?= =?UTF-8?q?=E6=95=B0=E6=8D=AE=E6=80=BB=E6=98=AF=E4=B8=80=E6=A0=B7=E4=BB=A5?= =?UTF-8?q?=E5=8F=8A=E5=9C=A8=20Table[]:{=20Table:{=20ChildTable:{}=20}=20?= =?UTF-8?q?}=20=E6=83=85=E5=86=B5=E4=B8=8B=E5=8F=AA=E6=9C=89=E9=A6=96?= =?UTF-8?q?=E4=B8=AA=20Table=20=E9=87=8C=E8=BF=94=E5=9B=9E=E4=BA=86=20Chil?= =?UTF-8?q?dTable?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/apijson/orm/AbstractObjectParser.java | 16 ++++++++++++---- .../main/java/apijson/orm/AbstractParser.java | 1 + .../src/main/java/apijson/orm/ObjectParser.java | 4 +++- 3 files changed, 16 insertions(+), 5 deletions(-) diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractObjectParser.java b/APIJSONORM/src/main/java/apijson/orm/AbstractObjectParser.java index 7fc29e1fb..fa4da155d 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractObjectParser.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractObjectParser.java @@ -112,7 +112,16 @@ public AbstractObjectParser(@NotNull JSONObject request, String parentPath, SQLC } - + @Override + public String getParentPath() { + return parentPath; + } + + @Override + public AbstractObjectParser setParentPath(String parentPath) { + this.parentPath = parentPath; + return this; + } protected int position; public int getPosition() { @@ -144,7 +153,6 @@ public boolean isBreakParse() { protected String table; protected String alias; protected boolean isReuse; - protected String parentName; protected String path; protected JSONObject response; @@ -824,7 +832,7 @@ public void onChildResponse() throws Exception { continue; } - response.put(entry.getKey(), child ); + response.put(entry.getKey(), child); index ++; } } @@ -856,6 +864,7 @@ public JSONObject onSQLExecute() throws Exception { isSimpleArray = (functionMap == null || functionMap.isEmpty()) && (customMap == null || customMap.isEmpty()) + && (childMap == null || childMap.isEmpty()) && (table.equals(arrayTable)); // 提取并缓存数组主表的列表数据 @@ -863,7 +872,6 @@ public JSONObject onSQLExecute() throws Exception { if (rawList != null) { String arrayPath = parentPath.substring(0, parentPath.lastIndexOf("[]") + 2); - if (isSimpleArray == false) { long startTime = System.currentTimeMillis(); diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java b/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java index 172e3c83e..b4de3e7c1 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java @@ -855,6 +855,7 @@ public JSONObject onObjectParse(final JSONObject request ObjectParser op = null; if (isReuse) { // 数组主表使用专门的缓存数据 op = arrayObjectParserCacheMap.get(parentPath.substring(0, parentPath.lastIndexOf("[]") + 2)); + op.setParentPath(parentPath); } if (op == null) { diff --git a/APIJSONORM/src/main/java/apijson/orm/ObjectParser.java b/APIJSONORM/src/main/java/apijson/orm/ObjectParser.java index 452ced293..3fbea8557 100755 --- a/APIJSONORM/src/main/java/apijson/orm/ObjectParser.java +++ b/APIJSONORM/src/main/java/apijson/orm/ObjectParser.java @@ -20,6 +20,9 @@ */ public interface ObjectParser { + String getParentPath(); + ObjectParser setParentPath(String parentPath); + /**解析成员 * response重新赋值 * @param parentPath @@ -140,7 +143,6 @@ public interface ObjectParser { void recycle(); - ObjectParser setMethod(RequestMethod method); RequestMethod getMethod(); From b23d884466bd7500463c77295e229c3c9dcef31f Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Mon, 27 Sep 2021 16:15:44 +0800 Subject: [PATCH 0479/1181] =?UTF-8?q?=E6=96=87=E6=A1=A3=EF=BC=9A3.2=20?= =?UTF-8?q?=E5=8A=9F=E8=83=BD=E7=AC=A6=20=E6=96=B0=E5=A2=9E=E5=85=A8?= =?UTF-8?q?=E5=B1=80=E5=85=B3=E9=94=AE=E8=AF=8D=E7=9A=84=E8=AF=B4=E6=98=8E?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Document.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Document.md b/Document.md index bd641f91e..bb5acd50c 100644 --- a/Document.md +++ b/Document.md @@ -5,7 +5,7 @@ https://github.com/Tencent/APIJSON ![image](https://user-images.githubusercontent.com/5738175/134520081-a63d3817-321c-4e7b-9e03-73c6827a19c1.png) -后端开发者可以先看 [图文入门教程1](https://vincentcheng.github.io/apijson-doc/zh) 或 [图文入门教程2](https://hanxu2018.github.io/APIJSON-DOC/) (都非官方,和本文档有出入的点以本文档为准,例如正则匹配 key? 已废弃,用 key~ 替代。) +后端开发者可以先看 [图文入门教程1](https://vincentcheng.github.io/apijson-doc/zh) 或 [图文入门教程2](https://hanxu2018.github.io/APIJSON-DOC/) (都非官方,和本文档有出入的点以本文档为准,例如正则匹配 key? 已废弃,用 key~ 替代;例如 "@column":"store_id,sum(amt):totAmt" 中逗号 , 有误,应该用分号 ; 隔开 SQL 函数) * ### [1.示例](#1) * ### [2.对比传统方式](#2) @@ -378,5 +378,5 @@ DELETE:
删除数据 | base_url/delete/ | {
   TableName:{< 逻辑运算 | &, \|, ! 逻辑运算符,对应数据库 SQL 中的 AND, OR, NOT。
横或纵与:同一键值对的值内条件默认 \| 或连接,可以在 key 后加逻辑运算符来具体指定;不同键值对的条件默认 & 与连接,可以用下面说明的对象关键词 @combine 来具体指定。

① & 可用于"key&{}":"条件"等

② \| 可用于"key\|{}":"条件", "key\|{}":[]等,一般可省略

③ ! 可单独使用,如"key!":Object,也可像&,\|一样配合其他功能符使用
"key!":null 无效,null 值会导致整个键值对被忽略解析,可以用 "key{}":"!=null" 替代,
"key":null 同理,用 "key{}":"=null" 替代。 | ① ["id&{}":">80000,<=90000"](http://apijson.cn:8080/head/{"User":{"id&{}":">80000,<=90000"}}),对应SQL是`id>80000 AND id<=90000`,即id满足id>80000 & id<=90000

② ["id\|{}":">90000,<=80000"](http://apijson.cn:8080/head/{"User":{"id\|{}":">90000,<=80000"}}),同"id{}":">90000,<=80000",对应SQL是`id>80000 OR id<=90000`,即id满足id>90000 \| id<=80000

③ ["id!{}":[82001,38710]](http://apijson.cn:8080/head/{"User":{"id!{}":[82001,38710]}}),对应SQL是`id NOT IN(82001,38710)`,即id满足 ! (id=82001 \| id=38710),可过滤黑名单的消息 数组关键词,可自定义 | "key":Object,key为 "[]":{} 中{}内的关键词,Object的类型由key指定

① "count":Integer,查询数量,0 表示最大值,默认最大值为100

② "page":Integer,查询页码,从0开始,默认最大值为100,一般和count一起用

③ "query":Integer,查询内容
0-对象,1-总数和分页详情,2-以上全部
总数关键词为 total,分页详情关键词为 info,
它们都和 query 同级,通过引用赋值得到,例如
"total@":"/[]/total", "info@":"/[]/info"
这里query及total仅为GET类型的请求提供方便,
一般可直接用HEAD类型的请求获取总数

④ "join":"&/Table0/key0@,\多表连接方式:
"\<" - LEFT JOIN
">" - RIGHT JOIN
"&" - INNER JOIN
"\|" - FULL JOIN
"!" - OUTER JOIN
"@" - APP JOIN
其中 @ APP JOIN 为应用层连表,会从已查出的主表里取得所有副表 key@ 关联的主表内的 refKey 作为一个数组 refKeys: [value0, value1...],然后把原来副表 count 次查询 key=$refKey 的 SQL 用 key IN($refKeys) 的方式合并为一条 SQL 来优化性能;
其它 JOIN 都是 SQL JOIN,具体功能和 MySQL,PostgreSQL 等数据库的 JOIN 一一对应
`"join":"`"MainTable":{},`
`"ViceTable":{"key@":"/MainTable/refKey"}`
会对应生成
`MainTable LEFT JOIN ViceTable`
`ON ViceTable.key=MainTable.refKey`

⑤ "otherKey":Object,自定义关键词,名称和以上系统关键词不一样,且原样返回上传的值 | ① 查询User数组,最多5个:
["count":5](http://apijson.cn:8080/get/{"[]":{"count":5,"User":{}}})
对应SQL是`LIMIT 5`

② 查询第3页的User数组,每页5个:
["count":5,
"page":3](http://apijson.cn:8080/get/{"[]":{"count":5,"page":3,"User":{}}})
对应SQL是`LIMIT 5 OFFSET 15`

③ 查询User数组和对应的User总数:
["[]":{
   "query":2,
   "User":{}
},
"total@":"/[]/total",
"info@":"/[]/info"](http://apijson.cn:8080/get/{"[]":{"query":2,"count":5,"User":{}},"total@":"%252F[]%252Ftotal","info@":"%252F[]%252Finfo"})
返回的数据中,总数及分页详情结构为:
"total":139, //总数
"info":{ //分页详情
   "total":139, //总数
   "count":5, //每页数量
   "page":0, //当前页码
   "max":27, //最大页码
   "more":true, //是否还有更多
   "first":true, //是否为首页
   "last":false //是否为尾页
}

④ Moment INNER JOIN User LEFT JOIN Comment:
["[]":{
   "join":"&/User/id@,\    "Moment":{
     "@group":"id" //主副表不是一对一,要去除重复数据
   },
   "User":{
     "name~":"t",
     "id@":"/Moment/userId"
   },
   "Comment":{
     "momentId@":"/Moment/id"
   }
}](http://apijson.cn:8080/get/{"[]":{"count":5,"join":"&%252FUser%252Fid@,\<%252FComment%252FmomentId@","Moment":{"@column":"id,userId,content","@group":"id"},"User":{"name~":"t","id@":"%252FMoment%252FuserId","@column":"id,name,head"},"Comment":{"momentId@":"%252FMoment%252Fid","@column":"id,momentId,content"}}})

⑤ 每一层都加当前用户名:
["User":{},
"[]":{
   "name@":"User/name", //自定义关键词
   "Moment":{}
}](http://apijson.cn:8080/get/{"User":{},"[]":{"name@":"User%252Fname","Moment":{}}}) 对象关键词,可自定义 | "@key":Object,@key为 Table:{} 中{}内的关键词,Object的类型由@key指定

① "@combine":"&key0,&key1,\|key2,key3,
!key4,!key5,&key6,key7...",条件组合方式,\| 可省略。会自动把同类的合并,外层按照 & \| ! 顺序,内层的按传参顺序组合成
(key0 & key1 & key6 & 其它key) & (key2 \| key3 \| key7) & !(key4 \| key5)
这种连接方式,其中 "其它key" 是指与 @combine 在同一对象,且未被它声明的条件 key,默认都是 & 连接

② "@column":"column;function(arg)...",返回字段

③ "@order":"column0+,column1-...",排序方式

④ "@group":"column0,column1...",分组方式。如果@column里声明了Table的id,则id也必须在@group中声明;其它情况下必须满足至少一个条件:
1.分组的key在@column里声明
2.Table主键在@group中声明

⑤ "@having":"function0(...)?value0;function1(...)?value1;function2(...)?value2...",SQL函数条件,一般和@group一起用,函数一般在@column里声明

⑥ "@schema":"sys",集合空间(模式),非默认的值可通过它来指定,可以在最外层作为全局默认配置

⑦ "@database":"POSTGRESQL",跨数据库,非默认的值可通过它来指定,可以在最外层作为全局默认配置

⑧ "@json":"key0,key1...",转为 JSON 格式返回,符合 JSONObject 则转为 {...},符合 JSONArray 则转为 \[...]

⑨ "@role":"OWNER",来访角色,包括
UNKNOWN,LOGIN,CONTACT,CIRCLE,OWNER,ADMIN,
可以在最外层作为全局默认配置,
可自定义其它角色并重写 Verifier.verify 等相关方法来自定义校验

⑩ "@explain":true,性能分析,可以在最外层作为全局默认配置

⑪ "@raw":"key0,key1...",其中 key0, key1 都对应有键值对
"key0":"SQL片段或SQL片段的别名",
"key1":"SQL片段或SQL片段的别名"
自定义原始SQL片段,可扩展嵌套SQL函数等复杂语句,必须是后端已配置的,只有其它功能符都做不到才考虑,谨慎使用,注意防SQL注入

⑫ "@otherKey":Object,自定义关键词,名称和以上系统关键词不一样,且原样返回上传的值 | ① 搜索name或tag任何一个字段包含字符a的User列表:
["name~":"a",
"tag~":"a",
"@combine":"name~,tag~"](http://apijson.cn:8080/get/{"User[]":{"count":10,"User":{"@column":"id,name,tag","name~":"a","tag~":"a","@combine":"name~,tag~"}}})
对应SQL是`name REGEXP 'a' OR tag REGEXP 'a'`

② 只查询id,sex,name这几列并且请求结果也按照这个顺序:
["@column":"id,sex,name"](http://apijson.cn:8080/get/{"User":{"@column":"id,sex,name","id":38710}})
对应SQL是`SELECT id,sex,name`

③ 查询按 name降序、id默认顺序 排序的User数组:
["@order":"name-,id"](http://apijson.cn:8080/get/{"[]":{"count":10,"User":{"@column":"name,id","@order":"name-,id"}}})
对应SQL是`ORDER BY name DESC,id`

④ 查询按userId分组的Moment数组:
["@group":"userId,id"](http://apijson.cn:8080/get/{"[]":{"count":10,"Moment":%7B"@column":"userId,id","@group":"userId,id"}}})
对应SQL是`GROUP BY userId,id`

⑤ 查询 按userId分组、id最大值>=100 的Moment数组:
["@column":"userId;max(id)",
"@group":"userId",
"@having":"max(id)>=100"](http://apijson.cn:8080/get/{"[]":{"count":10,"Moment":{"@column":"userId%253Bmax(id)","@group":"userId","@having":"max(id)>=100"}}})
对应SQL是`SELECT userId,max(id) ... GROUP BY userId HAVING max(id)>=100`
还可以指定函数返回名:
["@column":"userId;max(id):maxId",
"@group":"userId",
"@having":"maxId>=100"](http://apijson.cn:8080/get/{"[]":{"count":10,"Moment":{"@column":"userId%253Bmax(id):maxId","@group":"userId","@having":"maxId>=100"}}})
对应SQL是`SELECT userId,max(id) AS maxId ... GROUP BY userId HAVING maxId>=100`

⑥ 查询 sys 内的 User 表:
["@schema":"sys"](http://apijson.cn:8080/get/{"User":{"@schema":"sys"}})
对应SQL是`FROM sys.User`

⑦ 查询 PostgreSQL 数据库的 User 表:
["@database":"POSTGRESQL"](http://apijson.cn:8080/get/{"User":{"@database":"POSTGRESQL","@explain":true}})

⑧ 将 VARCHAR 字符串字段 get 转为 JSONArray 返回:
["@json":"get"](http://apijson.cn:8080/get/{"Access":{"@json":"get"}})

⑨ 查询当前用户的动态:
["@role":"OWNER"](http://apijson.cn:8080/get/{"[]":{"Moment":{"@role":"OWNER"}}})

⑩ 开启性能分析:
["@explain":true](http://apijson.cn:8080/get/{"[]":{"Moment":{"@explain":true}}})
对应SQL是`EXPLAIN`

⑪ 统计最近一周偶数userId的数量
["@column":"date;left(date,10):day;sum(if(userId%2=0,1,0))",
"@group":"day",
"@having":"to_days(now())-to_days(\`date\`)<=7",
"@raw":"@column,@having"](http://apijson.cn:8080/get/{"[]":{"Moment":{"@column":"date%3bleft(date,10):day%3bsum(if(userId%252=0,1,0))","@group":"day","@having":"to_days(now())-to_days(\`date\`)<=7","@raw":"@column,@having"}}})
对应SQL是``SELECT date, left(date,10) AS day, sum(if(userId%2=0,1,0)) ... GROUP BY day HAVING to_days(now())-to_days(`date`)<=7``

⑫ 从pictureList获取第0张图片:
["@position":0, //自定义关键词
"firstPicture()":"getFromArray(pictureList,@position)"](http://apijson.cn:8080/get/{"User":{"id":38710,"@position":0,"firstPicture()":"getFromArray(pictureList,@position)"}}) - + 全局关键词 | 为最外层对象 {} 内的关键词。其中 @database,@schema, @datasource, @role, @explain 基本同对象关键词,见上方说明,区别是全局关键词会每个表对象中没有时自动放入,作为默认值。

① "tag":String,后面的 tag 是非GET、HEAD 请求中匹配请求的 JSON 结构的标识,一般是要查询的 Table 的名称或该名称对应的数组 Table[] 或 Table:[],由后端 Request 表中指定。

② "version":Integer,接口版本,version 不传、为 null 或 <=0 都会使用最高版本,传了其它有效值则会使用最接近它的最低版本,由后端 Request 表中指定。

③ "format":Boolean,格式化返回 Response JSON 的 key,一般是将 TableName 转为 tableName, TableName[] 转为 tableNameList, Table:alias 转为 alias, TableName-key[] 转为 tableNameKeyList 等小驼峰格式。 | ① 查隐私信息:
[{"tag":"Privacy","Privacy":{"id":82001}}](http://apijson.cn:8080/gets/{"tag":"Privacy","Privacy":{"id":82001}})

② 使用第 1 版接口查隐私信息:
[{"version":1,"tag":"Privacy","Privacy":{"id":82001}}](http://apijson.cn:8080/gets/{"version":1,"tag":"Privacy","Privacy":{"id":82001}})

③ 格式化朋友圈接口返回 JSON 中的 key:
[{
   "format":true,    "[]":{
     "page":0,
     "count":3,
     "Moment":{},
     "User":{
       "id@":"/Moment/userId"
     },
     "Comment[]":{
       "count":3,
       "Comment":{
         "momentId@":"[]/Moment/id"
       }
     }
   }
}](http://apijson.cn:8080/get/{"format":true,"[]":{"page":0,"count":3,"Moment":{},"User":{"id@":"%252FMoment%252FuserId"},"Comment[]":{"count":3,"Comment":{"momentId@":"[]%252FMoment%252Fid"}}}})
From 5ccb064d98a9dfcdbb3c7fc75b44cae1fb625b96 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Mon, 27 Sep 2021 16:16:58 +0800 Subject: [PATCH 0480/1181] Update Document.md --- Document.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Document.md b/Document.md index bb5acd50c..a9320f146 100644 --- a/Document.md +++ b/Document.md @@ -5,7 +5,7 @@ https://github.com/Tencent/APIJSON ![image](https://user-images.githubusercontent.com/5738175/134520081-a63d3817-321c-4e7b-9e03-73c6827a19c1.png) -后端开发者可以先看 [图文入门教程1](https://vincentcheng.github.io/apijson-doc/zh) 或 [图文入门教程2](https://hanxu2018.github.io/APIJSON-DOC/) (都非官方,和本文档有出入的点以本文档为准,例如正则匹配 key? 已废弃,用 key~ 替代;例如 "@column":"store_id,sum(amt):totAmt" 中逗号 , 有误,应该用分号 ; 隔开 SQL 函数) +后端开发者可以先看 [图文入门教程1](https://vincentcheng.github.io/apijson-doc/zh) 或 [图文入门教程2](https://hanxu2018.github.io/APIJSON-DOC/) (都非官方,和本文档有出入的点以本文档为准,例如正则匹配 key? 已废弃,用 key~ 替代;例如 "@column":"store_id,sum(amt):totAmt" 中逗号 , 有误,应该用分号 ; 隔开 SQL 函数,改为 "@column":"store_id;sum(amt):totAmt") * ### [1.示例](#1) * ### [2.对比传统方式](#2) @@ -346,7 +346,7 @@ DELETE:
删除数据 | base_url/delete/ | {
   TableName:{< 1.TableName指要查询的数据库表Table的名称字符串。第一个字符为大写字母,剩下的字符要符合英语字母、数字、下划线中的任何一种。对应的值的类型为JSONObject,结构是 {...},里面放的是Table的字段(列名)。下同。
-2."tag":tag 后面的tag是非GET、HEAD请求中匹配请求的JSON结构的标识,一般是要查询的table的名称,由后端Request表中指定。下同。
+2."tag":tag 后面的tag是非GET、HEAD请求中匹配请求的JSON结构的标识,一般是要查询的Table的名称,由后端Request表中指定。下同。
3.GET、HEAD请求是开放请求,可任意组合任意嵌套。其它请求为受限制的安全/私密请求,对应的 方法(method), 标识(tag), 版本(version), 结构(structure) 都必须和 后端Request表中所指定的 一一对应,否则请求将不被通过。version 不传、为 null 或 <=0 都会使用最高版本,传了其它有效值则会使用最接近它的最低版本。下同。
4.GETS与GET、HEADS与HEAD分别为同一类型的操作方法,请求稍有不同但返回结果相同。下同。
5.在HTTP通信中,自动化接口(get,gets,head,heads,post,put,delete) 全用HTTP POST请求。下同。
From 581cace5f37e176e8639b242a05d97ecceb5e90f Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Mon, 27 Sep 2021 16:17:25 +0800 Subject: [PATCH 0481/1181] Update Document.md --- Document.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Document.md b/Document.md index a9320f146..00b3d2fc3 100644 --- a/Document.md +++ b/Document.md @@ -5,7 +5,7 @@ https://github.com/Tencent/APIJSON ![image](https://user-images.githubusercontent.com/5738175/134520081-a63d3817-321c-4e7b-9e03-73c6827a19c1.png) -后端开发者可以先看 [图文入门教程1](https://vincentcheng.github.io/apijson-doc/zh) 或 [图文入门教程2](https://hanxu2018.github.io/APIJSON-DOC/) (都非官方,和本文档有出入的点以本文档为准,例如正则匹配 key? 已废弃,用 key~ 替代;例如 "@column":"store_id,sum(amt):totAmt" 中逗号 , 有误,应该用分号 ; 隔开 SQL 函数,改为 "@column":"store_id;sum(amt):totAmt") +后端开发者可以先看 [图文入门教程1](https://vincentcheng.github.io/apijson-doc/zh) 或 [图文入门教程2](https://hanxu2018.github.io/APIJSON-DOC/) (都非官方,和本文档有出入的点以本文档为准。例如正则匹配 key? 已废弃,用 key~ 替代;例如 "@column":"store_id,sum(amt):totAmt" 中逗号 , 有误,应该用分号 ; 隔开 SQL 函数,改为 "@column":"store_id;sum(amt):totAmt") * ### [1.示例](#1) * ### [2.对比传统方式](#2) From 97fc8125458278aa6891a70f0d381fe72fb76daf Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Mon, 27 Sep 2021 16:27:39 +0800 Subject: [PATCH 0482/1181] Update Document.md --- Document.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Document.md b/Document.md index 00b3d2fc3..394174341 100644 --- a/Document.md +++ b/Document.md @@ -378,5 +378,5 @@ DELETE:
删除数据 | base_url/delete/ | {
   TableName:{< 逻辑运算 | &, \|, ! 逻辑运算符,对应数据库 SQL 中的 AND, OR, NOT。
横或纵与:同一键值对的值内条件默认 \| 或连接,可以在 key 后加逻辑运算符来具体指定;不同键值对的条件默认 & 与连接,可以用下面说明的对象关键词 @combine 来具体指定。

① & 可用于"key&{}":"条件"等

② \| 可用于"key\|{}":"条件", "key\|{}":[]等,一般可省略

③ ! 可单独使用,如"key!":Object,也可像&,\|一样配合其他功能符使用
"key!":null 无效,null 值会导致整个键值对被忽略解析,可以用 "key{}":"!=null" 替代,
"key":null 同理,用 "key{}":"=null" 替代。 | ① ["id&{}":">80000,<=90000"](http://apijson.cn:8080/head/{"User":{"id&{}":">80000,<=90000"}}),对应SQL是`id>80000 AND id<=90000`,即id满足id>80000 & id<=90000

② ["id\|{}":">90000,<=80000"](http://apijson.cn:8080/head/{"User":{"id\|{}":">90000,<=80000"}}),同"id{}":">90000,<=80000",对应SQL是`id>80000 OR id<=90000`,即id满足id>90000 \| id<=80000

③ ["id!{}":[82001,38710]](http://apijson.cn:8080/head/{"User":{"id!{}":[82001,38710]}}),对应SQL是`id NOT IN(82001,38710)`,即id满足 ! (id=82001 \| id=38710),可过滤黑名单的消息 数组关键词,可自定义 | "key":Object,key为 "[]":{} 中{}内的关键词,Object的类型由key指定

① "count":Integer,查询数量,0 表示最大值,默认最大值为100

② "page":Integer,查询页码,从0开始,默认最大值为100,一般和count一起用

③ "query":Integer,查询内容
0-对象,1-总数和分页详情,2-以上全部
总数关键词为 total,分页详情关键词为 info,
它们都和 query 同级,通过引用赋值得到,例如
"total@":"/[]/total", "info@":"/[]/info"
这里query及total仅为GET类型的请求提供方便,
一般可直接用HEAD类型的请求获取总数

④ "join":"&/Table0/key0@,\多表连接方式:
"\<" - LEFT JOIN
">" - RIGHT JOIN
"&" - INNER JOIN
"\|" - FULL JOIN
"!" - OUTER JOIN
"@" - APP JOIN
其中 @ APP JOIN 为应用层连表,会从已查出的主表里取得所有副表 key@ 关联的主表内的 refKey 作为一个数组 refKeys: [value0, value1...],然后把原来副表 count 次查询 key=$refKey 的 SQL 用 key IN($refKeys) 的方式合并为一条 SQL 来优化性能;
其它 JOIN 都是 SQL JOIN,具体功能和 MySQL,PostgreSQL 等数据库的 JOIN 一一对应
`"join":"`"MainTable":{},`
`"ViceTable":{"key@":"/MainTable/refKey"}`
会对应生成
`MainTable LEFT JOIN ViceTable`
`ON ViceTable.key=MainTable.refKey`

⑤ "otherKey":Object,自定义关键词,名称和以上系统关键词不一样,且原样返回上传的值 | ① 查询User数组,最多5个:
["count":5](http://apijson.cn:8080/get/{"[]":{"count":5,"User":{}}})
对应SQL是`LIMIT 5`

② 查询第3页的User数组,每页5个:
["count":5,
"page":3](http://apijson.cn:8080/get/{"[]":{"count":5,"page":3,"User":{}}})
对应SQL是`LIMIT 5 OFFSET 15`

③ 查询User数组和对应的User总数:
["[]":{
   "query":2,
   "User":{}
},
"total@":"/[]/total",
"info@":"/[]/info"](http://apijson.cn:8080/get/{"[]":{"query":2,"count":5,"User":{}},"total@":"%252F[]%252Ftotal","info@":"%252F[]%252Finfo"})
返回的数据中,总数及分页详情结构为:
"total":139, //总数
"info":{ //分页详情
   "total":139, //总数
   "count":5, //每页数量
   "page":0, //当前页码
   "max":27, //最大页码
   "more":true, //是否还有更多
   "first":true, //是否为首页
   "last":false //是否为尾页
}

④ Moment INNER JOIN User LEFT JOIN Comment:
["[]":{
   "join":"&/User/id@,\    "Moment":{
     "@group":"id" //主副表不是一对一,要去除重复数据
   },
   "User":{
     "name~":"t",
     "id@":"/Moment/userId"
   },
   "Comment":{
     "momentId@":"/Moment/id"
   }
}](http://apijson.cn:8080/get/{"[]":{"count":5,"join":"&%252FUser%252Fid@,\<%252FComment%252FmomentId@","Moment":{"@column":"id,userId,content","@group":"id"},"User":{"name~":"t","id@":"%252FMoment%252FuserId","@column":"id,name,head"},"Comment":{"momentId@":"%252FMoment%252Fid","@column":"id,momentId,content"}}})

⑤ 每一层都加当前用户名:
["User":{},
"[]":{
   "name@":"User/name", //自定义关键词
   "Moment":{}
}](http://apijson.cn:8080/get/{"User":{},"[]":{"name@":"User%252Fname","Moment":{}}}) 对象关键词,可自定义 | "@key":Object,@key为 Table:{} 中{}内的关键词,Object的类型由@key指定

① "@combine":"&key0,&key1,\|key2,key3,
!key4,!key5,&key6,key7...",条件组合方式,\| 可省略。会自动把同类的合并,外层按照 & \| ! 顺序,内层的按传参顺序组合成
(key0 & key1 & key6 & 其它key) & (key2 \| key3 \| key7) & !(key4 \| key5)
这种连接方式,其中 "其它key" 是指与 @combine 在同一对象,且未被它声明的条件 key,默认都是 & 连接

② "@column":"column;function(arg)...",返回字段

③ "@order":"column0+,column1-...",排序方式

④ "@group":"column0,column1...",分组方式。如果@column里声明了Table的id,则id也必须在@group中声明;其它情况下必须满足至少一个条件:
1.分组的key在@column里声明
2.Table主键在@group中声明

⑤ "@having":"function0(...)?value0;function1(...)?value1;function2(...)?value2...",SQL函数条件,一般和@group一起用,函数一般在@column里声明

⑥ "@schema":"sys",集合空间(模式),非默认的值可通过它来指定,可以在最外层作为全局默认配置

⑦ "@database":"POSTGRESQL",跨数据库,非默认的值可通过它来指定,可以在最外层作为全局默认配置

⑧ "@json":"key0,key1...",转为 JSON 格式返回,符合 JSONObject 则转为 {...},符合 JSONArray 则转为 \[...]

⑨ "@role":"OWNER",来访角色,包括
UNKNOWN,LOGIN,CONTACT,CIRCLE,OWNER,ADMIN,
可以在最外层作为全局默认配置,
可自定义其它角色并重写 Verifier.verify 等相关方法来自定义校验

⑩ "@explain":true,性能分析,可以在最外层作为全局默认配置

⑪ "@raw":"key0,key1...",其中 key0, key1 都对应有键值对
"key0":"SQL片段或SQL片段的别名",
"key1":"SQL片段或SQL片段的别名"
自定义原始SQL片段,可扩展嵌套SQL函数等复杂语句,必须是后端已配置的,只有其它功能符都做不到才考虑,谨慎使用,注意防SQL注入

⑫ "@otherKey":Object,自定义关键词,名称和以上系统关键词不一样,且原样返回上传的值 | ① 搜索name或tag任何一个字段包含字符a的User列表:
["name~":"a",
"tag~":"a",
"@combine":"name~,tag~"](http://apijson.cn:8080/get/{"User[]":{"count":10,"User":{"@column":"id,name,tag","name~":"a","tag~":"a","@combine":"name~,tag~"}}})
对应SQL是`name REGEXP 'a' OR tag REGEXP 'a'`

② 只查询id,sex,name这几列并且请求结果也按照这个顺序:
["@column":"id,sex,name"](http://apijson.cn:8080/get/{"User":{"@column":"id,sex,name","id":38710}})
对应SQL是`SELECT id,sex,name`

③ 查询按 name降序、id默认顺序 排序的User数组:
["@order":"name-,id"](http://apijson.cn:8080/get/{"[]":{"count":10,"User":{"@column":"name,id","@order":"name-,id"}}})
对应SQL是`ORDER BY name DESC,id`

④ 查询按userId分组的Moment数组:
["@group":"userId,id"](http://apijson.cn:8080/get/{"[]":{"count":10,"Moment":%7B"@column":"userId,id","@group":"userId,id"}}})
对应SQL是`GROUP BY userId,id`

⑤ 查询 按userId分组、id最大值>=100 的Moment数组:
["@column":"userId;max(id)",
"@group":"userId",
"@having":"max(id)>=100"](http://apijson.cn:8080/get/{"[]":{"count":10,"Moment":{"@column":"userId%253Bmax(id)","@group":"userId","@having":"max(id)>=100"}}})
对应SQL是`SELECT userId,max(id) ... GROUP BY userId HAVING max(id)>=100`
还可以指定函数返回名:
["@column":"userId;max(id):maxId",
"@group":"userId",
"@having":"maxId>=100"](http://apijson.cn:8080/get/{"[]":{"count":10,"Moment":{"@column":"userId%253Bmax(id):maxId","@group":"userId","@having":"maxId>=100"}}})
对应SQL是`SELECT userId,max(id) AS maxId ... GROUP BY userId HAVING maxId>=100`

⑥ 查询 sys 内的 User 表:
["@schema":"sys"](http://apijson.cn:8080/get/{"User":{"@schema":"sys"}})
对应SQL是`FROM sys.User`

⑦ 查询 PostgreSQL 数据库的 User 表:
["@database":"POSTGRESQL"](http://apijson.cn:8080/get/{"User":{"@database":"POSTGRESQL","@explain":true}})

⑧ 将 VARCHAR 字符串字段 get 转为 JSONArray 返回:
["@json":"get"](http://apijson.cn:8080/get/{"Access":{"@json":"get"}})

⑨ 查询当前用户的动态:
["@role":"OWNER"](http://apijson.cn:8080/get/{"[]":{"Moment":{"@role":"OWNER"}}})

⑩ 开启性能分析:
["@explain":true](http://apijson.cn:8080/get/{"[]":{"Moment":{"@explain":true}}})
对应SQL是`EXPLAIN`

⑪ 统计最近一周偶数userId的数量
["@column":"date;left(date,10):day;sum(if(userId%2=0,1,0))",
"@group":"day",
"@having":"to_days(now())-to_days(\`date\`)<=7",
"@raw":"@column,@having"](http://apijson.cn:8080/get/{"[]":{"Moment":{"@column":"date%3bleft(date,10):day%3bsum(if(userId%252=0,1,0))","@group":"day","@having":"to_days(now())-to_days(\`date\`)<=7","@raw":"@column,@having"}}})
对应SQL是``SELECT date, left(date,10) AS day, sum(if(userId%2=0,1,0)) ... GROUP BY day HAVING to_days(now())-to_days(`date`)<=7``

⑫ 从pictureList获取第0张图片:
["@position":0, //自定义关键词
"firstPicture()":"getFromArray(pictureList,@position)"](http://apijson.cn:8080/get/{"User":{"id":38710,"@position":0,"firstPicture()":"getFromArray(pictureList,@position)"}}) - 全局关键词 | 为最外层对象 {} 内的关键词。其中 @database,@schema, @datasource, @role, @explain 基本同对象关键词,见上方说明,区别是全局关键词会每个表对象中没有时自动放入,作为默认值。

① "tag":String,后面的 tag 是非GET、HEAD 请求中匹配请求的 JSON 结构的标识,一般是要查询的 Table 的名称或该名称对应的数组 Table[] 或 Table:[],由后端 Request 表中指定。

② "version":Integer,接口版本,version 不传、为 null 或 <=0 都会使用最高版本,传了其它有效值则会使用最接近它的最低版本,由后端 Request 表中指定。

③ "format":Boolean,格式化返回 Response JSON 的 key,一般是将 TableName 转为 tableName, TableName[] 转为 tableNameList, Table:alias 转为 alias, TableName-key[] 转为 tableNameKeyList 等小驼峰格式。 | ① 查隐私信息:
[{"tag":"Privacy","Privacy":{"id":82001}}](http://apijson.cn:8080/gets/{"tag":"Privacy","Privacy":{"id":82001}})

② 使用第 1 版接口查隐私信息:
[{"version":1,"tag":"Privacy","Privacy":{"id":82001}}](http://apijson.cn:8080/gets/{"version":1,"tag":"Privacy","Privacy":{"id":82001}})

③ 格式化朋友圈接口返回 JSON 中的 key:
[{
   "format":true,    "[]":{
     "page":0,
     "count":3,
     "Moment":{},
     "User":{
       "id@":"/Moment/userId"
     },
     "Comment[]":{
       "count":3,
       "Comment":{
         "momentId@":"[]/Moment/id"
       }
     }
   }
}](http://apijson.cn:8080/get/{"format":true,"[]":{"page":0,"count":3,"Moment":{},"User":{"id@":"%252FMoment%252FuserId"},"Comment[]":{"count":3,"Comment":{"momentId@":"[]%252FMoment%252Fid"}}}}) + 全局关键词 | 为最外层对象 {} 内的关键词。其中 @database,@schema, @datasource, @role, @explain 基本同对象关键词,见上方说明,区别是全局关键词会每个表对象中没有时自动放入,作为默认值。

① "tag":String,后面的 tag 是非 GET、HEAD 请求中匹配请求的 JSON 结构的标识,一般是要查询的 Table 的名称或该名称对应的数组 Table[] 或 Table:[],由后端 Request 表中指定。

② "version":Integer,接口版本,version 不传、为 null 或 <=0 都会使用最高版本,传了其它有效值则会使用最接近它的最低版本,由后端 Request 表中指定。

③ "format":Boolean,格式化返回 Response JSON 的 key,一般是将 TableName 转为 tableName, TableName[] 转为 tableNameList, Table:alias 转为 alias, TableName-key[] 转为 tableNameKeyList 等小驼峰格式。 | ① 查隐私信息:
[{"tag":"Privacy","Privacy":{"id":82001}}](http://apijson.cn/api?url=http%3A%2F%2Fapijson.cn%2Fapi%3A8080%2Fgets&type=JSON&req={%22tag%22:%22Privacy%22,%22Privacy%22:{%22id%22:82001}})

② 使用第 1 版接口查隐私信息:
[{"version":1,"tag":"Privacy","Privacy":{"id":82001}}](http://apijson.cn/api?url=http%3A%2F%2Fapijson.cn%2Fapi%3A8080%2Fgets&type=JSON&req={%22version%22:1,%22tag%22:%22Privacy%22,%22Privacy%22:{%22id%22:82001}})

③ 格式化朋友圈接口返回 JSON 中的 key:
[{
   "format":true,
   "[]":{
     "page":0,
     "count":3,
     "Moment":{},
     "User":{
       "id@":"/Moment/userId"
     },
     "Comment[]":{
       "count":3,
       "Comment":{
         "momentId@":"[]/Moment/id"
       }
     }
   }
}](http://apijson.cn:8080/get/{"format":true,"[]":{"page":0,"count":3,"Moment":{},"User":{"id@":"%252FMoment%252FuserId"},"Comment[]":{"count":3,"Comment":{"momentId@":"[]%252FMoment%252Fid"}}}})
From d0cf03908ed64f7f4c5f41e8961ac4e05182a551 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Mon, 27 Sep 2021 16:53:17 +0800 Subject: [PATCH 0483/1181] Update Document.md --- Document.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Document.md b/Document.md index 394174341..8ba0dbc9f 100644 --- a/Document.md +++ b/Document.md @@ -378,5 +378,5 @@ DELETE:
删除数据 | base_url/delete/ | {
   TableName:{< 逻辑运算 | &, \|, ! 逻辑运算符,对应数据库 SQL 中的 AND, OR, NOT。
横或纵与:同一键值对的值内条件默认 \| 或连接,可以在 key 后加逻辑运算符来具体指定;不同键值对的条件默认 & 与连接,可以用下面说明的对象关键词 @combine 来具体指定。

① & 可用于"key&{}":"条件"等

② \| 可用于"key\|{}":"条件", "key\|{}":[]等,一般可省略

③ ! 可单独使用,如"key!":Object,也可像&,\|一样配合其他功能符使用
"key!":null 无效,null 值会导致整个键值对被忽略解析,可以用 "key{}":"!=null" 替代,
"key":null 同理,用 "key{}":"=null" 替代。 | ① ["id&{}":">80000,<=90000"](http://apijson.cn:8080/head/{"User":{"id&{}":">80000,<=90000"}}),对应SQL是`id>80000 AND id<=90000`,即id满足id>80000 & id<=90000

② ["id\|{}":">90000,<=80000"](http://apijson.cn:8080/head/{"User":{"id\|{}":">90000,<=80000"}}),同"id{}":">90000,<=80000",对应SQL是`id>80000 OR id<=90000`,即id满足id>90000 \| id<=80000

③ ["id!{}":[82001,38710]](http://apijson.cn:8080/head/{"User":{"id!{}":[82001,38710]}}),对应SQL是`id NOT IN(82001,38710)`,即id满足 ! (id=82001 \| id=38710),可过滤黑名单的消息 数组关键词,可自定义 | "key":Object,key为 "[]":{} 中{}内的关键词,Object的类型由key指定

① "count":Integer,查询数量,0 表示最大值,默认最大值为100

② "page":Integer,查询页码,从0开始,默认最大值为100,一般和count一起用

③ "query":Integer,查询内容
0-对象,1-总数和分页详情,2-以上全部
总数关键词为 total,分页详情关键词为 info,
它们都和 query 同级,通过引用赋值得到,例如
"total@":"/[]/total", "info@":"/[]/info"
这里query及total仅为GET类型的请求提供方便,
一般可直接用HEAD类型的请求获取总数

④ "join":"&/Table0/key0@,\多表连接方式:
"\<" - LEFT JOIN
">" - RIGHT JOIN
"&" - INNER JOIN
"\|" - FULL JOIN
"!" - OUTER JOIN
"@" - APP JOIN
其中 @ APP JOIN 为应用层连表,会从已查出的主表里取得所有副表 key@ 关联的主表内的 refKey 作为一个数组 refKeys: [value0, value1...],然后把原来副表 count 次查询 key=$refKey 的 SQL 用 key IN($refKeys) 的方式合并为一条 SQL 来优化性能;
其它 JOIN 都是 SQL JOIN,具体功能和 MySQL,PostgreSQL 等数据库的 JOIN 一一对应
`"join":"`"MainTable":{},`
`"ViceTable":{"key@":"/MainTable/refKey"}`
会对应生成
`MainTable LEFT JOIN ViceTable`
`ON ViceTable.key=MainTable.refKey`

⑤ "otherKey":Object,自定义关键词,名称和以上系统关键词不一样,且原样返回上传的值 | ① 查询User数组,最多5个:
["count":5](http://apijson.cn:8080/get/{"[]":{"count":5,"User":{}}})
对应SQL是`LIMIT 5`

② 查询第3页的User数组,每页5个:
["count":5,
"page":3](http://apijson.cn:8080/get/{"[]":{"count":5,"page":3,"User":{}}})
对应SQL是`LIMIT 5 OFFSET 15`

③ 查询User数组和对应的User总数:
["[]":{
   "query":2,
   "User":{}
},
"total@":"/[]/total",
"info@":"/[]/info"](http://apijson.cn:8080/get/{"[]":{"query":2,"count":5,"User":{}},"total@":"%252F[]%252Ftotal","info@":"%252F[]%252Finfo"})
返回的数据中,总数及分页详情结构为:
"total":139, //总数
"info":{ //分页详情
   "total":139, //总数
   "count":5, //每页数量
   "page":0, //当前页码
   "max":27, //最大页码
   "more":true, //是否还有更多
   "first":true, //是否为首页
   "last":false //是否为尾页
}

④ Moment INNER JOIN User LEFT JOIN Comment:
["[]":{
   "join":"&/User/id@,\    "Moment":{
     "@group":"id" //主副表不是一对一,要去除重复数据
   },
   "User":{
     "name~":"t",
     "id@":"/Moment/userId"
   },
   "Comment":{
     "momentId@":"/Moment/id"
   }
}](http://apijson.cn:8080/get/{"[]":{"count":5,"join":"&%252FUser%252Fid@,\<%252FComment%252FmomentId@","Moment":{"@column":"id,userId,content","@group":"id"},"User":{"name~":"t","id@":"%252FMoment%252FuserId","@column":"id,name,head"},"Comment":{"momentId@":"%252FMoment%252Fid","@column":"id,momentId,content"}}})

⑤ 每一层都加当前用户名:
["User":{},
"[]":{
   "name@":"User/name", //自定义关键词
   "Moment":{}
}](http://apijson.cn:8080/get/{"User":{},"[]":{"name@":"User%252Fname","Moment":{}}}) 对象关键词,可自定义 | "@key":Object,@key为 Table:{} 中{}内的关键词,Object的类型由@key指定

① "@combine":"&key0,&key1,\|key2,key3,
!key4,!key5,&key6,key7...",条件组合方式,\| 可省略。会自动把同类的合并,外层按照 & \| ! 顺序,内层的按传参顺序组合成
(key0 & key1 & key6 & 其它key) & (key2 \| key3 \| key7) & !(key4 \| key5)
这种连接方式,其中 "其它key" 是指与 @combine 在同一对象,且未被它声明的条件 key,默认都是 & 连接

② "@column":"column;function(arg)...",返回字段

③ "@order":"column0+,column1-...",排序方式

④ "@group":"column0,column1...",分组方式。如果@column里声明了Table的id,则id也必须在@group中声明;其它情况下必须满足至少一个条件:
1.分组的key在@column里声明
2.Table主键在@group中声明

⑤ "@having":"function0(...)?value0;function1(...)?value1;function2(...)?value2...",SQL函数条件,一般和@group一起用,函数一般在@column里声明

⑥ "@schema":"sys",集合空间(模式),非默认的值可通过它来指定,可以在最外层作为全局默认配置

⑦ "@database":"POSTGRESQL",跨数据库,非默认的值可通过它来指定,可以在最外层作为全局默认配置

⑧ "@json":"key0,key1...",转为 JSON 格式返回,符合 JSONObject 则转为 {...},符合 JSONArray 则转为 \[...]

⑨ "@role":"OWNER",来访角色,包括
UNKNOWN,LOGIN,CONTACT,CIRCLE,OWNER,ADMIN,
可以在最外层作为全局默认配置,
可自定义其它角色并重写 Verifier.verify 等相关方法来自定义校验

⑩ "@explain":true,性能分析,可以在最外层作为全局默认配置

⑪ "@raw":"key0,key1...",其中 key0, key1 都对应有键值对
"key0":"SQL片段或SQL片段的别名",
"key1":"SQL片段或SQL片段的别名"
自定义原始SQL片段,可扩展嵌套SQL函数等复杂语句,必须是后端已配置的,只有其它功能符都做不到才考虑,谨慎使用,注意防SQL注入

⑫ "@otherKey":Object,自定义关键词,名称和以上系统关键词不一样,且原样返回上传的值 | ① 搜索name或tag任何一个字段包含字符a的User列表:
["name~":"a",
"tag~":"a",
"@combine":"name~,tag~"](http://apijson.cn:8080/get/{"User[]":{"count":10,"User":{"@column":"id,name,tag","name~":"a","tag~":"a","@combine":"name~,tag~"}}})
对应SQL是`name REGEXP 'a' OR tag REGEXP 'a'`

② 只查询id,sex,name这几列并且请求结果也按照这个顺序:
["@column":"id,sex,name"](http://apijson.cn:8080/get/{"User":{"@column":"id,sex,name","id":38710}})
对应SQL是`SELECT id,sex,name`

③ 查询按 name降序、id默认顺序 排序的User数组:
["@order":"name-,id"](http://apijson.cn:8080/get/{"[]":{"count":10,"User":{"@column":"name,id","@order":"name-,id"}}})
对应SQL是`ORDER BY name DESC,id`

④ 查询按userId分组的Moment数组:
["@group":"userId,id"](http://apijson.cn:8080/get/{"[]":{"count":10,"Moment":%7B"@column":"userId,id","@group":"userId,id"}}})
对应SQL是`GROUP BY userId,id`

⑤ 查询 按userId分组、id最大值>=100 的Moment数组:
["@column":"userId;max(id)",
"@group":"userId",
"@having":"max(id)>=100"](http://apijson.cn:8080/get/{"[]":{"count":10,"Moment":{"@column":"userId%253Bmax(id)","@group":"userId","@having":"max(id)>=100"}}})
对应SQL是`SELECT userId,max(id) ... GROUP BY userId HAVING max(id)>=100`
还可以指定函数返回名:
["@column":"userId;max(id):maxId",
"@group":"userId",
"@having":"maxId>=100"](http://apijson.cn:8080/get/{"[]":{"count":10,"Moment":{"@column":"userId%253Bmax(id):maxId","@group":"userId","@having":"maxId>=100"}}})
对应SQL是`SELECT userId,max(id) AS maxId ... GROUP BY userId HAVING maxId>=100`

⑥ 查询 sys 内的 User 表:
["@schema":"sys"](http://apijson.cn:8080/get/{"User":{"@schema":"sys"}})
对应SQL是`FROM sys.User`

⑦ 查询 PostgreSQL 数据库的 User 表:
["@database":"POSTGRESQL"](http://apijson.cn:8080/get/{"User":{"@database":"POSTGRESQL","@explain":true}})

⑧ 将 VARCHAR 字符串字段 get 转为 JSONArray 返回:
["@json":"get"](http://apijson.cn:8080/get/{"Access":{"@json":"get"}})

⑨ 查询当前用户的动态:
["@role":"OWNER"](http://apijson.cn:8080/get/{"[]":{"Moment":{"@role":"OWNER"}}})

⑩ 开启性能分析:
["@explain":true](http://apijson.cn:8080/get/{"[]":{"Moment":{"@explain":true}}})
对应SQL是`EXPLAIN`

⑪ 统计最近一周偶数userId的数量
["@column":"date;left(date,10):day;sum(if(userId%2=0,1,0))",
"@group":"day",
"@having":"to_days(now())-to_days(\`date\`)<=7",
"@raw":"@column,@having"](http://apijson.cn:8080/get/{"[]":{"Moment":{"@column":"date%3bleft(date,10):day%3bsum(if(userId%252=0,1,0))","@group":"day","@having":"to_days(now())-to_days(\`date\`)<=7","@raw":"@column,@having"}}})
对应SQL是``SELECT date, left(date,10) AS day, sum(if(userId%2=0,1,0)) ... GROUP BY day HAVING to_days(now())-to_days(`date`)<=7``

⑫ 从pictureList获取第0张图片:
["@position":0, //自定义关键词
"firstPicture()":"getFromArray(pictureList,@position)"](http://apijson.cn:8080/get/{"User":{"id":38710,"@position":0,"firstPicture()":"getFromArray(pictureList,@position)"}}) - 全局关键词 | 为最外层对象 {} 内的关键词。其中 @database,@schema, @datasource, @role, @explain 基本同对象关键词,见上方说明,区别是全局关键词会每个表对象中没有时自动放入,作为默认值。

① "tag":String,后面的 tag 是非 GET、HEAD 请求中匹配请求的 JSON 结构的标识,一般是要查询的 Table 的名称或该名称对应的数组 Table[] 或 Table:[],由后端 Request 表中指定。

② "version":Integer,接口版本,version 不传、为 null 或 <=0 都会使用最高版本,传了其它有效值则会使用最接近它的最低版本,由后端 Request 表中指定。

③ "format":Boolean,格式化返回 Response JSON 的 key,一般是将 TableName 转为 tableName, TableName[] 转为 tableNameList, Table:alias 转为 alias, TableName-key[] 转为 tableNameKeyList 等小驼峰格式。 | ① 查隐私信息:
[{"tag":"Privacy","Privacy":{"id":82001}}](http://apijson.cn/api?url=http%3A%2F%2Fapijson.cn%2Fapi%3A8080%2Fgets&type=JSON&req={%22tag%22:%22Privacy%22,%22Privacy%22:{%22id%22:82001}})

② 使用第 1 版接口查隐私信息:
[{"version":1,"tag":"Privacy","Privacy":{"id":82001}}](http://apijson.cn/api?url=http%3A%2F%2Fapijson.cn%2Fapi%3A8080%2Fgets&type=JSON&req={%22version%22:1,%22tag%22:%22Privacy%22,%22Privacy%22:{%22id%22:82001}})

③ 格式化朋友圈接口返回 JSON 中的 key:
[{
   "format":true,
   "[]":{
     "page":0,
     "count":3,
     "Moment":{},
     "User":{
       "id@":"/Moment/userId"
     },
     "Comment[]":{
       "count":3,
       "Comment":{
         "momentId@":"[]/Moment/id"
       }
     }
   }
}](http://apijson.cn:8080/get/{"format":true,"[]":{"page":0,"count":3,"Moment":{},"User":{"id@":"%252FMoment%252FuserId"},"Comment[]":{"count":3,"Comment":{"momentId@":"[]%252FMoment%252Fid"}}}}) + 全局关键词 | 为最外层对象 {} 内的关键词。其中 @database,@schema, @datasource, @role, @explain 基本同对象关键词,见上方说明,区别是全局关键词会每个表对象中没有时自动放入,作为默认值。

① "tag":String,后面的 tag 是非 GET、HEAD 请求中匹配请求的 JSON 结构的标识,一般是要查询的 Table 的名称或该名称对应的数组 Table[] 或 Table:[],由后端 Request 表中指定。

② "version":Integer,接口版本,version 不传、为 null 或 <=0 都会使用最高版本,传了其它有效值则会使用最接近它的最低版本,由后端 Request 表中指定。

③ "format":Boolean,格式化返回 Response JSON 的 key,一般是将 TableName 转为 tableName, TableName[] 转为 tableNameList, Table:alias 转为 alias, TableName-key[] 转为 tableNameKeyList 等小驼峰格式。 | ① 查隐私信息:
[{"tag":"Privacy","Privacy":{"id":82001}}](http://apijson.cn/api?url=http%3A%2F%2Fapijson.cn%3A8080%2Fgets&type=JSON&json={%22tag%22:%22Privacy%22,%22Privacy%22:{%22id%22:82001}})

② 使用第 1 版接口查隐私信息:
[{"version":1,"tag":"Privacy","Privacy":{"id":82001}}](http://apijson.cn/api?url=http%3A%2F%2Fapijson.cn%3A8080%2Fgets&type=JSON&json={%22version%22:1,%22tag%22:%22Privacy%22,%22Privacy%22:{%22id%22:82001}})

③ 格式化朋友圈接口返回 JSON 中的 key:
[{
   "format":true,
   "[]":{
     "page":0,
     "count":3,
     "Moment":{},
     "User":{
       "id@":"/Moment/userId"
     },
     "Comment[]":{
       "count":3,
       "Comment":{
         "momentId@":"[]/Moment/id"
       }
     }
   }
}](http://apijson.cn:8080/get/{"format":true,"[]":{"page":0,"count":3,"Moment":{},"User":{"id@":"%252FMoment%252FuserId"},"Comment[]":{"count":3,"Comment":{"momentId@":"[]%252FMoment%252Fid"}}}})
From e5638a3b70e21960faf5a4caff8f0d1e6e715439 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Mon, 27 Sep 2021 17:35:20 +0800 Subject: [PATCH 0484/1181] Update Document.md --- Document.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Document.md b/Document.md index 8ba0dbc9f..27eff492d 100644 --- a/Document.md +++ b/Document.md @@ -336,13 +336,13 @@ https://github.com/Tencent/APIJSON  方法及说明 | URL | Request | Response ------------ | ------------ | ------------ | ------------ -GET:
普通获取数据,
可用浏览器调试 | base_url/get/ | {
   TableName:{
     …
   }
}
{…}内为限制条件

例如获取一个 id = 235 的 Moment:
{
   "Moment":{
     "id":235
   }
}
后端校验通过后自动解析为 SQL 并执行:
`SELECT * FROM Moment WHERE id=235 LIMIT 1` | {
   TableName:{
     ...
   },
   "code":200,
   "msg":"success"
}
例如
{
   "Moment":{
     "id":235,
     "userId":38710,
     "content":"APIJSON,let interfaces and documents go to hell !"
   },
   "code":200,
   "msg":"success"
} -HEAD:
普通获取数量,
可用浏览器调试 | base_url/head/ | {
   TableName:{
     …
   }
}
{…}内为限制条件

例如获取一个 id = 38710 的 User 所发布的 Moment 总数:
{
   "Moment":{
     "userId":38710
   }
}
后端校验通过后自动解析为 SQL 并执行:
`SELECT count(*) FROM Moment WHERE userId=38710 LIMIT 1` | {
   TableName:{
     "code":200,
     "msg":"success",
     "count":10
   },
   "code":200,
   "msg":"success"
}
例如
{
   "Moment":{
     "code":200,
     "msg":"success",
     "count":10
   },
   "code":200,
   "msg":"success"
} +GET:
普通获取数据,
可用浏览器调试 | base_url/get/ | {
   TableName:{
     …
   }
}
{…}内为限制条件

例如获取一个 id = 235 的 Moment:
[{
   "Moment":{
     "id":235
   }
}](url=http%3A%2F%2Flocalhost%3A8080%2Fget&type=JSON&json={"Moment":{"id":235}})
后端校验通过后自动解析为 SQL 并执行:
`SELECT * FROM Moment WHERE id=235 LIMIT 1` | {
   TableName:{
     ...
   },
   "code":200,
   "msg":"success"
}
例如
{
   "Moment":{
     "id":235,
     "userId":38710,
     "content":"APIJSON,let interfaces and documents go to hell !"
   },
   "code":200,
   "msg":"success"
} +HEAD:
普通获取数量,
可用浏览器调试 | base_url/head/ | {
   TableName:{
     …
   }
}
{…}内为限制条件

例如获取一个 id = 38710 的 User 所发布的 Moment 总数:
[{
   "Moment":{
     "userId":38710
   }
}](url=http%3A%2F%2Flocalhost%3A8080%2Fhead&type=JSON&json={"Moment":{"userId":38710}})
后端校验通过后自动解析为 SQL 并执行:
`SELECT count(*) FROM Moment WHERE userId=38710 LIMIT 1` | {
   TableName:{
     "code":200,
     "msg":"success",
     "count":10
   },
   "code":200,
   "msg":"success"
}
例如
{
   "Moment":{
     "code":200,
     "msg":"success",
     "count":10
   },
   "code":200,
   "msg":"success"
} GETS:
安全/私密获取数据,
用于获取钱包等
对安全性要求高的数据 | base_url/gets/ | 最外层加一个 "tag":tag,其它同GET | 同GET HEADS:
安全/私密获取数量,
用于获取银行卡数量等
对安全性要求高的数据总数 | base_url/heads/ | 最外层加一个 "tag":tag,其它同HEAD | 同HEAD -POST:
新增数据 | base_url/post/ | 单个:
{
   TableName:{
     …
   },
   "tag":tag
}
{…}中id由后端生成,不能传

例如当前登录用户 38710 发布一个新 Comment:
{
   "Comment":{
     "momentId":12,
     "content":"APIJSON,let interfaces and documents go to hell !"
   },
   "tag":"Comment"
}
后端校验通过后自动解析为 SQL 并执行:
`INSERT INTO Comment(userId,momentId,content) VALUES(38710,12,'APIJSON,let interfaces and documents go to hell !')`

批量:
{
   TableName\[]:\[{
       …
     }, {
       …
     }
     …
   ],
   "tag":tag
}
{…}中id由后端生成,不能传

例如当前登录用户 82001 发布 2 个 Comment:
{
   "Comment\[]":\[{
     "momentId":12,
     "content":"APIJSON,let interfaces and documents go to hell !"
     }, {
     "momentId":15,
     "content":"APIJSON is a JSON transmision protocol."
   }],
   "tag":"Comment:[]"
}
后端校验通过后自动解析为 SQL 并执行:
`INSERT INTO Comment(userId,momentId,content) VALUES(82001,12,'APIJSON,let interfaces and documents go to hell !')`
`INSERT INTO Comment(userId,momentId,content) VALUES(82001,15,'APIJSON is a JSON transmision protocol.')` | 单个:
{
   TableName:{
     "code":200,
     "msg":"success",
     "id":38710
   },
   "code":200,
   "msg":"success"
}
例如
{
   "Comment":{
     "code":200,
     "msg":"success",
     "id":120
   },
   "code":200,
   "msg":"success"
}

批量:
{
   TableName:{
     "code":200,
     "msg":"success",
     "count":5,
     "id[]":[1, 2, 3, 4, 5]
   },
   "code":200,
   "msg":"success"
}
例如
{
   "Comment":{
     "code":200,
     "msg":"success",
     "count":2,
     "id[]":\[1, 2]
   },
   "code":200,
   "msg":"success"
} -PUT:
修改数据,
只修改所传的字段 | base_url/put/ | {
   TableName:{
     "id":id,
     …
   },
   "tag":tag
}
{…} 中 id 或 id{} 至少传一个

例如当前登录用户 82001 修改 id = 235 的 Moment 的 content:
{
   "Moment":{
     "id":235,
     "content":"APIJSON,let interfaces and documents go to hell !"
   },
   "tag":"Moment"
}
后端校验通过后自动解析为 SQL 并执行:
`UPDATE Moment SET content='APIJSON,let interfaces and documents go to hell !' WHERE id=235 AND userId=82001 LIMIT 1`

批量除了 id{}:\[] 也可类似批量 POST,只是每个 {...} 里面都必须有 id。
"tag":"Comment[]" 对应对象 "Comment":{"id{}":[1,2,3]},表示指定记录全部统一设置;
"tag":"Comment:[]" 多了冒号,对应数组 "Comment[]":[{"id":1},{"id":2},{"id":3}],表示每项单独设置 | 同POST -DELETE:
删除数据 | base_url/delete/ | {
   TableName:{
     "id":id
   },
   "tag":tag
}
{…} 中 id 或 id{} 至少传一个,一般只传 id 或 id{}

例如当前登录用户 82001 批量删除 id = 100,110,120 的 Comment:
{
   "Comment":{
     "id{}":[100,110,120]
   },
   "tag":"Comment[]"
}
后端校验通过后自动解析为 SQL 并执行:
`DELETE FROM Comment WHERE id IN(100,110,120) AND userId=82001 LIMIT 3` | {
   TableName:{
     "code":200,
     "msg":"success",
     "id[]":[100,110,120]
      "count":3
   },
   "code":200,
   "msg":"success"
}
例如
{
   "Comment":{
      "code":200,
      "msg":"success",
      "id[]":[100,110,120],
      "count":3
   },
   "code":200,
   "msg":"success"
} +POST:
新增数据 | base_url/post/ | 单个:
{
   TableName:{
     …
   },
   "tag":tag
}
{…}中id由后端生成,不能传

例如当前登录用户 38710 发布一个新 Comment:
[{
   "Comment":{
     "momentId":12,
     "content":"APIJSON,let interfaces and documents go to hell !"
   },
   "tag":"Comment"
}](http://apijson.cn/api?url=http%3A%2F%2Fapijson.cn%3A8080%2Fpost&type=JSON&json={"Comment":{"momentId":12,"content":"APIJSON,let%20interfaces%20and%20documents%20go%20to%20hell%20!"},"tag":"Comment"})
后端校验通过后自动解析为 SQL 并执行:
`INSERT INTO Comment(userId,momentId,content) VALUES(38710,12,'APIJSON,let interfaces and documents go to hell !')`

批量:
{
   TableName\[]:\[{
       …
     }, {
       …
     }
     …
   ],
   "tag":tag
}
{…}中id由后端生成,不能传

例如当前登录用户 82001 发布 2 个 Comment:
[{
   "Comment[]":[{
     "momentId":12,
     "content":"APIJSON,let interfaces and documents go to hell !"
     }, {
     "momentId":15,
     "content":"APIJSON is a JSON transmision protocol."
   }],
   "tag":"Comment:[]"
}](http://apijson.cn/api?url=http%3A%2F%2Fapijson.cn%3A8080%2Fpost&type=JSON&json={"Comment[]":[{"momentId":12,"content":"APIJSON,let%20interfaces%20and%20documents%20go%20to%20hell%20!"},{"momentId":15,"content":"APIJSON%20is%20a%20JSON%20transmision%20protocol."}],"tag":"Comment:[]"})
后端校验通过后自动解析为 SQL 并执行:
`INSERT INTO Comment(userId,momentId,content) VALUES(82001,12,'APIJSON,let interfaces and documents go to hell !')`
`INSERT INTO Comment(userId,momentId,content) VALUES(82001,15,'APIJSON is a JSON transmision protocol.')` | 单个:
{
   TableName:{
     "code":200,
     "msg":"success",
     "id":38710
   },
   "code":200,
   "msg":"success"
}
例如
{
   "Comment":{
     "code":200,
     "msg":"success",
     "id":120
   },
   "code":200,
   "msg":"success"
}

批量:
{
   TableName:{
     "code":200,
     "msg":"success",
     "count":5,
     "id[]":[1, 2, 3, 4, 5]
   },
   "code":200,
   "msg":"success"
}
例如
{
   "Comment":{
     "code":200,
     "msg":"success",
     "count":2,
     "id[]":\[1, 2]
   },
   "code":200,
   "msg":"success"
} +PUT:
修改数据,
只修改所传的字段 | base_url/put/ | {
   TableName:{
     "id":id,
     …
   },
   "tag":tag
}
{…} 中 id 或 id{} 至少传一个

例如当前登录用户 82001 修改 id = 235 的 Moment 的 content:
[{
   "Moment":{
     "id":235,
     "content":"APIJSON,let interfaces and documents go to hell !"
   },
   "tag":"Moment"
}](http://apijson.cn/api?url=http%3A%2F%2Fapijson.cn%3A8080%2Fput&type=JSON&json={"Moment":{"id":235,"content":"APIJSON,let%20interfaces%20and%20documents%20go%20to%20hell%20!"},"tag":"Moment"})
后端校验通过后自动解析为 SQL 并执行:
`UPDATE Moment SET content='APIJSON,let interfaces and documents go to hell !' WHERE id=235 AND userId=82001 LIMIT 1`

批量除了 id{}:\[] 也可类似批量 POST,只是每个 {...} 里面都必须有 id。
"tag":"Comment[]" 对应对象 "Comment":{"id{}":[1,2,3]},表示指定记录全部统一设置;
"tag":"Comment:[]" 多了冒号,对应数组 "Comment[]":[{"id":1},{"id":2},{"id":3}],表示每项单独设置 | 同POST +DELETE:
删除数据 | base_url/delete/ | {
   TableName:{
     "id":id
   },
   "tag":tag
}
{…} 中 id 或 id{} 至少传一个,一般只传 id 或 id{}

例如当前登录用户 82001 批量删除 id = 100,110,120 的 Comment:
[{
   "Comment":{
     "id{}":[100,110,120]
   },
   "tag":"Comment[]"
}](http://apijson.cn/api?url=http%3A%2F%2Fapijson.cn%3A8080%2Fdelete&type=JSON&json={"Comment":{"id{}":[100,110,120]},"tag":"Comment[]"})
后端校验通过后自动解析为 SQL 并执行:
`DELETE FROM Comment WHERE id IN(100,110,120) AND userId=82001 LIMIT 3` | {
   TableName:{
     "code":200,
     "msg":"success",
     "id[]":[100,110,120]
      "count":3
   },
   "code":200,
   "msg":"success"
}
例如
{
   "Comment":{
      "code":200,
      "msg":"success",
      "id[]":[100,110,120],
      "count":3
   },
   "code":200,
   "msg":"success"
} 1.TableName指要查询的数据库表Table的名称字符串。第一个字符为大写字母,剩下的字符要符合英语字母、数字、下划线中的任何一种。对应的值的类型为JSONObject,结构是 {...},里面放的是Table的字段(列名)。下同。
From d2a3b8ff7d43b0a42a553408801c16fc13a6da28 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Mon, 27 Sep 2021 17:40:46 +0800 Subject: [PATCH 0485/1181] Update Document.md --- Document.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Document.md b/Document.md index 27eff492d..30e7aa914 100644 --- a/Document.md +++ b/Document.md @@ -338,8 +338,8 @@ https://github.com/Tencent/APIJSON ------------ | ------------ | ------------ | ------------ GET:
普通获取数据,
可用浏览器调试 | base_url/get/ | {
   TableName:{
     …
   }
}
{…}内为限制条件

例如获取一个 id = 235 的 Moment:
[{
   "Moment":{
     "id":235
   }
}](url=http%3A%2F%2Flocalhost%3A8080%2Fget&type=JSON&json={"Moment":{"id":235}})
后端校验通过后自动解析为 SQL 并执行:
`SELECT * FROM Moment WHERE id=235 LIMIT 1` | {
   TableName:{
     ...
   },
   "code":200,
   "msg":"success"
}
例如
{
   "Moment":{
     "id":235,
     "userId":38710,
     "content":"APIJSON,let interfaces and documents go to hell !"
   },
   "code":200,
   "msg":"success"
} HEAD:
普通获取数量,
可用浏览器调试 | base_url/head/ | {
   TableName:{
     …
   }
}
{…}内为限制条件

例如获取一个 id = 38710 的 User 所发布的 Moment 总数:
[{
   "Moment":{
     "userId":38710
   }
}](url=http%3A%2F%2Flocalhost%3A8080%2Fhead&type=JSON&json={"Moment":{"userId":38710}})
后端校验通过后自动解析为 SQL 并执行:
`SELECT count(*) FROM Moment WHERE userId=38710 LIMIT 1` | {
   TableName:{
     "code":200,
     "msg":"success",
     "count":10
   },
   "code":200,
   "msg":"success"
}
例如
{
   "Moment":{
     "code":200,
     "msg":"success",
     "count":10
   },
   "code":200,
   "msg":"success"
} -GETS:
安全/私密获取数据,
用于获取钱包等
对安全性要求高的数据 | base_url/gets/ | 最外层加一个 "tag":tag,其它同GET | 同GET -HEADS:
安全/私密获取数量,
用于获取银行卡数量等
对安全性要求高的数据总数 | base_url/heads/ | 最外层加一个 "tag":tag,其它同HEAD | 同HEAD +GETS:
安全/私密获取数据,
用于获取钱包等
对安全性要求高的数据 | base_url/gets/ | 最外层加一个 "tag":tag,例如 ["tag":"Privacy"](url=http%3A%2F%2Flocalhost%3A8080%2Fgets&type=JSON&json={"Privacy":{"id":82001}}),其它同GET | 同GET +HEADS:
安全/私密获取数量,
用于获取银行卡数量等
对安全性要求高的数据总数 | base_url/heads/ | 最外层加一个 "tag":tag,例如 ["tag":"Verify"](url=http%3A%2F%2Flocalhost%3A8080%2Fheads&type=JSON&json={"Verify":{"phone":13000082001}}),其它同HEAD | 同HEAD POST:
新增数据 | base_url/post/ | 单个:
{
   TableName:{
     …
   },
   "tag":tag
}
{…}中id由后端生成,不能传

例如当前登录用户 38710 发布一个新 Comment:
[{
   "Comment":{
     "momentId":12,
     "content":"APIJSON,let interfaces and documents go to hell !"
   },
   "tag":"Comment"
}](http://apijson.cn/api?url=http%3A%2F%2Fapijson.cn%3A8080%2Fpost&type=JSON&json={"Comment":{"momentId":12,"content":"APIJSON,let%20interfaces%20and%20documents%20go%20to%20hell%20!"},"tag":"Comment"})
后端校验通过后自动解析为 SQL 并执行:
`INSERT INTO Comment(userId,momentId,content) VALUES(38710,12,'APIJSON,let interfaces and documents go to hell !')`

批量:
{
   TableName\[]:\[{
       …
     }, {
       …
     }
     …
   ],
   "tag":tag
}
{…}中id由后端生成,不能传

例如当前登录用户 82001 发布 2 个 Comment:
[{
   "Comment[]":[{
     "momentId":12,
     "content":"APIJSON,let interfaces and documents go to hell !"
     }, {
     "momentId":15,
     "content":"APIJSON is a JSON transmision protocol."
   }],
   "tag":"Comment:[]"
}](http://apijson.cn/api?url=http%3A%2F%2Fapijson.cn%3A8080%2Fpost&type=JSON&json={"Comment[]":[{"momentId":12,"content":"APIJSON,let%20interfaces%20and%20documents%20go%20to%20hell%20!"},{"momentId":15,"content":"APIJSON%20is%20a%20JSON%20transmision%20protocol."}],"tag":"Comment:[]"})
后端校验通过后自动解析为 SQL 并执行:
`INSERT INTO Comment(userId,momentId,content) VALUES(82001,12,'APIJSON,let interfaces and documents go to hell !')`
`INSERT INTO Comment(userId,momentId,content) VALUES(82001,15,'APIJSON is a JSON transmision protocol.')` | 单个:
{
   TableName:{
     "code":200,
     "msg":"success",
     "id":38710
   },
   "code":200,
   "msg":"success"
}
例如
{
   "Comment":{
     "code":200,
     "msg":"success",
     "id":120
   },
   "code":200,
   "msg":"success"
}

批量:
{
   TableName:{
     "code":200,
     "msg":"success",
     "count":5,
     "id[]":[1, 2, 3, 4, 5]
   },
   "code":200,
   "msg":"success"
}
例如
{
   "Comment":{
     "code":200,
     "msg":"success",
     "count":2,
     "id[]":\[1, 2]
   },
   "code":200,
   "msg":"success"
} PUT:
修改数据,
只修改所传的字段 | base_url/put/ | {
   TableName:{
     "id":id,
     …
   },
   "tag":tag
}
{…} 中 id 或 id{} 至少传一个

例如当前登录用户 82001 修改 id = 235 的 Moment 的 content:
[{
   "Moment":{
     "id":235,
     "content":"APIJSON,let interfaces and documents go to hell !"
   },
   "tag":"Moment"
}](http://apijson.cn/api?url=http%3A%2F%2Fapijson.cn%3A8080%2Fput&type=JSON&json={"Moment":{"id":235,"content":"APIJSON,let%20interfaces%20and%20documents%20go%20to%20hell%20!"},"tag":"Moment"})
后端校验通过后自动解析为 SQL 并执行:
`UPDATE Moment SET content='APIJSON,let interfaces and documents go to hell !' WHERE id=235 AND userId=82001 LIMIT 1`

批量除了 id{}:\[] 也可类似批量 POST,只是每个 {...} 里面都必须有 id。
"tag":"Comment[]" 对应对象 "Comment":{"id{}":[1,2,3]},表示指定记录全部统一设置;
"tag":"Comment:[]" 多了冒号,对应数组 "Comment[]":[{"id":1},{"id":2},{"id":3}],表示每项单独设置 | 同POST DELETE:
删除数据 | base_url/delete/ | {
   TableName:{
     "id":id
   },
   "tag":tag
}
{…} 中 id 或 id{} 至少传一个,一般只传 id 或 id{}

例如当前登录用户 82001 批量删除 id = 100,110,120 的 Comment:
[{
   "Comment":{
     "id{}":[100,110,120]
   },
   "tag":"Comment[]"
}](http://apijson.cn/api?url=http%3A%2F%2Fapijson.cn%3A8080%2Fdelete&type=JSON&json={"Comment":{"id{}":[100,110,120]},"tag":"Comment[]"})
后端校验通过后自动解析为 SQL 并执行:
`DELETE FROM Comment WHERE id IN(100,110,120) AND userId=82001 LIMIT 3` | {
   TableName:{
     "code":200,
     "msg":"success",
     "id[]":[100,110,120]
      "count":3
   },
   "code":200,
   "msg":"success"
}
例如
{
   "Comment":{
      "code":200,
      "msg":"success",
      "id[]":[100,110,120],
      "count":3
   },
   "code":200,
   "msg":"success"
} From 1e7b901c0cf7f9e3ad257086a65da51fc457ba9f Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Mon, 27 Sep 2021 17:49:34 +0800 Subject: [PATCH 0486/1181] Update Document.md --- Document.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Document.md b/Document.md index 30e7aa914..4fca78c97 100644 --- a/Document.md +++ b/Document.md @@ -336,10 +336,10 @@ https://github.com/Tencent/APIJSON  方法及说明 | URL | Request | Response ------------ | ------------ | ------------ | ------------ -GET:
普通获取数据,
可用浏览器调试 | base_url/get/ | {
   TableName:{
     …
   }
}
{…}内为限制条件

例如获取一个 id = 235 的 Moment:
[{
   "Moment":{
     "id":235
   }
}](url=http%3A%2F%2Flocalhost%3A8080%2Fget&type=JSON&json={"Moment":{"id":235}})
后端校验通过后自动解析为 SQL 并执行:
`SELECT * FROM Moment WHERE id=235 LIMIT 1` | {
   TableName:{
     ...
   },
   "code":200,
   "msg":"success"
}
例如
{
   "Moment":{
     "id":235,
     "userId":38710,
     "content":"APIJSON,let interfaces and documents go to hell !"
   },
   "code":200,
   "msg":"success"
} -HEAD:
普通获取数量,
可用浏览器调试 | base_url/head/ | {
   TableName:{
     …
   }
}
{…}内为限制条件

例如获取一个 id = 38710 的 User 所发布的 Moment 总数:
[{
   "Moment":{
     "userId":38710
   }
}](url=http%3A%2F%2Flocalhost%3A8080%2Fhead&type=JSON&json={"Moment":{"userId":38710}})
后端校验通过后自动解析为 SQL 并执行:
`SELECT count(*) FROM Moment WHERE userId=38710 LIMIT 1` | {
   TableName:{
     "code":200,
     "msg":"success",
     "count":10
   },
   "code":200,
   "msg":"success"
}
例如
{
   "Moment":{
     "code":200,
     "msg":"success",
     "count":10
   },
   "code":200,
   "msg":"success"
} -GETS:
安全/私密获取数据,
用于获取钱包等
对安全性要求高的数据 | base_url/gets/ | 最外层加一个 "tag":tag,例如 ["tag":"Privacy"](url=http%3A%2F%2Flocalhost%3A8080%2Fgets&type=JSON&json={"Privacy":{"id":82001}}),其它同GET | 同GET -HEADS:
安全/私密获取数量,
用于获取银行卡数量等
对安全性要求高的数据总数 | base_url/heads/ | 最外层加一个 "tag":tag,例如 ["tag":"Verify"](url=http%3A%2F%2Flocalhost%3A8080%2Fheads&type=JSON&json={"Verify":{"phone":13000082001}}),其它同HEAD | 同HEAD +GET:
普通获取数据,
可用浏览器调试 | base_url/get/ | {
   TableName:{
     …
   }
}
{…}内为限制条件

例如获取一个 id = 235 的 Moment:
[{
   "Moment":{
     "id":235
   }
}](http://apijson.cn/api/?url=http%3A%2F%2Fapijson.cn%3A8080%2Fget&type=JSON&json={"Moment"%3A{"id"%3A235}})
后端校验通过后自动解析为 SQL 并执行:
`SELECT * FROM Moment WHERE id=235 LIMIT 1` | {
   TableName:{
     ...
   },
   "code":200,
   "msg":"success"
}
例如
{
   "Moment":{
     "id":235,
     "userId":38710,
     "content":"APIJSON,let interfaces and documents go to hell !"
   },
   "code":200,
   "msg":"success"
} +HEAD:
普通获取数量,
可用浏览器调试 | base_url/head/ | {
   TableName:{
     …
   }
}
{…}内为限制条件

例如获取一个 id = 38710 的 User 所发布的 Moment 总数:
[{
   "Moment":{
     "userId":38710
   }
}](http://apijson.cn/api/?url=http%3A%2F%2Fapijson.cn%3A8080%2Fhead&type=JSON&json={"Moment"%3A{"userId"%3A38710}})
后端校验通过后自动解析为 SQL 并执行:
`SELECT count(*) FROM Moment WHERE userId=38710 LIMIT 1` | {
   TableName:{
     "code":200,
     "msg":"success",
     "count":10
   },
   "code":200,
   "msg":"success"
}
例如
{
   "Moment":{
     "code":200,
     "msg":"success",
     "count":10
   },
   "code":200,
   "msg":"success"
} +GETS:
安全/私密获取数据,
用于获取钱包等
对安全性要求高的数据 | base_url/gets/ | 最外层加一个 "tag":tag,例如 ["tag":"Privacy"](http://apijson.cn/api/?url=http%3A%2F%2Fapijson.cn%3A8080%2Fgets&type=JSON&json={"tag"%3A"Privacy","Privacy"%3A{"id"%3A82001}} ),其它同GET | 同GET +HEADS:
安全/私密获取数量,
用于获取银行卡数量等
对安全性要求高的数据总数 | base_url/heads/ | 最外层加一个 "tag":tag,例如 ["tag":"Verify"](http://apijson.cn/api/?url=http%3A%2F%2Fapijson.cn%3A8080%2Fheads&type=JSON&json={"tag"%3A"Verify","Verify"%3A{"phone"%3A13000082001}}),其它同HEAD | 同HEAD POST:
新增数据 | base_url/post/ | 单个:
{
   TableName:{
     …
   },
   "tag":tag
}
{…}中id由后端生成,不能传

例如当前登录用户 38710 发布一个新 Comment:
[{
   "Comment":{
     "momentId":12,
     "content":"APIJSON,let interfaces and documents go to hell !"
   },
   "tag":"Comment"
}](http://apijson.cn/api?url=http%3A%2F%2Fapijson.cn%3A8080%2Fpost&type=JSON&json={"Comment":{"momentId":12,"content":"APIJSON,let%20interfaces%20and%20documents%20go%20to%20hell%20!"},"tag":"Comment"})
后端校验通过后自动解析为 SQL 并执行:
`INSERT INTO Comment(userId,momentId,content) VALUES(38710,12,'APIJSON,let interfaces and documents go to hell !')`

批量:
{
   TableName\[]:\[{
       …
     }, {
       …
     }
     …
   ],
   "tag":tag
}
{…}中id由后端生成,不能传

例如当前登录用户 82001 发布 2 个 Comment:
[{
   "Comment[]":[{
     "momentId":12,
     "content":"APIJSON,let interfaces and documents go to hell !"
     }, {
     "momentId":15,
     "content":"APIJSON is a JSON transmision protocol."
   }],
   "tag":"Comment:[]"
}](http://apijson.cn/api?url=http%3A%2F%2Fapijson.cn%3A8080%2Fpost&type=JSON&json={"Comment[]":[{"momentId":12,"content":"APIJSON,let%20interfaces%20and%20documents%20go%20to%20hell%20!"},{"momentId":15,"content":"APIJSON%20is%20a%20JSON%20transmision%20protocol."}],"tag":"Comment:[]"})
后端校验通过后自动解析为 SQL 并执行:
`INSERT INTO Comment(userId,momentId,content) VALUES(82001,12,'APIJSON,let interfaces and documents go to hell !')`
`INSERT INTO Comment(userId,momentId,content) VALUES(82001,15,'APIJSON is a JSON transmision protocol.')` | 单个:
{
   TableName:{
     "code":200,
     "msg":"success",
     "id":38710
   },
   "code":200,
   "msg":"success"
}
例如
{
   "Comment":{
     "code":200,
     "msg":"success",
     "id":120
   },
   "code":200,
   "msg":"success"
}

批量:
{
   TableName:{
     "code":200,
     "msg":"success",
     "count":5,
     "id[]":[1, 2, 3, 4, 5]
   },
   "code":200,
   "msg":"success"
}
例如
{
   "Comment":{
     "code":200,
     "msg":"success",
     "count":2,
     "id[]":\[1, 2]
   },
   "code":200,
   "msg":"success"
} PUT:
修改数据,
只修改所传的字段 | base_url/put/ | {
   TableName:{
     "id":id,
     …
   },
   "tag":tag
}
{…} 中 id 或 id{} 至少传一个

例如当前登录用户 82001 修改 id = 235 的 Moment 的 content:
[{
   "Moment":{
     "id":235,
     "content":"APIJSON,let interfaces and documents go to hell !"
   },
   "tag":"Moment"
}](http://apijson.cn/api?url=http%3A%2F%2Fapijson.cn%3A8080%2Fput&type=JSON&json={"Moment":{"id":235,"content":"APIJSON,let%20interfaces%20and%20documents%20go%20to%20hell%20!"},"tag":"Moment"})
后端校验通过后自动解析为 SQL 并执行:
`UPDATE Moment SET content='APIJSON,let interfaces and documents go to hell !' WHERE id=235 AND userId=82001 LIMIT 1`

批量除了 id{}:\[] 也可类似批量 POST,只是每个 {...} 里面都必须有 id。
"tag":"Comment[]" 对应对象 "Comment":{"id{}":[1,2,3]},表示指定记录全部统一设置;
"tag":"Comment:[]" 多了冒号,对应数组 "Comment[]":[{"id":1},{"id":2},{"id":3}],表示每项单独设置 | 同POST DELETE:
删除数据 | base_url/delete/ | {
   TableName:{
     "id":id
   },
   "tag":tag
}
{…} 中 id 或 id{} 至少传一个,一般只传 id 或 id{}

例如当前登录用户 82001 批量删除 id = 100,110,120 的 Comment:
[{
   "Comment":{
     "id{}":[100,110,120]
   },
   "tag":"Comment[]"
}](http://apijson.cn/api?url=http%3A%2F%2Fapijson.cn%3A8080%2Fdelete&type=JSON&json={"Comment":{"id{}":[100,110,120]},"tag":"Comment[]"})
后端校验通过后自动解析为 SQL 并执行:
`DELETE FROM Comment WHERE id IN(100,110,120) AND userId=82001 LIMIT 3` | {
   TableName:{
     "code":200,
     "msg":"success",
     "id[]":[100,110,120]
      "count":3
   },
   "code":200,
   "msg":"success"
}
例如
{
   "Comment":{
      "code":200,
      "msg":"success",
      "id[]":[100,110,120],
      "count":3
   },
   "code":200,
   "msg":"success"
} From 778b1bcd0e8ff567ddda69a2caf8e4b9bdc2970a Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Mon, 27 Sep 2021 19:02:36 +0800 Subject: [PATCH 0487/1181] Update Document.md --- Document.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Document.md b/Document.md index 4fca78c97..729b6c4d1 100644 --- a/Document.md +++ b/Document.md @@ -338,12 +338,12 @@ https://github.com/Tencent/APIJSON ------------ | ------------ | ------------ | ------------ GET:
普通获取数据,
可用浏览器调试 | base_url/get/ | {
   TableName:{
     …
   }
}
{…}内为限制条件

例如获取一个 id = 235 的 Moment:
[{
   "Moment":{
     "id":235
   }
}](http://apijson.cn/api/?url=http%3A%2F%2Fapijson.cn%3A8080%2Fget&type=JSON&json={"Moment"%3A{"id"%3A235}})
后端校验通过后自动解析为 SQL 并执行:
`SELECT * FROM Moment WHERE id=235 LIMIT 1` | {
   TableName:{
     ...
   },
   "code":200,
   "msg":"success"
}
例如
{
   "Moment":{
     "id":235,
     "userId":38710,
     "content":"APIJSON,let interfaces and documents go to hell !"
   },
   "code":200,
   "msg":"success"
} HEAD:
普通获取数量,
可用浏览器调试 | base_url/head/ | {
   TableName:{
     …
   }
}
{…}内为限制条件

例如获取一个 id = 38710 的 User 所发布的 Moment 总数:
[{
   "Moment":{
     "userId":38710
   }
}](http://apijson.cn/api/?url=http%3A%2F%2Fapijson.cn%3A8080%2Fhead&type=JSON&json={"Moment"%3A{"userId"%3A38710}})
后端校验通过后自动解析为 SQL 并执行:
`SELECT count(*) FROM Moment WHERE userId=38710 LIMIT 1` | {
   TableName:{
     "code":200,
     "msg":"success",
     "count":10
   },
   "code":200,
   "msg":"success"
}
例如
{
   "Moment":{
     "code":200,
     "msg":"success",
     "count":10
   },
   "code":200,
   "msg":"success"
} -GETS:
安全/私密获取数据,
用于获取钱包等
对安全性要求高的数据 | base_url/gets/ | 最外层加一个 "tag":tag,例如 ["tag":"Privacy"](http://apijson.cn/api/?url=http%3A%2F%2Fapijson.cn%3A8080%2Fgets&type=JSON&json={"tag"%3A"Privacy","Privacy"%3A{"id"%3A82001}} ),其它同GET | 同GET +GETS:
安全/私密获取数据,
用于获取钱包等
对安全性要求高的数据 | base_url/gets/ | 最外层加一个 "tag":tag,例如 ["tag":"Privacy"](http://apijson.cn/api/?url=http%3A%2F%2Fapijson.cn%3A8080%2Fgets&type=JSON&json={"tag"%3A"Privacy","Privacy"%3A{"id"%3A82001}}),其它同GET | 同GET HEADS:
安全/私密获取数量,
用于获取银行卡数量等
对安全性要求高的数据总数 | base_url/heads/ | 最外层加一个 "tag":tag,例如 ["tag":"Verify"](http://apijson.cn/api/?url=http%3A%2F%2Fapijson.cn%3A8080%2Fheads&type=JSON&json={"tag"%3A"Verify","Verify"%3A{"phone"%3A13000082001}}),其它同HEAD | 同HEAD POST:
新增数据 | base_url/post/ | 单个:
{
   TableName:{
     …
   },
   "tag":tag
}
{…}中id由后端生成,不能传

例如当前登录用户 38710 发布一个新 Comment:
[{
   "Comment":{
     "momentId":12,
     "content":"APIJSON,let interfaces and documents go to hell !"
   },
   "tag":"Comment"
}](http://apijson.cn/api?url=http%3A%2F%2Fapijson.cn%3A8080%2Fpost&type=JSON&json={"Comment":{"momentId":12,"content":"APIJSON,let%20interfaces%20and%20documents%20go%20to%20hell%20!"},"tag":"Comment"})
后端校验通过后自动解析为 SQL 并执行:
`INSERT INTO Comment(userId,momentId,content) VALUES(38710,12,'APIJSON,let interfaces and documents go to hell !')`

批量:
{
   TableName\[]:\[{
       …
     }, {
       …
     }
     …
   ],
   "tag":tag
}
{…}中id由后端生成,不能传

例如当前登录用户 82001 发布 2 个 Comment:
[{
   "Comment[]":[{
     "momentId":12,
     "content":"APIJSON,let interfaces and documents go to hell !"
     }, {
     "momentId":15,
     "content":"APIJSON is a JSON transmision protocol."
   }],
   "tag":"Comment:[]"
}](http://apijson.cn/api?url=http%3A%2F%2Fapijson.cn%3A8080%2Fpost&type=JSON&json={"Comment[]":[{"momentId":12,"content":"APIJSON,let%20interfaces%20and%20documents%20go%20to%20hell%20!"},{"momentId":15,"content":"APIJSON%20is%20a%20JSON%20transmision%20protocol."}],"tag":"Comment:[]"})
后端校验通过后自动解析为 SQL 并执行:
`INSERT INTO Comment(userId,momentId,content) VALUES(82001,12,'APIJSON,let interfaces and documents go to hell !')`
`INSERT INTO Comment(userId,momentId,content) VALUES(82001,15,'APIJSON is a JSON transmision protocol.')` | 单个:
{
   TableName:{
     "code":200,
     "msg":"success",
     "id":38710
   },
   "code":200,
   "msg":"success"
}
例如
{
   "Comment":{
     "code":200,
     "msg":"success",
     "id":120
   },
   "code":200,
   "msg":"success"
}

批量:
{
   TableName:{
     "code":200,
     "msg":"success",
     "count":5,
     "id[]":[1, 2, 3, 4, 5]
   },
   "code":200,
   "msg":"success"
}
例如
{
   "Comment":{
     "code":200,
     "msg":"success",
     "count":2,
     "id[]":\[1, 2]
   },
   "code":200,
   "msg":"success"
} PUT:
修改数据,
只修改所传的字段 | base_url/put/ | {
   TableName:{
     "id":id,
     …
   },
   "tag":tag
}
{…} 中 id 或 id{} 至少传一个

例如当前登录用户 82001 修改 id = 235 的 Moment 的 content:
[{
   "Moment":{
     "id":235,
     "content":"APIJSON,let interfaces and documents go to hell !"
   },
   "tag":"Moment"
}](http://apijson.cn/api?url=http%3A%2F%2Fapijson.cn%3A8080%2Fput&type=JSON&json={"Moment":{"id":235,"content":"APIJSON,let%20interfaces%20and%20documents%20go%20to%20hell%20!"},"tag":"Moment"})
后端校验通过后自动解析为 SQL 并执行:
`UPDATE Moment SET content='APIJSON,let interfaces and documents go to hell !' WHERE id=235 AND userId=82001 LIMIT 1`

批量除了 id{}:\[] 也可类似批量 POST,只是每个 {...} 里面都必须有 id。
"tag":"Comment[]" 对应对象 "Comment":{"id{}":[1,2,3]},表示指定记录全部统一设置;
"tag":"Comment:[]" 多了冒号,对应数组 "Comment[]":[{"id":1},{"id":2},{"id":3}],表示每项单独设置 | 同POST DELETE:
删除数据 | base_url/delete/ | {
   TableName:{
     "id":id
   },
   "tag":tag
}
{…} 中 id 或 id{} 至少传一个,一般只传 id 或 id{}

例如当前登录用户 82001 批量删除 id = 100,110,120 的 Comment:
[{
   "Comment":{
     "id{}":[100,110,120]
   },
   "tag":"Comment[]"
}](http://apijson.cn/api?url=http%3A%2F%2Fapijson.cn%3A8080%2Fdelete&type=JSON&json={"Comment":{"id{}":[100,110,120]},"tag":"Comment[]"})
后端校验通过后自动解析为 SQL 并执行:
`DELETE FROM Comment WHERE id IN(100,110,120) AND userId=82001 LIMIT 3` | {
   TableName:{
     "code":200,
     "msg":"success",
     "id[]":[100,110,120]
      "count":3
   },
   "code":200,
   "msg":"success"
}
例如
{
   "Comment":{
      "code":200,
      "msg":"success",
      "id[]":[100,110,120],
      "count":3
   },
   "code":200,
   "msg":"success"
} - +以上接口的简单形式:
base_url/{method}/{tag} | GET: 普通获取数据
base_url/get/{tag}

HEAD: 普通获取数量
base_url/head/{tag}

GETS: 安全/私密获取数据
base_url/gets/{tag}

HEADS: 安全/私密获取数量
base_url/heads/{tag}

POST: 新增数据
base_url/post/{tag}

PUT: 修改数据 base_url/put/{tag}

DELETE: 删除数据
base_url/delete/{tag} | 例如安全/私密获取一个 id = 82001 的 Privacy:
[base_url/gets/Privacy/
{"id":82001}](http://apijson.cn/api/?url=http%3A%2F%2Fapijson.cn%3A8080%2Fgets%2FPrivacy&type=JSON&json={"id"%3A82001})
相当于
[base_url/gets/
{"tag":"Privacy","Privacy":{"id":82001}}](http://apijson.cn/api/?url=http%3A%2F%2Fapijson.cn%3A8080%2Fgets&type=JSON&json={"tag"%3A"Privacy","Privacy"%3A{"id"%3A82001}})

例如批量修改 id = 114, 124 的 Comment 的 content:
[base_url/put/Comemnt[]/
{
   "id{}":[114,124],
   "content":"test multi put"
}](http://apijson.cn/api?url=http%3A%2F%2Fapijson.cn%3A8080%2Fput%2FComment[]&type=JSON&json={"id{}"%3A[114,124],"content"%3A"test%20multi%20put"})
相当于
[base_url/put/
{
   "tag":"Comment[]",
   "Comment":{
     "id{}":[114,124],
     "content":"test multi put"
   }
}](http://apijson.cn/api?url=http%3A%2F%2Fapijson.cn%3A8080%2Fput&type=JSON&json={"tag"%3A"Comment[]","Comment"%3A{"id{}"%3A[114,124],"content"%3A"test%20multi%20put"}}) | 同以上对应的方法 1.TableName指要查询的数据库表Table的名称字符串。第一个字符为大写字母,剩下的字符要符合英语字母、数字、下划线中的任何一种。对应的值的类型为JSONObject,结构是 {...},里面放的是Table的字段(列名)。下同。
2."tag":tag 后面的tag是非GET、HEAD请求中匹配请求的JSON结构的标识,一般是要查询的Table的名称,由后端Request表中指定。下同。
From f3494d79aa31653a6ea45add58dcca89784b4d03 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Mon, 27 Sep 2021 19:03:39 +0800 Subject: [PATCH 0488/1181] Update Document.md --- Document.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Document.md b/Document.md index 729b6c4d1..0426f6b8f 100644 --- a/Document.md +++ b/Document.md @@ -343,7 +343,7 @@ HEADS:
安全/私密获取数量,
用于获取银行卡数量等
新增数据 | base_url/post/ | 单个:
{
   TableName:{
     …
   },
   "tag":tag
}
{…}中id由后端生成,不能传

例如当前登录用户 38710 发布一个新 Comment:
[{
   "Comment":{
     "momentId":12,
     "content":"APIJSON,let interfaces and documents go to hell !"
   },
   "tag":"Comment"
}](http://apijson.cn/api?url=http%3A%2F%2Fapijson.cn%3A8080%2Fpost&type=JSON&json={"Comment":{"momentId":12,"content":"APIJSON,let%20interfaces%20and%20documents%20go%20to%20hell%20!"},"tag":"Comment"})
后端校验通过后自动解析为 SQL 并执行:
`INSERT INTO Comment(userId,momentId,content) VALUES(38710,12,'APIJSON,let interfaces and documents go to hell !')`

批量:
{
   TableName\[]:\[{
       …
     }, {
       …
     }
     …
   ],
   "tag":tag
}
{…}中id由后端生成,不能传

例如当前登录用户 82001 发布 2 个 Comment:
[{
   "Comment[]":[{
     "momentId":12,
     "content":"APIJSON,let interfaces and documents go to hell !"
     }, {
     "momentId":15,
     "content":"APIJSON is a JSON transmision protocol."
   }],
   "tag":"Comment:[]"
}](http://apijson.cn/api?url=http%3A%2F%2Fapijson.cn%3A8080%2Fpost&type=JSON&json={"Comment[]":[{"momentId":12,"content":"APIJSON,let%20interfaces%20and%20documents%20go%20to%20hell%20!"},{"momentId":15,"content":"APIJSON%20is%20a%20JSON%20transmision%20protocol."}],"tag":"Comment:[]"})
后端校验通过后自动解析为 SQL 并执行:
`INSERT INTO Comment(userId,momentId,content) VALUES(82001,12,'APIJSON,let interfaces and documents go to hell !')`
`INSERT INTO Comment(userId,momentId,content) VALUES(82001,15,'APIJSON is a JSON transmision protocol.')` | 单个:
{
   TableName:{
     "code":200,
     "msg":"success",
     "id":38710
   },
   "code":200,
   "msg":"success"
}
例如
{
   "Comment":{
     "code":200,
     "msg":"success",
     "id":120
   },
   "code":200,
   "msg":"success"
}

批量:
{
   TableName:{
     "code":200,
     "msg":"success",
     "count":5,
     "id[]":[1, 2, 3, 4, 5]
   },
   "code":200,
   "msg":"success"
}
例如
{
   "Comment":{
     "code":200,
     "msg":"success",
     "count":2,
     "id[]":\[1, 2]
   },
   "code":200,
   "msg":"success"
} PUT:
修改数据,
只修改所传的字段 | base_url/put/ | {
   TableName:{
     "id":id,
     …
   },
   "tag":tag
}
{…} 中 id 或 id{} 至少传一个

例如当前登录用户 82001 修改 id = 235 的 Moment 的 content:
[{
   "Moment":{
     "id":235,
     "content":"APIJSON,let interfaces and documents go to hell !"
   },
   "tag":"Moment"
}](http://apijson.cn/api?url=http%3A%2F%2Fapijson.cn%3A8080%2Fput&type=JSON&json={"Moment":{"id":235,"content":"APIJSON,let%20interfaces%20and%20documents%20go%20to%20hell%20!"},"tag":"Moment"})
后端校验通过后自动解析为 SQL 并执行:
`UPDATE Moment SET content='APIJSON,let interfaces and documents go to hell !' WHERE id=235 AND userId=82001 LIMIT 1`

批量除了 id{}:\[] 也可类似批量 POST,只是每个 {...} 里面都必须有 id。
"tag":"Comment[]" 对应对象 "Comment":{"id{}":[1,2,3]},表示指定记录全部统一设置;
"tag":"Comment:[]" 多了冒号,对应数组 "Comment[]":[{"id":1},{"id":2},{"id":3}],表示每项单独设置 | 同POST DELETE:
删除数据 | base_url/delete/ | {
   TableName:{
     "id":id
   },
   "tag":tag
}
{…} 中 id 或 id{} 至少传一个,一般只传 id 或 id{}

例如当前登录用户 82001 批量删除 id = 100,110,120 的 Comment:
[{
   "Comment":{
     "id{}":[100,110,120]
   },
   "tag":"Comment[]"
}](http://apijson.cn/api?url=http%3A%2F%2Fapijson.cn%3A8080%2Fdelete&type=JSON&json={"Comment":{"id{}":[100,110,120]},"tag":"Comment[]"})
后端校验通过后自动解析为 SQL 并执行:
`DELETE FROM Comment WHERE id IN(100,110,120) AND userId=82001 LIMIT 3` | {
   TableName:{
     "code":200,
     "msg":"success",
     "id[]":[100,110,120]
      "count":3
   },
   "code":200,
   "msg":"success"
}
例如
{
   "Comment":{
      "code":200,
      "msg":"success",
      "id[]":[100,110,120],
      "count":3
   },
   "code":200,
   "msg":"success"
} -以上接口的简单形式:
base_url/{method}/{tag} | GET: 普通获取数据
base_url/get/{tag}

HEAD: 普通获取数量
base_url/head/{tag}

GETS: 安全/私密获取数据
base_url/gets/{tag}

HEADS: 安全/私密获取数量
base_url/heads/{tag}

POST: 新增数据
base_url/post/{tag}

PUT: 修改数据 base_url/put/{tag}

DELETE: 删除数据
base_url/delete/{tag} | 例如安全/私密获取一个 id = 82001 的 Privacy:
[base_url/gets/Privacy/
{"id":82001}](http://apijson.cn/api/?url=http%3A%2F%2Fapijson.cn%3A8080%2Fgets%2FPrivacy&type=JSON&json={"id"%3A82001})
相当于
[base_url/gets/
{"tag":"Privacy","Privacy":{"id":82001}}](http://apijson.cn/api/?url=http%3A%2F%2Fapijson.cn%3A8080%2Fgets&type=JSON&json={"tag"%3A"Privacy","Privacy"%3A{"id"%3A82001}})

例如批量修改 id = 114, 124 的 Comment 的 content:
[base_url/put/Comemnt[]/
{
   "id{}":[114,124],
   "content":"test multi put"
}](http://apijson.cn/api?url=http%3A%2F%2Fapijson.cn%3A8080%2Fput%2FComment[]&type=JSON&json={"id{}"%3A[114,124],"content"%3A"test%20multi%20put"})
相当于
[base_url/put/
{
   "tag":"Comment[]",
   "Comment":{
     "id{}":[114,124],
     "content":"test multi put"
   }
}](http://apijson.cn/api?url=http%3A%2F%2Fapijson.cn%3A8080%2Fput&type=JSON&json={"tag"%3A"Comment[]","Comment"%3A{"id{}"%3A[114,124],"content"%3A"test%20multi%20put"}}) | 同以上对应的方法 +以上接口的简单形式:
base_url/{method}/{tag} | GET: 普通获取数据
base_url/get/{tag}

HEAD: 普通获取数量
base_url/head/{tag}

GETS: 安全/私密获取数据
base_url/gets/{tag}

HEADS: 安全/私密获取数量
base_url/heads/{tag}

POST: 新增数据
base_url/post/{tag}

PUT: 修改数据 base_url/put/{tag}

DELETE: 删除数据
base_url/delete/{tag} | 例如安全/私密获取一个 id = 82001 的 Privacy:
[base_url/gets/Privacy/
{"id":82001}](http://apijson.cn/api/?url=http%3A%2F%2Fapijson.cn%3A8080%2Fgets%2FPrivacy&type=JSON&json={"id"%3A82001})
相当于
[base_url/gets/
{"tag":"Privacy", "Privacy":{"id":82001}}](http://apijson.cn/api/?url=http%3A%2F%2Fapijson.cn%3A8080%2Fgets&type=JSON&json={"tag"%3A"Privacy","Privacy"%3A{"id"%3A82001}})

例如批量修改 id = 114, 124 的 Comment 的 content:
[base_url/put/Comemnt[]/
{
   "id{}":[114,124],
   "content":"test multi put"
}](http://apijson.cn/api?url=http%3A%2F%2Fapijson.cn%3A8080%2Fput%2FComment[]&type=JSON&json={"id{}"%3A[114,124],"content"%3A"test%20multi%20put"})
相当于
[base_url/put/
{
   "tag":"Comment[]",
   "Comment":{
     "id{}":[114,124],
     "content":"test multi put"
   }
}](http://apijson.cn/api?url=http%3A%2F%2Fapijson.cn%3A8080%2Fput&type=JSON&json={"tag"%3A"Comment[]","Comment"%3A{"id{}"%3A[114,124],"content"%3A"test%20multi%20put"}}) | 同以上对应的方法 1.TableName指要查询的数据库表Table的名称字符串。第一个字符为大写字母,剩下的字符要符合英语字母、数字、下划线中的任何一种。对应的值的类型为JSONObject,结构是 {...},里面放的是Table的字段(列名)。下同。
2."tag":tag 后面的tag是非GET、HEAD请求中匹配请求的JSON结构的标识,一般是要查询的Table的名称,由后端Request表中指定。下同。
From b39c1f06980df44fc88099883335c62fe4a8b0d6 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Mon, 27 Sep 2021 19:12:30 +0800 Subject: [PATCH 0489/1181] Update Document.md --- Document.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Document.md b/Document.md index 0426f6b8f..907af5311 100644 --- a/Document.md +++ b/Document.md @@ -340,7 +340,7 @@ GET:
普通获取数据,
可用浏览器调试 | base_url/get/ | {< HEAD:
普通获取数量,
可用浏览器调试 | base_url/head/ | {
   TableName:{
     …
   }
}
{…}内为限制条件

例如获取一个 id = 38710 的 User 所发布的 Moment 总数:
[{
   "Moment":{
     "userId":38710
   }
}](http://apijson.cn/api/?url=http%3A%2F%2Fapijson.cn%3A8080%2Fhead&type=JSON&json={"Moment"%3A{"userId"%3A38710}})
后端校验通过后自动解析为 SQL 并执行:
`SELECT count(*) FROM Moment WHERE userId=38710 LIMIT 1` | {
   TableName:{
     "code":200,
     "msg":"success",
     "count":10
   },
   "code":200,
   "msg":"success"
}
例如
{
   "Moment":{
     "code":200,
     "msg":"success",
     "count":10
   },
   "code":200,
   "msg":"success"
} GETS:
安全/私密获取数据,
用于获取钱包等
对安全性要求高的数据 | base_url/gets/ | 最外层加一个 "tag":tag,例如 ["tag":"Privacy"](http://apijson.cn/api/?url=http%3A%2F%2Fapijson.cn%3A8080%2Fgets&type=JSON&json={"tag"%3A"Privacy","Privacy"%3A{"id"%3A82001}}),其它同GET | 同GET HEADS:
安全/私密获取数量,
用于获取银行卡数量等
对安全性要求高的数据总数 | base_url/heads/ | 最外层加一个 "tag":tag,例如 ["tag":"Verify"](http://apijson.cn/api/?url=http%3A%2F%2Fapijson.cn%3A8080%2Fheads&type=JSON&json={"tag"%3A"Verify","Verify"%3A{"phone"%3A13000082001}}),其它同HEAD | 同HEAD -POST:
新增数据 | base_url/post/ | 单个:
{
   TableName:{
     …
   },
   "tag":tag
}
{…}中id由后端生成,不能传

例如当前登录用户 38710 发布一个新 Comment:
[{
   "Comment":{
     "momentId":12,
     "content":"APIJSON,let interfaces and documents go to hell !"
   },
   "tag":"Comment"
}](http://apijson.cn/api?url=http%3A%2F%2Fapijson.cn%3A8080%2Fpost&type=JSON&json={"Comment":{"momentId":12,"content":"APIJSON,let%20interfaces%20and%20documents%20go%20to%20hell%20!"},"tag":"Comment"})
后端校验通过后自动解析为 SQL 并执行:
`INSERT INTO Comment(userId,momentId,content) VALUES(38710,12,'APIJSON,let interfaces and documents go to hell !')`

批量:
{
   TableName\[]:\[{
       …
     }, {
       …
     }
     …
   ],
   "tag":tag
}
{…}中id由后端生成,不能传

例如当前登录用户 82001 发布 2 个 Comment:
[{
   "Comment[]":[{
     "momentId":12,
     "content":"APIJSON,let interfaces and documents go to hell !"
     }, {
     "momentId":15,
     "content":"APIJSON is a JSON transmision protocol."
   }],
   "tag":"Comment:[]"
}](http://apijson.cn/api?url=http%3A%2F%2Fapijson.cn%3A8080%2Fpost&type=JSON&json={"Comment[]":[{"momentId":12,"content":"APIJSON,let%20interfaces%20and%20documents%20go%20to%20hell%20!"},{"momentId":15,"content":"APIJSON%20is%20a%20JSON%20transmision%20protocol."}],"tag":"Comment:[]"})
后端校验通过后自动解析为 SQL 并执行:
`INSERT INTO Comment(userId,momentId,content) VALUES(82001,12,'APIJSON,let interfaces and documents go to hell !')`
`INSERT INTO Comment(userId,momentId,content) VALUES(82001,15,'APIJSON is a JSON transmision protocol.')` | 单个:
{
   TableName:{
     "code":200,
     "msg":"success",
     "id":38710
   },
   "code":200,
   "msg":"success"
}
例如
{
   "Comment":{
     "code":200,
     "msg":"success",
     "id":120
   },
   "code":200,
   "msg":"success"
}

批量:
{
   TableName:{
     "code":200,
     "msg":"success",
     "count":5,
     "id[]":[1, 2, 3, 4, 5]
   },
   "code":200,
   "msg":"success"
}
例如
{
   "Comment":{
     "code":200,
     "msg":"success",
     "count":2,
     "id[]":\[1, 2]
   },
   "code":200,
   "msg":"success"
} +POST:
新增数据 | base_url/post/ | 单个:
{
   TableName:{
     …
   },
   "tag":tag
}
{…}中id由后端生成,不能传

例如当前登录用户 38710 发布一个新 Comment:
[{
   "Comment":{
     "momentId":12,
     "content":"APIJSON,let interfaces and documents go to hell !"
   },
   "tag":"Comment"
}](http://apijson.cn/api?url=http%3A%2F%2Fapijson.cn%3A8080%2Fpost&type=JSON&json={"Comment":{"momentId":12,"content":"APIJSON,let%20interfaces%20and%20documents%20go%20to%20hell%20!"},"tag":"Comment"})
后端校验通过后自动解析为 SQL 并执行:
`INSERT INTO Comment(userId,momentId,content) VALUES(38710,12,'APIJSON,let interfaces and documents go to hell !')`

批量:
{
   TableName\[]:\[{
       …
     }, {
       …
     }
     …
   ],
   "tag":tag
}
{…}中id由后端生成,不能传

例如当前登录用户 82001 发布 2 个 Comment:
[{
   "Comment[]":[{
     "momentId":12,
     "content":"APIJSON,let interfaces and documents go to hell !"
     }, {
     "momentId":15,
     "content":"APIJSON is a JSON transmision protocol."
   }],
   "tag":"Comment:[]"
}](http://apijson.cn/api?url=http%3A%2F%2Fapijson.cn%3A8080%2Fpost&type=JSON&json={"Comment[]":[{"momentId":12,"content":"APIJSON,let%20interfaces%20and%20documents%20go%20to%20hell%20!"},{"momentId":15,"content":"APIJSON%20is%20a%20JSON%20transmision%20protocol."}],"tag":"Comment:[]"})
后端校验通过后自动解析为 SQL 并执行:
`INSERT INTO Comment(userId,momentId,content) VALUES(82001,12,'APIJSON,let interfaces and documents go to hell !');`

`INSERT INTO Comment(userId,momentId,content) VALUES(82001,15,'APIJSON is a JSON transmision protocol.');` | 单个:
{
   TableName:{
     "code":200,
     "msg":"success",
     "id":38710
   },
   "code":200,
   "msg":"success"
}
例如
{
   "Comment":{
     "code":200,
     "msg":"success",
     "id":120
   },
   "code":200,
   "msg":"success"
}

批量:
{
   TableName:{
     "code":200,
     "msg":"success",
     "count":5,
     "id[]":[1, 2, 3, 4, 5]
   },
   "code":200,
   "msg":"success"
}
例如
{
   "Comment":{
     "code":200,
     "msg":"success",
     "count":2,
     "id[]":\[1, 2]
   },
   "code":200,
   "msg":"success"
} PUT:
修改数据,
只修改所传的字段 | base_url/put/ | {
   TableName:{
     "id":id,
     …
   },
   "tag":tag
}
{…} 中 id 或 id{} 至少传一个

例如当前登录用户 82001 修改 id = 235 的 Moment 的 content:
[{
   "Moment":{
     "id":235,
     "content":"APIJSON,let interfaces and documents go to hell !"
   },
   "tag":"Moment"
}](http://apijson.cn/api?url=http%3A%2F%2Fapijson.cn%3A8080%2Fput&type=JSON&json={"Moment":{"id":235,"content":"APIJSON,let%20interfaces%20and%20documents%20go%20to%20hell%20!"},"tag":"Moment"})
后端校验通过后自动解析为 SQL 并执行:
`UPDATE Moment SET content='APIJSON,let interfaces and documents go to hell !' WHERE id=235 AND userId=82001 LIMIT 1`

批量除了 id{}:\[] 也可类似批量 POST,只是每个 {...} 里面都必须有 id。
"tag":"Comment[]" 对应对象 "Comment":{"id{}":[1,2,3]},表示指定记录全部统一设置;
"tag":"Comment:[]" 多了冒号,对应数组 "Comment[]":[{"id":1},{"id":2},{"id":3}],表示每项单独设置 | 同POST DELETE:
删除数据 | base_url/delete/ | {
   TableName:{
     "id":id
   },
   "tag":tag
}
{…} 中 id 或 id{} 至少传一个,一般只传 id 或 id{}

例如当前登录用户 82001 批量删除 id = 100,110,120 的 Comment:
[{
   "Comment":{
     "id{}":[100,110,120]
   },
   "tag":"Comment[]"
}](http://apijson.cn/api?url=http%3A%2F%2Fapijson.cn%3A8080%2Fdelete&type=JSON&json={"Comment":{"id{}":[100,110,120]},"tag":"Comment[]"})
后端校验通过后自动解析为 SQL 并执行:
`DELETE FROM Comment WHERE id IN(100,110,120) AND userId=82001 LIMIT 3` | {
   TableName:{
     "code":200,
     "msg":"success",
     "id[]":[100,110,120]
      "count":3
   },
   "code":200,
   "msg":"success"
}
例如
{
   "Comment":{
      "code":200,
      "msg":"success",
      "id[]":[100,110,120],
      "count":3
   },
   "code":200,
   "msg":"success"
} 以上接口的简单形式:
base_url/{method}/{tag} | GET: 普通获取数据
base_url/get/{tag}

HEAD: 普通获取数量
base_url/head/{tag}

GETS: 安全/私密获取数据
base_url/gets/{tag}

HEADS: 安全/私密获取数量
base_url/heads/{tag}

POST: 新增数据
base_url/post/{tag}

PUT: 修改数据 base_url/put/{tag}

DELETE: 删除数据
base_url/delete/{tag} | 例如安全/私密获取一个 id = 82001 的 Privacy:
[base_url/gets/Privacy/
{"id":82001}](http://apijson.cn/api/?url=http%3A%2F%2Fapijson.cn%3A8080%2Fgets%2FPrivacy&type=JSON&json={"id"%3A82001})
相当于
[base_url/gets/
{"tag":"Privacy", "Privacy":{"id":82001}}](http://apijson.cn/api/?url=http%3A%2F%2Fapijson.cn%3A8080%2Fgets&type=JSON&json={"tag"%3A"Privacy","Privacy"%3A{"id"%3A82001}})

例如批量修改 id = 114, 124 的 Comment 的 content:
[base_url/put/Comemnt[]/
{
   "id{}":[114,124],
   "content":"test multi put"
}](http://apijson.cn/api?url=http%3A%2F%2Fapijson.cn%3A8080%2Fput%2FComment[]&type=JSON&json={"id{}"%3A[114,124],"content"%3A"test%20multi%20put"})
相当于
[base_url/put/
{
   "tag":"Comment[]",
   "Comment":{
     "id{}":[114,124],
     "content":"test multi put"
   }
}](http://apijson.cn/api?url=http%3A%2F%2Fapijson.cn%3A8080%2Fput&type=JSON&json={"tag"%3A"Comment[]","Comment"%3A{"id{}"%3A[114,124],"content"%3A"test%20multi%20put"}}) | 同以上对应的方法 From 773c2cb2e5e502a157a50c25d907da547cd06ea1 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Mon, 27 Sep 2021 19:15:50 +0800 Subject: [PATCH 0490/1181] Update Document.md --- Document.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Document.md b/Document.md index 907af5311..292153848 100644 --- a/Document.md +++ b/Document.md @@ -257,7 +257,7 @@ https://github.com/Tencent/APIJSON
-[在线测试](http://apijson.org/auto) +[在线测试](http://apijson.cn/api)

From 7cbc85b1603204519c301a54209ae544eed10104 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Mon, 27 Sep 2021 22:36:16 +0800 Subject: [PATCH 0491/1181] Update Document.md --- Document.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Document.md b/Document.md index 292153848..1b6129c2d 100644 --- a/Document.md +++ b/Document.md @@ -377,6 +377,6 @@ DELETE:
删除数据 | base_url/delete/ | {
   TableName:{< 比较运算 | >, <, >=, <= 比较运算符,用于
① 提供 "id{}":"<=90000" 这种条件范围的简化写法

② 实现子查询相关比较运算

不支持 "key=":Object 和 "key!=":Object 这两种写法,直接用更简单的 "key":Object 和 "key!":Object 替代。 | ① ["id<=":90000](http://apijson.cn:8080/get/{"[]":{"User":{"id<=":90000}}}),对应SQL是`id<=90000`,查询符合id<=90000的一个User数组

② ["id>@":{
   "from":"Comment",
   "Comment":{
      "@column":"min(userId)"
   }
}](http://apijson.cn:8080/get/{"User":{"id>@":{"from":"Comment","Comment":{"@column":"min(userId)"}}}})
WHERE id>(SELECT min(userId) FROM Comment) 逻辑运算 | &, \|, ! 逻辑运算符,对应数据库 SQL 中的 AND, OR, NOT。
横或纵与:同一键值对的值内条件默认 \| 或连接,可以在 key 后加逻辑运算符来具体指定;不同键值对的条件默认 & 与连接,可以用下面说明的对象关键词 @combine 来具体指定。

① & 可用于"key&{}":"条件"等

② \| 可用于"key\|{}":"条件", "key\|{}":[]等,一般可省略

③ ! 可单独使用,如"key!":Object,也可像&,\|一样配合其他功能符使用
"key!":null 无效,null 值会导致整个键值对被忽略解析,可以用 "key{}":"!=null" 替代,
"key":null 同理,用 "key{}":"=null" 替代。 | ① ["id&{}":">80000,<=90000"](http://apijson.cn:8080/head/{"User":{"id&{}":">80000,<=90000"}}),对应SQL是`id>80000 AND id<=90000`,即id满足id>80000 & id<=90000

② ["id\|{}":">90000,<=80000"](http://apijson.cn:8080/head/{"User":{"id\|{}":">90000,<=80000"}}),同"id{}":">90000,<=80000",对应SQL是`id>80000 OR id<=90000`,即id满足id>90000 \| id<=80000

③ ["id!{}":[82001,38710]](http://apijson.cn:8080/head/{"User":{"id!{}":[82001,38710]}}),对应SQL是`id NOT IN(82001,38710)`,即id满足 ! (id=82001 \| id=38710),可过滤黑名单的消息 数组关键词,可自定义 | "key":Object,key为 "[]":{} 中{}内的关键词,Object的类型由key指定

① "count":Integer,查询数量,0 表示最大值,默认最大值为100

② "page":Integer,查询页码,从0开始,默认最大值为100,一般和count一起用

③ "query":Integer,查询内容
0-对象,1-总数和分页详情,2-以上全部
总数关键词为 total,分页详情关键词为 info,
它们都和 query 同级,通过引用赋值得到,例如
"total@":"/[]/total", "info@":"/[]/info"
这里query及total仅为GET类型的请求提供方便,
一般可直接用HEAD类型的请求获取总数

④ "join":"&/Table0/key0@,\多表连接方式:
"\<" - LEFT JOIN
">" - RIGHT JOIN
"&" - INNER JOIN
"\|" - FULL JOIN
"!" - OUTER JOIN
"@" - APP JOIN
其中 @ APP JOIN 为应用层连表,会从已查出的主表里取得所有副表 key@ 关联的主表内的 refKey 作为一个数组 refKeys: [value0, value1...],然后把原来副表 count 次查询 key=$refKey 的 SQL 用 key IN($refKeys) 的方式合并为一条 SQL 来优化性能;
其它 JOIN 都是 SQL JOIN,具体功能和 MySQL,PostgreSQL 等数据库的 JOIN 一一对应
`"join":"`"MainTable":{},`
`"ViceTable":{"key@":"/MainTable/refKey"}`
会对应生成
`MainTable LEFT JOIN ViceTable`
`ON ViceTable.key=MainTable.refKey`

⑤ "otherKey":Object,自定义关键词,名称和以上系统关键词不一样,且原样返回上传的值 | ① 查询User数组,最多5个:
["count":5](http://apijson.cn:8080/get/{"[]":{"count":5,"User":{}}})
对应SQL是`LIMIT 5`

② 查询第3页的User数组,每页5个:
["count":5,
"page":3](http://apijson.cn:8080/get/{"[]":{"count":5,"page":3,"User":{}}})
对应SQL是`LIMIT 5 OFFSET 15`

③ 查询User数组和对应的User总数:
["[]":{
   "query":2,
   "User":{}
},
"total@":"/[]/total",
"info@":"/[]/info"](http://apijson.cn:8080/get/{"[]":{"query":2,"count":5,"User":{}},"total@":"%252F[]%252Ftotal","info@":"%252F[]%252Finfo"})
返回的数据中,总数及分页详情结构为:
"total":139, //总数
"info":{ //分页详情
   "total":139, //总数
   "count":5, //每页数量
   "page":0, //当前页码
   "max":27, //最大页码
   "more":true, //是否还有更多
   "first":true, //是否为首页
   "last":false //是否为尾页
}

④ Moment INNER JOIN User LEFT JOIN Comment:
["[]":{
   "join":"&/User/id@,\    "Moment":{
     "@group":"id" //主副表不是一对一,要去除重复数据
   },
   "User":{
     "name~":"t",
     "id@":"/Moment/userId"
   },
   "Comment":{
     "momentId@":"/Moment/id"
   }
}](http://apijson.cn:8080/get/{"[]":{"count":5,"join":"&%252FUser%252Fid@,\<%252FComment%252FmomentId@","Moment":{"@column":"id,userId,content","@group":"id"},"User":{"name~":"t","id@":"%252FMoment%252FuserId","@column":"id,name,head"},"Comment":{"momentId@":"%252FMoment%252Fid","@column":"id,momentId,content"}}})

⑤ 每一层都加当前用户名:
["User":{},
"[]":{
   "name@":"User/name", //自定义关键词
   "Moment":{}
}](http://apijson.cn:8080/get/{"User":{},"[]":{"name@":"User%252Fname","Moment":{}}}) - 对象关键词,可自定义 | "@key":Object,@key为 Table:{} 中{}内的关键词,Object的类型由@key指定

① "@combine":"&key0,&key1,\|key2,key3,
!key4,!key5,&key6,key7...",条件组合方式,\| 可省略。会自动把同类的合并,外层按照 & \| ! 顺序,内层的按传参顺序组合成
(key0 & key1 & key6 & 其它key) & (key2 \| key3 \| key7) & !(key4 \| key5)
这种连接方式,其中 "其它key" 是指与 @combine 在同一对象,且未被它声明的条件 key,默认都是 & 连接

② "@column":"column;function(arg)...",返回字段

③ "@order":"column0+,column1-...",排序方式

④ "@group":"column0,column1...",分组方式。如果@column里声明了Table的id,则id也必须在@group中声明;其它情况下必须满足至少一个条件:
1.分组的key在@column里声明
2.Table主键在@group中声明

⑤ "@having":"function0(...)?value0;function1(...)?value1;function2(...)?value2...",SQL函数条件,一般和@group一起用,函数一般在@column里声明

⑥ "@schema":"sys",集合空间(模式),非默认的值可通过它来指定,可以在最外层作为全局默认配置

⑦ "@database":"POSTGRESQL",跨数据库,非默认的值可通过它来指定,可以在最外层作为全局默认配置

⑧ "@json":"key0,key1...",转为 JSON 格式返回,符合 JSONObject 则转为 {...},符合 JSONArray 则转为 \[...]

⑨ "@role":"OWNER",来访角色,包括
UNKNOWN,LOGIN,CONTACT,CIRCLE,OWNER,ADMIN,
可以在最外层作为全局默认配置,
可自定义其它角色并重写 Verifier.verify 等相关方法来自定义校验

⑩ "@explain":true,性能分析,可以在最外层作为全局默认配置

⑪ "@raw":"key0,key1...",其中 key0, key1 都对应有键值对
"key0":"SQL片段或SQL片段的别名",
"key1":"SQL片段或SQL片段的别名"
自定义原始SQL片段,可扩展嵌套SQL函数等复杂语句,必须是后端已配置的,只有其它功能符都做不到才考虑,谨慎使用,注意防SQL注入

⑫ "@otherKey":Object,自定义关键词,名称和以上系统关键词不一样,且原样返回上传的值 | ① 搜索name或tag任何一个字段包含字符a的User列表:
["name~":"a",
"tag~":"a",
"@combine":"name~,tag~"](http://apijson.cn:8080/get/{"User[]":{"count":10,"User":{"@column":"id,name,tag","name~":"a","tag~":"a","@combine":"name~,tag~"}}})
对应SQL是`name REGEXP 'a' OR tag REGEXP 'a'`

② 只查询id,sex,name这几列并且请求结果也按照这个顺序:
["@column":"id,sex,name"](http://apijson.cn:8080/get/{"User":{"@column":"id,sex,name","id":38710}})
对应SQL是`SELECT id,sex,name`

③ 查询按 name降序、id默认顺序 排序的User数组:
["@order":"name-,id"](http://apijson.cn:8080/get/{"[]":{"count":10,"User":{"@column":"name,id","@order":"name-,id"}}})
对应SQL是`ORDER BY name DESC,id`

④ 查询按userId分组的Moment数组:
["@group":"userId,id"](http://apijson.cn:8080/get/{"[]":{"count":10,"Moment":%7B"@column":"userId,id","@group":"userId,id"}}})
对应SQL是`GROUP BY userId,id`

⑤ 查询 按userId分组、id最大值>=100 的Moment数组:
["@column":"userId;max(id)",
"@group":"userId",
"@having":"max(id)>=100"](http://apijson.cn:8080/get/{"[]":{"count":10,"Moment":{"@column":"userId%253Bmax(id)","@group":"userId","@having":"max(id)>=100"}}})
对应SQL是`SELECT userId,max(id) ... GROUP BY userId HAVING max(id)>=100`
还可以指定函数返回名:
["@column":"userId;max(id):maxId",
"@group":"userId",
"@having":"maxId>=100"](http://apijson.cn:8080/get/{"[]":{"count":10,"Moment":{"@column":"userId%253Bmax(id):maxId","@group":"userId","@having":"maxId>=100"}}})
对应SQL是`SELECT userId,max(id) AS maxId ... GROUP BY userId HAVING maxId>=100`

⑥ 查询 sys 内的 User 表:
["@schema":"sys"](http://apijson.cn:8080/get/{"User":{"@schema":"sys"}})
对应SQL是`FROM sys.User`

⑦ 查询 PostgreSQL 数据库的 User 表:
["@database":"POSTGRESQL"](http://apijson.cn:8080/get/{"User":{"@database":"POSTGRESQL","@explain":true}})

⑧ 将 VARCHAR 字符串字段 get 转为 JSONArray 返回:
["@json":"get"](http://apijson.cn:8080/get/{"Access":{"@json":"get"}})

⑨ 查询当前用户的动态:
["@role":"OWNER"](http://apijson.cn:8080/get/{"[]":{"Moment":{"@role":"OWNER"}}})

⑩ 开启性能分析:
["@explain":true](http://apijson.cn:8080/get/{"[]":{"Moment":{"@explain":true}}})
对应SQL是`EXPLAIN`

⑪ 统计最近一周偶数userId的数量
["@column":"date;left(date,10):day;sum(if(userId%2=0,1,0))",
"@group":"day",
"@having":"to_days(now())-to_days(\`date\`)<=7",
"@raw":"@column,@having"](http://apijson.cn:8080/get/{"[]":{"Moment":{"@column":"date%3bleft(date,10):day%3bsum(if(userId%252=0,1,0))","@group":"day","@having":"to_days(now())-to_days(\`date\`)<=7","@raw":"@column,@having"}}})
对应SQL是``SELECT date, left(date,10) AS day, sum(if(userId%2=0,1,0)) ... GROUP BY day HAVING to_days(now())-to_days(`date`)<=7``

⑫ 从pictureList获取第0张图片:
["@position":0, //自定义关键词
"firstPicture()":"getFromArray(pictureList,@position)"](http://apijson.cn:8080/get/{"User":{"id":38710,"@position":0,"firstPicture()":"getFromArray(pictureList,@position)"}}) + 对象关键词,可自定义 | "@key":Object,@key为 Table:{} 中{}内的关键词,Object的类型由@key指定

① "@combine":"&key0,&key1,\|key2,key3,
!key4,!key5,&key6,key7...",条件组合方式,\| 可省略。会自动把同类的合并,外层按照 & \| ! 顺序,内层的按传参顺序组合成
(key0 & key1 & key6 & 其它key) & (key2 \| key3 \| key7) & !(key4 \| key5)
这种连接方式,其中 "其它key" 是指与 @combine 在同一对象,且未被它声明的条件 key,默认都是 & 连接

② "@column":"column;function(arg)...",返回字段

③ "@order":"column0+,column1-...",排序方式

④ "@group":"column0,column1...",分组方式。如果@column里声明了Table的id,则id也必须在@group中声明;其它情况下必须满足至少一个条件:
1.分组的key在@column里声明
2.Table主键在@group中声明

⑤ "@having":"function0(...)?value0;function1(...)?value1;function2(...)?value2...",SQL函数条件,一般和@group一起用,函数一般在@column里声明

⑥ "@schema":"sys",集合空间(数据库名/模式),非默认的值可通过它来指定,可以在最外层作为全局默认配置

⑦ "@database":"POSTGRESQL",数据库类型,非默认的值可通过它来指定,可以在最外层作为全局默认配置

⑧ "@json":"key0,key1...",转为 JSON 格式返回,符合 JSONObject 则转为 {...},符合 JSONArray 则转为 \[...]

⑨ "@role":"OWNER",来访角色,包括
UNKNOWN,LOGIN,CONTACT,CIRCLE,OWNER,ADMIN,
可以在最外层作为全局默认配置,
可自定义其它角色并重写 Verifier.verify 等相关方法来自定义校验

⑩ "@explain":true,性能分析,可以在最外层作为全局默认配置

⑪ "@raw":"key0,key1...",其中 key0, key1 都对应有键值对
"key0":"SQL片段或SQL片段的别名",
"key1":"SQL片段或SQL片段的别名"
自定义原始SQL片段,可扩展嵌套SQL函数等复杂语句,必须是后端已配置的,只有其它功能符都做不到才考虑,谨慎使用,注意防SQL注入

⑫ "@otherKey":Object,自定义关键词,名称和以上系统关键词不一样,且原样返回上传的值 | ① 搜索name或tag任何一个字段包含字符a的User列表:
["name~":"a",
"tag~":"a",
"@combine":"name~,tag~"](http://apijson.cn:8080/get/{"User[]":{"count":10,"User":{"@column":"id,name,tag","name~":"a","tag~":"a","@combine":"name~,tag~"}}})
对应SQL是`name REGEXP 'a' OR tag REGEXP 'a'`

② 只查询id,sex,name这几列并且请求结果也按照这个顺序:
["@column":"id,sex,name"](http://apijson.cn:8080/get/{"User":{"@column":"id,sex,name","id":38710}})
对应SQL是`SELECT id,sex,name`

③ 查询按 name降序、id默认顺序 排序的User数组:
["@order":"name-,id"](http://apijson.cn:8080/get/{"[]":{"count":10,"User":{"@column":"name,id","@order":"name-,id"}}})
对应SQL是`ORDER BY name DESC,id`

④ 查询按userId分组的Moment数组:
["@group":"userId,id"](http://apijson.cn:8080/get/{"[]":{"count":10,"Moment":%7B"@column":"userId,id","@group":"userId,id"}}})
对应SQL是`GROUP BY userId,id`

⑤ 查询 按userId分组、id最大值>=100 的Moment数组:
["@column":"userId;max(id)",
"@group":"userId",
"@having":"max(id)>=100"](http://apijson.cn:8080/get/{"[]":{"count":10,"Moment":{"@column":"userId%253Bmax(id)","@group":"userId","@having":"max(id)>=100"}}})
对应SQL是`SELECT userId,max(id) ... GROUP BY userId HAVING max(id)>=100`
还可以指定函数返回名:
["@column":"userId;max(id):maxId",
"@group":"userId",
"@having":"maxId>=100"](http://apijson.cn:8080/get/{"[]":{"count":10,"Moment":{"@column":"userId%253Bmax(id):maxId","@group":"userId","@having":"maxId>=100"}}})
对应SQL是`SELECT userId,max(id) AS maxId ... GROUP BY userId HAVING maxId>=100`

⑥ 查询 sys 内的 User 表:
["@schema":"sys"](http://apijson.cn:8080/get/{"User":{"@schema":"sys"}})
对应SQL是`FROM sys.User`

⑦ 查询 PostgreSQL 数据库的 User 表:
["@database":"POSTGRESQL"](http://apijson.cn:8080/get/{"User":{"@database":"POSTGRESQL","@explain":true}})

⑧ 将 VARCHAR 字符串字段 get 转为 JSONArray 返回:
["@json":"get"](http://apijson.cn:8080/get/{"Access":{"@json":"get"}})

⑨ 查询当前用户的动态:
["@role":"OWNER"](http://apijson.cn:8080/get/{"[]":{"Moment":{"@role":"OWNER"}}})

⑩ 开启性能分析:
["@explain":true](http://apijson.cn:8080/get/{"[]":{"Moment":{"@explain":true}}})
对应SQL是`EXPLAIN`

⑪ 统计最近一周偶数userId的数量
["@column":"date;left(date,10):day;sum(if(userId%2=0,1,0))",
"@group":"day",
"@having":"to_days(now())-to_days(\`date\`)<=7",
"@raw":"@column,@having"](http://apijson.cn:8080/get/{"[]":{"Moment":{"@column":"date%3bleft(date,10):day%3bsum(if(userId%252=0,1,0))","@group":"day","@having":"to_days(now())-to_days(\`date\`)<=7","@raw":"@column,@having"}}})
对应SQL是``SELECT date, left(date,10) AS day, sum(if(userId%2=0,1,0)) ... GROUP BY day HAVING to_days(now())-to_days(`date`)<=7``

⑫ 从pictureList获取第0张图片:
["@position":0, //自定义关键词
"firstPicture()":"getFromArray(pictureList,@position)"](http://apijson.cn:8080/get/{"User":{"id":38710,"@position":0,"firstPicture()":"getFromArray(pictureList,@position)"}}) 全局关键词 | 为最外层对象 {} 内的关键词。其中 @database,@schema, @datasource, @role, @explain 基本同对象关键词,见上方说明,区别是全局关键词会每个表对象中没有时自动放入,作为默认值。

① "tag":String,后面的 tag 是非 GET、HEAD 请求中匹配请求的 JSON 结构的标识,一般是要查询的 Table 的名称或该名称对应的数组 Table[] 或 Table:[],由后端 Request 表中指定。

② "version":Integer,接口版本,version 不传、为 null 或 <=0 都会使用最高版本,传了其它有效值则会使用最接近它的最低版本,由后端 Request 表中指定。

③ "format":Boolean,格式化返回 Response JSON 的 key,一般是将 TableName 转为 tableName, TableName[] 转为 tableNameList, Table:alias 转为 alias, TableName-key[] 转为 tableNameKeyList 等小驼峰格式。 | ① 查隐私信息:
[{"tag":"Privacy","Privacy":{"id":82001}}](http://apijson.cn/api?url=http%3A%2F%2Fapijson.cn%3A8080%2Fgets&type=JSON&json={%22tag%22:%22Privacy%22,%22Privacy%22:{%22id%22:82001}})

② 使用第 1 版接口查隐私信息:
[{"version":1,"tag":"Privacy","Privacy":{"id":82001}}](http://apijson.cn/api?url=http%3A%2F%2Fapijson.cn%3A8080%2Fgets&type=JSON&json={%22version%22:1,%22tag%22:%22Privacy%22,%22Privacy%22:{%22id%22:82001}})

③ 格式化朋友圈接口返回 JSON 中的 key:
[{
   "format":true,
   "[]":{
     "page":0,
     "count":3,
     "Moment":{},
     "User":{
       "id@":"/Moment/userId"
     },
     "Comment[]":{
       "count":3,
       "Comment":{
         "momentId@":"[]/Moment/id"
       }
     }
   }
}](http://apijson.cn:8080/get/{"format":true,"[]":{"page":0,"count":3,"Moment":{},"User":{"id@":"%252FMoment%252FuserId"},"Comment[]":{"count":3,"Comment":{"momentId@":"[]%252FMoment%252Fid"}}}})
From 9bea66e37acb0bb641da61943520dde19f2d72d4 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Mon, 27 Sep 2021 22:50:10 +0800 Subject: [PATCH 0492/1181] =?UTF-8?q?=E9=80=9A=E7=94=A8=E6=96=87=E6=A1=A3?= =?UTF-8?q?=20=203.2=20=E5=8A=9F=E8=83=BD=E7=AC=A6=20=E5=AF=B9=E8=B1=A1?= =?UTF-8?q?=E5=85=B3=E9=94=AE=E8=AF=8D=20=E6=96=B0=E5=A2=9E=20"@datasource?= =?UTF-8?q?":"DRUID"=20=E8=B7=A8=E6=95=B0=E6=8D=AE=E6=BA=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Document.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Document.md b/Document.md index 1b6129c2d..282fa1ff4 100644 --- a/Document.md +++ b/Document.md @@ -377,6 +377,6 @@ DELETE:
删除数据 | base_url/delete/ | {
   TableName:{< 比较运算 | >, <, >=, <= 比较运算符,用于
① 提供 "id{}":"<=90000" 这种条件范围的简化写法

② 实现子查询相关比较运算

不支持 "key=":Object 和 "key!=":Object 这两种写法,直接用更简单的 "key":Object 和 "key!":Object 替代。 | ① ["id<=":90000](http://apijson.cn:8080/get/{"[]":{"User":{"id<=":90000}}}),对应SQL是`id<=90000`,查询符合id<=90000的一个User数组

② ["id>@":{
   "from":"Comment",
   "Comment":{
      "@column":"min(userId)"
   }
}](http://apijson.cn:8080/get/{"User":{"id>@":{"from":"Comment","Comment":{"@column":"min(userId)"}}}})
WHERE id>(SELECT min(userId) FROM Comment) 逻辑运算 | &, \|, ! 逻辑运算符,对应数据库 SQL 中的 AND, OR, NOT。
横或纵与:同一键值对的值内条件默认 \| 或连接,可以在 key 后加逻辑运算符来具体指定;不同键值对的条件默认 & 与连接,可以用下面说明的对象关键词 @combine 来具体指定。

① & 可用于"key&{}":"条件"等

② \| 可用于"key\|{}":"条件", "key\|{}":[]等,一般可省略

③ ! 可单独使用,如"key!":Object,也可像&,\|一样配合其他功能符使用
"key!":null 无效,null 值会导致整个键值对被忽略解析,可以用 "key{}":"!=null" 替代,
"key":null 同理,用 "key{}":"=null" 替代。 | ① ["id&{}":">80000,<=90000"](http://apijson.cn:8080/head/{"User":{"id&{}":">80000,<=90000"}}),对应SQL是`id>80000 AND id<=90000`,即id满足id>80000 & id<=90000

② ["id\|{}":">90000,<=80000"](http://apijson.cn:8080/head/{"User":{"id\|{}":">90000,<=80000"}}),同"id{}":">90000,<=80000",对应SQL是`id>80000 OR id<=90000`,即id满足id>90000 \| id<=80000

③ ["id!{}":[82001,38710]](http://apijson.cn:8080/head/{"User":{"id!{}":[82001,38710]}}),对应SQL是`id NOT IN(82001,38710)`,即id满足 ! (id=82001 \| id=38710),可过滤黑名单的消息 数组关键词,可自定义 | "key":Object,key为 "[]":{} 中{}内的关键词,Object的类型由key指定

① "count":Integer,查询数量,0 表示最大值,默认最大值为100

② "page":Integer,查询页码,从0开始,默认最大值为100,一般和count一起用

③ "query":Integer,查询内容
0-对象,1-总数和分页详情,2-以上全部
总数关键词为 total,分页详情关键词为 info,
它们都和 query 同级,通过引用赋值得到,例如
"total@":"/[]/total", "info@":"/[]/info"
这里query及total仅为GET类型的请求提供方便,
一般可直接用HEAD类型的请求获取总数

④ "join":"&/Table0/key0@,\多表连接方式:
"\<" - LEFT JOIN
">" - RIGHT JOIN
"&" - INNER JOIN
"\|" - FULL JOIN
"!" - OUTER JOIN
"@" - APP JOIN
其中 @ APP JOIN 为应用层连表,会从已查出的主表里取得所有副表 key@ 关联的主表内的 refKey 作为一个数组 refKeys: [value0, value1...],然后把原来副表 count 次查询 key=$refKey 的 SQL 用 key IN($refKeys) 的方式合并为一条 SQL 来优化性能;
其它 JOIN 都是 SQL JOIN,具体功能和 MySQL,PostgreSQL 等数据库的 JOIN 一一对应
`"join":"`"MainTable":{},`
`"ViceTable":{"key@":"/MainTable/refKey"}`
会对应生成
`MainTable LEFT JOIN ViceTable`
`ON ViceTable.key=MainTable.refKey`

⑤ "otherKey":Object,自定义关键词,名称和以上系统关键词不一样,且原样返回上传的值 | ① 查询User数组,最多5个:
["count":5](http://apijson.cn:8080/get/{"[]":{"count":5,"User":{}}})
对应SQL是`LIMIT 5`

② 查询第3页的User数组,每页5个:
["count":5,
"page":3](http://apijson.cn:8080/get/{"[]":{"count":5,"page":3,"User":{}}})
对应SQL是`LIMIT 5 OFFSET 15`

③ 查询User数组和对应的User总数:
["[]":{
   "query":2,
   "User":{}
},
"total@":"/[]/total",
"info@":"/[]/info"](http://apijson.cn:8080/get/{"[]":{"query":2,"count":5,"User":{}},"total@":"%252F[]%252Ftotal","info@":"%252F[]%252Finfo"})
返回的数据中,总数及分页详情结构为:
"total":139, //总数
"info":{ //分页详情
   "total":139, //总数
   "count":5, //每页数量
   "page":0, //当前页码
   "max":27, //最大页码
   "more":true, //是否还有更多
   "first":true, //是否为首页
   "last":false //是否为尾页
}

④ Moment INNER JOIN User LEFT JOIN Comment:
["[]":{
   "join":"&/User/id@,\    "Moment":{
     "@group":"id" //主副表不是一对一,要去除重复数据
   },
   "User":{
     "name~":"t",
     "id@":"/Moment/userId"
   },
   "Comment":{
     "momentId@":"/Moment/id"
   }
}](http://apijson.cn:8080/get/{"[]":{"count":5,"join":"&%252FUser%252Fid@,\<%252FComment%252FmomentId@","Moment":{"@column":"id,userId,content","@group":"id"},"User":{"name~":"t","id@":"%252FMoment%252FuserId","@column":"id,name,head"},"Comment":{"momentId@":"%252FMoment%252Fid","@column":"id,momentId,content"}}})

⑤ 每一层都加当前用户名:
["User":{},
"[]":{
   "name@":"User/name", //自定义关键词
   "Moment":{}
}](http://apijson.cn:8080/get/{"User":{},"[]":{"name@":"User%252Fname","Moment":{}}}) - 对象关键词,可自定义 | "@key":Object,@key为 Table:{} 中{}内的关键词,Object的类型由@key指定

① "@combine":"&key0,&key1,\|key2,key3,
!key4,!key5,&key6,key7...",条件组合方式,\| 可省略。会自动把同类的合并,外层按照 & \| ! 顺序,内层的按传参顺序组合成
(key0 & key1 & key6 & 其它key) & (key2 \| key3 \| key7) & !(key4 \| key5)
这种连接方式,其中 "其它key" 是指与 @combine 在同一对象,且未被它声明的条件 key,默认都是 & 连接

② "@column":"column;function(arg)...",返回字段

③ "@order":"column0+,column1-...",排序方式

④ "@group":"column0,column1...",分组方式。如果@column里声明了Table的id,则id也必须在@group中声明;其它情况下必须满足至少一个条件:
1.分组的key在@column里声明
2.Table主键在@group中声明

⑤ "@having":"function0(...)?value0;function1(...)?value1;function2(...)?value2...",SQL函数条件,一般和@group一起用,函数一般在@column里声明

⑥ "@schema":"sys",集合空间(数据库名/模式),非默认的值可通过它来指定,可以在最外层作为全局默认配置

⑦ "@database":"POSTGRESQL",数据库类型,非默认的值可通过它来指定,可以在最外层作为全局默认配置

⑧ "@json":"key0,key1...",转为 JSON 格式返回,符合 JSONObject 则转为 {...},符合 JSONArray 则转为 \[...]

⑨ "@role":"OWNER",来访角色,包括
UNKNOWN,LOGIN,CONTACT,CIRCLE,OWNER,ADMIN,
可以在最外层作为全局默认配置,
可自定义其它角色并重写 Verifier.verify 等相关方法来自定义校验

⑩ "@explain":true,性能分析,可以在最外层作为全局默认配置

⑪ "@raw":"key0,key1...",其中 key0, key1 都对应有键值对
"key0":"SQL片段或SQL片段的别名",
"key1":"SQL片段或SQL片段的别名"
自定义原始SQL片段,可扩展嵌套SQL函数等复杂语句,必须是后端已配置的,只有其它功能符都做不到才考虑,谨慎使用,注意防SQL注入

⑫ "@otherKey":Object,自定义关键词,名称和以上系统关键词不一样,且原样返回上传的值 | ① 搜索name或tag任何一个字段包含字符a的User列表:
["name~":"a",
"tag~":"a",
"@combine":"name~,tag~"](http://apijson.cn:8080/get/{"User[]":{"count":10,"User":{"@column":"id,name,tag","name~":"a","tag~":"a","@combine":"name~,tag~"}}})
对应SQL是`name REGEXP 'a' OR tag REGEXP 'a'`

② 只查询id,sex,name这几列并且请求结果也按照这个顺序:
["@column":"id,sex,name"](http://apijson.cn:8080/get/{"User":{"@column":"id,sex,name","id":38710}})
对应SQL是`SELECT id,sex,name`

③ 查询按 name降序、id默认顺序 排序的User数组:
["@order":"name-,id"](http://apijson.cn:8080/get/{"[]":{"count":10,"User":{"@column":"name,id","@order":"name-,id"}}})
对应SQL是`ORDER BY name DESC,id`

④ 查询按userId分组的Moment数组:
["@group":"userId,id"](http://apijson.cn:8080/get/{"[]":{"count":10,"Moment":%7B"@column":"userId,id","@group":"userId,id"}}})
对应SQL是`GROUP BY userId,id`

⑤ 查询 按userId分组、id最大值>=100 的Moment数组:
["@column":"userId;max(id)",
"@group":"userId",
"@having":"max(id)>=100"](http://apijson.cn:8080/get/{"[]":{"count":10,"Moment":{"@column":"userId%253Bmax(id)","@group":"userId","@having":"max(id)>=100"}}})
对应SQL是`SELECT userId,max(id) ... GROUP BY userId HAVING max(id)>=100`
还可以指定函数返回名:
["@column":"userId;max(id):maxId",
"@group":"userId",
"@having":"maxId>=100"](http://apijson.cn:8080/get/{"[]":{"count":10,"Moment":{"@column":"userId%253Bmax(id):maxId","@group":"userId","@having":"maxId>=100"}}})
对应SQL是`SELECT userId,max(id) AS maxId ... GROUP BY userId HAVING maxId>=100`

⑥ 查询 sys 内的 User 表:
["@schema":"sys"](http://apijson.cn:8080/get/{"User":{"@schema":"sys"}})
对应SQL是`FROM sys.User`

⑦ 查询 PostgreSQL 数据库的 User 表:
["@database":"POSTGRESQL"](http://apijson.cn:8080/get/{"User":{"@database":"POSTGRESQL","@explain":true}})

⑧ 将 VARCHAR 字符串字段 get 转为 JSONArray 返回:
["@json":"get"](http://apijson.cn:8080/get/{"Access":{"@json":"get"}})

⑨ 查询当前用户的动态:
["@role":"OWNER"](http://apijson.cn:8080/get/{"[]":{"Moment":{"@role":"OWNER"}}})

⑩ 开启性能分析:
["@explain":true](http://apijson.cn:8080/get/{"[]":{"Moment":{"@explain":true}}})
对应SQL是`EXPLAIN`

⑪ 统计最近一周偶数userId的数量
["@column":"date;left(date,10):day;sum(if(userId%2=0,1,0))",
"@group":"day",
"@having":"to_days(now())-to_days(\`date\`)<=7",
"@raw":"@column,@having"](http://apijson.cn:8080/get/{"[]":{"Moment":{"@column":"date%3bleft(date,10):day%3bsum(if(userId%252=0,1,0))","@group":"day","@having":"to_days(now())-to_days(\`date\`)<=7","@raw":"@column,@having"}}})
对应SQL是``SELECT date, left(date,10) AS day, sum(if(userId%2=0,1,0)) ... GROUP BY day HAVING to_days(now())-to_days(`date`)<=7``

⑫ 从pictureList获取第0张图片:
["@position":0, //自定义关键词
"firstPicture()":"getFromArray(pictureList,@position)"](http://apijson.cn:8080/get/{"User":{"id":38710,"@position":0,"firstPicture()":"getFromArray(pictureList,@position)"}}) + 对象关键词,可自定义 | "@key":Object,@key为 Table:{} 中{}内的关键词,Object的类型由@key指定

① "@combine":"&key0,&key1,\|key2,key3,
!key4,!key5,&key6,key7...",条件组合方式,\| 可省略。会自动把同类的合并,外层按照 & \| ! 顺序,内层的按传参顺序组合成
(key0 & key1 & key6 & 其它key) & (key2 \| key3 \| key7) & !(key4 \| key5)
这种连接方式,其中 "其它key" 是指与 @combine 在同一对象,且未被它声明的条件 key,默认都是 & 连接

② "@column":"column;function(arg)...",返回字段

③ "@order":"column0+,column1-...",排序方式

④ "@group":"column0,column1...",分组方式。如果@column里声明了Table的id,则id也必须在@group中声明;其它情况下必须满足至少一个条件:
1.分组的key在@column里声明
2.Table主键在@group中声明

⑤ "@having":"function0(...)?value0;function1(...)?value1;function2(...)?value2...",SQL函数条件,一般和@group一起用,函数一般在@column里声明

⑥ "@schema":"sys",集合空间(数据库名/模式),非默认的值可通过它来指定,可以在最外层作为全局默认配置

⑦ "@database":"POSTGRESQL",数据库类型,非默认的值可通过它来指定,可以在最外层作为全局默认配置

⑧ "@datasource":"DRUID",跨数据源,非默认的值可通过它来指定,可以在最外层作为全局默认配置

⑨ "@json":"key0,key1...",转为 JSON 格式返回,符合 JSONObject 则转为 {...},符合 JSONArray 则转为 \[...]

⑩ "@role":"OWNER",来访角色,包括
UNKNOWN,LOGIN,CONTACT,CIRCLE,OWNER,ADMIN,
可以在最外层作为全局默认配置,
可自定义其它角色并重写 Verifier.verify 等相关方法来自定义校验

⑪ "@explain":true,性能分析,可以在最外层作为全局默认配置

⑫ "@raw":"key0,key1...",其中 key0, key1 都对应有键值对
"key0":"SQL片段或SQL片段的别名",
"key1":"SQL片段或SQL片段的别名"
自定义原始SQL片段,可扩展嵌套SQL函数等复杂语句,必须是后端已配置的,只有其它功能符都做不到才考虑,谨慎使用,注意防SQL注入

⑬ "@otherKey":Object,自定义关键词,名称和以上系统关键词不一样,且原样返回上传的值 | ① 搜索name或tag任何一个字段包含字符a的User列表:
["name~":"a",
"tag~":"a",
"@combine":"name~,tag~"](http://apijson.cn:8080/get/{"User[]":{"count":10,"User":{"@column":"id,name,tag","name~":"a","tag~":"a","@combine":"name~,tag~"}}})
对应SQL是`name REGEXP 'a' OR tag REGEXP 'a'`

② 只查询id,sex,name这几列并且请求结果也按照这个顺序:
["@column":"id,sex,name"](http://apijson.cn:8080/get/{"User":{"@column":"id,sex,name","id":38710}})
对应SQL是`SELECT id,sex,name`

③ 查询按 name降序、id默认顺序 排序的User数组:
["@order":"name-,id"](http://apijson.cn:8080/get/{"[]":{"count":10,"User":{"@column":"name,id","@order":"name-,id"}}})
对应SQL是`ORDER BY name DESC,id`

④ 查询按userId分组的Moment数组:
["@group":"userId,id"](http://apijson.cn:8080/get/{"[]":{"count":10,"Moment":%7B"@column":"userId,id","@group":"userId,id"}}})
对应SQL是`GROUP BY userId,id`

⑤ 查询 按userId分组、id最大值>=100 的Moment数组:
["@column":"userId;max(id)",
"@group":"userId",
"@having":"max(id)>=100"](http://apijson.cn:8080/get/{"[]":{"count":10,"Moment":{"@column":"userId%253Bmax(id)","@group":"userId","@having":"max(id)>=100"}}})
对应SQL是`SELECT userId,max(id) ... GROUP BY userId HAVING max(id)>=100`
还可以指定函数返回名:
["@column":"userId;max(id):maxId",
"@group":"userId",
"@having":"maxId>=100"](http://apijson.cn:8080/get/{"[]":{"count":10,"Moment":{"@column":"userId%253Bmax(id):maxId","@group":"userId","@having":"maxId>=100"}}})
对应SQL是`SELECT userId,max(id) AS maxId ... GROUP BY userId HAVING maxId>=100`

⑥ 查询 sys 内的 User 表:
["@schema":"sys"](http://apijson.cn:8080/get/{"User":{"@schema":"sys"}})
对应SQL是`FROM sys.User`

⑦ 查询 PostgreSQL 数据库的 User 表:
["@database":"POSTGRESQL"](http://apijson.cn:8080/get/{"User":{"@database":"POSTGRESQL","@explain":true}})

⑧ 使用 Druid 连接池查询 User 表:
["@datasource":"DRUID"](http://apijson.cn:8080/get/{"User":{"@datasource":"DRUID"}})

⑨ 将 VARCHAR 字符串字段 get 转为 JSONArray 返回:
["@json":"get"](http://apijson.cn:8080/get/{"Access":{"@json":"get"}})

⑩ 查询当前用户的动态:
["@role":"OWNER"](http://apijson.cn:8080/get/{"[]":{"Moment":{"@role":"OWNER"}}})

⑪ 开启性能分析:
["@explain":true](http://apijson.cn:8080/get/{"[]":{"Moment":{"@explain":true}}})
对应SQL是`EXPLAIN`

⑫ 统计最近一周偶数userId的数量
["@column":"date;left(date,10):day;sum(if(userId%2=0,1,0))",
"@group":"day",
"@having":"to_days(now())-to_days(\`date\`)<=7",
"@raw":"@column,@having"](http://apijson.cn:8080/get/{"[]":{"Moment":{"@column":"date%3bleft(date,10):day%3bsum(if(userId%252=0,1,0))","@group":"day","@having":"to_days(now())-to_days(\`date\`)<=7","@raw":"@column,@having"}}})
对应SQL是``SELECT date, left(date,10) AS day, sum(if(userId%2=0,1,0)) ... GROUP BY day HAVING to_days(now())-to_days(`date`)<=7``

⑬ 从pictureList获取第0张图片:
["@position":0, //自定义关键词
"firstPicture()":"getFromArray(pictureList,@position)"](http://apijson.cn:8080/get/{"User":{"id":38710,"@position":0,"firstPicture()":"getFromArray(pictureList,@position)"}}) 全局关键词 | 为最外层对象 {} 内的关键词。其中 @database,@schema, @datasource, @role, @explain 基本同对象关键词,见上方说明,区别是全局关键词会每个表对象中没有时自动放入,作为默认值。

① "tag":String,后面的 tag 是非 GET、HEAD 请求中匹配请求的 JSON 结构的标识,一般是要查询的 Table 的名称或该名称对应的数组 Table[] 或 Table:[],由后端 Request 表中指定。

② "version":Integer,接口版本,version 不传、为 null 或 <=0 都会使用最高版本,传了其它有效值则会使用最接近它的最低版本,由后端 Request 表中指定。

③ "format":Boolean,格式化返回 Response JSON 的 key,一般是将 TableName 转为 tableName, TableName[] 转为 tableNameList, Table:alias 转为 alias, TableName-key[] 转为 tableNameKeyList 等小驼峰格式。 | ① 查隐私信息:
[{"tag":"Privacy","Privacy":{"id":82001}}](http://apijson.cn/api?url=http%3A%2F%2Fapijson.cn%3A8080%2Fgets&type=JSON&json={%22tag%22:%22Privacy%22,%22Privacy%22:{%22id%22:82001}})

② 使用第 1 版接口查隐私信息:
[{"version":1,"tag":"Privacy","Privacy":{"id":82001}}](http://apijson.cn/api?url=http%3A%2F%2Fapijson.cn%3A8080%2Fgets&type=JSON&json={%22version%22:1,%22tag%22:%22Privacy%22,%22Privacy%22:{%22id%22:82001}})

③ 格式化朋友圈接口返回 JSON 中的 key:
[{
   "format":true,
   "[]":{
     "page":0,
     "count":3,
     "Moment":{},
     "User":{
       "id@":"/Moment/userId"
     },
     "Comment[]":{
       "count":3,
       "Comment":{
         "momentId@":"[]/Moment/id"
       }
     }
   }
}](http://apijson.cn:8080/get/{"format":true,"[]":{"page":0,"count":3,"Moment":{},"User":{"id@":"%252FMoment%252FuserId"},"Comment[]":{"count":3,"Comment":{"momentId@":"[]%252FMoment%252Fid"}}}})
From 22a41c09fefb4e9878fd76c5a4715c11388360a0 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Tue, 28 Sep 2021 01:40:05 +0800 Subject: [PATCH 0493/1181] Update Document.md --- Document.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Document.md b/Document.md index 282fa1ff4..5da8589ae 100644 --- a/Document.md +++ b/Document.md @@ -376,7 +376,7 @@ DELETE:
删除数据 | base_url/delete/ | {
   TableName:{< 减少 或 去除 | "key-":Object,与"key+"相反 | "balance-":100.00,对应SQL是`balance = balance - 100.00`,余额减少100.00,即花费了100元 比较运算 | >, <, >=, <= 比较运算符,用于
① 提供 "id{}":"<=90000" 这种条件范围的简化写法

② 实现子查询相关比较运算

不支持 "key=":Object 和 "key!=":Object 这两种写法,直接用更简单的 "key":Object 和 "key!":Object 替代。 | ① ["id<=":90000](http://apijson.cn:8080/get/{"[]":{"User":{"id<=":90000}}}),对应SQL是`id<=90000`,查询符合id<=90000的一个User数组

② ["id>@":{
   "from":"Comment",
   "Comment":{
      "@column":"min(userId)"
   }
}](http://apijson.cn:8080/get/{"User":{"id>@":{"from":"Comment","Comment":{"@column":"min(userId)"}}}})
WHERE id>(SELECT min(userId) FROM Comment) 逻辑运算 | &, \|, ! 逻辑运算符,对应数据库 SQL 中的 AND, OR, NOT。
横或纵与:同一键值对的值内条件默认 \| 或连接,可以在 key 后加逻辑运算符来具体指定;不同键值对的条件默认 & 与连接,可以用下面说明的对象关键词 @combine 来具体指定。

① & 可用于"key&{}":"条件"等

② \| 可用于"key\|{}":"条件", "key\|{}":[]等,一般可省略

③ ! 可单独使用,如"key!":Object,也可像&,\|一样配合其他功能符使用
"key!":null 无效,null 值会导致整个键值对被忽略解析,可以用 "key{}":"!=null" 替代,
"key":null 同理,用 "key{}":"=null" 替代。 | ① ["id&{}":">80000,<=90000"](http://apijson.cn:8080/head/{"User":{"id&{}":">80000,<=90000"}}),对应SQL是`id>80000 AND id<=90000`,即id满足id>80000 & id<=90000

② ["id\|{}":">90000,<=80000"](http://apijson.cn:8080/head/{"User":{"id\|{}":">90000,<=80000"}}),同"id{}":">90000,<=80000",对应SQL是`id>80000 OR id<=90000`,即id满足id>90000 \| id<=80000

③ ["id!{}":[82001,38710]](http://apijson.cn:8080/head/{"User":{"id!{}":[82001,38710]}}),对应SQL是`id NOT IN(82001,38710)`,即id满足 ! (id=82001 \| id=38710),可过滤黑名单的消息 - 数组关键词,可自定义 | "key":Object,key为 "[]":{} 中{}内的关键词,Object的类型由key指定

① "count":Integer,查询数量,0 表示最大值,默认最大值为100

② "page":Integer,查询页码,从0开始,默认最大值为100,一般和count一起用

③ "query":Integer,查询内容
0-对象,1-总数和分页详情,2-以上全部
总数关键词为 total,分页详情关键词为 info,
它们都和 query 同级,通过引用赋值得到,例如
"total@":"/[]/total", "info@":"/[]/info"
这里query及total仅为GET类型的请求提供方便,
一般可直接用HEAD类型的请求获取总数

④ "join":"&/Table0/key0@,\多表连接方式:
"\<" - LEFT JOIN
">" - RIGHT JOIN
"&" - INNER JOIN
"\|" - FULL JOIN
"!" - OUTER JOIN
"@" - APP JOIN
其中 @ APP JOIN 为应用层连表,会从已查出的主表里取得所有副表 key@ 关联的主表内的 refKey 作为一个数组 refKeys: [value0, value1...],然后把原来副表 count 次查询 key=$refKey 的 SQL 用 key IN($refKeys) 的方式合并为一条 SQL 来优化性能;
其它 JOIN 都是 SQL JOIN,具体功能和 MySQL,PostgreSQL 等数据库的 JOIN 一一对应
`"join":"`"MainTable":{},`
`"ViceTable":{"key@":"/MainTable/refKey"}`
会对应生成
`MainTable LEFT JOIN ViceTable`
`ON ViceTable.key=MainTable.refKey`

⑤ "otherKey":Object,自定义关键词,名称和以上系统关键词不一样,且原样返回上传的值 | ① 查询User数组,最多5个:
["count":5](http://apijson.cn:8080/get/{"[]":{"count":5,"User":{}}})
对应SQL是`LIMIT 5`

② 查询第3页的User数组,每页5个:
["count":5,
"page":3](http://apijson.cn:8080/get/{"[]":{"count":5,"page":3,"User":{}}})
对应SQL是`LIMIT 5 OFFSET 15`

③ 查询User数组和对应的User总数:
["[]":{
   "query":2,
   "User":{}
},
"total@":"/[]/total",
"info@":"/[]/info"](http://apijson.cn:8080/get/{"[]":{"query":2,"count":5,"User":{}},"total@":"%252F[]%252Ftotal","info@":"%252F[]%252Finfo"})
返回的数据中,总数及分页详情结构为:
"total":139, //总数
"info":{ //分页详情
   "total":139, //总数
   "count":5, //每页数量
   "page":0, //当前页码
   "max":27, //最大页码
   "more":true, //是否还有更多
   "first":true, //是否为首页
   "last":false //是否为尾页
}

④ Moment INNER JOIN User LEFT JOIN Comment:
["[]":{
   "join":"&/User/id@,\    "Moment":{
     "@group":"id" //主副表不是一对一,要去除重复数据
   },
   "User":{
     "name~":"t",
     "id@":"/Moment/userId"
   },
   "Comment":{
     "momentId@":"/Moment/id"
   }
}](http://apijson.cn:8080/get/{"[]":{"count":5,"join":"&%252FUser%252Fid@,\<%252FComment%252FmomentId@","Moment":{"@column":"id,userId,content","@group":"id"},"User":{"name~":"t","id@":"%252FMoment%252FuserId","@column":"id,name,head"},"Comment":{"momentId@":"%252FMoment%252Fid","@column":"id,momentId,content"}}})

⑤ 每一层都加当前用户名:
["User":{},
"[]":{
   "name@":"User/name", //自定义关键词
   "Moment":{}
}](http://apijson.cn:8080/get/{"User":{},"[]":{"name@":"User%252Fname","Moment":{}}}) + 数组关键词,可自定义 | "key":Object,key为 "[]":{} 中{}内的关键词,Object的类型由key指定

① "count":Integer,查询数量,0 表示最大值,默认最大值为100

② "page":Integer,查询页码,从0开始,默认最大值为100,一般和count一起用

③ "query":Integer,查询内容
0-对象,1-总数和分页详情,2-数据、总数和分页详情
总数关键词为 total,分页详情关键词为 info,
它们都和 query 同级,通过引用赋值得到,例如
"total@":"/[]/total", "info@":"/[]/info"
这里query及total仅为GET类型的请求提供方便,
一般可直接用HEAD类型的请求获取总数

④ "join":"&/Table0/key0@,\多表连接方式:
"\<" - LEFT JOIN
">" - RIGHT JOIN
"&" - INNER JOIN
"\|" - FULL JOIN
"!" - OUTER JOIN
"@" - APP JOIN
其中 @ APP JOIN 为应用层连表,会从已查出的主表里取得所有副表 key@ 关联的主表内的 refKey 作为一个数组 refKeys: [value0, value1...],然后把原来副表 count 次查询 key=$refKey 的 SQL 用 key IN($refKeys) 的方式合并为一条 SQL 来优化性能;
其它 JOIN 都是 SQL JOIN,具体功能和 MySQL,PostgreSQL 等数据库的 JOIN 一一对应
`"join":"`"MainTable":{},`
`"ViceTable":{"key@":"/MainTable/refKey"}`
会对应生成
`MainTable LEFT JOIN ViceTable`
`ON ViceTable.key=MainTable.refKey`

⑤ "otherKey":Object,自定义关键词,名称和以上系统关键词不一样,且原样返回上传的值 | ① 查询User数组,最多5个:
["count":5](http://apijson.cn:8080/get/{"[]":{"count":5,"User":{}}})
对应SQL是`LIMIT 5`

② 查询第3页的User数组,每页5个:
["count":5,
"page":3](http://apijson.cn:8080/get/{"[]":{"count":5,"page":3,"User":{}}})
对应SQL是`LIMIT 5 OFFSET 15`

③ 查询User数组和对应的User总数:
["[]":{
   "query":2,
   "User":{}
},
"total@":"/[]/total",
"info@":"/[]/info"](http://apijson.cn:8080/get/{"[]":{"query":2,"count":5,"User":{}},"total@":"%252F[]%252Ftotal","info@":"%252F[]%252Finfo"})
返回的数据中,总数及分页详情结构为:
"total":139, //总数
"info":{ //分页详情
   "total":139, //总数
   "count":5, //每页数量
   "page":0, //当前页码
   "max":27, //最大页码
   "more":true, //是否还有更多
   "first":true, //是否为首页
   "last":false //是否为尾页
}

④ Moment INNER JOIN User LEFT JOIN Comment:
["[]":{
   "join":"&/User/id@,\    "Moment":{
     "@group":"id" //主副表不是一对一,要去除重复数据
   },
   "User":{
     "name~":"t",
     "id@":"/Moment/userId"
   },
   "Comment":{
     "momentId@":"/Moment/id"
   }
}](http://apijson.cn:8080/get/{"[]":{"count":5,"join":"&%252FUser%252Fid@,\<%252FComment%252FmomentId@","Moment":{"@column":"id,userId,content","@group":"id"},"User":{"name~":"t","id@":"%252FMoment%252FuserId","@column":"id,name,head"},"Comment":{"momentId@":"%252FMoment%252Fid","@column":"id,momentId,content"}}})

⑤ 每一层都加当前用户名:
["User":{},
"[]":{
   "name@":"User/name", //自定义关键词
   "Moment":{}
}](http://apijson.cn:8080/get/{"User":{},"[]":{"name@":"User%252Fname","Moment":{}}}) 对象关键词,可自定义 | "@key":Object,@key为 Table:{} 中{}内的关键词,Object的类型由@key指定

① "@combine":"&key0,&key1,\|key2,key3,
!key4,!key5,&key6,key7...",条件组合方式,\| 可省略。会自动把同类的合并,外层按照 & \| ! 顺序,内层的按传参顺序组合成
(key0 & key1 & key6 & 其它key) & (key2 \| key3 \| key7) & !(key4 \| key5)
这种连接方式,其中 "其它key" 是指与 @combine 在同一对象,且未被它声明的条件 key,默认都是 & 连接

② "@column":"column;function(arg)...",返回字段

③ "@order":"column0+,column1-...",排序方式

④ "@group":"column0,column1...",分组方式。如果@column里声明了Table的id,则id也必须在@group中声明;其它情况下必须满足至少一个条件:
1.分组的key在@column里声明
2.Table主键在@group中声明

⑤ "@having":"function0(...)?value0;function1(...)?value1;function2(...)?value2...",SQL函数条件,一般和@group一起用,函数一般在@column里声明

⑥ "@schema":"sys",集合空间(数据库名/模式),非默认的值可通过它来指定,可以在最外层作为全局默认配置

⑦ "@database":"POSTGRESQL",数据库类型,非默认的值可通过它来指定,可以在最外层作为全局默认配置

⑧ "@datasource":"DRUID",跨数据源,非默认的值可通过它来指定,可以在最外层作为全局默认配置

⑨ "@json":"key0,key1...",转为 JSON 格式返回,符合 JSONObject 则转为 {...},符合 JSONArray 则转为 \[...]

⑩ "@role":"OWNER",来访角色,包括
UNKNOWN,LOGIN,CONTACT,CIRCLE,OWNER,ADMIN,
可以在最外层作为全局默认配置,
可自定义其它角色并重写 Verifier.verify 等相关方法来自定义校验

⑪ "@explain":true,性能分析,可以在最外层作为全局默认配置

⑫ "@raw":"key0,key1...",其中 key0, key1 都对应有键值对
"key0":"SQL片段或SQL片段的别名",
"key1":"SQL片段或SQL片段的别名"
自定义原始SQL片段,可扩展嵌套SQL函数等复杂语句,必须是后端已配置的,只有其它功能符都做不到才考虑,谨慎使用,注意防SQL注入

⑬ "@otherKey":Object,自定义关键词,名称和以上系统关键词不一样,且原样返回上传的值 | ① 搜索name或tag任何一个字段包含字符a的User列表:
["name~":"a",
"tag~":"a",
"@combine":"name~,tag~"](http://apijson.cn:8080/get/{"User[]":{"count":10,"User":{"@column":"id,name,tag","name~":"a","tag~":"a","@combine":"name~,tag~"}}})
对应SQL是`name REGEXP 'a' OR tag REGEXP 'a'`

② 只查询id,sex,name这几列并且请求结果也按照这个顺序:
["@column":"id,sex,name"](http://apijson.cn:8080/get/{"User":{"@column":"id,sex,name","id":38710}})
对应SQL是`SELECT id,sex,name`

③ 查询按 name降序、id默认顺序 排序的User数组:
["@order":"name-,id"](http://apijson.cn:8080/get/{"[]":{"count":10,"User":{"@column":"name,id","@order":"name-,id"}}})
对应SQL是`ORDER BY name DESC,id`

④ 查询按userId分组的Moment数组:
["@group":"userId,id"](http://apijson.cn:8080/get/{"[]":{"count":10,"Moment":%7B"@column":"userId,id","@group":"userId,id"}}})
对应SQL是`GROUP BY userId,id`

⑤ 查询 按userId分组、id最大值>=100 的Moment数组:
["@column":"userId;max(id)",
"@group":"userId",
"@having":"max(id)>=100"](http://apijson.cn:8080/get/{"[]":{"count":10,"Moment":{"@column":"userId%253Bmax(id)","@group":"userId","@having":"max(id)>=100"}}})
对应SQL是`SELECT userId,max(id) ... GROUP BY userId HAVING max(id)>=100`
还可以指定函数返回名:
["@column":"userId;max(id):maxId",
"@group":"userId",
"@having":"maxId>=100"](http://apijson.cn:8080/get/{"[]":{"count":10,"Moment":{"@column":"userId%253Bmax(id):maxId","@group":"userId","@having":"maxId>=100"}}})
对应SQL是`SELECT userId,max(id) AS maxId ... GROUP BY userId HAVING maxId>=100`

⑥ 查询 sys 内的 User 表:
["@schema":"sys"](http://apijson.cn:8080/get/{"User":{"@schema":"sys"}})
对应SQL是`FROM sys.User`

⑦ 查询 PostgreSQL 数据库的 User 表:
["@database":"POSTGRESQL"](http://apijson.cn:8080/get/{"User":{"@database":"POSTGRESQL","@explain":true}})

⑧ 使用 Druid 连接池查询 User 表:
["@datasource":"DRUID"](http://apijson.cn:8080/get/{"User":{"@datasource":"DRUID"}})

⑨ 将 VARCHAR 字符串字段 get 转为 JSONArray 返回:
["@json":"get"](http://apijson.cn:8080/get/{"Access":{"@json":"get"}})

⑩ 查询当前用户的动态:
["@role":"OWNER"](http://apijson.cn:8080/get/{"[]":{"Moment":{"@role":"OWNER"}}})

⑪ 开启性能分析:
["@explain":true](http://apijson.cn:8080/get/{"[]":{"Moment":{"@explain":true}}})
对应SQL是`EXPLAIN`

⑫ 统计最近一周偶数userId的数量
["@column":"date;left(date,10):day;sum(if(userId%2=0,1,0))",
"@group":"day",
"@having":"to_days(now())-to_days(\`date\`)<=7",
"@raw":"@column,@having"](http://apijson.cn:8080/get/{"[]":{"Moment":{"@column":"date%3bleft(date,10):day%3bsum(if(userId%252=0,1,0))","@group":"day","@having":"to_days(now())-to_days(\`date\`)<=7","@raw":"@column,@having"}}})
对应SQL是``SELECT date, left(date,10) AS day, sum(if(userId%2=0,1,0)) ... GROUP BY day HAVING to_days(now())-to_days(`date`)<=7``

⑬ 从pictureList获取第0张图片:
["@position":0, //自定义关键词
"firstPicture()":"getFromArray(pictureList,@position)"](http://apijson.cn:8080/get/{"User":{"id":38710,"@position":0,"firstPicture()":"getFromArray(pictureList,@position)"}}) 全局关键词 | 为最外层对象 {} 内的关键词。其中 @database,@schema, @datasource, @role, @explain 基本同对象关键词,见上方说明,区别是全局关键词会每个表对象中没有时自动放入,作为默认值。

① "tag":String,后面的 tag 是非 GET、HEAD 请求中匹配请求的 JSON 结构的标识,一般是要查询的 Table 的名称或该名称对应的数组 Table[] 或 Table:[],由后端 Request 表中指定。

② "version":Integer,接口版本,version 不传、为 null 或 <=0 都会使用最高版本,传了其它有效值则会使用最接近它的最低版本,由后端 Request 表中指定。

③ "format":Boolean,格式化返回 Response JSON 的 key,一般是将 TableName 转为 tableName, TableName[] 转为 tableNameList, Table:alias 转为 alias, TableName-key[] 转为 tableNameKeyList 等小驼峰格式。 | ① 查隐私信息:
[{"tag":"Privacy","Privacy":{"id":82001}}](http://apijson.cn/api?url=http%3A%2F%2Fapijson.cn%3A8080%2Fgets&type=JSON&json={%22tag%22:%22Privacy%22,%22Privacy%22:{%22id%22:82001}})

② 使用第 1 版接口查隐私信息:
[{"version":1,"tag":"Privacy","Privacy":{"id":82001}}](http://apijson.cn/api?url=http%3A%2F%2Fapijson.cn%3A8080%2Fgets&type=JSON&json={%22version%22:1,%22tag%22:%22Privacy%22,%22Privacy%22:{%22id%22:82001}})

③ 格式化朋友圈接口返回 JSON 中的 key:
[{
   "format":true,
   "[]":{
     "page":0,
     "count":3,
     "Moment":{},
     "User":{
       "id@":"/Moment/userId"
     },
     "Comment[]":{
       "count":3,
       "Comment":{
         "momentId@":"[]/Moment/id"
       }
     }
   }
}](http://apijson.cn:8080/get/{"format":true,"[]":{"page":0,"count":3,"Moment":{},"User":{"id@":"%252FMoment%252FuserId"},"Comment[]":{"count":3,"Comment":{"momentId@":"[]%252FMoment%252Fid"}}}})
From bbf0fc33a9380bbfad0b465d29cb781d907188c5 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Tue, 28 Sep 2021 02:14:26 +0800 Subject: [PATCH 0494/1181] =?UTF-8?q?=E5=89=8D=E7=AB=AF=E4=BC=A0=20SQL=20?= =?UTF-8?q?=E5=85=B3=E9=94=AE=E8=AF=8D=20OVER=20=E5=92=8C=20AGAINST=20?= =?UTF-8?q?=E5=8E=BB=E6=8E=89=E5=A4=9A=E4=BD=99=E7=9A=84=E7=A9=BA=E6=A0=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../main/java/apijson/orm/AbstractSQLConfig.java | 13 +++++++------ .../src/main/java/apijson/orm/AbstractVerifier.java | 2 +- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java index fc3bb3ba5..8204a84b2 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java @@ -1562,10 +1562,10 @@ public String getColumnPrase(String expression, boolean containRaw) { // 全文索引 math(name,tag) AGAINST ('a b +c -d' IN NATURALE LANGUAGE MODE) // IN BOOLEAN MODE //有函数,但不是窗口函数 - int overIndex = expression.indexOf(") OVER ("); - int againstIndex = expression.indexOf(") AGAINST ("); - boolean containOver = overIndex > 0 && overIndex < expression.length() - ") OVER (".length(); - boolean containAgainst = againstIndex > 0 && againstIndex < expression.length() - ") AGAINST (".length(); + int overIndex = expression.indexOf(")OVER("); // 传参不传空格,拼接带空格 ") OVER ("); + int againstIndex = expression.indexOf(")AGAINST("); // 传参不传空格,拼接带空格 ") AGAINST ("); + boolean containOver = overIndex > 0 && overIndex < expression.length() - ")OVER(".length(); + boolean containAgainst = againstIndex > 0 && againstIndex < expression.length() - ")AGAINST(".length(); if (containOver && containAgainst) { throw new IllegalArgumentException("字符 " + expression + " 不合法!" @@ -1658,8 +1658,9 @@ public String getColumnPrase(String expression, boolean containRaw) { // 别名 String alias = s2.lastIndexOf(":") < s2.lastIndexOf(")") ? null : s2.substring(s2.lastIndexOf(":") + 1); // 获取后半部分的参数解析 (agr0 agr1 ...) - String argsString2[] = parseArgsSplitWithComma(argString2, false, containRaw); - expression = fun + "(" + StringUtil.getString(agrsString1) + (containOver ? ") OVER (" : ") AGAINST (") + StringUtil.getString(argsString2) + ")" + (StringUtil.isEmpty(alias, true) ? "" : " AS " + quote + alias + quote); } + String argsString2[] = parseArgsSplitWithComma(argString2, false, containRaw); + expression = fun + "(" + StringUtil.getString(agrsString1) + (containOver ? ") OVER (" : ") AGAINST (") // 传参不传空格,拼接带空格 + + StringUtil.getString(argsString2) + ")" + (StringUtil.isEmpty(alias, true) ? "" : " AS " + quote + alias + quote); } } return expression; diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractVerifier.java b/APIJSONORM/src/main/java/apijson/orm/AbstractVerifier.java index 5527f17c4..4cab85683 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractVerifier.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractVerifier.java @@ -961,7 +961,7 @@ public static JSONObject parse(@NotNull final RequestMethod method, String name, //不在target内的 key:{} if (rk.startsWith("@") == false && objKeySet.contains(rk) == false) { if (rv instanceof JSONObject) { - throw new UnsupportedOperationException(method + " 请求," +name + " 里面不允许传 " + rk + ":{} !"); + throw new UnsupportedOperationException(method + " 请求," + name + " 里面不允许传 " + rk + ":{} !"); } if ((method == RequestMethod.POST || method == RequestMethod.PUT) && rv instanceof JSONArray && JSONRequest.isArrayKey(rk)) { throw new UnsupportedOperationException(method + " 请求," + name + " 里面不允许 " + rk + ":[] 等未定义的 Table[]:[{}] 批量操作键值对!"); From 406ab54e67a682b28e8edeebc63ca6dbfae70939 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Tue, 28 Sep 2021 02:41:52 +0800 Subject: [PATCH 0495/1181] =?UTF-8?q?=E4=B8=BB=E9=A1=B9=E7=9B=AE=E8=B4=A1?= =?UTF-8?q?=E7=8C=AE=E8=80=85=E6=96=B0=E5=A2=9E=201=20=E4=BA=BA=EF=BC=8C?= =?UTF-8?q?=E7=94=9F=E6=80=81=E9=A1=B9=E7=9B=AE=E8=B4=A1=E7=8C=AE=E8=80=85?= =?UTF-8?q?=E6=96=B0=E5=A2=9E=207=20=E4=BA=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 0bfb971a6..79d4209ef 100644 --- a/README.md +++ b/README.md @@ -283,10 +283,11 @@ https://github.com/Tencent/APIJSON/blob/master/CONTRIBUTING.md
+
-生态周边项目的作者们(2 个腾讯工程师、1 个字节跳动工程师 等):
+生态周边项目的作者们(2 个腾讯工程师、1 个 BAT 技术专家、1 个字节跳动工程师 等):
https://github.com/search?o=desc&q=apijson&s=stars&type=Repositories
https://search.gitee.com/?skin=rec&type=repository&q=apijson&sort=stars_count
@@ -297,7 +298,7 @@ https://search.gitee.com/?skin=rec&type=repository&q=apijson&sort=stars_count - + + + + + + + + + -

@@ -436,12 +444,12 @@ https://github.com/Tencent/APIJSON/blob/master/CONTRIBUTING.md [FfApiJson](https://gitee.com/own_3_0/ff-api-json) 用 JSON 格式直接生成 SQL,借鉴 APIJSON 支持多数据源 -[apijson-practice](https://github.com/vcoolwind/apijson-practice) 实践一下apijson,对做管理平台还是能有不少提效的 - [APIJSON-ToDo-Demo](https://github.com/jerrylususu/apijson_todo_demo) 一个简单的 todo 示例项目,精简数据,简化上手流程,带自定义鉴权逻辑 [apijson-learn](https://github.com/rainboy-learn/apijson-learn) APIJSON 学习笔记和源码解析 +[apijson-practice](https://github.com/vcoolwind/apijson-practice) 实践一下 apijson,对做管理平台还是能有不少提效的 + [apijson-sample](https://gitee.com/greyzeng/apijson-sample) APIJSON 简单使用 Demo 及教程 [apijson-examples](https://gitee.com/drone/apijson-examples) APIJSON 的前端、业务后端、管理后端 Demo @@ -462,7 +470,7 @@ https://github.com/Tencent/APIJSON/blob/master/CONTRIBUTING.md [APIJSON-Android-RxJava](https://github.com/TommyLemon/APIJSON-Android-RxJava) 仿微信朋友圈动态实战项目,ZBLibrary(UI) + APIJSON(HTTP) + RxJava(Data) -[Android-ZBLibrary](https://github.com/TommyLemon/Android-ZBLibrary) Android MVP快速开发框架,Demo全面,注释详细,使用简单,代码严谨 +[Android-ZBLibrary](https://github.com/TommyLemon/Android-ZBLibrary) Android MVP 快速开发框架,Demo 全面,注释详细,使用简单,代码严谨 感谢热心的作者们的贡献,点 ⭐Star 支持下他们吧。 From 47a83330e6e149c68bbb2b4bbcda87ebc8ec7201 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Tue, 28 Sep 2021 02:44:50 +0800 Subject: [PATCH 0496/1181] =?UTF-8?q?=E8=A1=A5=E5=85=85=20-=20=E4=B8=BB?= =?UTF-8?q?=E9=A1=B9=E7=9B=AE=E8=B4=A1=E7=8C=AE=E8=80=85=E6=96=B0=E5=A2=9E?= =?UTF-8?q?=201=20=E4=BA=BA=EF=BC=8C=E7=94=9F=E6=80=81=E9=A1=B9=E7=9B=AE?= =?UTF-8?q?=E8=B4=A1=E7=8C=AE=E8=80=85=E6=96=B0=E5=A2=9E=207=20=E4=BA=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 79d4209ef..9a8c4aa3f 100644 --- a/README.md +++ b/README.md @@ -310,7 +310,8 @@ https://search.gitee.com/?skin=rec&type=repository&q=apijson&sort=stars_count - + + From d6cb2e14fb878cd83d0bb4f6972935fd8c3aa7e9 Mon Sep 17 00:00:00 2001 From: tianzhenyu Date: Tue, 28 Sep 2021 16:30:39 +0800 Subject: [PATCH 0497/1181] =?UTF-8?q?fix=20PG=E7=9A=84=E9=87=8D=E5=A4=8D?= =?UTF-8?q?=E8=AE=BE=E7=BD=AE=E4=BA=8B=E5=8A=A1=E7=AD=89=E7=BA=A7=E7=9A=84?= =?UTF-8?q?bug?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/main/java/apijson/orm/AbstractSQLExecutor.java | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java index 760781c3a..89a672aac 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java @@ -764,13 +764,18 @@ public void setTransactionIsolation(int transactionIsolation) { } @Override + private boolean setIsolationStatus = false; //设置事务等级 public void begin(int transactionIsolation) throws SQLException { Log.d("\n\n" + TAG, "<<<<<<<<<<<<<< TRANSACTION begin transactionIsolation = " + transactionIsolation + " >>>>>>>>>>>>>>>>>>>>>>> \n\n"); //不做判断,如果掩盖了问题,调用层都不知道为啥事务没有提交成功 // if (connection == null || connection.isClosed()) { // return; // } - connection.setTransactionIsolation(transactionIsolation); + connection.setAutoCommit(false); + if(! this.setIsolationStatus){ //只设置一次Isolation等级 PG重复设置事务等级会报错 + connection.setTransactionIsolation(transactionIsolation); + } + this.setIsolationStatus=true; connection.setAutoCommit(false); //java.sql.SQLException: Can''t call commit when autocommit=true } @Override From 0ca192ab5d3e5960842752d3ef61b9596f93fd6d Mon Sep 17 00:00:00 2001 From: tianzhenyu Date: Tue, 28 Sep 2021 16:35:25 +0800 Subject: [PATCH 0498/1181] =?UTF-8?q?fix=20PG=E7=9A=84=E9=87=8D=E5=A4=8D?= =?UTF-8?q?=E8=AE=BE=E7=BD=AE=E4=BA=8B=E5=8A=A1=E7=AD=89=E7=BA=A7=E7=9A=84?= =?UTF-8?q?bug?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java | 1 - 1 file changed, 1 deletion(-) diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java index 89a672aac..c9f2d0dc4 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java @@ -771,7 +771,6 @@ public void begin(int transactionIsolation) throws SQLException { // if (connection == null || connection.isClosed()) { // return; // } - connection.setAutoCommit(false); if(! this.setIsolationStatus){ //只设置一次Isolation等级 PG重复设置事务等级会报错 connection.setTransactionIsolation(transactionIsolation); } From fe3d7f6c82b3257028e722803056c2383d968a5d Mon Sep 17 00:00:00 2001 From: yeyuezhishou <626732147@qq.com> Date: Thu, 30 Sep 2021 13:30:47 +0800 Subject: [PATCH 0499/1181] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E4=BD=BF=E7=94=A8?= =?UTF-8?q?=E7=99=BB=E5=BD=95=EF=BC=9A=E5=9C=86=E9=80=9A=E7=A7=91=E6=8A=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 公司:圆通科技 链接:https://www.yto.net.cn/ 场景:大数据应用APP内部接口 --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 7c7f2a34a..086ef296e 100644 --- a/README.md +++ b/README.md @@ -237,6 +237,7 @@ https://github.com/Tencent/APIJSON/issues/187 +
From 08780c77d03ec1989871ea68614889da7918b3ca Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Thu, 30 Sep 2021 14:06:33 +0800 Subject: [PATCH 0500/1181] =?UTF-8?q?=E4=BC=98=E5=8C=96=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/main/java/apijson/orm/AbstractSQLExecutor.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java index c9f2d0dc4..b7cf2d0f9 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java @@ -763,18 +763,18 @@ public void setTransactionIsolation(int transactionIsolation) { this.transactionIsolation = transactionIsolation; } + private boolean isIsolationStatusSet = false; //已设置事务等级 @Override - private boolean setIsolationStatus = false; //设置事务等级 public void begin(int transactionIsolation) throws SQLException { Log.d("\n\n" + TAG, "<<<<<<<<<<<<<< TRANSACTION begin transactionIsolation = " + transactionIsolation + " >>>>>>>>>>>>>>>>>>>>>>> \n\n"); //不做判断,如果掩盖了问题,调用层都不知道为啥事务没有提交成功 // if (connection == null || connection.isClosed()) { // return; // } - if(! this.setIsolationStatus){ //只设置一次Isolation等级 PG重复设置事务等级会报错 + if (! isIsolationStatusSet) { //只设置一次Isolation等级 PG重复设置事务等级会报错 + isIsolationStatusSet = true; connection.setTransactionIsolation(transactionIsolation); } - this.setIsolationStatus=true; connection.setAutoCommit(false); //java.sql.SQLException: Can''t call commit when autocommit=true } @Override From 310f611477f8d94e129748dc83cdbd1340820c76 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Thu, 30 Sep 2021 15:57:33 +0800 Subject: [PATCH 0501/1181] =?UTF-8?q?Update=20=E8=AF=A6=E7=BB=86=E7=9A=84?= =?UTF-8?q?=E8=AF=B4=E6=98=8E=E6=96=87=E6=A1=A3.md?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...\232\204\350\257\264\346\230\216\346\226\207\346\241\243.md" | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git "a/\350\257\246\347\273\206\347\232\204\350\257\264\346\230\216\346\226\207\346\241\243.md" "b/\350\257\246\347\273\206\347\232\204\350\257\264\346\230\216\346\226\207\346\241\243.md" index 8a4d7f7c6..fd436f605 100644 --- "a/\350\257\246\347\273\206\347\232\204\350\257\264\346\230\216\346\226\207\346\241\243.md" +++ "b/\350\257\246\347\273\206\347\232\204\350\257\264\346\230\216\346\226\207\346\241\243.md" @@ -684,7 +684,7 @@ MOMENT表示动态,类似微信朋友圈、QQ空间的动态,每一条动态 { "[]": { "Sale":{ - "@column":"store_id,sum(amt):totAmt", + "@column":"store_id;sum(amt):totAmt", "@group":"store_id" } } From 57b45f16efdd5e0435570411a33ba3d450766d44 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Thu, 30 Sep 2021 16:02:32 +0800 Subject: [PATCH 0502/1181] =?UTF-8?q?Update=20=E8=AF=A6=E7=BB=86=E7=9A=84?= =?UTF-8?q?=E8=AF=B4=E6=98=8E=E6=96=87=E6=A1=A3.md?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...64\346\230\216\346\226\207\346\241\243.md" | 35 +++++++++++++------ 1 file changed, 24 insertions(+), 11 deletions(-) diff --git "a/\350\257\246\347\273\206\347\232\204\350\257\264\346\230\216\346\226\207\346\241\243.md" "b/\350\257\246\347\273\206\347\232\204\350\257\264\346\230\216\346\226\207\346\241\243.md" index fd436f605..96b4bfc20 100644 --- "a/\350\257\246\347\273\206\347\232\204\350\257\264\346\230\216\346\226\207\346\241\243.md" +++ "b/\350\257\246\347\273\206\347\232\204\350\257\264\346\230\216\346\226\207\346\241\243.md" @@ -43,7 +43,7 @@ ### B1.下载项目 ```bash -git clone https://github.com/TommyLemon/APIJSON.git +git clone https://github.com/Tencent/APIJSON.git ``` 或者,直接下载ZIP打包好的项目文件。 @@ -101,25 +101,38 @@ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0%20http://maven.apache.org/xs%20%E9%82%A3%E4%B9%88%E9%9C%80%E8%A6%81%E5%9C%A8%60DemoSQLConfig%60%EF%BC%8C40-61%E8%A1%8C%EF%BC%8C%E6%94%B9%E4%B8%BA%E8%87%AA%E5%B7%B1%E6%95%B0%E6%8D%AE%E5%BA%93%E5%AF%B9%E5%BA%94%E7%9A%84%E9%93%BE%E6%8E%A5%20%20%60%60%60java+static%20%7B+DEFAULT_DATABASE%20=%20DATABASE_MYSQL;%20%20//%20TODO%20%E9%BB%98%E8%AE%A4%E6%95%B0%E6%8D%AE%E5%BA%93%E7%B1%BB%E5%9E%8B%EF%BC%8C%E6%94%B9%E6%88%90%E4%BD%A0%E8%87%AA%E5%B7%B1%E7%9A%84+DEFAULT_SCHEMA%20="sys"; // TODO 默认数据库名/模式,改成你自己的,默认情况是 MySQL: sys, PostgreSQL: public, SQL Server: dbo, Oracle: + } + + @Override + public String getDBVersion() { + return "5.7.22"; // "8.0.11"; // TODO 改成你自己的 MySQL 或 PostgreSQL 数据库版本号 // MYSQL 8 和 7 使用的 JDBC 配置不一样 + } + + @JSONField(serialize = false) // 不在日志打印 账号/密码 等敏感信息 @Override public String getDBUri() { - //TODO 改成你自己的 - return DATABASE_POSTGRESQL.equalsIgnoreCase(getDatabase()) ? "jdbc:postgresql://localhost:5432/postgres" : "jdbc:mysql://192.168.71.146:3306/"; + // 这个是 MySQL 8.0 及以上,要加 userSSL=false return "jdbc:mysql://localhost:3306?userSSL=false&serverTimezone=GMT%2B8&useUnicode=true&characterEncoding=UTF-8"; + // 以下是 MySQL 5.7 及以下 + return "jdbc:mysql://localhost:3306?serverTimezone=GMT%2B8&useUnicode=true&characterEncoding=UTF-8"; //TODO 改成你自己的,TiDB 可以当成 MySQL 使用,默认端口为 4000 } + + @JSONField(serialize = false) // 不在日志打印 账号/密码 等敏感信息 @Override public String getDBAccount() { - return DATABASE_POSTGRESQL.equalsIgnoreCase(getDatabase()) ? "postgres" : "root"; //TODO 改成你自己的 + return "root"; // TODO 改成你自己的 } + + @JSONField(serialize = false) // 不在日志打印 账号/密码 等敏感信息 @Override public String getDBPassword() { - return DATABASE_POSTGRESQL.equalsIgnoreCase(getDatabase()) ? null : "root"; //TODO 改成你自己的 - } - @Override - public String getSchema() { - String s = super.getSchema(); - return StringUtil.isEmpty(s, true) ? "thea" : s; //TODO 改成你自己的。例如:将"thea"替换成你自己的“数据库名字” + return "apijson"; // TODO 改成你自己的,TiDB 可以当成 MySQL 使用, 默认密码为空字符串 "" } ``` +具体见源码
+https://github.com/APIJSON/APIJSON-Demo/blob/master/APIJSON-Java-Server/APIJSONDemo/src/main/java/apijson/demo/DemoSQLConfig.java #### C-1-2.导入表 @@ -142,7 +155,7 @@ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0%20http://maven.apache.org/xs%20%20%E4%B8%BA%E4%BA%86%E6%96%B9%E4%BE%BF%E6%B5%8B%E8%AF%95%EF%BC%8C%E6%88%91%E8%BF%99%E9%87%8C%E4%BD%BF%E7%94%A8%E7%9A%84Chrome%E6%B5%8F%E8%A7%88%E5%99%A8%E7%9A%84Restlet%20Client%E6%8F%92%E4%BB%B6%EF%BC%8C%E5%A4%A7%E5%AE%B6%E5%8F%AF%E4%BB%A5%E6%A0%B9%E6%8D%AE%E8%87%AA%E5%B7%B1%E7%9A%84%E5%96%9C%E5%A5%BD%E4%BD%BF%E7%94%A8%E4%B8%8D%E5%90%8C%E7%9A%84%E5%B7%A5%E5%85%B7%E6%B5%8B%E8%AF%95%E3%80%82%20-%E4%BD%BF%E7%94%A8%60%20http://localhost:8080/get%60%E6%B5%8B%E8%AF%95%E7%BB%93%E6%9E%9C%E3%80%82%EF%BC%88%E8%AF%B7%E6%B3%A8%E6%84%8F%20APIJSONApplication.java%20%E4%B8%AD%E4%BD%BF%E7%94%A8%20Tomcat%20%E9%BB%98%E8%AE%A4%E7%9A%84%208080%20%E7%AB%AF%E5%8F%A3%EF%BC%8C%E5%A6%82%E6%9E%9C%E4%B8%8D%E5%B0%8F%E5%BF%83%E5%BC%80%E7%9D%80PC%E7%AB%AF%E7%9A%84%E2%80%9C%E5%BE%AE%E4%BF%A1%E2%80%9D%EF%BC%8C8080%E7%AB%AF%E5%8F%A3%E4%BC%9A%E8%A2%AB%E5%8D%A0%E7%94%A8%EF%BC%8C%E5%BB%BA%E8%AE%AE%E6%94%B9%E6%88%90%208081,%209090%20%E7%AD%89%E5%85%B6%E5%AE%83%E5%BA%94%E7%94%A8%E7%A8%8B%E5%BA%8F%E6%9C%AA%E5%8D%A0%E7%94%A8%E7%9A%84%E7%AB%AF%E5%8F%A3%E3%80%82%EF%BC%89+%E4%BD%BF%E7%94%A8%60%20http://localhost:8080/get%60%E6%B5%8B%E8%AF%95%E7%BB%93%E6%9E%9C%E3%80%82%EF%BC%88%E8%AF%B7%E6%B3%A8%E6%84%8F%20DemoApplication.java%20%E4%B8%AD%E4%BD%BF%E7%94%A8%20Tomcat%20%E9%BB%98%E8%AE%A4%E7%9A%84%208080%20%E7%AB%AF%E5%8F%A3%EF%BC%8C%E5%A6%82%E6%9E%9C%E4%B8%8D%E5%B0%8F%E5%BF%83%E5%BC%80%E7%9D%80PC%E7%AB%AF%E7%9A%84%E2%80%9C%E5%BE%AE%E4%BF%A1%E2%80%9D%EF%BC%8C8080%E7%AB%AF%E5%8F%A3%E4%BC%9A%E8%A2%AB%E5%8D%A0%E7%94%A8%EF%BC%8C%E5%BB%BA%E8%AE%AE%E6%94%B9%E6%88%90%208081,%209090%20%E7%AD%89%E5%85%B6%E5%AE%83%E5%BA%94%E7%94%A8%E7%A8%8B%E5%BA%8F%E6%9C%AA%E5%8D%A0%E7%94%A8%E7%9A%84%E7%AB%AF%E5%8F%A3%E3%80%82%EF%BC%89%20%20%E9%9A%8F%E4%BE%BF%E6%89%BE%E4%B8%80%E4%B8%AA%E8%A1%A8%EF%BC%8C%E6%AF%94%E5%A6%82%60Moment%60%E8%A1%A8%EF%BC%8C%E6%88%91%E4%BB%AC%E5%8F%96%E5%85%B6%E4%B8%ADID%E4%B8%BA12%E7%9A%84%E4%B8%80%E6%9D%A1%E5%87%BA%E6%9D%A5%E7%9C%8B%E7%9C%8B%20From%205d99841ccd9003f4bb1c89c58360e0759a52196d%20Mon%20Sep%2017%2000:00:00%202001From:%20TommyLemon%20%3C1184482681@qq.com%3EDate:%20Thu,%2030%20Sep%202021%2016:10:07%20+0800Subject:%20[PATCH%200503/1181]%20=?UTF-8?q?Update=20=E8=AF=A6=E7=BB=86=E7=9A=84?=%20=?UTF-8?q?=E8=AF=B4=E6=98=8E=E6=96=87=E6=A1=A3.md?=MIME-Version:%201.0Content-Type:%20text/plain;%20charset=UTF-8Content-Transfer-Encoding:%208bit---%20...257\264\346\230\216\346\226\207\346\241\243.md" | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git "a/\350\257\246\347\273\206\347\232\204\350\257\264\346\230\216\346\226\207\346\241\243.md" "b/\350\257\246\347\273\206\347\232\204\350\257\264\346\230\216\346\226\207\346\241\243.md" index 96b4bfc20..57bfd5379 100644 --- "a/\350\257\246\347\273\206\347\232\204\350\257\264\346\230\216\346\226\207\346\241\243.md" +++ "b/\350\257\246\347\273\206\347\232\204\350\257\264\346\230\216\346\226\207\346\241\243.md" @@ -1,16 +1,16 @@ # APIJSON -[TOC] - +可以先看更清晰直观的视频教程
+https://search.bilibili.com/all?keyword=APIJSON&from_source=webtop_search&spm_id_from=333.851 +![image](https://user-images.githubusercontent.com/5738175/135413311-0207ec13-f7ea-4767-9e34-1a6d08438295.png) ## A.介绍 ### A1.关于接口开发 -首先是看名字`APIJSON`,API是说这个项目是属于接口开发的项目,JSON是指传输数据格式是JSON格式。介于各位看官的水平高低不齐,这里就先为没有项目经验朋友啰嗦两句接口开发的内容。有经验的朋友可以跳到`A2`继续查看。 - -(此处内容以后会有的) +首先是看名字`APIJSON`,API是说这个项目是属于接口开发的项目,JSON是指传输数据格式是JSON格式。介于各位看官的水平高低不齐,这里就先为没有项目经验朋友啰嗦两句接口开发的内容。有经验的朋友可以跳到`A2`继续查看。完整的详细介绍见项目首页
+https://github.com/Tencent/APIJSON#--apijson ### A2.功能说明 @@ -43,12 +43,12 @@ ### B1.下载项目 ```bash -git clone https://github.com/Tencent/APIJSON.git +git clone https://github.com/APIJSON/APIJSON-Demo.git ``` 或者,直接下载ZIP打包好的项目文件。 -![1542255627809](assets/1542255627809.png) +![image](https://user-images.githubusercontent.com/5738175/135412855-574cae7b-402c-4fe0-9959-711e580799af.png) From 9bc895b53759cb294878588f13c86558adc8a717 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Thu, 30 Sep 2021 16:12:40 +0800 Subject: [PATCH 0504/1181] =?UTF-8?q?Update=20=E8=AF=A6=E7=BB=86=E7=9A=84?= =?UTF-8?q?=E8=AF=B4=E6=98=8E=E6=96=87=E6=A1=A3.md?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...\232\204\350\257\264\346\230\216\346\226\207\346\241\243.md" | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git "a/\350\257\246\347\273\206\347\232\204\350\257\264\346\230\216\346\226\207\346\241\243.md" "b/\350\257\246\347\273\206\347\232\204\350\257\264\346\230\216\346\226\207\346\241\243.md" index 57bfd5379..d6f8b6979 100644 --- "a/\350\257\246\347\273\206\347\232\204\350\257\264\346\230\216\346\226\207\346\241\243.md" +++ "b/\350\257\246\347\273\206\347\232\204\350\257\264\346\230\216\346\226\207\346\241\243.md" @@ -66,7 +66,7 @@ Eclipse导入: 为了方便修改源代码,你可以像我一样不添加`libs/apijson-orm-3.5.1.jar`文件到`Build Path`中。而是`libs/apijson-orm-3.5.1.jar`的源码,复制到当前项目里。 -源代码在`APIJSON-Master/APIJSON-Java-Server/APIJSONORM`项目中。 +源代码在`https://github.com/Tencent/APIJSON/tree/master/APIJSONORM`项目中。 ### B3. pom.xml的错误修改。 有可能这时候pom.xml中报错,例如: From 10c3916887209b8896bc4dedb7833af805e2a8fa Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Thu, 30 Sep 2021 16:13:34 +0800 Subject: [PATCH 0505/1181] =?UTF-8?q?Update=20=E8=AF=A6=E7=BB=86=E7=9A=84?= =?UTF-8?q?=E8=AF=B4=E6=98=8E=E6=96=87=E6=A1=A3.md?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...2\204\350\257\264\346\230\216\346\226\207\346\241\243.md" | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git "a/\350\257\246\347\273\206\347\232\204\350\257\264\346\230\216\346\226\207\346\241\243.md" "b/\350\257\246\347\273\206\347\232\204\350\257\264\346\230\216\346\226\207\346\241\243.md" index d6f8b6979..971653b4e 100644 --- "a/\350\257\246\347\273\206\347\232\204\350\257\264\346\230\216\346\226\207\346\241\243.md" +++ "b/\350\257\246\347\273\206\347\232\204\350\257\264\346\230\216\346\226\207\346\241\243.md" @@ -64,9 +64,10 @@ Eclipse导入: ![1542345887787](assets/1542345887787.png) -为了方便修改源代码,你可以像我一样不添加`libs/apijson-orm-3.5.1.jar`文件到`Build Path`中。而是`libs/apijson-orm-3.5.1.jar`的源码,复制到当前项目里。 +为了方便修改源代码,你可以像我一样不添加`libs/apijson-orm.jar`文件到`Build Path`中。而是`libs/apijson-orm.jar`的源码,复制到当前项目里。 -源代码在`https://github.com/Tencent/APIJSON/tree/master/APIJSONORM`项目中。 +源代码在
+https://github.com/Tencent/APIJSON/tree/master/APIJSONORM ### B3. pom.xml的错误修改。 有可能这时候pom.xml中报错,例如: From de915017c437671fe21f160501e7f0537f962c8d Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Thu, 30 Sep 2021 16:16:34 +0800 Subject: [PATCH 0506/1181] =?UTF-8?q?Update=20=E8=AF=A6=E7=BB=86=E7=9A=84?= =?UTF-8?q?=E8=AF=B4=E6=98=8E=E6=96=87=E6=A1=A3.md?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...32\204\350\257\264\346\230\216\346\226\207\346\241\243.md" | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git "a/\350\257\246\347\273\206\347\232\204\350\257\264\346\230\216\346\226\207\346\241\243.md" "b/\350\257\246\347\273\206\347\232\204\350\257\264\346\230\216\346\226\207\346\241\243.md" index 971653b4e..a2ab977be 100644 --- "a/\350\257\246\347\273\206\347\232\204\350\257\264\346\230\216\346\226\207\346\241\243.md" +++ "b/\350\257\246\347\273\206\347\232\204\350\257\264\346\230\216\346\226\207\346\241\243.md" @@ -58,7 +58,7 @@ Eclipse导入: 顶部菜单File > Import > Maven > Existing Maven Projects > Next > Browse -`APIJSON-Master/APIJSON-Java-Server/APIJSONBoot` +[APIJSON-Demo-Master/APIJSON-Java-Server/APIJSONDemo](https://github.com/APIJSON/APIJSON-Demo/tree/master/APIJSON-Java-Server/APIJSONDemo) 报依赖错误的时候,同目录下的`lib`里面的`jar`添加到`Build Path`中。 @@ -137,7 +137,7 @@ https://github.com/APIJSON/APIJSON-Demo/blob/master/APIJSON-Java-Server/APIJSOND #### C-1-2.导入表 -在`APIJSON-Master/MySQL`目录下有一批SQL脚本,他们看起来是这样的 +在 [APIJSON-Demo-Master/MySQL](https://github.com/APIJSON/APIJSON-Demo/tree/master/MySQL) 目录下有一批SQL脚本,他们看起来是这样的 ![1542345654422](assets/1542345654422.png) From 724cf362308526b606a7af5e509678da410293c0 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Thu, 30 Sep 2021 16:18:39 +0800 Subject: [PATCH 0507/1181] =?UTF-8?q?Update=20=E8=AF=A6=E7=BB=86=E7=9A=84?= =?UTF-8?q?=E8=AF=B4=E6=98=8E=E6=96=87=E6=A1=A3.md?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...32\204\350\257\264\346\230\216\346\226\207\346\241\243.md" | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git "a/\350\257\246\347\273\206\347\232\204\350\257\264\346\230\216\346\226\207\346\241\243.md" "b/\350\257\246\347\273\206\347\232\204\350\257\264\346\230\216\346\226\207\346\241\243.md" index a2ab977be..b38c2e6ca 100644 --- "a/\350\257\246\347\273\206\347\232\204\350\257\264\346\230\216\346\226\207\346\241\243.md" +++ "b/\350\257\246\347\273\206\347\232\204\350\257\264\346\230\216\346\226\207\346\241\243.md" @@ -4,6 +4,8 @@ https://search.bilibili.com/all?keyword=APIJSON&from_source=webtop_search&spm_id_from=333.851 ![image](https://user-images.githubusercontent.com/5738175/135413311-0207ec13-f7ea-4767-9e34-1a6d08438295.png) +其它各种官方和第三方文档见首页相关推荐
+https://github.com/Tencent/APIJSON#%E7%9B%B8%E5%85%B3%E6%8E%A8%E8%8D%90 ## A.介绍 @@ -924,7 +926,7 @@ static { //注册权限 "INSERT": { "@role": "OWNER" } //如果没传@role就自动添加 "UPDATE": { "id@": "User/id" } //强制放入键值对 ``` -全部操作符见 [Operation.java](https://github.com/APIJSON/APIJSON/blob/master/APIJSON-Java-Server/APIJSONORM/src/main/java/apijson/orm/Operation.java) 的注释 +全部操作符见 [Operation.java](https://github.com/Tencent/APIJSON/blob/master/APIJSONORM/src/main/java/apijson/orm/Operation.java) 的注释

From 77a07af56c5c7a4951b6cc221be694f93c4d5f6c Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Thu, 30 Sep 2021 16:19:07 +0800 Subject: [PATCH 0508/1181] =?UTF-8?q?Update=20=E8=AF=A6=E7=BB=86=E7=9A=84?= =?UTF-8?q?=E8=AF=B4=E6=98=8E=E6=96=87=E6=A1=A3.md?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...\232\204\350\257\264\346\230\216\346\226\207\346\241\243.md" | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git "a/\350\257\246\347\273\206\347\232\204\350\257\264\346\230\216\346\226\207\346\241\243.md" "b/\350\257\246\347\273\206\347\232\204\350\257\264\346\230\216\346\226\207\346\241\243.md" index b38c2e6ca..216311ea4 100644 --- "a/\350\257\246\347\273\206\347\232\204\350\257\264\346\230\216\346\226\207\346\241\243.md" +++ "b/\350\257\246\347\273\206\347\232\204\350\257\264\346\230\216\346\226\207\346\241\243.md" @@ -1,4 +1,4 @@ -# APIJSON +# APIJSON 入门教程 可以先看更清晰直观的视频教程
https://search.bilibili.com/all?keyword=APIJSON&from_source=webtop_search&spm_id_from=333.851 From 6d6de899c8d30ed08c84af34dd6b8e670782b57d Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Thu, 30 Sep 2021 16:29:48 +0800 Subject: [PATCH 0509/1181] =?UTF-8?q?Update=20=E8=AF=A6=E7=BB=86=E7=9A=84?= =?UTF-8?q?=E8=AF=B4=E6=98=8E=E6=96=87=E6=A1=A3.md?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...\232\204\350\257\264\346\230\216\346\226\207\346\241\243.md" | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git "a/\350\257\246\347\273\206\347\232\204\350\257\264\346\230\216\346\226\207\346\241\243.md" "b/\350\257\246\347\273\206\347\232\204\350\257\264\346\230\216\346\226\207\346\241\243.md" index 216311ea4..7ebab3f40 100644 --- "a/\350\257\246\347\273\206\347\232\204\350\257\264\346\230\216\346\226\207\346\241\243.md" +++ "b/\350\257\246\347\273\206\347\232\204\350\257\264\346\230\216\346\226\207\346\241\243.md" @@ -934,6 +934,6 @@ static { //注册权限 :first_quarter_moon_with_face:此处的介绍都只是简要介绍,只是为了引导刚刚接触APIJSON的道友快速了解APIJSON,并不代表APIJSON只有这些功能,具体功能详情参考下列图表 #### 4. 完整功能图表 -https://github.com/TommyLemon/APIJSON/blob/master/Document.md#3 +https://github.com/Tencent/APIJSON/blob/master/Document.md#3 From 29129cf5bf4d5e9f8928b68f40e6cab58a804a3e Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Thu, 30 Sep 2021 16:41:54 +0800 Subject: [PATCH 0510/1181] =?UTF-8?q?=E4=BD=BF=E7=94=A8=E7=99=BB=E8=AE=B0?= =?UTF-8?q?=E6=96=B0=E5=A2=9E=E5=9C=86=E9=80=9A=E5=85=AC=E5=8F=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index c1ccf4582..cae9e4f50 100644 --- a/README.md +++ b/README.md @@ -238,7 +238,7 @@ https://github.com/Tencent/APIJSON/issues/187 - +
* [腾讯科技有限公司](https://www.tencent.com) From 52a2b64e28c61de95fa3898de7e73dded1bdea71 Mon Sep 17 00:00:00 2001 From: WaizLee <91610687+WaizLee@users.noreply.github.com> Date: Mon, 18 Oct 2021 14:38:12 +0800 Subject: [PATCH 0511/1181] =?UTF-8?q?list=E7=B1=BB=E5=9E=8B=E8=AF=B7?= =?UTF-8?q?=E6=B1=82=E5=8F=82=E6=95=B0=E9=80=9A=E8=BF=87put=E8=AF=B7?= =?UTF-8?q?=E6=B1=82=E5=88=B0=E8=BF=9C=E7=A8=8B=E5=87=BD=E6=95=B0=E5=90=8E?= =?UTF-8?q?=E4=B8=A2=E5=A4=B1=E7=9A=84bug?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- APIJSONORM/src/main/java/apijson/orm/AbstractObjectParser.java | 1 + 1 file changed, 1 insertion(+) diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractObjectParser.java b/APIJSONORM/src/main/java/apijson/orm/AbstractObjectParser.java index fa4da155d..537eea16f 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractObjectParser.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractObjectParser.java @@ -549,6 +549,7 @@ public JSON onChildParse(int index, String key, JSONObject value) throws Excepti @Override public void onPUTArrayParse(@NotNull String key, @NotNull JSONArray array) throws Exception { if (isTable == false || array.isEmpty()) { + sqlRequest.put(key, array); Log.e(TAG, "onPUTArrayParse isTable == false || array == null || array.isEmpty() >> return;"); return; } From bf1457276e52e2bf7f0bb5022f4883900e1bfb9b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=B2=88=E5=94=81?= <52o@qq52o.cn> Date: Thu, 21 Oct 2021 14:05:42 +0800 Subject: [PATCH 0512/1181] Update README.md --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index cae9e4f50..1a7274f76 100644 --- a/README.md +++ b/README.md @@ -226,12 +226,12 @@ https://github.com/Tencent/APIJSON/issues/187
- - + + - + From 9b25ee37ab7e3f1355dea452edb96d4b3c4bc71f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=B2=88=E5=94=81?= <52o@qq52o.cn> Date: Thu, 21 Oct 2021 14:06:48 +0800 Subject: [PATCH 0513/1181] Update README-English.md --- README-English.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README-English.md b/README-English.md index cef9f1c02..c945ce8c2 100644 --- a/README-English.md +++ b/README-English.md @@ -314,12 +314,12 @@ https://github.com/Tencent/APIJSON/issues/187
- - + + - + From 3868e8e308e527f50f2b599897e86651cd98a4e8 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Mon, 25 Oct 2021 14:11:44 +0800 Subject: [PATCH 0514/1181] =?UTF-8?q?=20=E8=A7=A3=E5=86=B3=E6=A0=A1?= =?UTF-8?q?=E9=AA=8C=20CIRCLE=20=E8=A7=92=E8=89=B2=E6=97=B6=E6=B2=A1?= =?UTF-8?q?=E6=9C=89=E7=AE=97=E5=BD=93=E5=89=8D=E7=94=A8=E6=88=B7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- APIJSONORM/pom.xml | 2 +- APIJSONORM/src/main/java/apijson/orm/AbstractVerifier.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/APIJSONORM/pom.xml b/APIJSONORM/pom.xml index 00b542ab0..f1fdcd63a 100755 --- a/APIJSONORM/pom.xml +++ b/APIJSONORM/pom.xml @@ -5,7 +5,7 @@ apijson.orm apijson-orm - 4.7.2 + 4.8.0 jar APIJSONORM diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractVerifier.java b/APIJSONORM/src/main/java/apijson/orm/AbstractVerifier.java index 4cab85683..cf8dd8954 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractVerifier.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractVerifier.java @@ -282,7 +282,7 @@ public boolean verifyAccess(SQLConfig config) throws Exception { //不能在Visitor内null -> [] ! 否则会导致某些查询加上不需要的条件! List list = visitor.getContactIdList() == null ? new ArrayList() : new ArrayList(visitor.getContactIdList()); - if (role == CIRCLE) { + if (CIRCLE.equals(role)) { list.add(visitorId); } From 4327d6a3a34622db5a5ba28ca63e53be6bf4eb16 Mon Sep 17 00:00:00 2001 From: sy-records <52o@qq52o.cn> Date: Mon, 25 Oct 2021 14:30:35 +0800 Subject: [PATCH 0515/1181] Update README --- CONTRIBUTING.md | 1 + README-English.md | 26 ++++++++++++++++++-------- README.md | 17 +++++++++-------- 3 files changed, 28 insertions(+), 16 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index de776b2b9..703b9224b 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -42,6 +42,7 @@ - [jerrylususu](https://github.com/jerrylususu)(还开源了 apijson_todo_demo 和 apijson_role_extend) - [Dalezee](https://github.com/Dalezee)(还开源了 apijson_camp) - [aaronlinv](https://github.com/aaronlinv) +- [sy-records](https://github.com/sy-records) #### 其中特别致谢:
justinfengchen 提交的 6 个 Commits, 对 APIJSON 做出了 3,130 增加和 0 处删减(截止 2020/11/04 日);
diff --git a/README-English.md b/README-English.md index c945ce8c2..331ed25b2 100644 --- a/README-English.md +++ b/README-English.md @@ -316,16 +316,16 @@ https://github.com/Tencent/APIJSON/issues/187 - - + + - - - - - - + + + + + +
[More APIJSON Users](https://github.com/Tencent/APIJSON/issues/73) @@ -362,6 +362,16 @@ https://github.com/Tencent/APIJSON/blob/master/CONTRIBUTING.md
+ + + + + + + + + +
diff --git a/README.md b/README.md index 1a7274f76..ffd4d057f 100644 --- a/README.md +++ b/README.md @@ -228,16 +228,16 @@ https://github.com/Tencent/APIJSON/issues/187 - - + + - - - - - - + + + + + +
@@ -285,6 +285,7 @@ https://github.com/Tencent/APIJSON/blob/master/CONTRIBUTING.md
+
From d662ca3f3540d731d4b5d38d1aa5a1715305e6cf Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Mon, 25 Oct 2021 16:02:24 +0800 Subject: [PATCH 0516/1181] =?UTF-8?q?=E5=B0=86=E9=9A=90=E8=97=8F=E5=AD=97?= =?UTF-8?q?=E6=AE=B5=E5=8A=9F=E8=83=BD=E5=8D=95=E7=8B=AC=E6=8A=BD=E5=8F=96?= =?UTF-8?q?=E6=96=B9=E6=B3=95=20isHideColumn=EF=BC=8C=E6=96=B9=E4=BE=BF?= =?UTF-8?q?=E9=87=8D=E5=86=99=E6=9D=A5=E8=87=AA=E5=AE=9A=E4=B9=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/apijson/orm/AbstractSQLExecutor.java | 21 ++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java index b7cf2d0f9..c0fce49f7 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java @@ -514,9 +514,8 @@ protected void executeAppJoin(SQLConfig config, List resultList, Map protected JSONObject onPutColumn(@NotNull SQLConfig config, @NotNull ResultSet rs, @NotNull ResultSetMetaData rsmd , final int tablePosition, @NotNull JSONObject table, final int columnIndex, Map childMap) throws Exception { - if (rsmd.getColumnName(columnIndex).startsWith("_")) { - Log.i(TAG, "select while (rs.next()){ ..." - + " >> rsmd.getColumnName(i).startsWith(_) >> continue;"); + if (isHideColumn(config, rs, rsmd, tablePosition, table, columnIndex, childMap)) { + Log.i(TAG, "onPutColumn isHideColumn(config, rs, rsmd, tablePosition, table, columnIndex, childMap) >> return table;"); return table; } @@ -572,6 +571,22 @@ protected JSONObject onPutColumn(@NotNull SQLConfig config, @NotNull ResultSet r return table; } + + /**如果不需要这个功能,在子类重写并直接 return false; 来提高性能 + * @param config + * @param rs + * @param rsmd + * @param tablePosition + * @param table + * @param columnIndex + * @param childMap + * @return + * @throws SQLException + */ + protected boolean isHideColumn(@NotNull SQLConfig config, @NotNull ResultSet rs, @NotNull ResultSetMetaData rsmd + , final int tablePosition, @NotNull JSONObject table, final int columnIndex, Map childMap) throws SQLException { + return rsmd.getColumnName(columnIndex).startsWith("_"); + } /**resultList.put(position, table); * @param config From 5a2ab0f2bcf81c20a20c86fa5431206e03aaf520 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Mon, 25 Oct 2021 16:05:11 +0800 Subject: [PATCH 0517/1181] =?UTF-8?q?AbstractSQLConfig.getValue=20?= =?UTF-8?q?=E4=BF=AE=E9=A5=B0=E7=AC=A6=20private=20=E6=94=B9=E4=B8=BA=20pr?= =?UTF-8?q?otected=20=E6=96=B9=E4=BE=BF=E5=AD=90=E7=B1=BB=E9=87=8D?= =?UTF-8?q?=E5=86=99=E6=9D=A5=E5=AE=9E=E7=8E=B0=E5=85=BC=E5=AE=B9=20Oracle?= =?UTF-8?q?=20DATETIME=20=E7=AD=89=E7=B1=BB=E5=9E=8B=EF=BC=8C=E5=AF=B9?= =?UTF-8?q?=E5=BA=94=20POST/PUT=20=20to=5Fdate(=3F,'yyyy-mm-dd=20hh24:mi:s?= =?UTF-8?q?s')?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java index 8204a84b2..11651c9fe 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java @@ -2503,7 +2503,7 @@ public String getSQLKey(String key) { * 使用prepareStatement预编译,值为 ? ,后续动态set进去 */ private List preparedValueList = new ArrayList<>(); - private Object getValue(@NotNull Object value) { + protected Object getValue(@NotNull Object value) { if (isPrepared()) { preparedValueList.add(value); return "?"; From bac5eab40d8ce8d7b378c1cf47962de51beadb6e Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Mon, 25 Oct 2021 16:41:14 +0800 Subject: [PATCH 0518/1181] =?UTF-8?q?AbstractSQLConfig.preparedValueList?= =?UTF-8?q?=20=E4=BF=AE=E9=A5=B0=E7=AC=A6=E6=94=B9=E4=B8=BA=20protected=20?= =?UTF-8?q?=E6=96=B9=E4=BE=BF=E5=AD=90=E7=B1=BB=E9=87=8D=E5=86=99=E6=9D=A5?= =?UTF-8?q?=E5=AE=9E=E7=8E=B0=E5=85=BC=E5=AE=B9=20Oracle=20DATETIME,TIMEST?= =?UTF-8?q?AMP=20=E7=AD=89=E6=97=A5=E6=9C=9F=E6=97=B6=E9=97=B4=E7=B1=BB?= =?UTF-8?q?=E5=9E=8B=EF=BC=8C=E5=AF=B9=E5=BA=94=20POST/PUT=20to=5Fdate(=3F?= =?UTF-8?q?,'yyyy-mm-dd=20hh24:mi:ss')?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java index 11651c9fe..442e9eb4a 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java @@ -2502,7 +2502,7 @@ public String getSQLKey(String key) { /** * 使用prepareStatement预编译,值为 ? ,后续动态set进去 */ - private List preparedValueList = new ArrayList<>(); + protected List preparedValueList = new ArrayList<>(); protected Object getValue(@NotNull Object value) { if (isPrepared()) { preparedValueList.add(value); From ad412fc4fe80c75b4a809834e29e76b08c25d10d Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Mon, 25 Oct 2021 17:25:27 +0800 Subject: [PATCH 0519/1181] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E6=94=AF=E6=8C=81?= =?UTF-8?q?=20@column:"`key`"=20=E5=8F=8D=E5=BC=95=E5=8F=B7=E6=8C=87?= =?UTF-8?q?=E5=AE=9A=E5=AD=97=E6=AE=B5=E5=90=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/apijson/orm/AbstractSQLConfig.java | 51 ++++++++++++++----- 1 file changed, 37 insertions(+), 14 deletions(-) diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java index 442e9eb4a..af5199b04 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java @@ -145,9 +145,11 @@ public abstract class AbstractSQLConfig implements SQLConfig { RAW_MAP.put("DISTINCT", ""); //时间 - RAW_MAP.put("DATE", ""); RAW_MAP.put("now()", ""); + RAW_MAP.put("DATE", ""); + RAW_MAP.put("TIME", ""); RAW_MAP.put("DATETIME", ""); + RAW_MAP.put("TIMESTAMP", ""); RAW_MAP.put("DateTime", ""); RAW_MAP.put("SECOND", ""); RAW_MAP.put("MINUTE", ""); @@ -157,17 +159,33 @@ public abstract class AbstractSQLConfig implements SQLConfig { RAW_MAP.put("MONTH", ""); RAW_MAP.put("QUARTER", ""); RAW_MAP.put("YEAR", ""); - RAW_MAP.put("json", ""); - RAW_MAP.put("unit", ""); +// RAW_MAP.put("json", ""); +// RAW_MAP.put("unit", ""); //MYSQL 数据类型 BINARY,CHAR,DATETIME,TIME,DECIMAL,SIGNED,UNSIGNED RAW_MAP.put("BINARY", ""); RAW_MAP.put("SIGNED", ""); RAW_MAP.put("DECIMAL", ""); + RAW_MAP.put("DOUBLE", ""); + RAW_MAP.put("FLOAT", ""); + RAW_MAP.put("BOOLEAN", ""); + RAW_MAP.put("ENUM", ""); + RAW_MAP.put("SET", ""); + RAW_MAP.put("POINT", ""); + RAW_MAP.put("BLOB", ""); + RAW_MAP.put("LONGBLOB", ""); RAW_MAP.put("BINARY", ""); RAW_MAP.put("UNSIGNED", ""); + RAW_MAP.put("BIT", ""); + RAW_MAP.put("TINYINT", ""); + RAW_MAP.put("SMALLINT", ""); + RAW_MAP.put("INT", ""); + RAW_MAP.put("BIGINT", ""); RAW_MAP.put("CHAR", ""); - RAW_MAP.put("TIME", ""); + RAW_MAP.put("VARCHAR", ""); + RAW_MAP.put("TEXT", ""); + RAW_MAP.put("LONGTEXT", ""); + RAW_MAP.put("JSON", ""); //窗口函数关键字 RAW_MAP.put("OVER", ""); @@ -1686,28 +1704,33 @@ private String[] parseArgsSplitWithComma(String param, boolean isColumn, boolean String ck = ckeys[i]; // 如果参数包含 "'" ,解析字符串 - if (ck.contains("'")) { - int count = 0; - for (int j = 0; j < ck.length(); j++) { - if (ck.charAt(j) == '\'') count++; + if (ck.startsWith("`") && ck.endsWith("`")) { + origin = ck.substring(1, ck.length() - 1); + //sql 注入判断 判断 + if (StringUtil.isName(origin) == false) { + throw new IllegalArgumentException("字符 " + ck + " 不合法!" + + "预编译模式下 @column:\"`column0`,`column1`:alias;function0(arg0,arg1,...);function1(...):alias...\"" + + " 中所有字符串 column 都必须必须为1个单词 !"); } - // FIXME 把 `column` 和 '2 values with [ / : ] ..' 按引号位置分割才能满足全文索引、窗口函数的需要 - // 排除字符串中参数中包含 ' 的情况和不以' 开头和结尾的情况,同时排除 cast('s' as ...) 以空格分隔的参数中包含字符串的情况 - if (count != 2 || !(ck.startsWith("'") && ck.endsWith("'"))) { + + ckeys[i] = getKey(origin).toString(); + } + else if (ck.startsWith("'") && ck.endsWith("'")) { + origin = ck.substring(1, ck.length() - 1); + if (origin.contains("'")) { throw new IllegalArgumentException("字符串 " + ck + " 不合法!" + "预编译模式下 @column:\"column0,column1:alias;function0(arg0,arg1,...);function1(...):alias...\"" + " 中字符串参数不合法,必须以 ' 开头, ' 结尾,字符串中不能包含 ' "); } //sql 注入判断 判断 - origin = (ck.substring(1, ck.length() - 1)); if (origin.contains("--") || PATTERN_STRING.matcher(origin).matches() == true) { throw new IllegalArgumentException("字符 " + ck + " 不合法!" + "预编译模式下 @column:\"column0,column1:alias;function0(arg0,arg1,...);function1(...):alias...\"" + " 中所有字符串 arg 都必须不符合正则表达式 " + PATTERN_STRING + " 且不包含连续减号 -- !"); } - + // 1.字符串不是字段也没有别名,所以不解析别名 2. 是字符串,进行预编译,使用getValue() ,对字符串进行截取 - ckeys[i] = getValue(ck.substring(1, ck.length() - 1)).toString(); + ckeys[i] = getValue(origin).toString(); } else { // 参数不包含",",即不是字符串 From b1522c6e7b4c79ba6101f5592f222a405b0a33ed Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Mon, 25 Oct 2021 18:07:30 +0800 Subject: [PATCH 0520/1181] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E6=94=AF=E6=8C=81?= =?UTF-8?q?=20@column:"cast(`date`=20AS=20TIME)"=20=E8=BF=99=E7=A7=8D?= =?UTF-8?q?=E5=9C=A8=E5=87=BD=E6=95=B0=E5=86=85=20`key`=20=E4=B8=8E?= =?UTF-8?q?=E5=85=B3=E9=94=AE=E8=AF=8D=E7=AD=89=E7=BB=84=E5=90=88=E7=9A=84?= =?UTF-8?q?=E6=A0=BC=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java index af5199b04..97ee35a25 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java @@ -65,7 +65,6 @@ import apijson.orm.model.PgAttribute; import apijson.orm.model.PgClass; import apijson.orm.model.Request; -import apijson.orm.model.Response; import apijson.orm.model.SysColumn; import apijson.orm.model.SysTable; import apijson.orm.model.Table; @@ -115,7 +114,6 @@ public abstract class AbstractSQLConfig implements SQLConfig { CONFIG_TABLE_LIST = new ArrayList<>(); // Table, Column 等是系统表 AbstractVerifier.SYSTEM_ACCESS_MAP.keySet()); CONFIG_TABLE_LIST.add(Function.class.getSimpleName()); CONFIG_TABLE_LIST.add(Request.class.getSimpleName()); - CONFIG_TABLE_LIST.add(Response.class.getSimpleName()); CONFIG_TABLE_LIST.add(Access.class.getSimpleName()); CONFIG_TABLE_LIST.add(Document.class.getSimpleName()); CONFIG_TABLE_LIST.add(TestRecord.class.getSimpleName()); From 8b00c69caa05b2157d1734d86d1f794ff9209779 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Mon, 25 Oct 2021 18:50:50 +0800 Subject: [PATCH 0521/1181] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E6=94=AF=E6=8C=81?= =?UTF-8?q?=20CASE=20WHEN=EF=BC=8C=E4=BE=8B=E5=A6=82=20(CASE=20WHEN=20sex?= =?UTF-8?q?=20*=201=20=3D=200=20THEN=20'=E7=94=B7'=20WHEN=20sex=20>=3D=201?= =?UTF-8?q?=20THEN=20'=E5=A5=B3'=20ELSE=20'=E5=85=B6=E5=AE=83'=20END)?= =?UTF-8?q?=EF=BC=9B=E8=A7=A3=E5=86=B3=E9=80=9A=E8=BF=87=20`=5Fkey`=20?= =?UTF-8?q?=E7=BB=95=E8=BF=87=E9=9A=90=E8=97=8F=E5=AD=97=E6=AE=B5=E6=A0=A1?= =?UTF-8?q?=E9=AA=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/apijson/orm/AbstractSQLConfig.java | 60 +++++++++++++++---- 1 file changed, 48 insertions(+), 12 deletions(-) diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java index 97ee35a25..5e5a59917 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java @@ -129,6 +129,20 @@ public abstract class AbstractSQLConfig implements SQLConfig { RAW_MAP = new LinkedHashMap<>(); // 保证顺序,避免配置冲突等意外情况 + + RAW_MAP.put("+", ""); + RAW_MAP.put("-", ""); + RAW_MAP.put("*", ""); + RAW_MAP.put("/", ""); + RAW_MAP.put("=", ""); + RAW_MAP.put("!=", ""); + RAW_MAP.put(">", ""); + RAW_MAP.put(">=", ""); + RAW_MAP.put("<", ""); + RAW_MAP.put("<=", ""); + RAW_MAP.put("%", ""); + RAW_MAP.put("(", ""); + RAW_MAP.put(")", ""); // MySQL 关键字 RAW_MAP.put("AS", ""); @@ -141,6 +155,11 @@ public abstract class AbstractSQLConfig implements SQLConfig { RAW_MAP.put("NOT", ""); RAW_MAP.put("VALUE", ""); RAW_MAP.put("DISTINCT", ""); + RAW_MAP.put("CASE", ""); + RAW_MAP.put("WHEN", ""); + RAW_MAP.put("THEN", ""); + RAW_MAP.put("ELSE", ""); + RAW_MAP.put("END", ""); //时间 RAW_MAP.put("now()", ""); @@ -1705,7 +1724,7 @@ private String[] parseArgsSplitWithComma(String param, boolean isColumn, boolean if (ck.startsWith("`") && ck.endsWith("`")) { origin = ck.substring(1, ck.length() - 1); //sql 注入判断 判断 - if (StringUtil.isName(origin) == false) { + if (origin.startsWith("_") || StringUtil.isName(origin) == false) { throw new IllegalArgumentException("字符 " + ck + " 不合法!" + "预编译模式下 @column:\"`column0`,`column1`:alias;function0(arg0,arg1,...);function1(...):alias...\"" + " 中所有字符串 column 都必须必须为1个单词 !"); @@ -1720,12 +1739,6 @@ else if (ck.startsWith("'") && ck.endsWith("'")) { + "预编译模式下 @column:\"column0,column1:alias;function0(arg0,arg1,...);function1(...):alias...\"" + " 中字符串参数不合法,必须以 ' 开头, ' 结尾,字符串中不能包含 ' "); } - //sql 注入判断 判断 - if (origin.contains("--") || PATTERN_STRING.matcher(origin).matches() == true) { - throw new IllegalArgumentException("字符 " + ck + " 不合法!" - + "预编译模式下 @column:\"column0,column1:alias;function0(arg0,arg1,...);function1(...):alias...\"" - + " 中所有字符串 arg 都必须不符合正则表达式 " + PATTERN_STRING + " 且不包含连续减号 -- !"); - } // 1.字符串不是字段也没有别名,所以不解析别名 2. 是字符串,进行预编译,使用getValue() ,对字符串进行截取 ckeys[i] = getValue(origin).toString(); @@ -1745,7 +1758,7 @@ else if (ck.startsWith("'") && ck.endsWith("'")) { + "关键字必须全大写,且以空格分隔的参数,空格必须只有 1 个!其它情况不允许空格!"); } } else { - if (origin.startsWith("_") || origin.contains("--") || PATTERN_FUNCTION.matcher(origin).matches() == false) { + if (origin.startsWith("_") || origin.contains("--")) { // || PATTERN_FUNCTION.matcher(origin).matches() == false) { throw new IllegalArgumentException("字符 " + ck + " 不合法!" + "预编译模式下 @column:\"column0,column1:alias;function0(arg0,arg1,...);function1(...):alias...\"" + " 中所有 arg 都必须是1个不以 _ 开头的单词 或者符合正则表达式 " + PATTERN_FUNCTION + " 且不包含连续减号 -- !DISTINCT 必须全大写,且后面必须有且只有 1 个空格!其它情况不允许空格!"); @@ -1818,12 +1831,35 @@ private String praseArgsSplitWithSpace(String mkes[]) { } //这里为什么还要做一次判断 是因为解析窗口函数调用的时候会判断一次 - if (isPrepared()) { - if (origin.startsWith("_") || origin.contains("--") || PATTERN_FUNCTION.matcher(origin).matches() == false) { - throw new IllegalArgumentException("字符 " + origin + " 不合法!" + String ck = origin; + // 如果参数包含 "`" 或 "'" ,解析字符串 + if (ck.startsWith("`") && ck.endsWith("`")) { + origin = ck.substring(1, ck.length() - 1); + if (origin.startsWith("_") || StringUtil.isName(origin) == false) { + throw new IllegalArgumentException("字符 " + ck + " 不合法!" + + "预编译模式下 @column:\"`column0`,`column1`:alias;function0(arg0,arg1,...);function1(...):alias...\"" + + " 中所有字符串 column 都必须必须为1个单词 !"); + } + + mkes[j] = getKey(origin).toString(); + continue; + } + else if (ck.startsWith("'") && ck.endsWith("'")) { + origin = ck.substring(1, ck.length() - 1); + if (origin.contains("'")) { + throw new IllegalArgumentException("字符串 " + ck + " 不合法!" + "预编译模式下 @column:\"column0,column1:alias;function0(arg0,arg1,...);function1(...):alias...\"" - + " 中所有 arg 都必须是1个不以 _ 开头的单词 或者符合正则表达式 " + PATTERN_FUNCTION + " 且不包含连续减号 -- !DISTINCT 必须全大写,且后面必须有且只有 1 个空格!其它情况不允许空格!"); + + " 中字符串参数不合法,必须以 ' 开头, ' 结尾,字符串中不能包含 ' "); } + + // 1.字符串不是字段也没有别名,所以不解析别名 2. 是字符串,进行预编译,使用getValue() ,对字符串进行截取 + mkes[j] = getValue(origin).toString(); + continue; + } + else if (ck.contains("`") || ck.contains("'") || origin.startsWith("_") || origin.contains("--")) { // || PATTERN_FUNCTION.matcher(origin).matches() == false) { + throw new IllegalArgumentException("字符 " + origin + " 不合法!" + + "预编译模式下 @column:\"column0,column1:alias;function0(arg0,arg1,...);function1(...):alias...\"" + + " 中所有 arg 都必须是1个不以 _ 开头的单词 或者符合正则表达式 " + PATTERN_FUNCTION + " 且不包含连续减号 -- !DISTINCT 必须全大写,且后面必须有且只有 1 个空格!其它情况不允许空格!"); } boolean isName = false; From 0ca17e631fb851090adb6a8e8376973839871dd1 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Mon, 25 Oct 2021 23:06:51 +0800 Subject: [PATCH 0522/1181] =?UTF-8?q?=E5=88=A0=E9=99=A4=E5=A4=9A=E4=BD=99?= =?UTF-8?q?=E4=B8=94=E6=9C=AA=E5=AE=9E=E9=99=85=E7=94=A8=E4=B8=8A=E7=9A=84?= =?UTF-8?q?=E7=9A=84=20Response.java?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../main/java/apijson/orm/AbstractVerifier.java | 2 -- .../src/main/java/apijson/orm/model/Response.java | 15 --------------- 2 files changed, 17 deletions(-) delete mode 100755 APIJSONORM/src/main/java/apijson/orm/model/Response.java diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractVerifier.java b/APIJSONORM/src/main/java/apijson/orm/AbstractVerifier.java index cf8dd8954..d983da6ae 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractVerifier.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractVerifier.java @@ -63,7 +63,6 @@ import apijson.orm.model.PgAttribute; import apijson.orm.model.PgClass; import apijson.orm.model.Request; -import apijson.orm.model.Response; import apijson.orm.model.SysColumn; import apijson.orm.model.SysTable; import apijson.orm.model.Table; @@ -149,7 +148,6 @@ public abstract class AbstractVerifier implements Verifier, IdCallback { SYSTEM_ACCESS_MAP.put(Access.class.getSimpleName(), getAccessMap(Access.class.getAnnotation(MethodAccess.class))); SYSTEM_ACCESS_MAP.put(Function.class.getSimpleName(), getAccessMap(Function.class.getAnnotation(MethodAccess.class))); SYSTEM_ACCESS_MAP.put(Request.class.getSimpleName(), getAccessMap(Request.class.getAnnotation(MethodAccess.class))); - SYSTEM_ACCESS_MAP.put(Response.class.getSimpleName(), getAccessMap(Response.class.getAnnotation(MethodAccess.class))); if (Log.DEBUG) { SYSTEM_ACCESS_MAP.put(Table.class.getSimpleName(), getAccessMap(Table.class.getAnnotation(MethodAccess.class))); diff --git a/APIJSONORM/src/main/java/apijson/orm/model/Response.java b/APIJSONORM/src/main/java/apijson/orm/model/Response.java deleted file mode 100755 index f5dbc3926..000000000 --- a/APIJSONORM/src/main/java/apijson/orm/model/Response.java +++ /dev/null @@ -1,15 +0,0 @@ -/*Copyright (C) 2020 THL A29 Limited, a Tencent company. All rights reserved. - -This source code is licensed under the Apache License Version 2.0.*/ - - -package apijson.orm.model; - -import apijson.MethodAccess; - -/**结果处理 - * @author Lemon - */ -@MethodAccess(POST = {}, PUT = {}, DELETE = {}) -public class Response { -} From 3c92e777b54ea2a0ffb126dd40cb47685ae1a9eb Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Tue, 26 Oct 2021 00:02:40 +0800 Subject: [PATCH 0523/1181] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index ffd4d057f..6588a07cb 100644 --- a/README.md +++ b/README.md @@ -451,7 +451,7 @@ https://github.com/Tencent/APIJSON/blob/master/CONTRIBUTING.md [apijson-learn](https://github.com/rainboy-learn/apijson-learn) APIJSON 学习笔记和源码解析 -[apijson-practice](https://github.com/vcoolwind/apijson-practice) 实践一下 apijson,对做管理平台还是能有不少提效的 +[apijson-practice](https://github.com/vcoolwind/apijson-practice) BAT 技术专家开源的 APIJSON 参数校验注解 Library 及相关 Demo [apijson-sample](https://gitee.com/greyzeng/apijson-sample) APIJSON 简单使用 Demo 及教程 From aaf38e10e2cc8795eaed46ad91a9eb31eacb50b1 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Tue, 26 Oct 2021 00:16:45 +0800 Subject: [PATCH 0524/1181] =?UTF-8?q?=E7=94=9F=E6=80=81=E9=A1=B9=E7=9B=AE?= =?UTF-8?q?=E6=96=B0=E5=A2=9E=20apijson-db2=20=E5=92=8C=20ClickHouse=20Dem?= =?UTF-8?q?o=20APIJSONDemo?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit APIJSON 接入 IBM 数据库 DB2 的 Demo: https://github.com/andream7/apijson-db2 APIJSON 接入 ClickHouse 的 Demo:https://github.com/qiujunlin/APIJSONDemo --- README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.md b/README.md index 6588a07cb..a0669b22e 100644 --- a/README.md +++ b/README.md @@ -465,6 +465,10 @@ https://github.com/Tencent/APIJSON/blob/master/CONTRIBUTING.md [ApiJsonByJFinal](https://gitee.com/zhiyuexin/ApiJsonByJFinal) 整合 APIJSON 和 JFinal 的 Demo +[apijson-db2](https://github.com/andream7/apijson-db2) APIJSON 接入 IBM 数据库 DB2 的 Demo + +[APIJSONDemo](https://github.com/qiujunlin/APIJSONDemo) APIJSON 接入 ClickHouse 的 Demo + [APIJSONDemo_ClickHouse](https://github.com/chenyanlann/APIJSONDemo_ClickHouse) APIJSON + SpringBoot 连接 ClickHouse 使用的 Demo [apijson-builder](https://github.com/pengxianggui/apijson-builder) 一个方便为 APIJSON 构建 RESTful 请求的 JavaScript 库 From b4de2c2ee3de4436602b1c90196e83aa0bf54e3e Mon Sep 17 00:00:00 2001 From: abigeater Date: Tue, 2 Nov 2021 17:57:39 +0800 Subject: [PATCH 0525/1181] =?UTF-8?q?add=20=E5=A2=9E=E5=8A=A0=E7=94=9F?= =?UTF-8?q?=E6=80=81=E9=A1=B9=E7=9B=AEapijson-hyperf?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index a0669b22e..a3465b33d 100644 --- a/README.md +++ b/README.md @@ -439,6 +439,8 @@ https://github.com/Tencent/APIJSON/blob/master/CONTRIBUTING.md [apijson-php](https://github.com/qq547057827/apijson-php) PHP 版 APIJSON,基于 ThinkPHP,支持 MySQL, PostgreSQL, SQL Server, Oracle 等 +[apijson-hyperf](https://github.com/kvnZero/hyperf-APIJSON.git) PHP 版 APIJSON,基于 Hyperf 支持 MySQL + [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 等 From 147cb7a3c8c0e677a66cacf3f9000c24f1d7ccbf Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Tue, 2 Nov 2021 21:19:42 +0800 Subject: [PATCH 0526/1181] =?UTF-8?q?=E7=94=9F=E6=80=81=E9=A1=B9=E7=9B=AE?= =?UTF-8?q?=E6=96=B0=E5=A2=9E=20PHP=20=E7=89=88=20APIJSON=20=E5=8F=AB=20ap?= =?UTF-8?q?ijson-hyperf=EF=BC=8C=E6=84=9F=E8=B0=A2=E4=BD=9C=E8=80=85?= =?UTF-8?q?=E7=9A=84=E8=B4=A1=E7=8C=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit https://github.com/kvnZero/hyperf-APIJSON --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index a3465b33d..dd8d36af8 100644 --- a/README.md +++ b/README.md @@ -22,7 +22,7 @@ This source code is licensed under the Apache License Version 2.0
  -   +    

@@ -435,12 +435,12 @@ https://github.com/Tencent/APIJSON/blob/master/CONTRIBUTING.md [apijson-go](https://gitee.com/tiangao/apijson-go) Go 版 APIJSON ,支持单表查询、数组查询、多表一对一关联查询、多表一对多关联查询 等 +[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-hyperf](https://github.com/kvnZero/hyperf-APIJSON.git) PHP 版 APIJSON,基于 Hyperf 支持 MySQL - [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 等 From 914e22dc5e54e6ba245798437866b8057284016f Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Thu, 11 Nov 2021 19:57:36 +0800 Subject: [PATCH 0527/1181] =?UTF-8?q?=E6=8A=A5=E9=94=99=E4=BF=A1=E6=81=AF?= =?UTF-8?q?=E6=96=B0=E5=A2=9E=E6=90=9C=E7=B4=A2=E9=93=BE=E6=8E=A5=E5=8F=8A?= =?UTF-8?q?=E5=B8=A6=E7=B3=BB=E7=BB=9F=E4=BF=A1=E6=81=AF=E7=9A=84=E6=8F=90?= =?UTF-8?q?=E4=BA=A4=E9=97=AE=E9=A2=98=E6=A8=A1=E6=9D=BF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- APIJSONORM/src/main/java/apijson/Log.java | 3 + .../apijson/orm/AbstractObjectParser.java | 50 ++++++++++++-- .../main/java/apijson/orm/AbstractParser.java | 66 ++++++++++++++++++- 3 files changed, 111 insertions(+), 8 deletions(-) diff --git a/APIJSONORM/src/main/java/apijson/Log.java b/APIJSONORM/src/main/java/apijson/Log.java index e3f64f86d..d67f19a71 100755 --- a/APIJSONORM/src/main/java/apijson/Log.java +++ b/APIJSONORM/src/main/java/apijson/Log.java @@ -13,6 +13,9 @@ public class Log { public static boolean DEBUG = true; + + public static final String VERSION = "4.8.0"; + public static final String KEY_SYSTEM_INFO_DIVIDER = "---|-----APIJSON SYSTEM INFO-----|---"; //默认的时间格式 public static SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss.SSS"); diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractObjectParser.java b/APIJSONORM/src/main/java/apijson/orm/AbstractObjectParser.java index 537eea16f..b35d0c5a4 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractObjectParser.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractObjectParser.java @@ -116,7 +116,7 @@ public AbstractObjectParser(@NotNull JSONObject request, String parentPath, SQLC public String getParentPath() { return parentPath; } - + @Override public AbstractObjectParser setParentPath(String parentPath) { this.parentPath = parentPath; @@ -284,6 +284,42 @@ else if (method == PUT && value instanceof JSONArray && (whereList == null || wh } } catch (Exception e) { if (tri == false) { + if (Log.DEBUG && sqlConfig != null && e.getMessage().contains(Log.KEY_SYSTEM_INFO_DIVIDER) == false) { + try { + String db = sqlConfig.getDatabase(); + if (db == null) { + if (sqlConfig.isPostgreSQL()) { + db = SQLConfig.DATABASE_POSTGRESQL; + } + else if (sqlConfig.isSQLServer()) { + db = SQLConfig.DATABASE_SQLSERVER; + } + else if (sqlConfig.isOracle()) { + db = SQLConfig.DATABASE_ORACLE; + } + else if (sqlConfig.isDb2()) { + db = SQLConfig.DATABASE_DB2; + } + else if (sqlConfig.isClickHouse()) { + db = SQLConfig.DATABASE_CLICKHOUSE; + } + else { + db = SQLConfig.DATABASE_MYSQL; + } + } + + Class clazz = e.getClass(); + e = clazz.getConstructor(String.class).newInstance( + e.getMessage() + + " " + Log.KEY_SYSTEM_INFO_DIVIDER + " \n**环境信息** " + + "\n系统: " + System.getProperty("os.name") + " " + System.getProperty("os.version") + + "\nJDK: " + System.getProperty("java.version") + " " + System.getProperty("os.arch") + + "\n数据库: " + db + " " + sqlConfig.getDBVersion() + + "\nAPIJSON: " + Log.VERSION + ); + } catch (Throwable e2) {} + } + throw e; // 不忽略错误,抛异常 } invalidate(); // 忽略错误,还原request @@ -860,14 +896,14 @@ public JSONObject onSQLExecute() throws Exception { boolean isSimpleArray = false; List rawList = null; - + if (isArrayMainTable && position == 0 && result != null) { - + isSimpleArray = (functionMap == null || functionMap.isEmpty()) && (customMap == null || customMap.isEmpty()) && (childMap == null || childMap.isEmpty()) && (table.equals(arrayTable)); - + // 提取并缓存数组主表的列表数据 rawList = (List) result.remove(SQLExecutor.KEY_RAW_LIST); if (rawList != null) { @@ -875,7 +911,7 @@ public JSONObject onSQLExecute() throws Exception { if (isSimpleArray == false) { long startTime = System.currentTimeMillis(); - + for (int i = 1; i < rawList.size(); i++) { // 从 1 开始,0 已经处理过 JSONObject obj = rawList.get(i); @@ -883,7 +919,7 @@ public JSONObject onSQLExecute() throws Exception { parser.putQueryResult(arrayPath + "/" + i + "/" + name, obj); // 解决获取关联数据时requestObject里不存在需要的关联数据 } } - + long endTime = System.currentTimeMillis(); // 3ms - 8ms Log.e(TAG, "\n onSQLExecute <<<<<<<<<<<<<<<<<<<<<<<<<<<<\n for (int i = 1; i < list.size(); i++) startTime = " + startTime + "; endTime = " + endTime + "; duration = " + (endTime - startTime) + "\n >>>>>>>>>>>>>>>>>>>>>>>>>>>>>\n "); @@ -895,7 +931,7 @@ public JSONObject onSQLExecute() throws Exception { if (isSubquery == false && result != null) { parser.putQueryResult(path, result); // 解决获取关联数据时requestObject里不存在需要的关联数据 - + if (isSimpleArray && rawList != null) { result.put(SQLExecutor.KEY_RAW_LIST, rawList); } diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java b/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java index b4de3e7c1..5210e58b6 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java @@ -9,6 +9,7 @@ import static apijson.RequestMethod.GET; import java.io.UnsupportedEncodingException; +import java.net.URLEncoder; import java.sql.Connection; import java.sql.SQLException; import java.sql.Savepoint; @@ -641,8 +642,35 @@ public static JSONObject newSuccessResult() { * @return */ public static JSONObject extendErrorResult(JSONObject object, Exception e) { + String msg = e.getMessage(); + + if (Log.DEBUG) { + try { + int index = msg.lastIndexOf(Log.KEY_SYSTEM_INFO_DIVIDER); + String info = index >= 0 ? msg.substring(index + Log.KEY_SYSTEM_INFO_DIVIDER.length()).trim() + : "\n**环境信息** " + + "\n系统: " + System.getProperty("os.name") + " " + System.getProperty("os.version") + + "\nJDK: " + System.getProperty("java.version") + " " + System.getProperty("os.arch") + + "\n数据库: " + + "\nAPIJSON: " + Log.VERSION; + + msg = index < 0 ? msg : msg.substring(0, index).trim(); + String encodedMsg = URLEncoder.encode(msg, "UTF-8"); + + msg += " \n\n\n浏览器打开以下链接搜索答案\nGitHub: https://github.com/Tencent/APIJSON/issues?q=is%3Aissue+" + encodedMsg + + " \n\nGoogle:https://www.google.com/search?q=" + encodedMsg + + " \n\nBaidu:https://www.baidu.com/s?ie=UTF-8&wd=" + encodedMsg + + " \n\n都没找到答案?打开这个链接 https://github.com/Tencent/APIJSON/issues/new?assignees=&labels=&template=--bug.md " + + "\n然后提交问题,推荐用以下模板修改,注意要换行保持清晰可读。" + + "\n【标题】:" + msg + + "\n【内容】:" + info + "\n\n**问题描述**\n" + msg; + } catch (Throwable e2) { + e2.printStackTrace(); + } + } + JSONObject error = newErrorResult(e); - return extendResult(object, error.getIntValue(JSONResponse.KEY_CODE), error.getString(JSONResponse.KEY_MSG)); + return extendResult(object, error.getIntValue(JSONResponse.KEY_CODE), msg); } /**新建错误状态内容 * @param e @@ -1682,6 +1710,42 @@ public JSONObject executeSQL(SQLConfig config, boolean isSubquery) throws Except return result; } catch (Exception e) { + if (Log.DEBUG && e.getMessage().contains(Log.KEY_SYSTEM_INFO_DIVIDER) == false) { + try { + String db = config.getDatabase(); + if (db == null) { + if (config.isPostgreSQL()) { + db = SQLConfig.DATABASE_POSTGRESQL; + } + else if (config.isSQLServer()) { + db = SQLConfig.DATABASE_SQLSERVER; + } + else if (config.isOracle()) { + db = SQLConfig.DATABASE_ORACLE; + } + else if (config.isDb2()) { + db = SQLConfig.DATABASE_DB2; + } + else if (config.isClickHouse()) { + db = SQLConfig.DATABASE_CLICKHOUSE; + } + else { + db = SQLConfig.DATABASE_MYSQL; + } + } + + Class clazz = e.getClass(); + e = clazz.getConstructor(String.class).newInstance( + e.getMessage() + + " " + Log.KEY_SYSTEM_INFO_DIVIDER + " \n**环境信息** " + + "\n系统: " + System.getProperty("os.name") + " " + System.getProperty("os.version") + + "\nJDK: " + System.getProperty("java.version") + " " + System.getProperty("os.arch") + + "\n数据库: " + db + " " + config.getDBVersion() + + "\nAPIJSON: " + Log.VERSION + ); + } catch (Throwable e2) {} + } + if (Log.DEBUG == false && e instanceof SQLException) { throw new SQLException("数据库驱动执行异常SQLException,非 Log.DEBUG 模式下不显示详情,避免泄漏真实模式名、表名等隐私信息", e); } From c397c82584daede3ce1237618174ca2ad744514a Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Thu, 11 Nov 2021 23:01:35 +0800 Subject: [PATCH 0528/1181] =?UTF-8?q?=E4=BC=98=E5=8C=96=E6=8A=A5=E9=94=99?= =?UTF-8?q?=E6=8F=90=E7=A4=BA=EF=BC=8C=E5=BC=95=E5=AF=BC=E7=94=A8=E6=88=B7?= =?UTF-8?q?=E8=87=AA=E8=A1=8C=E8=A7=A3=E5=86=B3=E5=8F=8A=E6=8F=90=E4=BA=A4?= =?UTF-8?q?=E9=97=AE=E9=A2=98=EF=BC=9B=E8=A7=A3=E5=86=B3=20AbstractVerifie?= =?UTF-8?q?r.verifyAccess=20=E5=8F=AA=E5=85=81=E8=AE=B8=20Number=20?= =?UTF-8?q?=E7=B1=BB=E5=9E=8B=E7=9A=84=20id=EF=BC=8C=E5=B9=B6=E4=BC=98?= =?UTF-8?q?=E5=8C=96=E5=8F=98=E9=87=8F=E5=90=8D=EF=BC=9B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../apijson/orm/AbstractObjectParser.java | 2 +- .../main/java/apijson/orm/AbstractParser.java | 125 +++++++++++++----- .../java/apijson/orm/AbstractVerifier.java | 22 +-- 3 files changed, 107 insertions(+), 42 deletions(-) diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractObjectParser.java b/APIJSONORM/src/main/java/apijson/orm/AbstractObjectParser.java index b35d0c5a4..453db9c5f 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractObjectParser.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractObjectParser.java @@ -313,8 +313,8 @@ else if (sqlConfig.isClickHouse()) { e.getMessage() + " " + Log.KEY_SYSTEM_INFO_DIVIDER + " \n**环境信息** " + "\n系统: " + System.getProperty("os.name") + " " + System.getProperty("os.version") - + "\nJDK: " + System.getProperty("java.version") + " " + System.getProperty("os.arch") + "\n数据库: " + db + " " + sqlConfig.getDBVersion() + + "\nJDK: " + System.getProperty("java.version") + " " + System.getProperty("os.arch") + "\nAPIJSON: " + Log.VERSION ); } catch (Throwable e2) {} diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java b/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java index 5210e58b6..65b79cea9 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java @@ -9,6 +9,8 @@ import static apijson.RequestMethod.GET; import java.io.UnsupportedEncodingException; +import java.lang.management.ManagementFactory; +import java.net.InetAddress; import java.net.URLEncoder; import java.sql.Connection; import java.sql.SQLException; @@ -26,6 +28,9 @@ import java.util.concurrent.TimeoutException; import javax.activation.UnsupportedDataTypeException; +import javax.management.MBeanServer; +import javax.management.ObjectName; +import javax.management.Query; import com.alibaba.fastjson.JSONArray; import com.alibaba.fastjson.JSONObject; @@ -153,6 +158,15 @@ public AbstractParser setTag(String tag) { return this; } + protected String requestURL; + public String getRequestURL() { + return requestURL; + } + public AbstractParser setRequestURL(String requestURL) { + this.requestURL = requestURL; + return this; + } + protected JSONObject requestObject; @Override public JSONObject getRequest() { @@ -354,7 +368,7 @@ public JSONObject parseResponse(JSONObject request) { onVerifyContent(); } } catch (Exception e) { - return extendErrorResult(requestObject, e); + return extendErrorResult(requestObject, e, requestMethod, getRequestURL()); } } @@ -364,7 +378,7 @@ public JSONObject parseResponse(JSONObject request) { setGlobleRole(requestObject.getString(JSONRequest.KEY_ROLE)); requestObject.remove(JSONRequest.KEY_ROLE); } catch (Exception e) { - return extendErrorResult(requestObject, e); + return extendErrorResult(requestObject, e, requestMethod, getRequestURL()); } } @@ -383,7 +397,7 @@ public JSONObject parseResponse(JSONObject request) { requestObject.remove(JSONRequest.KEY_EXPLAIN); requestObject.remove(JSONRequest.KEY_CACHE); } catch (Exception e) { - return extendErrorResult(requestObject, e); + return extendErrorResult(requestObject, e, requestMethod, getRequestURL()); } final String requestString = JSON.toJSONString(request);//request传进去解析后已经变了 @@ -407,7 +421,7 @@ public JSONObject parseResponse(JSONObject request) { onRollback(); } - requestObject = error == null ? extendSuccessResult(requestObject) : extendErrorResult(requestObject, error); + requestObject = error == null ? extendSuccessResult(requestObject) : extendErrorResult(requestObject, error, requestMethod, getRequestURL()); JSONObject res = (globleFormat != null && globleFormat) && JSONResponse.isSuccess(requestObject) ? new JSONResponse(requestObject) : requestObject; @@ -608,8 +622,9 @@ public static JSONObject extendResult(JSONObject object, int code, String msg) { if (object == null) { object = new JSONObject(true); } + boolean isOk = JSONResponse.isSuccess(code); if (object.containsKey(JSONResponse.KEY_OK) == false) { - object.put(JSONResponse.KEY_OK, JSONResponse.isSuccess(code)); + object.put(JSONResponse.KEY_OK, isOk); } if (object.containsKey(JSONResponse.KEY_CODE) == false) { object.put(JSONResponse.KEY_CODE, code); @@ -619,6 +634,7 @@ public static JSONObject extendResult(JSONObject object, int code, String msg) { if (m.isEmpty() == false) { msg = m + " ;\n " + StringUtil.getString(msg); } + object.put(JSONResponse.KEY_MSG, msg); return object; } @@ -642,36 +658,84 @@ public static JSONObject newSuccessResult() { * @return */ public static JSONObject extendErrorResult(JSONObject object, Exception e) { + return extendErrorResult(object, e, null, null); + } + /**添加请求成功的状态内容 + * @param object + * @return + */ + public static JSONObject extendErrorResult(JSONObject object, Exception e, RequestMethod requestMethod, String url) { String msg = e.getMessage(); - + if (Log.DEBUG) { try { int index = msg.lastIndexOf(Log.KEY_SYSTEM_INFO_DIVIDER); String info = index >= 0 ? msg.substring(index + Log.KEY_SYSTEM_INFO_DIVIDER.length()).trim() : "\n**环境信息** " + "\n系统: " + System.getProperty("os.name") + " " + System.getProperty("os.version") - + "\nJDK: " + System.getProperty("java.version") + " " + System.getProperty("os.arch") + "\n数据库: " + + "\nJDK: " + System.getProperty("java.version") + " " + System.getProperty("os.arch") + "\nAPIJSON: " + Log.VERSION; msg = index < 0 ? msg : msg.substring(0, index).trim(); String encodedMsg = URLEncoder.encode(msg, "UTF-8"); + + if (StringUtil.isEmpty(url, true)) { + String host = "localhost"; + try { + host = InetAddress.getLocalHost().getHostAddress(); + } catch (Throwable e2) {} + + String port = "8080"; + try { + MBeanServer beanServer = ManagementFactory.getPlatformMBeanServer(); + + Set objectNames = beanServer.queryNames( + new ObjectName("*:type=Connector,*"), + Query.match(Query.attr("protocol"), Query.value("HTTP/1.1")) + ); + String p = objectNames.iterator().next().getKeyProperty("port"); + port = StringUtil.isEmpty(p, true) ? port : p; + } catch (Throwable e2) {} + + url = "http://" + host + ":" + port + "/" + (requestMethod == null ? RequestMethod.GET : requestMethod).name().toLowerCase(); + } + + String req = JSON.toJSONString(object); + try { + req = URLEncoder.encode(req, "UTF-8"); + } catch (Throwable e2) {} + + + boolean isSQLException = e instanceof SQLException; // SQL 报错一般都是通用问题,优先搜索引擎 + String apiatuoAndGitHubLink = "\n【APIAuto】: \n http://apijson.cn/api?type=JSON&url=" + URLEncoder.encode(url, "UTF-8") + "&json=" + req + + " \n\n【GitHub】: \n https://www.google.com/search?q=site%3Agithub.com%2FTencent%2FAPIJSON+++" + encodedMsg; - msg += " \n\n\n浏览器打开以下链接搜索答案\nGitHub: https://github.com/Tencent/APIJSON/issues?q=is%3Aissue+" + encodedMsg - + " \n\nGoogle:https://www.google.com/search?q=" + encodedMsg - + " \n\nBaidu:https://www.baidu.com/s?ie=UTF-8&wd=" + encodedMsg - + " \n\n都没找到答案?打开这个链接 https://github.com/Tencent/APIJSON/issues/new?assignees=&labels=&template=--bug.md " + msg += " \n\n\n浏览器打开以下链接查看解答" + + (isSQLException ? "" : apiatuoAndGitHubLink) + // GitHub Issue 搜索貌似是精准包含,不易找到答案 + " \n\nGitHub: \n https://github.com/Tencent/APIJSON/issues?q=is%3Aissue+" + encodedMsg + + " \n\n【Google】:\n https://www.google.com/search?q=" + encodedMsg + + " \n\n【百度】:\n https://www.baidu.com/s?ie=UTF-8&wd=" + encodedMsg + + (isSQLException ? apiatuoAndGitHubLink : "") + + " \n\n都没找到答案?打开这个链接 \n https://github.com/Tencent/APIJSON/issues/new?assignees=&labels=&template=--bug.md " + "\n然后提交问题,推荐用以下模板修改,注意要换行保持清晰可读。" + "\n【标题】:" + msg - + "\n【内容】:" + info + "\n\n**问题描述**\n" + msg; - } catch (Throwable e2) { - e2.printStackTrace(); - } + + "\n【内容】:" + info + "\n\n**问题描述**\n" + msg + + "\n\n" + + "\n\nPOST " + url + + "\n请求 Request JSON:\n ```js" + + "\n 请填写,例如 { \"Users\":{} }" + + "\n```" + + "\n\n返回结果 Response JSON:\n ```js" + + "\n 请填写,例如 { \"Users\": {}, \"code\": 401, \"msg\": \"Users 不允许 UNKNOWN 用户的 GET 请求!\" }" + + "\n```"; + } catch (Throwable e2) {} } - + JSONObject error = newErrorResult(e); return extendResult(object, error.getIntValue(JSONResponse.KEY_CODE), msg); } + /**新建错误状态内容 * @param e * @return @@ -1000,7 +1064,7 @@ public JSONArray onArrayParse(JSONObject request, String parentPath, String name //不能改变,因为后面可能继续用到,导致1以上都改变 []:{0:{Comment[]:{0:{Comment:{}},1:{...},...}},1:{...},...} final String query = request.getString(JSONRequest.KEY_QUERY); final Integer count = request.getInteger(JSONRequest.KEY_COUNT); //TODO 如果不想用默认数量可以改成 getIntValue(JSONRequest.KEY_COUNT); - final int page = request.getIntValue(JSONRequest.KEY_PAGE); + final Integer page = request.getInteger(JSONRequest.KEY_PAGE); final Object join = request.get(JSONRequest.KEY_JOIN); int query2; @@ -1026,8 +1090,9 @@ public JSONArray onArrayParse(JSONObject request, String parentPath, String name } } + int page2 = page == null ? 0 : page; int maxPage = getMaxQueryPage(); - if (page < 0 || page > maxPage) { + if (page2 < 0 || page2 > maxPage) { throw new IllegalArgumentException(path + "/" + JSONRequest.KEY_PAGE + ":value 中 value 的值不合法!必须在 0-" + maxPage + " 内 !"); } @@ -1052,8 +1117,8 @@ public JSONArray onArrayParse(JSONObject request, String parentPath, String name JSONArray response = null; try { - int size = count2 == 0 ? max : count2;//count为每页数量,size为第page页实际数量,max(size) = count - Log.d(TAG, "onArrayParse size = " + size + "; page = " + page); + int size = count2 == 0 ? max : count2; //count为每页数量,size为第page页实际数量,max(size) = count + Log.d(TAG, "onArrayParse size = " + size + "; page = " + page2); //key[]:{Table:{}}中key equals Table时 提取Table @@ -1076,13 +1141,13 @@ else if (childKeys.length == 1 && JSONRequest.isTableKey(childKeys[0])) { // SQLConfig config = createSQLConfig() .setMethod(requestMethod) .setCount(size) - .setPage(page) + .setPage(page2) .setQuery(query2) .setTable(arrTableKey) .setJoinList(onJoinParse(join, request)); JSONObject parent; - + boolean isExtract = true; //生成size个 @@ -1091,7 +1156,7 @@ else if (childKeys.length == 1 && JSONRequest.isTableKey(childKeys[0])) { // if (parent == null || parent.isEmpty()) { break; } - + long startTime = System.currentTimeMillis(); /* 这里优化了 Table[]: { Table:{} } 这种情况下的性能 @@ -1101,13 +1166,13 @@ else if (childKeys.length == 1 && JSONRequest.isTableKey(childKeys[0])) { // JSONObject fo = i != 0 || arrTableKey == null ? null : parent.getJSONObject(arrTableKey); @SuppressWarnings("unchecked") List list = fo == null ? null : (List) fo.remove(SQLExecutor.KEY_RAW_LIST); - + if (list != null && list.isEmpty() == false) { isExtract = false; - + list.set(0, fo); // 不知道为啥第 0 项也加了 @RAW@LIST response.addAll(list); // List cannot match List response = new JSONArray(list); - + long endTime = System.currentTimeMillis(); // 0ms Log.d(TAG, "\n onArrayParse <<<<<<<<<<<<<<<<<<<<<<<<<<<<\n for (int i = 0; i < (isSubquery ? 1 : size); i++) " + " startTime = " + startTime + "; endTime = " + endTime + "; duration = " + (endTime - startTime) + "\n >>>>>>>>>>>>>>>>>>>>>>>>>>>>>\n"); @@ -1117,7 +1182,7 @@ else if (childKeys.length == 1 && JSONRequest.isTableKey(childKeys[0])) { // //key[]:{Table:{}}中key equals Table时 提取Table response.add(getValue(parent, childKeys)); //null有意义 } - + //Table>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> @@ -1143,7 +1208,7 @@ else if (childKeys.length == 1 && JSONRequest.isTableKey(childKeys[0])) { // if (fo instanceof Boolean || fo instanceof Number || fo instanceof String) { //[{}] 和 [[]] 都没意义 putQueryResult(path, response); } - + long endTime = System.currentTimeMillis(); Log.d(TAG, "\n onArrayParse <<<<<<<<<<<<<<<<<<<<<<<<<<<<\n isExtract >> putQueryResult " + " startTime = " + startTime + "; endTime = " + endTime + "; duration = " + (endTime - startTime) + "\n >>>>>>>>>>>>>>>>>>>>>>>>>>>>>\n"); @@ -1739,13 +1804,13 @@ else if (config.isClickHouse()) { e.getMessage() + " " + Log.KEY_SYSTEM_INFO_DIVIDER + " \n**环境信息** " + "\n系统: " + System.getProperty("os.name") + " " + System.getProperty("os.version") - + "\nJDK: " + System.getProperty("java.version") + " " + System.getProperty("os.arch") + "\n数据库: " + db + " " + config.getDBVersion() + + "\nJDK: " + System.getProperty("java.version") + " " + System.getProperty("os.arch") + "\nAPIJSON: " + Log.VERSION ); } catch (Throwable e2) {} } - + if (Log.DEBUG == false && e instanceof SQLException) { throw new SQLException("数据库驱动执行异常SQLException,非 Log.DEBUG 模式下不显示详情,避免泄漏真实模式名、表名等隐私信息", e); } diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractVerifier.java b/APIJSONORM/src/main/java/apijson/orm/AbstractVerifier.java index d983da6ae..1f366d0c0 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractVerifier.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractVerifier.java @@ -268,7 +268,7 @@ public boolean verifyAccess(SQLConfig config) throws Exception { //验证角色,假定真实强制匹配<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< - String visitorIdkey = getVisitorIdKey(config); + String visitorIdKey = getVisitorIdKey(config); Object requestId; switch (role) { @@ -285,9 +285,9 @@ public boolean verifyAccess(SQLConfig config) throws Exception { } //key!{}:[] 或 其它没有明确id的条件 等 可以和key{}:list组合。类型错误就报错 - requestId = (Number) config.getWhere(visitorIdkey, true);//JSON里数值不能保证是Long,可能是Integer + requestId = config.getWhere(visitorIdKey, true);//JSON里数值不能保证是Long,可能是Integer @SuppressWarnings("unchecked") - Collection requestIdArray = (Collection) config.getWhere(visitorIdkey + "{}", true);//不能是 &{}, |{} 不要传,直接{} + Collection requestIdArray = (Collection) config.getWhere(visitorIdKey + "{}", true);//不能是 &{}, |{} 不要传,直接{} if (requestId != null) { if (requestIdArray == null) { requestIdArray = new JSONArray(); @@ -296,7 +296,7 @@ public boolean verifyAccess(SQLConfig config) throws Exception { } if (requestIdArray == null) {//可能是@得到 || requestIdArray.isEmpty()) {//请求未声明key:id或key{}:[...]条件,自动补全 - config.putWhere(visitorIdkey+"{}", JSON.parseArray(list), true); //key{}:[]有效,SQLConfig里throw NotExistException + config.putWhere(visitorIdKey+"{}", JSON.parseArray(list), true); //key{}:[]有效,SQLConfig里throw NotExistException } else {//请求已声明key:id或key{}:[]条件,直接验证 for (Object id : requestIdArray) { @@ -307,7 +307,7 @@ public boolean verifyAccess(SQLConfig config) throws Exception { throw new UnsupportedDataTypeException(table + ".id类型错误,id类型必须是Long!"); } if (list.contains(Long.valueOf("" + id)) == false) {//Integer等转为Long才能正确判断。强转崩溃 - throw new IllegalAccessException(visitorIdkey + " = " + id + " 的 " + table + throw new IllegalAccessException(visitorIdKey + " = " + id + " 的 " + table + " 不允许 " + role + " 用户的 " + method.name() + " 请求!"); } } @@ -321,20 +321,20 @@ public boolean verifyAccess(SQLConfig config) throws Exception { throw new IllegalArgumentException("POST 请求必须在Table内设置要保存的 key:value !"); } - int index = c.indexOf(visitorIdkey); + int index = c.indexOf(visitorIdKey); if (index >= 0) { Object oid; for (List ovl : ovs) { oid = ovl == null || index >= ovl.size() ? null : ovl.get(index); if (oid == null || StringUtil.getString(oid).equals("" + visitorId) == false) { - throw new IllegalAccessException(visitorIdkey + " = " + oid + " 的 " + table + throw new IllegalAccessException(visitorIdKey + " = " + oid + " 的 " + table + " 不允许 " + role + " 用户的 " + method.name() + " 请求!"); } } } else { List nc = new ArrayList<>(c); - nc.add(visitorIdkey); + nc.add(visitorIdKey); config.setColumn(nc); List> nvs = new ArrayList<>(); @@ -349,13 +349,13 @@ public boolean verifyAccess(SQLConfig config) throws Exception { } } else { - requestId = config.getWhere(visitorIdkey, true);//JSON里数值不能保证是Long,可能是Integer + requestId = config.getWhere(visitorIdKey, true);//JSON里数值不能保证是Long,可能是Integer if (requestId != null && StringUtil.getString(requestId).equals(StringUtil.getString(visitorId)) == false) { - throw new IllegalAccessException(visitorIdkey + " = " + requestId + " 的 " + table + throw new IllegalAccessException(visitorIdKey + " = " + requestId + " 的 " + table + " 不允许 " + role + " 用户的 " + method.name() + " 请求!"); } - config.putWhere(visitorIdkey, visitorId, true); + config.putWhere(visitorIdKey, visitorId, true); } break; case ADMIN://这里不好做,在特定接口内部判。 可以是 /get/admin + 固定秘钥 Parser#needVerify,之后全局跳过验证 From 0ffe78c9d9c60c0144a5d8bf99a3763382e62616 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Fri, 12 Nov 2021 05:58:05 +0800 Subject: [PATCH 0529/1181] =?UTF-8?q?Update=20=E8=AF=A6=E7=BB=86=E7=9A=84?= =?UTF-8?q?=E8=AF=B4=E6=98=8E=E6=96=87=E6=A1=A3.md?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...\232\204\350\257\264\346\230\216\346\226\207\346\241\243.md" | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git "a/\350\257\246\347\273\206\347\232\204\350\257\264\346\230\216\346\226\207\346\241\243.md" "b/\350\257\246\347\273\206\347\232\204\350\257\264\346\230\216\346\226\207\346\241\243.md" index 7ebab3f40..93279022b 100644 --- "a/\350\257\246\347\273\206\347\232\204\350\257\264\346\230\216\346\226\207\346\241\243.md" +++ "b/\350\257\246\347\273\206\347\232\204\350\257\264\346\230\216\346\226\207\346\241\243.md" @@ -148,7 +148,7 @@ https://github.com/APIJSON/APIJSON-Demo/blob/master/APIJSON-Java-Server/APIJSOND ### C-1-2-1.更多测试用例 如果需要更多测试用例,请按照以下步骤获取: -1、在浏览器中输入 apijson.org; +1、在浏览器中输入 apijson.cn; 2、点击右上角的“登录”按钮登录; 3、点击“测试账号”按钮左边第二个按钮。(也就是“-”左边的第一个)获取各种测试用例 4、欢迎大家踊跃共享自己的测试用例; From 0a5b950dbca681aef80f4bff5d0b1a3d3507ef2d Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Mon, 15 Nov 2021 00:39:08 +0800 Subject: [PATCH 0530/1181] Update README-English.md --- README-English.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README-English.md b/README-English.md index 331ed25b2..eeceb8783 100644 --- a/README-English.md +++ b/README-English.md @@ -331,7 +331,7 @@ https://github.com/Tencent/APIJSON/issues/187 [More APIJSON Users](https://github.com/Tencent/APIJSON/issues/73) ### Contributers of APIJSON: -Contributers for the APIJSON core project(6 Tencent engineers、1 Zhihu architect、1 YTO Express engineer, etc.):
+Contributers for the APIJSON core project(6 Tencent engineers, 1 Zhihu architect, 1 YTO Express engineer, etc.):
https://github.com/Tencent/APIJSON/blob/master/CONTRIBUTING.md

-Authors of other projects for ecosystem of APIJSON(2 Tencent engineers、1 Bytedance(TikTok) engineer, etc.):
+Authors of other projects for ecosystem of APIJSON(2 Tencent engineers, 1 BAT(Baidu/Alibaba/Tencent) specialist, 1 Bytedance(TikTok) engineer, etc.):
https://github.com/search?o=desc&q=apijson&s=stars&type=Repositories
https://search.gitee.com/?skin=rec&type=repository&q=apijson&sort=stars_count
From f044d5f903e6a109014ab438b23a36689340b705 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Mon, 15 Nov 2021 00:41:42 +0800 Subject: [PATCH 0531/1181] Update Document.md --- Document.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Document.md b/Document.md index 5da8589ae..73f262b15 100644 --- a/Document.md +++ b/Document.md @@ -5,7 +5,7 @@ https://github.com/Tencent/APIJSON ![image](https://user-images.githubusercontent.com/5738175/134520081-a63d3817-321c-4e7b-9e03-73c6827a19c1.png) -后端开发者可以先看 [图文入门教程1](https://vincentcheng.github.io/apijson-doc/zh) 或 [图文入门教程2](https://hanxu2018.github.io/APIJSON-DOC/) (都非官方,和本文档有出入的点以本文档为准。例如正则匹配 key? 已废弃,用 key~ 替代;例如 "@column":"store_id,sum(amt):totAmt" 中逗号 , 有误,应该用分号 ; 隔开 SQL 函数,改为 "@column":"store_id;sum(amt):totAmt") +后端开发者可以先看 [图文入门教程1](http://apijson.cn/doc/zh/) 或 [图文入门教程2](https://hanxu2018.github.io/APIJSON-DOC/) (和本文档有出入的点以本文档为准。例如正则匹配 key? 已废弃,用 key~ 替代;例如 "@column":"store_id,sum(amt):totAmt" 中逗号 , 有误,应该用分号 ; 隔开 SQL 函数,改为 "@column":"store_id;sum(amt):totAmt") * ### [1.示例](#1) * ### [2.对比传统方式](#2) From 7e60828122961f7cd50fa29deec64c1814e04a85 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Mon, 15 Nov 2021 00:52:57 +0800 Subject: [PATCH 0532/1181] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index dd8d36af8..7b1908260 100644 --- a/README.md +++ b/README.md @@ -353,7 +353,7 @@ https://search.gitee.com/?skin=rec&type=repository&q=apijson&sort=stars_count -[DB2](https://www.ibm.com/support/knowledgecenter/SSEPGG_11.1.0/com.ibm.db2.luw.sql.ref.doc/doc/r0059224.html), [Elasticsearch](https://www.elastic.co/cn/what-is/elasticsearch-sql), [ClickHouse](https://clickhouse.tech/docs/zh/sql-reference/syntax/), [OceanBase](https://www.oceanbase.com/docs/oceanbase/V2.2.50/ss-sr-select_daur3l), [Presto](https://prestodb.io/docs/current/admin/function-namespace-managers.html), [Spark](http://spark.apache.org/sql/), [Hive](https://cwiki.apache.org/confluence/display/Hive/LanguageManual+Select)(延伸支持 Hadoop, Spark), [Phoenix](http://phoenix.apache.org/language/index.html#select)(延伸支持 HBase), [Presto/Trino](https://prestodb.io/docs/current/sql/select.html)(延伸支持 Redis, Hive, Kafka, Elasticsearch, Thrift, Cassandra, MySQL, PostgreSQL, Oracle, MongoDB...) +[Elasticsearch](https://www.elastic.co/cn/what-is/elasticsearch-sql), [OceanBase](https://www.oceanbase.com/docs/oceanbase/V2.2.50/ss-sr-select_daur3l), [Presto](https://prestodb.io/docs/current/admin/function-namespace-managers.html), [Spark](http://spark.apache.org/sql/), [Hive](https://cwiki.apache.org/confluence/display/Hive/LanguageManual+Select)(延伸支持 Hadoop, Spark), [Phoenix](http://phoenix.apache.org/language/index.html#select)(延伸支持 HBase), [Presto/Trino](https://prestodb.io/docs/current/sql/select.html)(延伸支持 Redis, Hive, Kafka, Elasticsearch, Thrift, Cassandra, MySQL, PostgreSQL, Oracle, MongoDB...) ### 我要赞赏 如果你喜欢 APIJSON,感觉 APIJSON 帮助到了你,可以点右上角 ⭐Star 支持一下,谢谢 ^_^
From 76c8885a9d47198fb07aee314944f4b39c91d0f0 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Mon, 15 Nov 2021 00:53:45 +0800 Subject: [PATCH 0533/1181] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 7b1908260..61e4709bb 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,7 @@ This source code is licensed under the Apache License Version 2.0
- +

From b85aed18b27e3ff130b6a6c6b707ceb80d666b23 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Mon, 15 Nov 2021 00:58:35 +0800 Subject: [PATCH 0534/1181] Update Roadmap.md --- Roadmap.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Roadmap.md b/Roadmap.md index 74c3b4ca4..572fa41c1 100644 --- a/Roadmap.md +++ b/Roadmap.md @@ -7,7 +7,7 @@ ### 新增功能 部分功能描述可在 [APIAuto](https://github.com/TommyLemon/APIAuto) 上查看
账号 13000002020 密码 123456
-http://apijson.org:8000/auto/
+http://apijson.cn:8000/api
##### 基本原则 1.一定要有相关的应用场景,不能是伪需求,最好举例说明
@@ -35,6 +35,7 @@ POST: 用不上,不处理
@having! 必须性不大,可通过反转内部条件来实现,但如果实现简单、且不影响原来的功能,则可以顺便加上。
#### 新增支持 @column! +【更新:已提供字段插件 [apijson-column](https://github.com/APIJSON/apijson-column),支持 字段名映射 和 !key 反选字段。】 这个只在 [apijson-framework](https://github.com/APIJSON/apijson-framework) 支持,需要配置每个接口版本、每张表所拥有的全部字段,然后排除掉 @column! 的。
可新增一个 VersionedColumn 表记录来代替 HashMap 代码配置。
@@ -191,7 +192,8 @@ https://github.com/Tencent/APIJSON/blob/master/APIJSONORM/src/main/java/apijson/ ### 提高性能 -20200205 更新:最近的两次大幅提升性能相关优化及 Release
+20200205 更新:最近的及次大幅提升性能相关优化及 Release
+[4.8.0【性能】大幅提升提升单表数组查询性能](https://github.com/Tencent/APIJSON/releases/tag/4.8.0)
[4.6.0【性能】大幅提升数组内主表查询性能](https://github.com/Tencent/APIJSON/releases/tag/4.6.0)
[4.4.5【性能】大幅提升增删改的性能](https://github.com/Tencent/APIJSON/releases/tag/4.4.5)
From 1d9f0e24998e2c4bea2234bb214c694a7bb46905 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Mon, 15 Nov 2021 01:03:20 +0800 Subject: [PATCH 0535/1181] Update Roadmap.md --- Roadmap.md | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/Roadmap.md b/Roadmap.md index 572fa41c1..4a479cd2e 100644 --- a/Roadmap.md +++ b/Roadmap.md @@ -7,7 +7,7 @@ ### 新增功能 部分功能描述可在 [APIAuto](https://github.com/TommyLemon/APIAuto) 上查看
账号 13000002020 密码 123456
-http://apijson.cn:8000/api
+http://apijson.cn/api
##### 基本原则 1.一定要有相关的应用场景,不能是伪需求,最好举例说明
@@ -35,7 +35,7 @@ POST: 用不上,不处理
@having! 必须性不大,可通过反转内部条件来实现,但如果实现简单、且不影响原来的功能,则可以顺便加上。
#### 新增支持 @column! -【更新:已提供字段插件 [apijson-column](https://github.com/APIJSON/apijson-column),支持 字段名映射 和 !key 反选字段。】 +20210415 更新:已提供字段插件 [apijson-column](https://github.com/APIJSON/apijson-column),支持 字段名映射 和 !key 反选字段。 这个只在 [apijson-framework](https://github.com/APIJSON/apijson-framework) 支持,需要配置每个接口版本、每张表所拥有的全部字段,然后排除掉 @column! 的。
可新增一个 VersionedColumn 表记录来代替 HashMap 代码配置。
@@ -193,7 +193,6 @@ https://github.com/Tencent/APIJSON/blob/master/APIJSONORM/src/main/java/apijson/ ### 提高性能 20200205 更新:最近的及次大幅提升性能相关优化及 Release
-[4.8.0【性能】大幅提升提升单表数组查询性能](https://github.com/Tencent/APIJSON/releases/tag/4.8.0)
[4.6.0【性能】大幅提升数组内主表查询性能](https://github.com/Tencent/APIJSON/releases/tag/4.6.0)
[4.4.5【性能】大幅提升增删改的性能](https://github.com/Tencent/APIJSON/releases/tag/4.4.5)
@@ -231,7 +230,7 @@ https://github.com/Tencent/APIJSON/issues/created_by/QiAnXinCodeSafe ##### [APIAuto](https://github.com/TommyLemon/APIAuto) 上统计的 bug 账号 13000002000 密码 123456
-http://apijson.org:8000/auto/
+http://apijson.cn/api
##### 其它发现的 Bug https://github.com/Tencent/APIJSON/issues?q=is%3Aissue+is%3Aopen+label%3Abug
@@ -239,13 +238,14 @@ https://github.com/Tencent/APIJSON/issues?q=is%3Aissue+is%3Aopen+label%3Abug
+http://apijson.cn/api
-##### 接入 UnitAuto-机器学习自动化单元测试平台,每次启动都自动测试所有可测方法并输出报告 -https://gitee.com/TommyLemon/UnitAuto
+##### 在 UnitAuto-机器学习自动化单元测试平台 上传更多、更全面、更细致的测试用例、动态参数等 +http://apijson.cn/unit
### 完善文档 +20211112 更新:已在官网部署文档 http://apijson.cn/doc/zh 20200205 更新:最近完善及更新了通用文档、上手文档、图文入门文档等,还对首页引导文档加了导航目录 https://github.com/Tencent/APIJSON/blob/master/Navigation.md From d970eeda31912621bc1c01f7732d7900b825b58a Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Mon, 15 Nov 2021 01:11:42 +0800 Subject: [PATCH 0536/1181] =?UTF-8?q?Update=20=E8=AF=A6=E7=BB=86=E7=9A=84?= =?UTF-8?q?=E8=AF=B4=E6=98=8E=E6=96=87=E6=A1=A3.md?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...232\204\350\257\264\346\230\216\346\226\207\346\241\243.md" | 3 +++ 1 file changed, 3 insertions(+) diff --git "a/\350\257\246\347\273\206\347\232\204\350\257\264\346\230\216\346\226\207\346\241\243.md" "b/\350\257\246\347\273\206\347\232\204\350\257\264\346\230\216\346\226\207\346\241\243.md" index 93279022b..4a25253a3 100644 --- "a/\350\257\246\347\273\206\347\232\204\350\257\264\346\230\216\346\226\207\346\241\243.md" +++ "b/\350\257\246\347\273\206\347\232\204\350\257\264\346\230\216\346\226\207\346\241\243.md" @@ -4,6 +4,9 @@ https://search.bilibili.com/all?keyword=APIJSON&from_source=webtop_search&spm_id_from=333.851 ![image](https://user-images.githubusercontent.com/5738175/135413311-0207ec13-f7ea-4767-9e34-1a6d08438295.png) +本文档已部署到官网,浏览和检索体验更好
+http://apijson.cn/doc/zh/ + 其它各种官方和第三方文档见首页相关推荐
https://github.com/Tencent/APIJSON#%E7%9B%B8%E5%85%B3%E6%8E%A8%E8%8D%90 From fb26ccfc5af79270865f1a81489d09222c06550f Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Thu, 25 Nov 2021 21:20:59 +0800 Subject: [PATCH 0537/1181] =?UTF-8?q?=E8=B7=AF=E7=BA=BF=E8=A7=84=E5=88=92?= =?UTF-8?q?=EF=BC=9A=E6=96=B0=E5=A2=9E=204.8.0=20=E5=A4=A7=E5=B9=85?= =?UTF-8?q?=E6=8F=90=E5=8D=87=E5=8D=95=E8=BE=B9=E6=95=B0=E7=BB=84=E6=9F=A5?= =?UTF-8?q?=E8=AF=A2=E6=80=A7=E8=83=BD=E7=9A=84=E8=AF=B4=E6=98=8E?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit https://github.com/Tencent/APIJSON/releases/tag/4.8.0 --- Roadmap.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Roadmap.md b/Roadmap.md index 4a479cd2e..dd8fcea9e 100644 --- a/Roadmap.md +++ b/Roadmap.md @@ -193,6 +193,7 @@ https://github.com/Tencent/APIJSON/blob/master/APIJSONORM/src/main/java/apijson/ ### 提高性能 20200205 更新:最近的及次大幅提升性能相关优化及 Release
+[新增支持 ClickHouse、窗口函数 OVER、反引号 `key`、单引号 'value';大幅提升单表数组查询性能](https://github.com/Tencent/APIJSON/releases/tag/4.8.0)
[4.6.0【性能】大幅提升数组内主表查询性能](https://github.com/Tencent/APIJSON/releases/tag/4.6.0)
[4.4.5【性能】大幅提升增删改的性能](https://github.com/Tencent/APIJSON/releases/tag/4.4.5)
@@ -279,7 +280,7 @@ https://github.com/APIJSON/APIJSON#%E7%94%9F%E6%80%81%E9%A1%B9%E7%9B%AE
JavaScript 前端,TypeScript 前端,微信等小程序,
Android 客户端,iOS 客户端,C# 游戏客户端等。
-Java, C#, Node, Python 等后端 Demo 及数据。
+Java, C#, PHP, Node, Python 等后端 Demo 及数据。
https://github.com/APIJSON/APIJSON-Demo
#### 新增扩展 From 12cdd0f5848fe58a56ca6c0799e69cead5553f94 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Sat, 27 Nov 2021 02:24:37 +0800 Subject: [PATCH 0538/1181] Update README.md --- README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 61e4709bb..282325e22 100644 --- a/README.md +++ b/README.md @@ -147,13 +147,13 @@ https://www.bilibili.com/video/BV1yv411p7Y4
### 为什么选择 APIJSON? -前后端 关于接口的 开发、文档、联调 等 10 个痛点解析
+前后端 关于接口的 开发、文档、联调 等 10 大痛点解析
https://github.com/Tencent/APIJSON/wiki -* **解决十个痛点** (APIJSON 可帮助用户 提振开发效率、杜绝联调扯皮、规避文档缺陷、节省流量带宽 等) +* **解决十大痛点** (APIJSON 可帮助用户 提振开发效率、杜绝联调扯皮、规避文档缺陷、节省流量带宽 等) * **开发提速很大** (CRUD 零代码热更新自动化,APIJSONBoot 对比 SSM、SSH 等保守估计可提速 20 倍以上) * **腾讯官方开源** (使用 GitHub、Gitee、工蜂 等平台的官方账号开源,微信公众号、腾讯云+社区 等官方公告) -* **社区影响力大** (GitHub 1W+ Star 在 350W Java 项目中排名前 140,远超 FLAG, BAT 等国内外绝大部分开源项目) +* **社区影响力大** (GitHub 1W+ Star 在 350W Java 项目中排名前 120,远超 FLAG, BAT 等国内外绝大部分开源项目) * **多样用户案例** (腾讯内部用户包含 互娱、音乐、云与智慧,外部用户包含 500 强上市公司、数千亿资本国企 等) * **适用场景广泛** (社交聊天、阅读资讯、影音视频、办公学习 等各种 App、网站、公众号、小程序 等非金融类项目) * **周边生态丰富** (Android, iOS, Web 等各种 Demo、继承 JSON 的海量生态、零代码 接口测试 和 单元测试 工具等) @@ -164,7 +164,7 @@ https://github.com/Tencent/APIJSON/wiki * **高质可靠代码** (代码严谨规范,商业分析软件源伞 Pinpoint 代码扫描报告平均每行代码 Bug 率低至 0.15%) * **兼容各种项目** (协议不限 HTTP,与其它库无冲突,对各类 Web 框架集成友好且提供 SpringBoot, JFinal 的 Demo) * **工程轻量小巧** (仅依赖 fastjson,Jar 仅 280KB,Java 文件仅 59 个共 13719 行代码,例如 APIJSONORM 4.3.1) -* **多年持续迭代** (自 2016 年开源至今已连续维护 4 年,累计 2000+ Commits、70+ Releases,不断更新迭代中...) +* **多年持续迭代** (自 2016 年开源至今已连续维护 5 年,累计 2000+ Commits、80+ Releases,不断更新迭代中...) ### 常见问题 From 128ca8e76a2d56a849ce63e717c2b8f3932311e7 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Sat, 27 Nov 2021 02:25:22 +0800 Subject: [PATCH 0539/1181] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 282325e22..7704d2e5e 100644 --- a/README.md +++ b/README.md @@ -162,7 +162,7 @@ https://github.com/Tencent/APIJSON/wiki * **使用安全简单** (自动增删改查、自动生成文档、自动管理版本、自动控制权限、自动校验参数、自动防SQL注入等) * **灵活定制业务** (在后端编写 远程函数,可以拿到 session、version、当前 JSON 对象 等,然后自定义处理) * **高质可靠代码** (代码严谨规范,商业分析软件源伞 Pinpoint 代码扫描报告平均每行代码 Bug 率低至 0.15%) -* **兼容各种项目** (协议不限 HTTP,与其它库无冲突,对各类 Web 框架集成友好且提供 SpringBoot, JFinal 的 Demo) +* **兼容各种项目** (协议不限 HTTP,与其它库无冲突,对各类 Web 框架集成友好且提供 SpringBoot, JFinal 的示例) * **工程轻量小巧** (仅依赖 fastjson,Jar 仅 280KB,Java 文件仅 59 个共 13719 行代码,例如 APIJSONORM 4.3.1) * **多年持续迭代** (自 2016 年开源至今已连续维护 5 年,累计 2000+ Commits、80+ Releases,不断更新迭代中...) From 44917295296fa4dd6479ea08f9618ccb84238774 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Wed, 1 Dec 2021 01:37:04 +0800 Subject: [PATCH 0540/1181] =?UTF-8?q?=E6=96=B0=E5=A2=9E=207=20=E7=AF=87?= =?UTF-8?q?=E4=BB=A3=E7=A0=81=E5=88=86=E6=9E=90=E7=9B=B8=E5=85=B3=E6=96=87?= =?UTF-8?q?=E7=AB=A0=EF=BC=8C=E5=9F=BA=E6=9C=AC=E9=83=BD=E6=98=AF=2027=20?= =?UTF-8?q?=E7=AF=87=E4=B8=AD=E7=9A=84=E5=BC=80=E7=AF=87=EF=BC=8C=E6=84=9F?= =?UTF-8?q?=E8=B0=A2=203=20=E4=B8=AA=E5=8D=9A=E4=B8=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit https://github.com/Tencent/APIJSON/edit/master/README.md#%E7%9B%B8%E5%85%B3%E6%8E%A8%E8%8D%90 --- README.md | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/README.md b/README.md index 7704d2e5e..bb6639c11 100644 --- a/README.md +++ b/README.md @@ -411,6 +411,21 @@ https://github.com/Tencent/APIJSON/blob/master/CONTRIBUTING.md [全国行政区划数据抓取与处理](https://www.xlongwei.com/detail/21032616) [新手搭建 APIJSON 项目指北](https://github.com/jerrylususu/apijson_todo_demo/blob/master/FULLTEXT.md) + +[APIJSON(一:综述)](https://blog.csdn.net/qq_50861917/article/details/120556168) + +[APIJSON 代码分析(三:demo主体代码)](https://blog.csdn.net/qq_50861917/article/details/120751630) + +[APIJSON 代码分析(二)AbstractParser类(解析器)](https://blog.csdn.net/weixin_45767055/article/details/120815927) + +[APIJSON 代码分析(四:AbstractObjectParser源码阅读)](https://blog.csdn.net/qq_50861917/article/details/120896381) + +[APIJSON 代码分析 AbstractSQLConfig 第二篇](https://blog.csdn.net/csascscascd/article/details/120684889) + +[APIJSON 代码分析(六)APIJSON—Verifier检查类](https://blog.csdn.net/weixin_45767055/article/details/121321731) + +[APIJSON 代码分析(四)AbstractSQLExecutor—SQL执行器](https://blog.csdn.net/weixin_45767055/article/details/121069887) + ### 生态项目 [APIJSON-Demo](https://github.com/APIJSON/APIJSON-Demo) APIJSON 各种语言、各种框架 的 使用示例项目、上手文档、测试数据 SQL 文件 等 @@ -483,6 +498,7 @@ https://github.com/Tencent/APIJSON/blob/master/CONTRIBUTING.md 感谢热心的作者们的贡献,点 ⭐Star 支持下他们吧。 + ### 腾讯犀牛鸟开源人才培养计划 https://github.com/Tencent/APIJSON/issues/229 From e114eff51ed1c9c2c9fc2776a3a5462c4a311ed2 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Wed, 1 Dec 2021 01:38:36 +0800 Subject: [PATCH 0541/1181] =?UTF-8?q?=E6=96=B0=E5=A2=9E=207=20=E7=AF=87?= =?UTF-8?q?=E4=BB=A3=E7=A0=81=E5=88=86=E6=9E=90=E7=9B=B8=E5=85=B3=E6=96=87?= =?UTF-8?q?=E7=AB=A0=EF=BC=8C=E5=9F=BA=E6=9C=AC=E9=83=BD=E6=98=AF=2027=20?= =?UTF-8?q?=E7=AF=87=E4=B8=AD=E7=9A=84=E5=BC=80=E7=AF=87=EF=BC=8C=E6=84=9F?= =?UTF-8?q?=E8=B0=A2=203=20=E4=B8=AA=E5=8D=9A=E4=B8=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit https://github.com/Tencent/APIJSON#%E7%9B%B8%E5%85%B3%E6%8E%A8%E8%8D%90 --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index bb6639c11..376bb2a2f 100644 --- a/README.md +++ b/README.md @@ -412,6 +412,7 @@ https://github.com/Tencent/APIJSON/blob/master/CONTRIBUTING.md [新手搭建 APIJSON 项目指北](https://github.com/jerrylususu/apijson_todo_demo/blob/master/FULLTEXT.md) + [APIJSON(一:综述)](https://blog.csdn.net/qq_50861917/article/details/120556168) [APIJSON 代码分析(三:demo主体代码)](https://blog.csdn.net/qq_50861917/article/details/120751630) From 7a0c85d961116c191b73fd919335b7e66a90db22 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Thu, 2 Dec 2021 20:20:59 +0800 Subject: [PATCH 0542/1181] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E6=96=87=E7=AB=A0?= =?UTF-8?q?=20=E4=BD=BF=E7=94=A8APIJSON=E5=86=99=E4=BD=8E=E4=BB=A3?= =?UTF-8?q?=E7=A0=81Crud=E6=8E=A5=E5=8F=A3=EF=BC=8C=E6=84=9F=E8=B0=A2?= =?UTF-8?q?=E5=8D=9A=E4=B8=BB=E8=B4=A1=E7=8C=AE~?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit https://blog.csdn.net/weixin_42375862/article/details/121654264 位于相关推荐的多篇代码分析博文上方 https://github.com/Tencent/APIJSON#%E7%9B%B8%E5%85%B3%E6%8E%A8%E8%8D%90 --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 376bb2a2f..7388173da 100644 --- a/README.md +++ b/README.md @@ -412,6 +412,7 @@ https://github.com/Tencent/APIJSON/blob/master/CONTRIBUTING.md [新手搭建 APIJSON 项目指北](https://github.com/jerrylususu/apijson_todo_demo/blob/master/FULLTEXT.md) +[使用APIJSON写低代码Crud接口](https://blog.csdn.net/weixin_42375862/article/details/121654264) [APIJSON(一:综述)](https://blog.csdn.net/qq_50861917/article/details/120556168) From 6023bc0fba0ad9a6d4101e361d9fc987d4548139 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Sun, 5 Dec 2021 01:24:56 +0800 Subject: [PATCH 0543/1181] =?UTF-8?q?=E8=A7=A3=E5=86=B3=E6=9F=90=E4=B8=AA?= =?UTF-8?q?=E5=AD=97=E6=AE=B5=E5=80=BC=E4=B8=BA=20null=20=E5=AF=BC?= =?UTF-8?q?=E8=87=B4=E4=B8=AD=E6=96=AD=E5=90=8E=E7=BB=AD=E6=AD=A3=E5=B8=B8?= =?UTF-8?q?=E8=BF=94=E5=9B=9E=E5=80=BC=EF=BC=9B=E8=A7=A3=E5=86=B3=20LEFT/R?= =?UTF-8?q?IGHT=20JOIN=20=E5=89=AF=E8=A1=A8=E5=85=B3=E8=81=94=E4=B8=BB?= =?UTF-8?q?=E8=A1=A8=E5=A4=96=E9=94=AE=E7=9A=84=E5=AD=97=E6=AE=B5=E5=8F=96?= =?UTF-8?q?=E5=88=AB=E5=90=8D=E5=AF=BC=E8=87=B4=20SQL=20=E6=8A=A5=E9=94=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../main/java/apijson/orm/AbstractParser.java | 1 + .../java/apijson/orm/AbstractSQLConfig.java | 45 +++++++++++-------- .../java/apijson/orm/AbstractSQLExecutor.java | 15 ++++--- 3 files changed, 35 insertions(+), 26 deletions(-) diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java b/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java index 65b79cea9..9be40b0d2 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java @@ -432,6 +432,7 @@ public JSONObject parseResponse(JSONObject request) { requestObject.put("sql:generate|cache|execute|maxExecute", getSQLExecutor().getGeneratedSQLCount() + "|" + getSQLExecutor().getCachedSQLCount() + "|" + getSQLExecutor().getExecutedSQLCount() + "|" + getMaxSQLCount()); requestObject.put("depth:count|max", queryDepth + "|" + getMaxQueryDepth()); requestObject.put("time:start|duration|end", startTime + "|" + duration + "|" + endTime); +// TODO 放在 msg 中的调试和提示信息应该单独放一个字段,避免 APIAuto 异常分支不显示提示语或太长,以及 DEBUG 和非 DEBUG 模式下提示语不一致 requestObject.put("debug", debugStr); if (error != null) { requestObject.put("throw", error.getClass().getName()); requestObject.put("trace", error.getStackTrace()); diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java index 5e5a59917..dc6a33647 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java @@ -83,7 +83,7 @@ public abstract class AbstractSQLConfig implements SQLConfig { // * 和 / 不能同时出现,防止 /* */ 段注释! # 和 -- 不能出现,防止行注释! ; 不能出现,防止隔断SQL语句!空格不能出现,防止 CRUD,DROP,SHOW TABLES等语句! private static final Pattern PATTERN_RANGE; private static final Pattern PATTERN_FUNCTION; - private static final Pattern PATTERN_STRING; + private static final Pattern PATTERN_STRING; /** * 表名映射,隐藏真实表名,对安全要求很高的表可以这么做 @@ -1490,9 +1490,8 @@ public String getColumnString(boolean inSQLJoin) throws Exception { return "(" + s + ")"; case GET: case GETS: - boolean isQuery = RequestMethod.isQueryMethod(method); //TODO 这个有啥用?上面应是 getMethod 的值 GET 和 GETS 了。 String joinColumn = ""; - if (isQuery && joinList != null) { + if (joinList != null) { SQLConfig ecfg; SQLConfig cfg; String c; @@ -1501,23 +1500,31 @@ public String getColumnString(boolean inSQLJoin) throws Exception { if (j.isAppJoin()) { continue; } - - ecfg = j.getOuterConfig(); - if (ecfg != null && ecfg.getColumn() != null) { //优先级更高 - cfg = ecfg; - } - else { - cfg = j.getJoinConfig(); - } - - if (StringUtil.isEmpty(cfg.getAlias(), true)) { - cfg.setAlias(cfg.getTable()); - } - - c = ((AbstractSQLConfig) cfg).getColumnString(true); - if (StringUtil.isEmpty(c, true) == false) { - joinColumn += (first ? "" : ", ") + c; + + if (j.isLeftOrRightJoin()) { + // 改为 SELECT ViceTable.* 解决 SELECT sum(ViceTable.id) LEFT/RIGHT JOIN (SELECT sum(id) FROM ViceTable...) AS ViceTable + // 不仅导致 SQL 函数重复计算,还有时导致 SQL 报错或对应字段未返回 + String quote = getQuote(); + joinColumn += (first ? "" : ", ") + quote + (StringUtil.isEmpty(j.getAlias(), true) ? j.getTable() : j.getAlias()) + quote + ".*"; first = false; + } else { + ecfg = j.getOuterConfig(); + if (ecfg != null && ecfg.getColumn() != null) { //优先级更高 + cfg = ecfg; + } + else { + cfg = j.getJoinConfig(); + } + + if (StringUtil.isEmpty(cfg.getAlias(), true)) { + cfg.setAlias(cfg.getTable()); + } + + c = ((AbstractSQLConfig) cfg).getColumnString(true); + if (StringUtil.isEmpty(c, true) == false) { + joinColumn += (first ? "" : ", ") + c; + first = false; + } } inSQLJoin = true; diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java index c0fce49f7..49703a2a1 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java @@ -542,7 +542,8 @@ protected JSONObject onPutColumn(@NotNull SQLConfig config, @NotNull ResultSet r if (joinList != null) { for (Join j : joinList) { childConfig = j.isAppJoin() ? null : j.getCacheConfig(); //这里用config改了getSQL后再还原很麻烦,所以提前给一个config2更好 - + + // FIXME 副表的 SQL 函数,甚至普通字段都可能从 rsmd.getTableName(columnIndex) 拿到 "" if (childConfig != null && childTable.equalsIgnoreCase(childConfig.getSQLTable())) { childConfig.putWhere(j.getKey(), table.get(j.getTargetKey()), true); @@ -561,13 +562,13 @@ protected JSONObject onPutColumn(@NotNull SQLConfig config, @NotNull ResultSet r } Object value = getValue(config, rs, rsmd, tablePosition, table, columnIndex, lable, childMap); - if (value != null) { - if (finalTable == null) { - finalTable = new JSONObject(true); - childMap.put(childSql, finalTable); - } - finalTable.put(lable, value); + // 必须 put 进去,否则某个字段为 null 可能导致中断后续正常返回值 if (value != null) { + if (finalTable == null) { + finalTable = new JSONObject(true); + childMap.put(childSql, finalTable); } + finalTable.put(lable, value); + // } return table; } From 00dae1b6bfa0de09b3e2465f62319c278524f375 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Tue, 7 Dec 2021 03:49:49 +0800 Subject: [PATCH 0544/1181] =?UTF-8?q?=E8=A7=A3=E5=86=B3=20JOIN=20=E5=89=AF?= =?UTF-8?q?=E8=A1=A8=E5=8C=85=E5=90=AB=20SQL=20=E5=87=BD=E6=95=B0=E6=97=B6?= =?UTF-8?q?=E6=B2=A1=E6=9C=89=E8=BF=94=E5=9B=9E=20SQL=20=E5=87=BD=E6=95=B0?= =?UTF-8?q?=E7=9A=84=E6=89=A7=E8=A1=8C=E7=BB=93=E6=9E=9C=E4=BB=A5=E5=8F=8A?= =?UTF-8?q?=E6=9C=AA=E7=94=A8=E4=B8=8A=20SQL=20=E7=BC=93=E5=AD=98=E5=AF=BC?= =?UTF-8?q?=E8=87=B4=E5=86=97=E4=BD=99=20SQL=20=E6=9F=A5=E8=AF=A2=20#341?= =?UTF-8?q?=EF=BC=9B=E6=8F=90=E5=8D=87=20JOIN=20=E5=B0=81=E8=A3=85?= =?UTF-8?q?=E7=BB=93=E6=9E=9C=E7=9A=84=E6=80=A7=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/apijson/orm/AbstractSQLExecutor.java | 183 ++++++++++++++---- 1 file changed, 146 insertions(+), 37 deletions(-) diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java index 49703a2a1..72065302f 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java @@ -25,6 +25,7 @@ import java.util.List; import java.util.Map; import java.util.Map.Entry; +import java.util.Objects; import java.util.Set; import com.alibaba.fastjson.JSON; @@ -267,17 +268,29 @@ public JSONObject execute(@NotNull SQLConfig config, boolean unknowType) throws // childMap = new HashMap<>(); //要存到cacheMap @@ -286,7 +342,14 @@ public JSONObject execute(@NotNull SQLConfig config, boolean unknowType) throws Join[] columnIndexAndJoinMap = isExplain || ! hasJoin ? null : new Join[length]; // int viceColumnStart = length + 1; //第一个副表字段的index + +// FIXME 统计游标查找的时长?可能 ResultSet.next() 及 getTableName, getColumnName, getObject 比较耗时,因为不是一次加载到内存,而是边读边发 + + long lastCursorTime = System.currentTimeMillis(); while (rs.next()) { + sqlResultDuration += System.currentTimeMillis() - lastCursorTime; + lastCursorTime = System.currentTimeMillis(); + index ++; Log.d(TAG, "\n\n<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<\n execute while (rs.next()){ index = " + index + "\n\n"); @@ -318,8 +381,10 @@ public JSONObject execute(@NotNull SQLConfig config, boolean unknowType) throws if (StringUtil.isEmpty(sqlTable, true)) { if (toFindJoin) { // 在主表字段数量内的都归属主表 + long startTime3 = System.currentTimeMillis(); sqlTable = rsmd.getTableName(i); // SQL 函数甚至部分字段都不返回表名,当然如果没传 @column 生成的 Table.* 则返回的所有字段都会带表名 - + sqlResultDuration += System.currentTimeMillis() - startTime3; + if (StringUtil.isEmpty(sqlTable, true)) { // hasJoin 已包含这个判断 && joinList != null) { int nextViceColumnStart = lastViceColumnStart; // 主表没有 @column 时会偏小 lastViceColumnStart @@ -588,12 +653,19 @@ protected void executeAppJoin(SQLConfig config, List resultList, Map int index = -1; + long startTime2 = System.currentTimeMillis(); ResultSetMetaData rsmd = rs.getMetaData(); final int length = rsmd.getColumnCount(); - + sqlResultDuration += System.currentTimeMillis() - startTime2; + JSONObject result; String cacheSql; + + long lastCursorTime = System.currentTimeMillis(); while (rs.next()) { //FIXME 同时有 @ APP JOIN 和 < 等 SQL JOIN 时,next = false 总是无法进入循环,导致缓存失效,可能是连接池或线程问题 + sqlResultDuration += System.currentTimeMillis() - lastCursorTime; + lastCursorTime = System.currentTimeMillis(); + index ++; Log.d(TAG, "\n\n<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<\n executeAppJoin while (rs.next()){ index = " + index + "\n\n"); @@ -708,13 +780,20 @@ protected List onPutTable(@NotNull SQLConfig config, @NotNull Result protected String getKey(@NotNull SQLConfig config, @NotNull ResultSet rs, @NotNull ResultSetMetaData rsmd , final int tablePosition, @NotNull JSONObject table, final int columnIndex, Map childMap) throws Exception { - String key = rsmd.getColumnLabel(columnIndex);// dotIndex < 0 ? lable : lable.substring(dotIndex + 1); + long startTime = System.currentTimeMillis(); + String key = rsmd.getColumnLabel(columnIndex); // dotIndex < 0 ? lable : lable.substring(dotIndex + 1); + sqlResultDuration += System.currentTimeMillis() - startTime; + if (config.isHive()) { String table_name = config.getTable(); - if(AbstractSQLConfig.TABLE_KEY_MAP.containsKey(table_name)) table_name = AbstractSQLConfig.TABLE_KEY_MAP.get(table_name); + if (AbstractSQLConfig.TABLE_KEY_MAP.containsKey(table_name)) { + table_name = AbstractSQLConfig.TABLE_KEY_MAP.get(table_name); + } String pattern = "^" + table_name + "\\." + "[a-zA-Z]+$"; boolean isMatch = Pattern.matches(pattern, key); - if(isMatch) key = key.split("\\.")[1]; + if (isMatch) { + key = key.split("\\.")[1]; + } } return key; } @@ -722,7 +801,10 @@ protected String getKey(@NotNull SQLConfig config, @NotNull ResultSet rs, @NotNu protected Object getValue(@NotNull SQLConfig config, @NotNull ResultSet rs, @NotNull ResultSetMetaData rsmd , final int tablePosition, @NotNull JSONObject table, final int columnIndex, String lable, Map childMap) throws Exception { + long startTime = System.currentTimeMillis(); Object value = rs.getObject(columnIndex); + sqlResultDuration += System.currentTimeMillis() - startTime; + // Log.d(TAG, "name:" + rsmd.getColumnName(i)); // Log.d(TAG, "lable:" + rsmd.getColumnLabel(i)); // Log.d(TAG, "type:" + rsmd.getColumnType(i)); @@ -799,7 +881,10 @@ else if (value instanceof Clob) { //SQL Server TEXT 类型 居然走这个 @Override public boolean isJSONType(@NotNull SQLConfig config, ResultSetMetaData rsmd, int position, String lable) { try { + long startTime = System.currentTimeMillis(); String column = rsmd.getColumnTypeName(position); + sqlResultDuration += System.currentTimeMillis() - startTime; + //TODO CHAR和JSON类型的字段,getColumnType返回值都是1 ,如果不用CHAR,改用VARCHAR,则可以用上面这行来提高性能。 //return rsmd.getColumnType(position) == 1; @@ -965,19 +1050,28 @@ public void close() { @Override public ResultSet executeQuery(@NotNull SQLConfig config) throws Exception { - return getStatement(config).executeQuery(); //PreparedStatement 不用传 SQL + PreparedStatement s = getStatement(config); +// 不准,getStatement 有时比 execute sql 更耗时 executedSQLStartTime = System.currentTimeMillis(); + ResultSet rs = s.executeQuery(); //PreparedStatement 不用传 SQL +// executedSQLEndTime = System.currentTimeMillis(); + return rs; } @Override public int executeUpdate(@NotNull SQLConfig config) throws Exception { PreparedStatement s = getStatement(config); - int count = s.executeUpdate(); //PreparedStatement 不用传 SQL - if (config.isHive() && count==0) count = 1; - - if (config.getMethod() == RequestMethod.POST && config.getId() == null) { //自增id +// 不准,getStatement 有时比 execute sql 更耗时 executedSQLStartTime = System.currentTimeMillis(); + int count = s.executeUpdate(); // PreparedStatement 不用传 SQL +// executedSQLEndTime = System.currentTimeMillis(); + + if (count <= 0 && config.isHive()) { + count = 1; + } + + if (config.getId() == null && config.getMethod() == RequestMethod.POST) { // 自增id ResultSet rs = s.getGeneratedKeys(); if (rs != null && rs.next()) { - config.setId(rs.getLong(1));//返回插入的主键id + config.setId(rs.getLong(1)); //返回插入的主键id FIXME Oracle 拿不到 } } diff --git a/APIJSONORM/src/main/java/apijson/orm/SQLExecutor.java b/APIJSONORM/src/main/java/apijson/orm/SQLExecutor.java index 9fe5917a7..3cb2bf60a 100755 --- a/APIJSONORM/src/main/java/apijson/orm/SQLExecutor.java +++ b/APIJSONORM/src/main/java/apijson/orm/SQLExecutor.java @@ -115,5 +115,8 @@ public interface SQLExecutor { int getExecutedSQLCount(); - + long getExecutedSQLDuration(); + + long getSqlResultDuration(); + } From a6ac4b726a649f277384f525e0d9f339b7f59862 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Sun, 30 Jan 2022 18:44:50 +0800 Subject: [PATCH 0555/1181] Update README.md --- README.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 33e7ec713..3a6f0cf6a 100644 --- a/README.md +++ b/README.md @@ -244,7 +244,11 @@ https://github.com/Tencent/APIJSON/issues/187
* [腾讯科技有限公司](https://www.tencent.com) - + * [腾讯音乐娱乐集团](https://www.tencentmusic.com) + * [华能贵成信托有限公司](https://www.hngtrust.com) + * [投投科技](https://www.toutou.com.cn) + * [圆通科技](https://www.tencentmusic.com) + * [乐拼科技](https://www.lepinyongche.com) ### 贡献者们 主项目 APIJSON 的贡献者们(6 个腾讯工程师、1 个知乎基础研发架构师、1 个圆通工程师 等):
From ff8efebd33eb89eeb997bc8555d527ad4ee449f0 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Sun, 30 Jan 2022 18:46:49 +0800 Subject: [PATCH 0556/1181] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 3a6f0cf6a..eaf7445e4 100644 --- a/README.md +++ b/README.md @@ -245,7 +245,7 @@ https://github.com/Tencent/APIJSON/issues/187 * [腾讯科技有限公司](https://www.tencent.com) * [腾讯音乐娱乐集团](https://www.tencentmusic.com) - * [华能贵成信托有限公司](https://www.hngtrust.com) + * [华能贵诚信托有限公司](https://www.hngtrust.com) * [投投科技](https://www.toutou.com.cn) * [圆通科技](https://www.tencentmusic.com) * [乐拼科技](https://www.lepinyongche.com) From bfe4c9d8a1c0b4c1cef510e9ddb3e273ef473378 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Sun, 30 Jan 2022 18:50:12 +0800 Subject: [PATCH 0557/1181] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index eaf7445e4..f63435b13 100644 --- a/README.md +++ b/README.md @@ -247,7 +247,7 @@ https://github.com/Tencent/APIJSON/issues/187 * [腾讯音乐娱乐集团](https://www.tencentmusic.com) * [华能贵诚信托有限公司](https://www.hngtrust.com) * [投投科技](https://www.toutou.com.cn) - * [圆通科技](https://www.tencentmusic.com) + * [圆通科技](https://www.yto.net.cn) * [乐拼科技](https://www.lepinyongche.com) ### 贡献者们 From 6ef55cb4ca8f01a15f690fa31295fb87e1fd2c33 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Sun, 30 Jan 2022 18:52:21 +0800 Subject: [PATCH 0558/1181] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index f63435b13..d367d6109 100644 --- a/README.md +++ b/README.md @@ -247,7 +247,7 @@ https://github.com/Tencent/APIJSON/issues/187 * [腾讯音乐娱乐集团](https://www.tencentmusic.com) * [华能贵诚信托有限公司](https://www.hngtrust.com) * [投投科技](https://www.toutou.com.cn) - * [圆通科技](https://www.yto.net.cn) + * [圆通速递](https://www.yto.net.cn) * [乐拼科技](https://www.lepinyongche.com) ### 贡献者们 From 2da22e618585f4009145c69965092ab3987004b3 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Sun, 6 Feb 2022 21:08:59 +0800 Subject: [PATCH 0559/1181] =?UTF-8?q?=E6=8F=90=E5=8D=87=E7=89=88=E6=9C=AC?= =?UTF-8?q?=E5=8F=B7=E4=B8=BA=204.9.0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- APIJSONORM/pom.xml | 2 +- APIJSONORM/src/main/java/apijson/Log.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/APIJSONORM/pom.xml b/APIJSONORM/pom.xml index f1fdcd63a..377ecc644 100755 --- a/APIJSONORM/pom.xml +++ b/APIJSONORM/pom.xml @@ -5,7 +5,7 @@ apijson.orm apijson-orm - 4.8.0 + 4.9.0 jar APIJSONORM diff --git a/APIJSONORM/src/main/java/apijson/Log.java b/APIJSONORM/src/main/java/apijson/Log.java index 6dfc96caf..f97fae342 100755 --- a/APIJSONORM/src/main/java/apijson/Log.java +++ b/APIJSONORM/src/main/java/apijson/Log.java @@ -14,7 +14,7 @@ public class Log { public static boolean DEBUG = true; - public static final String VERSION = "4.8.5"; + public static final String VERSION = "4.9.0"; public static final String KEY_SYSTEM_INFO_DIVIDER = "---|-----APIJSON SYSTEM INFO-----|---"; //默认的时间格式 From d6bd9dd4e4725c35acdcddfca5feb7347dd7ef12 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Sun, 6 Feb 2022 21:15:24 +0800 Subject: [PATCH 0560/1181] Update README.md --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index d367d6109..c5f4f9069 100644 --- a/README.md +++ b/README.md @@ -245,6 +245,8 @@ https://github.com/Tencent/APIJSON/issues/187 * [腾讯科技有限公司](https://www.tencent.com) * [腾讯音乐娱乐集团](https://www.tencentmusic.com) + * [深圳市传音通讯有限公司](http://www.transsion.com) + * [社宝信息科技(上海)有限公司](http://shebaochina.com) * [华能贵诚信托有限公司](https://www.hngtrust.com) * [投投科技](https://www.toutou.com.cn) * [圆通速递](https://www.yto.net.cn) From 7531e2e6602a52c51472d0c6722ca7a3bf04c99f Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Sun, 6 Feb 2022 21:18:14 +0800 Subject: [PATCH 0561/1181] Update README.md --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index c5f4f9069..7fea8ebd9 100644 --- a/README.md +++ b/README.md @@ -245,8 +245,8 @@ https://github.com/Tencent/APIJSON/issues/187 * [腾讯科技有限公司](https://www.tencent.com) * [腾讯音乐娱乐集团](https://www.tencentmusic.com) - * [深圳市传音通讯有限公司](http://www.transsion.com) - * [社宝信息科技(上海)有限公司](http://shebaochina.com) + * [深圳市传音通讯有限公司](https://www.transsion.com) + * [社宝信息科技(上海)有限公司](https://shebaochina.com) * [华能贵诚信托有限公司](https://www.hngtrust.com) * [投投科技](https://www.toutou.com.cn) * [圆通速递](https://www.yto.net.cn) From 5b29c96691df01b5a06da51fe58c8d490c9ce439 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Sun, 20 Feb 2022 20:45:08 +0800 Subject: [PATCH 0562/1181] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E5=8A=9F=E8=83=BD?= =?UTF-8?q?=E6=BC=94=E7=A4=BA=E8=AF=B4=E6=98=8E=20GIF=20=E5=9B=BE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.md b/README.md index 7fea8ebd9..2a1d989a5 100644 --- a/README.md +++ b/README.md @@ -91,6 +91,7 @@ APIJSON 是一种专为 API 而生的 JSON 网络传输协议 以及 基于这

![](https://oscimg.oschina.net/oscnet/up-bbbec4fc5edc472be127c02a4f3cd8f4ec2.JPEG) +![](https://raw.githubusercontent.com/TommyLemon/StaticResources/master/APIJSON/APIJSON_query_associate.gif)

@@ -119,6 +120,8 @@ APIJSON 是一种专为 API 而生的 JSON 网络传输协议 以及 基于这

![](https://oscimg.oschina.net/oscnet/up-e21240ef3770326ee6015e052226d0da184.JPEG) +![](https://raw.githubusercontent.com/TommyLemon/StaticResources/master/APIJSON/APIJSON_query_summary.gif) +

From 60f6bbe73f183f08b9b6f02ebd7ea2626f750c61 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Sun, 20 Feb 2022 21:01:28 +0800 Subject: [PATCH 0563/1181] =?UTF-8?q?=E6=96=87=E6=A1=A3=EF=BC=9A=E6=96=B0?= =?UTF-8?q?=E5=A2=9E=E5=8A=9F=E8=83=BD=E6=BC=94=E7=A4=BA=E5=8F=8A=E8=AF=B4?= =?UTF-8?q?=E6=98=8E=E7=9A=84=20GIF=20=E5=9B=BE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Document.md | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/Document.md b/Document.md index 73f262b15..fdcc24238 100644 --- a/Document.md +++ b/Document.md @@ -52,6 +52,8 @@ https://github.com/Tencent/APIJSON } +![](https://raw.githubusercontent.com/TommyLemon/StaticResources/master/APIJSON/APIJSON_query_single.gif) +
#### 获取用户列表 @@ -95,6 +97,8 @@ https://github.com/Tencent/APIJSON } +![](https://raw.githubusercontent.com/TommyLemon/StaticResources/master/APIJSON/APIJSON_query_array.gif) +
#### 获取动态及发布者用户 @@ -134,6 +138,8 @@ https://github.com/Tencent/APIJSON "msg":"success" } + +![](https://raw.githubusercontent.com/TommyLemon/StaticResources/master/APIJSON/APIJSON_query_associate.gif)
@@ -254,6 +260,29 @@ https://github.com/Tencent/APIJSON } +
+ +

+ APIJSON 各种 JOIN:< LEFT, > RIGHT, & INNER 等 +

+ +![](https://raw.githubusercontent.com/TommyLemon/StaticResources/master/APIJSON/APIJSON_query_join.gif) + +
+ +

+ APIJSON 各种子查询:@from@ 数据源, key@ =, key{}@ IN, key<>@ CONTAINS, key}{@ EXISTS 等 +

+ +![](https://raw.githubusercontent.com/TommyLemon/StaticResources/master/APIJSON/APIJSON_query_subquery.gif) + +
+ +

+ APIJSON 部分功能演示集合,由浅入深、由简单到复杂 +

+ +![](https://raw.githubusercontent.com/TommyLemon/StaticResources/master/APIJSON/APIJSON_query_summary.gif)
From 7214c8d66ce48bfd3d48f4dc4dbf2c30213183f8 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Sun, 20 Feb 2022 21:54:31 +0800 Subject: [PATCH 0564/1181] =?UTF-8?q?=E9=80=9A=E7=94=A8=E6=96=87=E6=A1=A3?= =?UTF-8?q?=EF=BC=9A=E5=AE=8C=E5=96=84=E5=8A=9F=E8=83=BD=E6=BC=94=E7=A4=BA?= =?UTF-8?q?=E5=8F=8A=E8=AF=B4=E6=98=8E=E7=9A=84=20GIF=20=E5=9B=BE=E6=A0=87?= =?UTF-8?q?=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Document.md | 26 +++++++++++++++++++------- 1 file changed, 19 insertions(+), 7 deletions(-) diff --git a/Document.md b/Document.md index fdcc24238..9187c4a43 100644 --- a/Document.md +++ b/Document.md @@ -52,6 +52,10 @@ https://github.com/Tencent/APIJSON } +

+ APIJSON 各种单表对象查询:简单查询、统计、分组、排序、聚合、比较、筛选字段、字段别名 等 +

+ ![](https://raw.githubusercontent.com/TommyLemon/StaticResources/master/APIJSON/APIJSON_query_single.gif)
@@ -97,6 +101,10 @@ https://github.com/Tencent/APIJSON } +

+ APIJSON 各种单表数组查询:简单查询、统计、分组、排序、聚合、分页、比较、搜索、正则、条件组合 等 +

+ ![](https://raw.githubusercontent.com/TommyLemon/StaticResources/master/APIJSON/APIJSON_query_array.gif)
@@ -139,8 +147,6 @@ https://github.com/Tencent/APIJSON } -![](https://raw.githubusercontent.com/TommyLemon/StaticResources/master/APIJSON/APIJSON_query_associate.gif) -
#### 获取类似微信朋友圈的动态列表 @@ -260,20 +266,26 @@ https://github.com/Tencent/APIJSON } +

+ APIJSON 各种多表关联查询:一对一、一对多、多对一、各种条件 等 +

+ +![](https://raw.githubusercontent.com/TommyLemon/StaticResources/master/APIJSON/APIJSON_query_associate.gif) +

- APIJSON 各种 JOIN:< LEFT, > RIGHT, & INNER 等 + APIJSON 各种 JOIN:< LEFT JOIN, & INNER JOIN 等

- + ![](https://raw.githubusercontent.com/TommyLemon/StaticResources/master/APIJSON/APIJSON_query_join.gif)

- APIJSON 各种子查询:@from@ 数据源, key@ =, key{}@ IN, key<>@ CONTAINS, key}{@ EXISTS 等 + APIJSON 各种子查询:@from@ FROM, key@ =, key>@ >, key{}@ IN, key}{@ EXISTS 等

- + ![](https://raw.githubusercontent.com/TommyLemon/StaticResources/master/APIJSON/APIJSON_query_subquery.gif)
@@ -281,7 +293,7 @@ https://github.com/Tencent/APIJSON

APIJSON 部分功能演示集合,由浅入深、由简单到复杂

- + ![](https://raw.githubusercontent.com/TommyLemon/StaticResources/master/APIJSON/APIJSON_query_summary.gif)
From cc34a54e27f467af5b82651d16f15fea6c24ec19 Mon Sep 17 00:00:00 2001 From: fanpocha <289484900@qq.com> Date: Tue, 22 Feb 2022 10:34:27 +0800 Subject: [PATCH 0565/1181] Update README.md add caizu --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 2a1d989a5..61a28a7cf 100644 --- a/README.md +++ b/README.md @@ -244,6 +244,7 @@ https://github.com/Tencent/APIJSON/issues/187 + 珠海采筑电子商务有限公司
* [腾讯科技有限公司](https://www.tencent.com) From 24e5c0b264fbd858bb4af9ab7ece71e548d225cf Mon Sep 17 00:00:00 2001 From: fanpocha <289484900@qq.com> Date: Tue, 22 Feb 2022 10:37:11 +0800 Subject: [PATCH 0566/1181] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 61a28a7cf..a7057b70a 100644 --- a/README.md +++ b/README.md @@ -244,7 +244,7 @@ https://github.com/Tencent/APIJSON/issues/187 - 珠海采筑电子商务有限公司 +
* [腾讯科技有限公司](https://www.tencent.com) From b2059445ab16e94d5a39227c29444fba67a76c06 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Tue, 22 Feb 2022 23:43:51 +0800 Subject: [PATCH 0567/1181] =?UTF-8?q?=E4=BD=BF=E7=94=A8=E7=99=BB=E8=AE=B0?= =?UTF-8?q?=E6=96=B0=E5=A2=9E=20=E7=8F=A0=E6=B5=B7=E9=87=87=E7=AD=91?= =?UTF-8?q?=E7=94=B5=E5=AD=90=E5=95=86=E5=8A=A1=E6=9C=89=E9=99=90=E5=85=AC?= =?UTF-8?q?=E5=8F=B8=EF=BC=8C=E6=96=B0=E5=A2=9E=20=E4=B9=90=E6=8B=BC?= =?UTF-8?q?=E7=94=A8=E8=BD=A6=20=E7=9A=84=20Logo?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit https://github.com/Tencent/APIJSON#%E4%BD%BF%E7%94%A8%E7%99%BB%E8%AE%B0 --- README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index a7057b70a..22390da85 100644 --- a/README.md +++ b/README.md @@ -226,7 +226,7 @@ https://github.com/Tencent/APIJSON/issues/36 如果您在使用 APIJSON,请让我们知道,您的使用对我们非常重要(按登记顺序排列):
https://github.com/Tencent/APIJSON/issues/187
- +
@@ -244,6 +244,7 @@ https://github.com/Tencent/APIJSON/issues/187 +
@@ -255,6 +256,7 @@ https://github.com/Tencent/APIJSON/issues/187 * [投投科技](https://www.toutou.com.cn) * [圆通速递](https://www.yto.net.cn) * [乐拼科技](https://www.lepinyongche.com) + * [珠海采筑电子商务有限公司](https://www.aupup.com) ### 贡献者们 主项目 APIJSON 的贡献者们(6 个腾讯工程师、1 个知乎基础研发架构师、1 个圆通工程师 等):
From dda1120c5d0e16344d4ac905ed79ea7abf947537 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Sun, 27 Feb 2022 02:31:16 +0800 Subject: [PATCH 0568/1181] =?UTF-8?q?JOIN=20=E6=94=AF=E6=8C=81=E5=A4=9A?= =?UTF-8?q?=E4=B8=AA=E5=AD=97=E6=AE=B5=E5=85=B3=E8=81=94=E5=8F=8A=E5=BC=95?= =?UTF-8?q?=E7=94=A8=E8=B5=8B=E5=80=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../main/java/apijson/orm/AbstractParser.java | 350 ++++++++++-------- .../java/apijson/orm/AbstractSQLConfig.java | 62 ++-- .../java/apijson/orm/AbstractSQLExecutor.java | 48 ++- .../src/main/java/apijson/orm/Entry.java | 7 +- .../src/main/java/apijson/orm/Join.java | 204 +++++----- 5 files changed, 384 insertions(+), 287 deletions(-) diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java b/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java index 529f4025f..3e3703468 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java @@ -18,6 +18,7 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; +import java.util.LinkedHashMap; import java.util.LinkedHashSet; import java.util.List; import java.util.Map; @@ -1307,8 +1308,25 @@ else if (childKeys.length == 1 && JSONRequest.isTableKey(childKeys[0])) { // return response; } - /**多表同时筛选 - * @param join "&/User/id@, JOIN_COPY_KEY_LIST; + static { // TODO 不全 + JOIN_COPY_KEY_LIST = new ArrayList(); + JOIN_COPY_KEY_LIST.add(JSONRequest.KEY_ROLE); + JOIN_COPY_KEY_LIST.add(JSONRequest.KEY_DATABASE); + JOIN_COPY_KEY_LIST.add(JSONRequest.KEY_SCHEMA); + JOIN_COPY_KEY_LIST.add(JSONRequest.KEY_DATASOURCE); + JOIN_COPY_KEY_LIST.add(JSONRequest.KEY_COLUMN); + JOIN_COPY_KEY_LIST.add(JSONRequest.KEY_COMBINE); + JOIN_COPY_KEY_LIST.add(JSONRequest.KEY_GROUP); + JOIN_COPY_KEY_LIST.add(JSONRequest.KEY_HAVING); + JOIN_COPY_KEY_LIST.add(JSONRequest.KEY_ORDER); + JOIN_COPY_KEY_LIST.add(JSONRequest.KEY_RAW); + } + + /**JOIN 多表同时筛选 + * @param join "&/User, joinList = new ArrayList<>(); - - JSONObject tableObj; - String targetPath; - - JSONObject targetObj; - String targetTable; - String targetKey; - - String path; - - // List onList = new ArrayList<>(); - for (Entry e : set) {//User/id@ - if (e.getValue() instanceof JSONObject == false) { + for (Entry e : set) { // { &/User:{}, ( ) <> () * // if (StringUtil.isEmpty(joinType, true)) { @@ -1373,192 +1380,223 @@ else if (join != null){ path = path.substring(index + 1); index = path.indexOf("/"); - String tableKey = index < 0 ? null : path.substring(0, index); //User:owner + String tableKey = index < 0 ? path : path.substring(0, index); // User:owner apijson.orm.Entry entry = Pair.parseEntry(tableKey, true); - String table = entry.getKey(); //User + String table = entry.getKey(); // User if (StringUtil.isName(table) == false) { throw new IllegalArgumentException(JSONRequest.KEY_JOIN + ":value 中 value 的 Table 值 " + table + " 不合法!" - + "必须为 &/Table0/key0,> tableSet = tableObj.entrySet(); + // 取出所有 join 条件 + JSONObject requestObj = new JSONObject(true); // (JSONObject) obj.clone(); + + boolean matchSingle = false; + for (Entry tableEntry : tableSet) { + String k = tableEntry.getKey(); + Object v = k == null ? null : tableEntry.getValue(); + if (v == null) { + continue; + } - // 主表不允许别名 - // apijson.orm.Entry targetEntry = Pair.parseEntry(targetTableKey, true); - // targetTable = targetEntry.getKey(); //User - // if (StringUtil.isName(targetTable) == false) { - // throw new IllegalArgumentException("/" + path + ":'/targetTable/targetKey' 中 targetTable 值 " + targetTable + " 不合法!必须满足大写字母开头的表对象英文单词 key 格式!"); - // } - // - // String targetAlias = targetEntry.getValue(); //owner - // if (StringUtil.isNotEmpty(targetAlias, true) && StringUtil.isName(targetAlias) == false) { - // throw new IllegalArgumentException("/" + path + ":'/targetTable:targetAlias/targetKey' 中 targetAlias 值 " + targetAlias + " 不合法!必须满足英文单词变量名格式!"); - // } + matchSingle = matchSingle == false && k.equals(key); + if (matchSingle) { + continue; + } - targetTable = targetTableKey; // 主表不允许别名 - if (StringUtil.isName(targetTable) == false) { - throw new IllegalArgumentException("/" + path + ":'/targetTable/targetKey' 中 targetTable 值 " + targetTable + " 不合法!必须满足大写字母开头的表对象英文单词 key 格式!"); - } + if (k.length() > 1 && k.indexOf("@") == k.length() - 1 && v instanceof String) { + String sv = (String) v; + int ind = sv.endsWith("@") ? -1 : sv.indexOf("/"); + if (ind == 0 && key == null) { // 指定了某个就只允许一个 ON 条件 + String p = sv.substring(1); + int ind2 = p.indexOf("/"); + String tk = ind2 < 0 ? null : p.substring(0, ind2); - //对引用的JSONObject添加条件 - try { - targetObj = request.getJSONObject(targetTableKey); - } - catch (Exception e2) { - throw new IllegalArgumentException("/" + path + ":'/targetTable/targetKey' 中路径对应的 '" + targetTableKey + "':value 中 value 类型不合法!必须是 {} 这种 JSONObject 格式!" + e2.getMessage()); - } + apijson.orm.Entry te = tk == null || p.substring(ind2 + 1).indexOf("/") >= 0 ? null : Pair.parseEntry(tk, true); - if (targetObj == null) { - throw new IllegalArgumentException("/" + path + ":'/targetTable/targetKey' 中路径对应的对象 '" + targetTableKey + "':{} 不存在或值为 null !必须是 {} 这种 JSONObject 格式!"); - } + if (te != null && JSONRequest.isTableKey(te.getKey()) && request.get(tk) instanceof JSONObject) { + refObj.put(k, v); + continue; + } + } - // 保证和 SQLExcecutor 缓存的 Config 里 where 顺序一致,生成的 SQL 也就一致 <<<<<<<<< - // AbstractSQLConfig.newSQLConfig 中强制把 id, id{}, userId, userId{} 放到了最前面 tableObj.put(key, tableObj.remove(key)); + Object rv = getValueByPath(sv); + if (rv != null && rv.equals(sv) == false) { + requestObj.put(k.substring(0, k.length() - 1), rv); + continue; + } - if (tableObj.size() > 1) { // 把 key 强制放最前,AbstractSQLExcecutor 中 config.putWhere 也是放尽可能最前 - JSONObject newTableObj = new JSONObject(tableObj.size(), true); - newTableObj.put(key, tableObj.remove(key)); - newTableObj.putAll(tableObj); + throw new UnsupportedOperationException(table + "/" + k + " 不合法!" + JSONRequest.KEY_JOIN + " 关联的 Table 中," + + "join: ?/Table/key 时只能有 1 个 key@:value;join: ?/Table 时所有 key@:value 要么是符合 join 格式,要么能直接解析成具体值!"); // TODO 支持 join on + } - tableObj = newTableObj; - request.put(tableKey, tableObj); + if (k.startsWith("@")) { + if (JOIN_COPY_KEY_LIST.contains(k)) { + requestObj.put(k, v); // 保留 + } + } + else { + if (k.endsWith("@")) { + throw new UnsupportedOperationException(table + "/" + k + " 不合法!" + JSONRequest.KEY_JOIN + " 关联的 Table 中," + + "join: ?/Table/key 时只能有 1 个 key@:value;join: ?/Table 时所有 key@:value 要么是符合 join 格式,要么能直接解析成具体值!"); // TODO 支持 join on + } -// tableObj.clear(); -// tableObj.putAll(newTableObj); + if (k.contains("()") == false) { // 不需要远程函数 + requestObj.put(k, v); // 保留 + } + } + } + + Set> refSet = refObj.entrySet(); + if (refSet.isEmpty()) { + throw new IllegalArgumentException(JSONRequest.KEY_JOIN + ":value 中 value 的 alias 值 " + alias + " 不合法!" + + "必须为 &/Table0,>>>>>>>> Join j = new Join(); - j.setPath(path); - j.setOriginKey(key); - j.setOriginValue(targetPath); + j.setPath(e.getKey()); j.setJoinType(joinType); j.setTable(table); j.setAlias(alias); - j.setTargetTable(targetTable); - // j.setTargetAlias(targetAlias); - j.setTargetKey(targetKey); - j.setKeyAndType(key); - j.setRequest(getJoinObject(table, tableObj, key)); - j.setOuter((JSONObject) e.getValue()); - - if (StringUtil.isName(j.getKey()) == false) { - throw new IllegalArgumentException(JSONRequest.KEY_JOIN + ":value 中 value 的 key@ 中 key 值 " + j.getKey() + " 不合法!必须满足英文单词变量名格式!"); - } + j.setOuter((JSONObject) outer); + j.setRequest(requestObj); - joinList.add(j); - - // onList.add(table + "." + key + " = " + targetTable + "." + targetKey); // ON User.id = Moment.userId + List onList = new ArrayList<>(); + for (Entry refEntry : refSet) { + String originKey = refEntry.getKey(); - } - - - //拼接多个 SQLConfig 的SQL语句,然后执行,再把结果分别缓存(Moment, User等)到 SQLExecutor 的 cacheMap - // AbstractSQLConfig config0 = null; - // String sql = "SELECT " + config0.getColumnString() + " FROM " + config0.getTable() + " INNER JOIN " + targetTable + " ON " - // + onList.get(0) + config0.getGroupString() + config0.getHavingString() + config0.getOrderString(); - - - return joinList; - } + String targetPath = (String) refEntry.getValue(); + if (StringUtil.isEmpty(targetPath, true)) { + throw new IllegalArgumentException(e.getKey() + ":value 中 value 值 " + targetPath + " 不合法!必须为引用赋值的路径 '/targetTable/targetKey' !"); + } + // 取出引用赋值路径 targetPath 对应的 Table 和 key + index = targetPath.lastIndexOf("/"); + String targetKey = index < 0 ? null : targetPath.substring(index + 1); + if (StringUtil.isName(targetKey) == false) { + throw new IllegalArgumentException(e.getKey() + ":'/targetTable/targetKey' 中 targetKey 值 " + targetKey + " 不合法!必须满足英文单词变量名格式!"); + } + targetPath = targetPath.substring(0, index); + index = targetPath.lastIndexOf("/"); + String targetTableKey = index < 0 ? targetPath : targetPath.substring(index + 1); - private static final List JOIN_COPY_KEY_LIST; - static { // TODO 不全 - JOIN_COPY_KEY_LIST = new ArrayList(); - JOIN_COPY_KEY_LIST.add(JSONRequest.KEY_ROLE); - JOIN_COPY_KEY_LIST.add(JSONRequest.KEY_DATABASE); - JOIN_COPY_KEY_LIST.add(JSONRequest.KEY_SCHEMA); - JOIN_COPY_KEY_LIST.add(JSONRequest.KEY_DATASOURCE); - JOIN_COPY_KEY_LIST.add(JSONRequest.KEY_COLUMN); - JOIN_COPY_KEY_LIST.add(JSONRequest.KEY_COMBINE); - JOIN_COPY_KEY_LIST.add(JSONRequest.KEY_GROUP); - JOIN_COPY_KEY_LIST.add(JSONRequest.KEY_HAVING); - JOIN_COPY_KEY_LIST.add(JSONRequest.KEY_ORDER); - JOIN_COPY_KEY_LIST.add(JSONRequest.KEY_RAW); - } + // 主表允许别名 + apijson.orm.Entry targetEntry = Pair.parseEntry(targetTableKey, true); + String targetTable = targetEntry.getKey(); //User + if (StringUtil.isName(targetTable) == false) { + throw new IllegalArgumentException(e.getKey() + ":'/targetTable/targetKey' 中 targetTable 值 " + targetTable + " 不合法!必须满足大写字母开头的表对象英文单词 key 格式!"); + } - /**取指定 JSON 对象的 id 集合 - * @param table - * @param key - * @param obj - * @return null ? 全部 : 有限的数组 - */ - private JSONObject getJoinObject(String table, JSONObject obj, String key) { - if (obj == null || obj.isEmpty()) { - Log.e(TAG, "getIdList obj == null || obj.isEmpty() >> return null;"); - return null; - } - if (StringUtil.isEmpty(key, true)) { - Log.e(TAG, "getIdList StringUtil.isEmpty(key, true) >> return null;"); - return null; - } + String targetAlias = targetEntry.getValue(); //owner + if (StringUtil.isNotEmpty(targetAlias, true) && StringUtil.isName(targetAlias) == false) { + throw new IllegalArgumentException(e.getKey() + ":'/targetTable:targetAlias/targetKey' 中 targetAlias 值 " + targetAlias + " 不合法!必须满足英文单词变量名格式!"); + } - // 取出所有 join 条件 - JSONObject requestObj = new JSONObject(true); // (JSONObject) obj.clone(); - Set set = new LinkedHashSet<>(obj.keySet()); - for (String k : set) { - if (StringUtil.isEmpty(k, true)) { - continue; - } + targetTable = targetTableKey; // 主表允许别名 + if (StringUtil.isName(targetTable) == false) { + throw new IllegalArgumentException(e.getKey() + ":'/targetTable/targetKey' 中 targetTable 值 " + targetTable + " 不合法!必须满足大写字母开头的表对象英文单词 key 格式!"); + } - if (k.startsWith("@")) { - if (JOIN_COPY_KEY_LIST.contains(k)) { - requestObj.put(k, obj.get(k)); //保留 + //对引用的JSONObject添加条件 + JSONObject targetObj; + try { + targetObj = request.getJSONObject(targetTableKey); } - } - else { - if (k.endsWith("@")) { - if (k.equals(key)) { - continue; - } - throw new UnsupportedOperationException(table + "." + k + " 不合法!" + JSONRequest.KEY_JOIN - + " 关联的Table中只能有1个 key@:value !"); // TODO 支持 join on + catch (Exception e2) { + throw new IllegalArgumentException(e.getKey() + ":'/targetTable/targetKey' 中路径对应的 '" + targetTableKey + "':value 中 value 类型不合法!必须是 {} 这种 JSONObject 格式!" + e2.getMessage()); } - if (k.contains("()") == false) { //不需要远程函数 - // requestObj.put(k, obj.remove(k)); //remove是为了避免重复查询副表 - requestObj.put(k, obj.get(k)); //remove是为了避免重复查询副表 + if (targetObj == null) { + throw new IllegalArgumentException(e.getKey() + ":'/targetTable/targetKey' 中路径对应的对象 '" + targetTableKey + "':{} 不存在或值为 null !必须是 {} 这种 JSONObject 格式!"); } + + Join.On on = new Join.On(); + on.setKeyAndType(j.getJoinType(), j.getTable(), originKey); + if (StringUtil.isName(on.getKey()) == false) { + throw new IllegalArgumentException(JSONRequest.KEY_JOIN + ":value 中 value 的 key@ 中 key 值 " + on.getKey() + " 不合法!必须满足英文单词变量名格式!"); + } + + on.setOriginKey(originKey); + on.setOriginValue((String) refEntry.getValue()); + on.setTargetTable(targetTable); + on.setTargetAlias(targetAlias); + on.setTargetKey(targetKey); + + onList.add(on); } + + j.setOnList(onList); + + joinList.add(j); + // onList.add(table + "." + key + " = " + targetTable + "." + targetKey); // ON User.id = Moment.userId + + // 保证和 SQLExcecutor 缓存的 Config 里 where 顺序一致,生成的 SQL 也就一致 <<<<<<<<< + // AbstractSQLConfig.newSQLConfig 中强制把 id, id{}, userId, userId{} 放到了最前面 tableObj.put(key, tableObj.remove(key)); + + if (refObj.size() != tableObj.size()) { // 把 key 强制放最前,AbstractSQLExcecutor 中 config.putWhere 也是放尽可能最前 + refObj.putAll(tableObj); + request.put(tableKey, refObj); + +// tableObj.clear(); +// tableObj.putAll(refObj); + } + // 保证和 SQLExcecutor 缓存的 Config 里 where 顺序一致,生成的 SQL 也就一致 >>>>>>>>> } + //拼接多个 SQLConfig 的SQL语句,然后执行,再把结果分别缓存(Moment, User等)到 SQLExecutor 的 cacheMap + // AbstractSQLConfig config0 = null; + // String sql = "SELECT " + config0.getColumnString() + " FROM " + config0.getTable() + " INNER JOIN " + targetTable + " ON " + // + onList.get(0) + config0.getGroupString() + config0.getHavingString() + config0.getOrderString(); - return requestObj; + return joinList; } + + + @Override public int getDefaultQueryCount() { return DEFAULT_QUERY_COUNT; diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java index c28d07ad1..eb78e1613 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java @@ -56,6 +56,7 @@ import apijson.RequestMethod; import apijson.SQL; import apijson.StringUtil; +import apijson.orm.Join.On; import apijson.orm.exception.NotExistException; import apijson.orm.model.Access; import apijson.orm.model.Column; @@ -83,7 +84,6 @@ public abstract class AbstractSQLConfig implements SQLConfig { // * 和 / 不能同时出现,防止 /* */ 段注释! # 和 -- 不能出现,防止行注释! ; 不能出现,防止隔断SQL语句!空格不能出现,防止 CRUD,DROP,SHOW TABLES等语句! private static final Pattern PATTERN_RANGE; private static final Pattern PATTERN_FUNCTION; - private static final Pattern PATTERN_STRING; /** * 表名映射,隐藏真实表名,对安全要求很高的表可以这么做 @@ -97,10 +97,9 @@ public abstract class AbstractSQLConfig implements SQLConfig { // 允许调用的 SQL 函数:当 substring 为 null 时忽略;当 substring 为 "" 时整个 value 是 raw SQL;其它情况则只是 substring 这段为 raw SQL public static final Map SQL_FUNCTION_MAP; - static { // 凡是 SQL 边界符、分隔符、注释符 都不允许,例如 ' " ` ( ) ; # -- ,以免拼接 SQL 时被注入意外可执行指令 + static { // 凡是 SQL 边界符、分隔符、注释符 都不允许,例如 ' " ` ( ) ; # -- /**/ ,以免拼接 SQL 时被注入意外可执行指令 PATTERN_RANGE = Pattern.compile("^[0-9%,!=\\<\\>/\\.\\+\\-\\*\\^]+$"); // ^[a-zA-Z0-9_*%!=<>(),"]+$ 导致 exists(select*from(Comment)) 通过! PATTERN_FUNCTION = Pattern.compile("^[A-Za-z0-9%,:_@&~`!=\\<\\>\\|\\[\\]\\{\\} /\\.\\+\\-\\*\\^\\?\\(\\)\\$]+$"); //TODO 改成更好的正则,校验前面为单词,中间为操作符,后面为值 - PATTERN_STRING = Pattern.compile("^[,#;\"`]+$"); TABLE_KEY_MAP = new HashMap(); TABLE_KEY_MAP.put(Table.class.getSimpleName(), Table.TABLE_NAME); @@ -3378,10 +3377,6 @@ public String getJoinString() throws Exception { List pvl = new ArrayList<>(); boolean changed = false; - String sql = null; - SQLConfig jc; - String jt; - String tt; // 主表不用别名 String ta; for (Join j : joinList) { if (j.isAppJoin()) { // APP JOIN,只是作为一个标记,执行完主表的查询后自动执行副表的查询 User.id IN($commentIdList) @@ -3391,18 +3386,20 @@ public String getJoinString() throws Exception { //LEFT JOIN sys.apijson_user AS User ON User.id = Moment.userId, 都是用 = ,通过relateType处理缓存 // <"INNER JOIN User ON User.id = Moment.userId", UserConfig> TODO AS 放 getSQLTable 内 - jc = j.getJoinConfig(); + SQLConfig jc = j.getJoinConfig(); jc.setPrepared(isPrepared()); - jt = StringUtil.isEmpty(jc.getAlias(), true) ? jc.getTable() : jc.getAlias(); - tt = j.getTargetTable(); + String jt = StringUtil.isEmpty(jc.getAlias(), true) ? jc.getTable() : jc.getAlias(); + List onList = j.getOnList(); //如果要强制小写,则可在子类重写这个方法再 toLowerCase // if (DATABASE_POSTGRESQL.equals(getDatabase())) { // jt = jt.toLowerCase(); // tn = tn.toLowerCase(); // } - + + String sql; + switch (type) { //前面已跳过 case "@": // APP JOIN // continue; @@ -3413,9 +3410,17 @@ public String getJoinString() throws Exception { case ">": // RIGHT JOIN jc.setMain(true).setKeyPrefix(false); sql = ( "<".equals(type) ? " LEFT" : (">".equals(type) ? " RIGHT" : " CROSS") ) - + " JOIN ( " + jc.getSQL(isPrepared()) + " ) AS " - + quote + jt + quote + " ON " + quote + jt + quote + "." + quote + j.getKey() + quote + " = " - + quote + tt + quote + "." + quote + j.getTargetKey() + quote; + + " JOIN ( " + jc.getSQL(isPrepared()) + " ) AS " + quote + jt + quote; + + if (onList != null) { + boolean first = true; + for (On on : onList) { + sql += (first ? " ON " : " AND ") + quote + jt + quote + "." + quote + on.getKey() + quote + " = " + + quote + on.getTargetTable() + quote + "." + quote + on.getTargetKey() + quote; + first = false; + } + } + jc.setMain(false).setKeyPrefix(true); pvl.addAll(jc.getPreparedValueList()); @@ -3429,8 +3434,15 @@ public String getJoinString() throws Exception { case "^": // SIDE JOIN: ! (A & B) case "(": // ANTI JOIN: A & ! B case ")": // FOREIGN JOIN: B & ! A - sql = " INNER JOIN " + jc.getTablePath() - + " ON " + quote + jt + quote + "." + quote + j.getKey() + quote + " = " + quote + tt + quote + "." + quote + j.getTargetKey() + quote; + sql = " INNER JOIN " + jc.getTablePath(); + if (onList != null) { + boolean first = true; + for (On on : onList) { + sql += (first ? " ON " : " AND ") + quote + jt + quote + "." + quote + on.getKey() + quote + " = " + + quote + on.getTargetTable() + quote + "." + quote + on.getTargetKey() + quote; + first = false; + } + } break; default: throw new UnsupportedOperationException( @@ -3451,7 +3463,7 @@ public String getJoinString() throws Exception { } - return joinOns; + return StringUtil.isEmpty(joinOns, true) ? "" : joinOns + " \n"; } protected void onGetCrossJoinString(Join j) throws UnsupportedOperationException { @@ -3924,12 +3936,20 @@ else if (joinConfig.getDatabase().equals(config.getDatabase()) == false) { /* SELECT count(*) AS count FROM sys.Moment AS Moment LEFT JOIN ( SELECT count(*) AS count FROM sys.Comment ) AS Comment ON Comment.momentId = Moment.id LIMIT 1 OFFSET 0 */ if (RequestMethod.isHeadMethod(method, true)) { - joinConfig.setMethod(GET); //子查询不能为 SELECT count(*) ,而应该是 SELECT momentId - joinConfig.setColumn(Arrays.asList(j.getKey())); //优化性能,不取非必要的字段 + List onList = j.getOnList(); + List column = onList == null ? null : new ArrayList<>(onList.size()); + if (column != null) { + for (On on : onList) { + column.add(on.getKey()); + } + } + + joinConfig.setMethod(GET); // 子查询不能为 SELECT count(*) ,而应该是 SELECT momentId + joinConfig.setColumn(column); // 优化性能,不取非必要的字段 if (cacheConfig != null) { - cacheConfig.setMethod(GET); //子查询不能为 SELECT count(*) ,而应该是 SELECT momentId - cacheConfig.setColumn(Arrays.asList(j.getKey())); //优化性能,不取非必要的字段 + cacheConfig.setMethod(GET); // 子查询不能为 SELECT count(*) ,而应该是 SELECT momentId + cacheConfig.setColumn(column); // 优化性能,不取非必要的字段 } } diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java index 85a2fc146..51705dca4 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java @@ -23,6 +23,7 @@ import java.util.Arrays; import java.util.Collection; import java.util.HashMap; +import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Map.Entry; @@ -37,6 +38,7 @@ import apijson.NotNull; import apijson.RequestMethod; import apijson.StringUtil; +import apijson.orm.Join.On; /**executor for query(read) or update(write) MySQL database * @author Lemon @@ -531,7 +533,14 @@ else if (config.isClickHouse() && (sqlTable.startsWith("`") || sqlTable.startsWi if (curJoin != prevJoin) { // 前后字段不在同一个表对象,即便后面出现 null,也不该是主表数据,而是逻辑 bug 导致 SQLConfig viceConfig = curJoin != null && curJoin.isSQLJoin() ? curJoin.getCacheConfig() : null; if (viceConfig != null) { //FIXME 只有和主表关联才能用 item,否则应该从 childMap 查其它副表数据 - viceConfig.putWhere(curJoin.getKey(), item.get(curJoin.getTargetKey()), true); + List onList = curJoin.getOnList(); + if (onList != null) { + for (On on : onList) { + if (on != null) { + viceConfig.putWhere(on.getKey(), item.get(on.getTargetKey()), true); + } + } + } } String viceSql = viceConfig == null ? null : viceConfig.getSQL(false); //TODO 在 SQLConfig 缓存 SQL,减少大量的重复生成 @@ -660,25 +669,27 @@ protected void executeAppJoin(SQLConfig config, List resultList, Map jc = join.getJoinConfig(); - //取出 "id@": "@/User/userId" 中所有 userId 的值 - List targetValueList = new ArrayList<>(); - JSONObject mainTable; - Object targetValue; + List onList = join.getOnList(); + if (onList != null) { + for (On on : onList) { + //取出 "id@": "@/User/userId" 中所有 userId 的值 + List targetValueList = new ArrayList<>(); - for (int i = 0; i < resultList.size(); i++) { - mainTable = resultList.get(i); - targetValue = mainTable == null ? null : mainTable.get(join.getTargetKey()); + for (int i = 0; i < resultList.size(); i++) { + JSONObject mainTable = resultList.get(i); + Object targetValue = mainTable == null ? null : mainTable.get(on.getTargetKey()); + + if (targetValue != null && targetValueList.contains(targetValue) == false) { + targetValueList.add(targetValue); + } + } - if (targetValue != null && targetValueList.contains(targetValue) == false) { - targetValueList.add(targetValue); + //替换为 "id{}": [userId1, userId2, userId3...] + jc.putWhere(on.getOriginKey(), null, false); // remove orginKey + jc.putWhere(on.getKey() + "{}", targetValueList, true); // add orginKey{} } } - - //替换为 "id{}": [userId1, userId2, userId3...] - jc.putWhere(join.getOriginKey(), null, false); // remove orginKey - jc.putWhere(join.getKey() + "{}", targetValueList, true); // add orginKey{} - jc.setMain(true).setPreparedValueList(new ArrayList<>()); boolean prepared = jc.isPrepared(); @@ -721,7 +732,6 @@ protected void executeAppJoin(SQLConfig config, List resultList, Map result = new JSONObject(true); for (int i = 1; i <= length; i++) { - result = onPutColumn(jc, rs, rsmd, index, result, i, null, null); } @@ -731,7 +741,11 @@ protected void executeAppJoin(SQLConfig config, List resultList, Map + "\n >>>>>>>>>>>>>>>>>>>>>>>>>>> \n\n"); //缓存到 childMap - cc.putWhere(join.getKey(), result.get(join.getKey()), true); + if (onList != null) { + for (On on : onList) { + cc.putWhere(on.getKey(), result.get(on.getKey()), true); + } + } cacheSql = cc.getSQL(false); childMap.put(cacheSql, result); diff --git a/APIJSONORM/src/main/java/apijson/orm/Entry.java b/APIJSONORM/src/main/java/apijson/orm/Entry.java index 93e9f0418..a8aaf7bfd 100755 --- a/APIJSONORM/src/main/java/apijson/orm/Entry.java +++ b/APIJSONORM/src/main/java/apijson/orm/Entry.java @@ -5,6 +5,8 @@ package apijson.orm; +import java.util.Map; + /**自定义Entry * *java.util.Map.Entry是interface,new Entry(...)不好用,其它的Entry也不好用 * @author Lemon @@ -13,7 +15,7 @@ * @use new Entry(...) * @warn K,V都需要基本类型时不建议使用,判空麻烦,不如新建一个Model */ -public class Entry { +public class Entry implements Map.Entry { public K key; public V value; @@ -39,8 +41,9 @@ public void setKey(K key) { public V getValue() { return value; } - public void setValue(V value) { + public V setValue(V value) { this.value = value; + return value; } public boolean isEmpty() { diff --git a/APIJSONORM/src/main/java/apijson/orm/Join.java b/APIJSONORM/src/main/java/apijson/orm/Join.java index 7d8707393..24ad36ec8 100644 --- a/APIJSONORM/src/main/java/apijson/orm/Join.java +++ b/APIJSONORM/src/main/java/apijson/orm/Join.java @@ -5,6 +5,8 @@ package apijson.orm; +import java.util.List; + import com.alibaba.fastjson.JSONObject; import apijson.NotNull; @@ -13,27 +15,21 @@ * @author Lemon */ public class Join { - + private String path; - private String originKey; - private String originValue; - - private String joinType; // "@" - APP, "<" - LEFT, ">" - RIGHT, "*" - CROSS, "&" - INNER, "|" - FULL, "!" - OUTER, "^" - SIDE, "(" - ANTI, ")" - FOREIGN - private String relateType; // "" - 一对一, "{}" - 一对多, "<>" - 多对一 - private JSONObject request; // { "id@":"/Moment/userId" } - private String table; //User - private String alias; //owner - private String key; //id - private String targetTable; // Moment - private String targetAlias; //main - private String targetKey; // userId - - private JSONObject outter; + private String joinType; // "@" - APP, "<" - LEFT, ">" - RIGHT, "*" - CROSS, "&" - INNER, "|" - FULL, "!" - OUTER, "^" - SIDE, "(" - ANTI, ")" - FOREIGN + private String table; // User + private String alias; // owner + private List onList; // ON User.id = Moment.userId AND ... + + private JSONObject request; // { "id@":"/Moment/userId" } + private JSONObject outer; // "join": { " getOnList() { + return onList; + } + public void setOnList(List onList) { + this.onList = onList; + } + public JSONObject getRequest() { return request; } public void setRequest(JSONObject request) { this.request = request; } - public String getKey() { - return key; - } - public void setKey(String key) { - this.key = key; - } - public void setTargetTable(String targetTable) { - this.targetTable = targetTable; - } - public String getTargetTable() { - return targetTable; - } - public void setTargetAlias(String targetAlias) { - this.targetAlias = targetAlias; - } - public String getTargetAlias() { - return targetAlias; - } - public String getTargetKey() { - return targetKey; - } - public void setTargetKey(String targetKey) { - this.targetKey = targetKey; - } - public JSONObject getOuter() { - return outter; + return outer; } - public void setOuter(JSONObject outter) { - this.outter = outter; + public void setOuter(JSONObject outer) { + this.outer = outer; } public SQLConfig getJoinConfig() { @@ -130,35 +89,11 @@ public SQLConfig getCacheConfig() { public void setCacheConfig(SQLConfig cacheConfig) { this.cacheConfig = cacheConfig; } - public SQLConfig getOuterConfig() { - return outterConfig; + return outerConfig; } - public void setOuterConfig(SQLConfig outterConfig) { - this.outterConfig = outterConfig; - } - - - public void setKeyAndType(@NotNull String originKey) throws Exception { //id, id@, id{}@, contactIdList<>@ ... - if (originKey.endsWith("@")) { - originKey = originKey.substring(0, originKey.length() - 1); - } - else { //TODO 暂时只允许 User.id = Moment.userId 字段关联,不允许 User.id = 82001 这种 - throw new IllegalArgumentException(joinType + "/.../" + table + "/" + originKey + " 不合法!join:'.../refKey'" + " 中 refKey 必须以 @ 结尾!"); - } - - if (originKey.endsWith("{}")) { - setRelateType("{}"); - setKey(originKey.substring(0, originKey.length() - 2)); - } - else if (originKey.endsWith("<>")) { - setRelateType("<>"); - setKey(originKey.substring(0, originKey.length() - 2)); - } - else { - setRelateType(""); - setKey(originKey); - } + public void setOuterConfig(SQLConfig outerConfig) { + this.outerConfig = outerConfig; } @@ -221,5 +156,92 @@ public static boolean isLeftOrRightJoin(Join j) { return j != null && j.isLeftOrRightJoin(); } + + + public static class On { + + private String originKey; + private String originValue; + + private String relateType; // "" - 一对一, "{}" - 一对多, "<>" - 多对一 + private String key; // id + private String targetTable; // Moment + private String targetAlias; // main + private String targetKey; // userId + + public String getOriginKey() { + return originKey; + } + public void setOriginKey(String originKey) { + this.originKey = originKey; + } + public String getOriginValue() { + return originValue; + } + public void setOriginValue(String originValue) { + this.originValue = originValue; + } + + + public String getRelateType() { + return relateType; + } + public void setRelateType(String relateType) { + this.relateType = relateType; + } + + + public String getKey() { + return key; + } + public void setKey(String key) { + this.key = key; + } + public void setTargetTable(String targetTable) { + this.targetTable = targetTable; + } + public String getTargetTable() { + return targetTable; + } + public void setTargetAlias(String targetAlias) { + this.targetAlias = targetAlias; + } + public String getTargetAlias() { + return targetAlias; + } + public String getTargetKey() { + return targetKey; + } + public void setTargetKey(String targetKey) { + this.targetKey = targetKey; + } + + + public void setKeyAndType(String joinType, String table, @NotNull String originKey) throws Exception { //id, id@, id{}@, contactIdList<>@ ... + if (originKey.endsWith("@")) { + originKey = originKey.substring(0, originKey.length() - 1); + } + else { //TODO 暂时只允许 User.id = Moment.userId 字段关联,不允许 User.id = 82001 这种 + throw new IllegalArgumentException(joinType + "/.../" + table + "/" + originKey + " 不合法!join:'.../refKey'" + " 中 refKey 必须以 @ 结尾!"); + } + + if (originKey.endsWith("{}")) { + setRelateType("{}"); + setKey(originKey.substring(0, originKey.length() - 2)); + } + else if (originKey.endsWith("<>")) { + setRelateType("<>"); + setKey(originKey.substring(0, originKey.length() - 2)); + } + else { + setRelateType(""); + setKey(originKey); + } + } + + } + + + } From 5e709edcff434e8dd115dde1c0e7e77bf8aa0405 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Sun, 27 Feb 2022 03:05:08 +0800 Subject: [PATCH 0569/1181] =?UTF-8?q?JOIN=20ON=20=E6=94=AF=E6=8C=81?= =?UTF-8?q?=E5=B8=A6=E9=9D=9E=E5=BC=95=E7=94=A8=E8=B5=8B=E5=80=BC=E5=85=B3?= =?UTF-8?q?=E8=81=94=E7=9A=84=E6=99=AE=E9=80=9A=E6=9D=A1=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../main/java/apijson/orm/AbstractSQLConfig.java | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java index eb78e1613..ed95a0682 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java @@ -3451,8 +3451,20 @@ public String getJoinString() throws Exception { + ", & INNER, | FULL, ! OUTER, ^ SIDE, ( ANTI, ) FOREIGN ] 之外的 JOIN 类型 !" ); } + + SQLConfig oc = j.getOuterConfig(); + String ow = null; + if (oc != null) { + oc.setPrepared(isPrepared()); + oc.setPreparedValueList(new ArrayList<>()); + oc.setMain(false).setKeyPrefix(true); + ow = oc.getWhereString(false); + + pvl.addAll(oc.getPreparedValueList()); + changed = true; + } - joinOns += " \n " + sql; + joinOns += " \n " + sql + (StringUtil.isEmpty(ow, true) ? "" : " AND ( " + ow + " ) "); } @@ -3925,7 +3937,7 @@ else if (joinConfig.getDatabase().equals(config.getDatabase()) == false) { joinConfig.setMain(false).setKeyPrefix(true); - if (j.isLeftOrRightJoin()) { + if (j.getOuter() != null) { SQLConfig outterConfig = newSQLConfig(method, table, alias, j.getOuter(), null, false, callback); outterConfig.setMain(false).setKeyPrefix(true).setDatabase(joinConfig.getDatabase()).setSchema(joinConfig.getSchema()); //解决主表 JOIN 副表,引号不一致 j.setOuterConfig(outterConfig); From f7b82fd90907b954b9ef90a4bfb8b8f05c72f513 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Sun, 27 Feb 2022 04:17:05 +0800 Subject: [PATCH 0570/1181] =?UTF-8?q?&=20INNER=20JOIN=20=E6=94=AF=E6=8C=81?= =?UTF-8?q?=E5=8D=95=E7=8B=AC=E8=AE=BE=E7=BD=AE=20JOIN=20=E8=AF=AD?= =?UTF-8?q?=E5=8F=A5=E4=B8=AD=E7=9A=84=E5=AD=97=E6=AE=B5=E3=80=81=E6=9D=A1?= =?UTF-8?q?=E4=BB=B6=E3=80=81=E5=88=86=E7=BB=84=E3=80=81=E8=81=9A=E5=90=88?= =?UTF-8?q?=E3=80=81=E6=8E=92=E5=BA=8F=E7=AD=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 和 < LEFT JOIN, > RIGHT JOIN 一样,例如 "join": { "&/User/id": { "id>": 82001, "@order": "id+" } } --- .../java/apijson/orm/AbstractSQLConfig.java | 111 +++++++++--------- 1 file changed, 57 insertions(+), 54 deletions(-) diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java index ed95a0682..8e4cac06c 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java @@ -1036,25 +1036,27 @@ public String getGroupString(boolean hasPrefix) { //加上子表的 group String joinGroup = ""; if (joinList != null) { - SQLConfig cfg; - String c; boolean first = true; for (Join j : joinList) { if (j.isAppJoin()) { continue; } - cfg = j.isLeftOrRightJoin() ? j.getOuterConfig() : j.getJoinConfig(); - if (StringUtil.isEmpty(cfg.getAlias(), true)) { - cfg.setAlias(cfg.getTable()); - } + SQLConfig ocfg = j.getOuterConfig(); + SQLConfig cfg = (ocfg != null && ocfg.getGroup() != null) || j.isLeftOrRightJoin() ? ocfg : j.getJoinConfig(); - c = ((AbstractSQLConfig) cfg).getGroupString(false); - if (StringUtil.isEmpty(c, true) == false) { - joinGroup += (first ? "" : ", ") + c; - first = false; - } + if (cfg != null) { + cfg.setMain(false).setKeyPrefix(true); + if (StringUtil.isEmpty(cfg.getAlias(), true)) { + cfg.setAlias(cfg.getTable()); + } + String c = ((AbstractSQLConfig) cfg).getGroupString(false); + if (StringUtil.isEmpty(c, true) == false) { + joinGroup += (first ? "" : ", ") + c; + first = false; + } + } } } @@ -1098,25 +1100,27 @@ public String getHavingString(boolean hasPrefix) { //加上子表的 having String joinHaving = ""; if (joinList != null) { - SQLConfig cfg; - String c; boolean first = true; for (Join j : joinList) { if (j.isAppJoin()) { continue; } - cfg = j.isLeftOrRightJoin() ? j.getOuterConfig() : j.getJoinConfig(); - if (StringUtil.isEmpty(cfg.getAlias(), true)) { - cfg.setAlias(cfg.getTable()); - } + SQLConfig ocfg = j.getOuterConfig(); + SQLConfig cfg = (ocfg != null && ocfg.getHaving() != null) || j.isLeftOrRightJoin() ? ocfg : j.getJoinConfig(); + + if (cfg != null) { + cfg.setMain(false).setKeyPrefix(true); + if (StringUtil.isEmpty(cfg.getAlias(), true)) { + cfg.setAlias(cfg.getTable()); + } + String c = ((AbstractSQLConfig) cfg).getHavingString(false); - c = ((AbstractSQLConfig) cfg).getHavingString(false); - if (StringUtil.isEmpty(c, true) == false) { - joinHaving += (first ? "" : ", ") + c; - first = false; + if (StringUtil.isEmpty(c, true) == false) { + joinHaving += (first ? "" : ", ") + c; + first = false; + } } - } } @@ -1255,25 +1259,27 @@ public String getOrderString(boolean hasPrefix) { //加上子表的 order String joinOrder = ""; if (joinList != null) { - SQLConfig cfg; - String c; boolean first = true; for (Join j : joinList) { if (j.isAppJoin()) { continue; } - cfg = j.isLeftOrRightJoin() ? j.getOuterConfig() : j.getJoinConfig(); - if (StringUtil.isEmpty(cfg.getAlias(), true)) { - cfg.setAlias(cfg.getTable()); - } + SQLConfig ocfg = j.getOuterConfig(); + SQLConfig cfg = (ocfg != null && ocfg.getOrder() != null) || j.isLeftOrRightJoin() ? ocfg : j.getJoinConfig(); - c = ((AbstractSQLConfig) cfg).getOrderString(false); - if (StringUtil.isEmpty(c, true) == false) { - joinOrder += (first ? "" : ", ") + c; - first = false; - } + if (cfg != null) { + cfg.setMain(false).setKeyPrefix(true); + if (StringUtil.isEmpty(cfg.getAlias(), true)) { + cfg.setAlias(cfg.getTable()); + } + String c = ((AbstractSQLConfig) cfg).getOrderString(false); + if (StringUtil.isEmpty(c, true) == false) { + joinOrder += (first ? "" : ", ") + c; + first = false; + } + } } } @@ -1499,41 +1505,38 @@ public String getColumnString(boolean inSQLJoin) throws Exception { case GETS: String joinColumn = ""; if (joinList != null) { - SQLConfig ecfg; - SQLConfig cfg; - String c; boolean first = true; for (Join j : joinList) { if (j.isAppJoin()) { continue; } - if (j.isLeftOrRightJoin()) { + SQLConfig ocfg = j.getOuterConfig(); + boolean isEmpty = ocfg == null || ocfg.getColumn() == null; + boolean isLeftOrRightJoin = j.isLeftOrRightJoin(); + + if (isEmpty && isLeftOrRightJoin) { // 改为 SELECT ViceTable.* 解决 SELECT sum(ViceTable.id) LEFT/RIGHT JOIN (SELECT sum(id) FROM ViceTable...) AS ViceTable // 不仅导致 SQL 函数重复计算,还有时导致 SQL 报错或对应字段未返回 String quote = getQuote(); joinColumn += (first ? "" : ", ") + quote + (StringUtil.isEmpty(j.getAlias(), true) ? j.getTable() : j.getAlias()) + quote + ".*"; first = false; } else { - ecfg = j.getOuterConfig(); - if (ecfg != null && ecfg.getColumn() != null) { //优先级更高 - cfg = ecfg; - } - else { - cfg = j.getJoinConfig(); - } - - if (StringUtil.isEmpty(cfg.getAlias(), true)) { - cfg.setAlias(cfg.getTable()); - } - - c = ((AbstractSQLConfig) cfg).getColumnString(true); - if (StringUtil.isEmpty(c, true) == false) { - joinColumn += (first ? "" : ", ") + c; - first = false; + SQLConfig cfg = isLeftOrRightJoin == false && isEmpty ? j.getJoinConfig() : ocfg; + if (cfg != null) { + cfg.setMain(false).setKeyPrefix(true); + if (StringUtil.isEmpty(cfg.getAlias(), true)) { + cfg.setAlias(cfg.getTable()); + } + + String c = ((AbstractSQLConfig) cfg).getColumnString(true); + if (StringUtil.isEmpty(c, true) == false) { + joinColumn += (first ? "" : ", ") + c; + first = false; + } } } - + inSQLJoin = true; } } From 9d2c95e0653dd4aec7aef022f50b5c7e432a6e25 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Tue, 1 Mar 2022 20:30:24 +0800 Subject: [PATCH 0571/1181] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E6=8F=90=E9=97=AE?= =?UTF-8?q?=E6=B3=A8=E6=84=8F=E4=BA=8B=E9=A1=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- APIJSONORM/src/main/java/apijson/orm/AbstractParser.java | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java b/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java index 3e3703468..020406326 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java @@ -649,7 +649,11 @@ public static JSONObject newResult(int code, String msg, boolean isRoot) { public static JSONObject extendResult(JSONObject object, int code, String msg, boolean isRoot) { int index = Log.DEBUG == false || isRoot == false || msg == null ? -1 : msg.lastIndexOf(Log.KEY_SYSTEM_INFO_DIVIDER); String debug = Log.DEBUG == false || isRoot == false ? null : (index >= 0 ? msg.substring(index + Log.KEY_SYSTEM_INFO_DIVIDER.length()).trim() - : " \n **环境信息** " + : " \n 提 bug 请发请求和响应的【完整截屏】,没图的自行解决!" + + " \n 开发者有限的时间和精力主要放在【维护项目源码和文档】上!" + + " \n 【描述不详细】 或 【文档/常见问题 已有答案】 的问题可能会被忽略!!" + + " \n 【态度 不文明/不友善】的可能会被踢出群,问题也可能不予解答!!!" + + " \n\n **环境信息** " + " \n 系统: " + System.getProperty("os.name") + " " + System.getProperty("os.version") + " \n 数据库: DEFAULT_DATABASE = " + AbstractSQLConfig.DEFAULT_DATABASE + " \n JDK: " + System.getProperty("java.version") + " " + System.getProperty("os.arch") From 028093d1fb43d6bc802d3b9aa00846313776f7ab Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Tue, 1 Mar 2022 20:34:52 +0800 Subject: [PATCH 0572/1181] Update Document.md --- Document.md | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/Document.md b/Document.md index 9187c4a43..46c090233 100644 --- a/Document.md +++ b/Document.md @@ -1,5 +1,5 @@ # APIJSON 通用文档 -本文是通用文档,只和 APIJSON 协议有关,和 C#, Go, Java, JavaScript, Python, PHP 等开发语言无关。
+本文是通用文档,只和 APIJSON 协议有关,和 C#, Go, Java, JavaScript, PHP, Python, TypeScript 等开发语言无关。
具体开发语言相关的 配置、运行、部署 等文档见各个相关项目的文档,可以在首页点击对应语言的入口来查看。
https://github.com/Tencent/APIJSON ![image](https://user-images.githubusercontent.com/5738175/134520081-a63d3817-321c-4e7b-9e03-73c6827a19c1.png) @@ -53,7 +53,7 @@ https://github.com/Tencent/APIJSON

- APIJSON 各种单表对象查询:简单查询、统计、分组、排序、聚合、比较、筛选字段、字段别名 等 + [GIF] APIJSON 各种单表对象查询:简单查询、统计、分组、排序、聚合、比较、筛选字段、字段别名 等

![](https://raw.githubusercontent.com/TommyLemon/StaticResources/master/APIJSON/APIJSON_query_single.gif) @@ -102,7 +102,7 @@ https://github.com/Tencent/APIJSON

- APIJSON 各种单表数组查询:简单查询、统计、分组、排序、聚合、分页、比较、搜索、正则、条件组合 等 + [GIF] APIJSON 各种单表数组查询:简单查询、统计、分组、排序、聚合、分页、比较、搜索、正则、条件组合 等

![](https://raw.githubusercontent.com/TommyLemon/StaticResources/master/APIJSON/APIJSON_query_array.gif) @@ -267,7 +267,7 @@ https://github.com/Tencent/APIJSON

- APIJSON 各种多表关联查询:一对一、一对多、多对一、各种条件 等 + [GIF] APIJSON 各种多表关联查询:一对一、一对多、多对一、各种条件 等

![](https://raw.githubusercontent.com/TommyLemon/StaticResources/master/APIJSON/APIJSON_query_associate.gif) @@ -275,7 +275,7 @@ https://github.com/Tencent/APIJSON

- APIJSON 各种 JOIN:< LEFT JOIN, & INNER JOIN 等 + [GIF] APIJSON 各种 JOIN:< LEFT JOIN, & INNER JOIN 等

![](https://raw.githubusercontent.com/TommyLemon/StaticResources/master/APIJSON/APIJSON_query_join.gif) @@ -283,7 +283,7 @@ https://github.com/Tencent/APIJSON

- APIJSON 各种子查询:@from@ FROM, key@ =, key>@ >, key{}@ IN, key}{@ EXISTS 等 + [GIF] APIJSON 各种子查询:@from@ FROM, key@ =, key>@ >, key{}@ IN, key}{@ EXISTS 等

![](https://raw.githubusercontent.com/TommyLemon/StaticResources/master/APIJSON/APIJSON_query_subquery.gif) @@ -291,7 +291,7 @@ https://github.com/Tencent/APIJSON

- APIJSON 部分功能演示集合,由浅入深、由简单到复杂 + [GIF] APIJSON 部分功能演示集合,由浅入深、由简单到复杂

![](https://raw.githubusercontent.com/TommyLemon/StaticResources/master/APIJSON/APIJSON_query_summary.gif) From 5328809c2d4cc247f8c9cb6211c6fb509eb742be Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Wed, 2 Mar 2022 00:47:55 +0800 Subject: [PATCH 0573/1181] Update README.md --- README.md | 36 ++++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/README.md b/README.md index 22390da85..8e8f72995 100644 --- a/README.md +++ b/README.md @@ -226,26 +226,26 @@ https://github.com/Tencent/APIJSON/issues/36 如果您在使用 APIJSON,请让我们知道,您的使用对我们非常重要(按登记顺序排列):
https://github.com/Tencent/APIJSON/issues/187
- - - + + +
- - - - - - - - - - - - - - - + + + + + + + + + + + + + + +
* [腾讯科技有限公司](https://www.tencent.com) From 36a5612f86e9b9b557a62f85a0f1487cc8f51ac0 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Sat, 5 Mar 2022 21:11:23 +0800 Subject: [PATCH 0574/1181] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E6=94=AF=E6=8C=81?= =?UTF-8?q?=20NULL=20=E5=80=BC=20@null:"tag"=20=E5=92=8C=E7=B1=BB=E5=9E=8B?= =?UTF-8?q?=E8=BD=AC=E6=8D=A2=20@cast:"date:DATE"?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/main/java/apijson/JSONObject.java | 37 +- APIJSONORM/src/main/java/apijson/SQL.java | 7 +- .../main/java/apijson/orm/AbstractParser.java | 4 +- .../java/apijson/orm/AbstractSQLConfig.java | 431 ++++++++++++------ .../src/main/java/apijson/orm/SQLConfig.java | 21 +- 5 files changed, 337 insertions(+), 163 deletions(-) diff --git a/APIJSONORM/src/main/java/apijson/JSONObject.java b/APIJSONORM/src/main/java/apijson/JSONObject.java index 925735bb9..8000e231b 100755 --- a/APIJSONORM/src/main/java/apijson/JSONObject.java +++ b/APIJSONORM/src/main/java/apijson/JSONObject.java @@ -134,6 +134,7 @@ public JSONObject setUserIdIn(List list) { // public static final String KEY_KEEP = "@keep"; //一定会返回,为 null 或 空对象时,会使用默认值(非空),解决其它对象因为不关联的第一个对为空导致也不返回 public static final String KEY_DEFULT = "@default"; //TODO 自定义默认值 { "@default":true },@default 可完全替代 @keep public static final String KEY_NULL = "@null"; //TODO 值为 null 的键值对 "@null":"tag,pictureList",允许 is NULL 条件判断, SET tag = NULL 修改值为 NULL 等 + public static final String KEY_CAST = "@cast"; //TODO 类型转换 cast(date AS DATE) public static final String KEY_ROLE = "@role"; //角色,拥有对某些数据的某些操作的权限 public static final String KEY_DATABASE = "@database"; //数据库类型,默认为MySQL @@ -161,6 +162,8 @@ public JSONObject setUserIdIn(List list) { TABLE_KEY_LIST.add(KEY_CACHE); TABLE_KEY_LIST.add(KEY_COLUMN); TABLE_KEY_LIST.add(KEY_FROM); + TABLE_KEY_LIST.add(KEY_NULL); + TABLE_KEY_LIST.add(KEY_CAST); TABLE_KEY_LIST.add(KEY_COMBINE); TABLE_KEY_LIST.add(KEY_GROUP); TABLE_KEY_LIST.add(KEY_HAVING); @@ -275,15 +278,45 @@ public JSONObject setColumn(String keys) { return puts(KEY_COLUMN, keys); } + /**set keys whose value is null + * @param keys key0, key1, key2 ... + * @return {@link #setNull(String)} + */ + public JSONObject setNull(String... keys) { + return setNull(StringUtil.getString(keys, true)); + } + /**set keys whose value is null + * @param keys "key0,key1,key2..." + * @return + */ + public JSONObject setNull(String keys) { + return puts(KEY_NULL, keys); + } + + /**set keys and types whose value should be cast to type, cast(value AS DATE) + * @param keyTypes key0:type0, key1:type1, key2:type2 ... + * @return {@link #setCast(String)} + */ + public JSONObject setCast(String... keyTypes) { + return setCast(StringUtil.getString(keyTypes, true)); + } + /**set keys and types whose value should be cast to type, cast(value AS DATE) + * @param keyTypes "key0:type0,key1:type1,key2:type2..." + * @return + */ + public JSONObject setCast(String keyTypes) { + return puts(KEY_CAST, keyTypes); + } + /**set combination of keys for conditions - * @param keys key0,&key1,|key2,!kye3 ... + * @param keys key0,&key1,|key2,!key3 ... TODO or key0> | (key1{} & !key2)... * @return {@link #setColumn(String)} */ public JSONObject setCombine(String... keys) { return setCombine(StringUtil.getString(keys, true)); } /**set combination of keys for conditions - * @param keys key0,&key1,|key2,!kye3 ... + * @param keys key0,&key1,|key2,!key3 ... TODO or key0> | (key1{} & !key2)... * @return */ public JSONObject setCombine(String keys) { diff --git a/APIJSONORM/src/main/java/apijson/SQL.java b/APIJSONORM/src/main/java/apijson/SQL.java index ce55eab29..397c2915f 100755 --- a/APIJSONORM/src/main/java/apijson/SQL.java +++ b/APIJSONORM/src/main/java/apijson/SQL.java @@ -14,8 +14,11 @@ public class SQL { public static final String AND = " AND "; public static final String NOT = " NOT "; public static final String AS = " AS "; - public static final String IS = " is "; - public static final String NULL = " null "; + public static final String IS = " IS "; + public static final String NULL = " NULL "; + public static final String IS_NOT = " IS NOT "; + public static final String IS_NULL = " IS NULL "; + public static final String IS_NOT_NULL = " IS NOT NULL "; //括号必须紧跟函数名! count (...) 报错! public static final String COUNT = "count"; diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java b/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java index 020406326..5bfa7da93 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java @@ -1322,6 +1322,8 @@ else if (childKeys.length == 1 && JSONRequest.isTableKey(childKeys[0])) { // JOIN_COPY_KEY_LIST.add(JSONRequest.KEY_SCHEMA); JOIN_COPY_KEY_LIST.add(JSONRequest.KEY_DATASOURCE); JOIN_COPY_KEY_LIST.add(JSONRequest.KEY_COLUMN); + JOIN_COPY_KEY_LIST.add(JSONRequest.KEY_NULL); + JOIN_COPY_KEY_LIST.add(JSONRequest.KEY_CAST); JOIN_COPY_KEY_LIST.add(JSONRequest.KEY_COMBINE); JOIN_COPY_KEY_LIST.add(JSONRequest.KEY_GROUP); JOIN_COPY_KEY_LIST.add(JSONRequest.KEY_HAVING); @@ -1330,7 +1332,7 @@ else if (childKeys.length == 1 && JSONRequest.isTableKey(childKeys[0])) { // } /**JOIN 多表同时筛选 - * @param join "&/User,0"} * @param request * @return * @throws Exception diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java index 8e4cac06c..3f0aafcdd 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java @@ -8,6 +8,8 @@ import static apijson.JSONObject.KEY_CACHE; import static apijson.JSONObject.KEY_COLUMN; import static apijson.JSONObject.KEY_COMBINE; +import static apijson.JSONObject.KEY_NULL; +import static apijson.JSONObject.KEY_CAST; import static apijson.JSONObject.KEY_DATABASE; import static apijson.JSONObject.KEY_DATASOURCE; import static apijson.JSONObject.KEY_EXPLAIN; @@ -32,6 +34,11 @@ import static apijson.SQL.NOT; import static apijson.SQL.OR; +import java.math.BigDecimal; +import java.sql.Array; +import java.sql.Date; +import java.sql.Time; +import java.sql.Timestamp; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; @@ -738,6 +745,8 @@ public String getUserIdKey() { private Subquery from; //子查询临时表 private List column; //表内字段名(或函数名,仅查询操作可用)的字符串数组,','分隔 private List> values; //对应表内字段的值的字符串数组,','分隔 + private List nulls; + private Map cast; private Map content; //Request内容,key:value形式,column = content.keySet(),values = content.values() private Map where; //筛选条件,key:value形式 private Map> combine; //条件组合,{ "&":[key], "|":[key], "!":[key] } @@ -1376,6 +1385,10 @@ public SQLConfig setRaw(List raw) { */ @Override public String getRawSQL(String key, Object value) throws Exception { + if (value == null) { + return null; + } + List rawList = getRaw(); boolean containRaw = rawList != null && rawList.contains(key); if (containRaw && value instanceof String == false) { @@ -1582,7 +1595,7 @@ public String getColumnString(boolean inSQLJoin) throws Exception { throw new UnsupportedOperationException("@column:value 的 value 中字符串 " + expression + " 不合法!" + "不允许传超过 100 个字符的函数或表达式!请用 @raw 简化传参!"); } - keys[i] = getColumnPrase(expression, containRaw); + keys[i] = parseColumn(expression, containRaw); } String c = StringUtil.getString(keys); @@ -1602,7 +1615,7 @@ public String getColumnString(boolean inSQLJoin) throws Exception { * @param expression * @return */ - public String getColumnPrase(String expression, boolean containRaw) { + public String parseColumn(String expression, boolean containRaw) { String quote = getQuote(); int start = expression.indexOf('('); if (start < 0) { @@ -2106,17 +2119,29 @@ public static String getLimitString(int page, int count, boolean isTSQL, boolean return " LIMIT " + count + (offset <= 0 ? "" : " OFFSET " + offset); // DELETE, UPDATE 不支持 OFFSET } + + @Override + public List getNull() { + return nulls; + } + @Override + public SQLConfig setNull(List nulls) { + this.nulls = nulls; + return this; + } - //WHERE <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< @Override - public Map getWhere() { - return where; + public Map getCast() { + return cast; } @Override - public AbstractSQLConfig setWhere(Map where) { - this.where = where; + public SQLConfig setCast(Map cast) { + this.cast = cast; return this; } + + //WHERE <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< + @NotNull @Override public Map> getCombine() { @@ -2135,6 +2160,17 @@ public AbstractSQLConfig setCombine(Map> combine) { this.combine = combine; return this; } + + @Override + public Map getWhere() { + return where; + } + @Override + public AbstractSQLConfig setWhere(Map where) { + this.where = where; + return this; + } + /** * noFunctionChar = false * @param key @@ -2307,7 +2343,6 @@ else if ("!".equals(ce.getKey())) { logic = Logic.TYPE_AND; } - isItemFirst = true; cs = ""; for (String key : keyList) { @@ -2477,9 +2512,8 @@ else if (isSideJoin) { // ^ SIDE JOIN: ! (A & B) protected String getWhereItem(String key, Object value, RequestMethod method, boolean verifyName) throws Exception { Log.d(TAG, "getWhereItem key = " + key); //避免筛选到全部 value = key == null ? null : where.get(key); - if (key == null || value == null || key.endsWith("()") || key.startsWith("@")) { //关键字||方法, +或-直接报错 - Log.d(TAG, "getWhereItem key == null || value == null" - + " || key.startsWith(@) || key.endsWith(()) >> continue;"); + if (key == null || key.endsWith("()") || key.startsWith("@")) { //关键字||方法, +或-直接报错 + Log.d(TAG, "getWhereItem key == null || key.endsWith(()) || key.startsWith(@) >> continue;"); return null; } if (key.endsWith("@")) {//引用 @@ -2524,63 +2558,64 @@ else if (key.endsWith("<")) { keyType = 0; } - key = getRealKey(method, key, false, true, verifyName); + String column = getRealKey(method, key, false, true, verifyName); switch (keyType) { case 1: - return getSearchString(key, value, rawSQL); + return getSearchString(key, column, value, rawSQL); case -2: case 2: - return getRegExpString(key, value, keyType < 0, rawSQL); + return getRegExpString(key, column, value, keyType < 0, rawSQL); case 3: - return getBetweenString(key, value, rawSQL); + return getBetweenString(key, column, value, rawSQL); case 4: - return getRangeString(key, value, rawSQL); + return getRangeString(key, column, value, rawSQL); case 5: - return getExistsString(key, value, rawSQL); + return getExistsString(key, column, value, rawSQL); case 6: - return getContainString(key, value, rawSQL); + return getContainString(key, column, value, rawSQL); case 7: - return getCompareString(key, value, ">=", rawSQL); + return getCompareString(key, column, value, ">=", rawSQL); case 8: - return getCompareString(key, value, "<=", rawSQL); + return getCompareString(key, column, value, "<=", rawSQL); case 9: - return getCompareString(key, value, ">", rawSQL); + return getCompareString(key, column, value, ">", rawSQL); case 10: - return getCompareString(key, value, "<", rawSQL); + return getCompareString(key, column, value, "<", rawSQL); default: // TODO MySQL JSON类型的字段对比 key='[]' 会无结果! key LIKE '[1, 2, 3]' //TODO MySQL , 后面有空格! - return getEqualString(key, value, rawSQL); + return getEqualString(key, column, value, rawSQL); } } @JSONField(serialize = false) - public String getEqualString(String key, Object value, String rawSQL) throws Exception { - if (JSON.isBooleanOrNumberOrString(value) == false && value instanceof Subquery == false) { + public String getEqualString(String key, String column, Object value, String rawSQL) throws Exception { + if (value != null && JSON.isBooleanOrNumberOrString(value) == false && value instanceof Subquery == false) { throw new IllegalArgumentException(key + ":value 中value不合法!非PUT请求只支持 [Boolean, Number, String] 内的类型 !"); } - boolean not = key.endsWith("!"); // & | 没有任何意义,写法多了不好控制 + boolean not = column.endsWith("!"); // & | 没有任何意义,写法多了不好控制 if (not) { - key = key.substring(0, key.length() - 1); + column = column.substring(0, column.length() - 1); } - if (StringUtil.isName(key) == false) { + if (StringUtil.isName(column) == false) { throw new IllegalArgumentException(key + ":value 中key不合法!不支持 ! 以外的逻辑符 !"); } - - return getKey(key) + (not ? " != " : " = ") + (value instanceof Subquery ? getSubqueryString((Subquery) value) : (rawSQL != null ? rawSQL : getValue(value))); + + String logic = value == null && rawSQL == null ? (not ? SQL.IS_NOT : SQL.IS) : (not ? " != " : " = "); + return getKey(column) + logic + (value instanceof Subquery ? getSubqueryString((Subquery) value) : (rawSQL != null ? rawSQL : getValue(key, column, value))); } @JSONField(serialize = false) - public String getCompareString(String key, Object value, String type, String rawSQL) throws Exception { - if (JSON.isBooleanOrNumberOrString(value) == false && value instanceof Subquery == false) { - throw new IllegalArgumentException(key + type + ":value 中value不合法!比较运算 [>, <, >=, <=] 只支持 [Boolean, Number, String] 内的类型 !"); + public String getCompareString(String key, String column, Object value, String type, String rawSQL) throws Exception { + if (value != null && JSON.isBooleanOrNumberOrString(value) == false && value instanceof Subquery == false) { + throw new IllegalArgumentException(key + ":value 中 value 不合法!比较运算 [>, <, >=, <=] 只支持 [Boolean, Number, String] 内的类型 !"); } - if (StringUtil.isName(key) == false) { - throw new IllegalArgumentException(key + type + ":value 中key不合法!比较运算 [>, <, >=, <=] 不支持 [&, !, |] 中任何逻辑运算符 !"); + if (StringUtil.isName(column) == false) { + throw new IllegalArgumentException(key + ":value 中 key 不合法!比较运算 [>, <, >=, <=] 不支持 [&, !, |] 中任何逻辑运算符 !"); } - return getKey(key) + " " + type + " " + (value instanceof Subquery ? getSubqueryString((Subquery) value) : (rawSQL != null ? rawSQL : getValue(value))); + return getKey(column) + " " + type + " " + (value instanceof Subquery ? getSubqueryString((Subquery) value) : (rawSQL != null ? rawSQL : getValue(key, column, value))); } public String getKey(String key) { @@ -2601,17 +2636,56 @@ public String getSQLKey(String key) { /** * 使用prepareStatement预编译,值为 ? ,后续动态set进去 */ - protected List preparedValueList = new ArrayList<>(); protected Object getValue(@NotNull Object value) { + return getValue(null, null, value); + } + + protected List preparedValueList = new ArrayList<>(); + protected Object getValue(String key, String column, Object value) { if (isPrepared()) { + if (value == null) { + return null; + } + + Map castMap = getCast(); + String type = key == null || castMap == null ? null : castMap.get(key); + +// if ("DATE".equalsIgnoreCase(type) && value instanceof Date == false) { +// value = value instanceof Number ? new Date(((Number) value).longValue()) : Date.valueOf((String) value); +// } +// else if ("TIME".equalsIgnoreCase(type) && value instanceof Time == false) { +// value = value instanceof Number ? new Time(((Number) value).longValue()) : Time.valueOf((String) value); +// } +// else if ("TIMESTAMP".equalsIgnoreCase(type) && value instanceof Timestamp == false) { +// value = value instanceof Number ? new Timestamp(((Number) value).longValue()) : Timestamp.valueOf((String) value); +// } +// else if ("ARRAY".equalsIgnoreCase(type) && value instanceof Array == false) { +// value = ((Collection) value).toArray(); +// } +// else if (StringUtil.isEmpty(type, true) == false) { +// preparedValueList.add(value); +// return "cast(?" + SQL.AS + type + ")"; +// } + preparedValueList.add(value); - return "?"; + return StringUtil.isEmpty(type, true) ? "?" : "cast(?" + SQL.AS + type + ")"; } - return getSQLValue(value); + + return key == null ? getSQLValue(value) : getSQLValue(key, column, value); + } + + public Object getSQLValue(String key, String column, @NotNull Object value) { + Map castMap = getCast(); + String type = key == null || castMap == null ? null : castMap.get(key); + Object val = getSQLValue(value); + return StringUtil.isEmpty(type, true) ? val : "cast(" + val + SQL.AS + type + ")"; } public Object getSQLValue(@NotNull Object value) { + if (value == null) { + return SQL.NULL; + } // return (value instanceof Number || value instanceof Boolean) && DATABASE_POSTGRESQL.equals(getDatabase()) ? value : "'" + value + "'"; - return (value instanceof Number || value instanceof Boolean) ? value : "'" + value + "'"; //MySQL 隐式转换用不了索引 + return (value instanceof Number || value instanceof Boolean) ? value : "'" + value.toString().replaceAll("'", "\\'") + "'"; //MySQL 隐式转换用不了索引 } @Override @@ -2631,7 +2705,7 @@ public AbstractSQLConfig setPreparedValueList(List preparedValueList) { * @throws IllegalArgumentException */ @JSONField(serialize = false) - public String getSearchString(String key, Object value, String rawSQL) throws IllegalArgumentException { + public String getSearchString(String key, String column, Object value, String rawSQL) throws IllegalArgumentException { if (rawSQL != null) { throw new UnsupportedOperationException("@raw:value 中 " + key + " 不合法!@raw 不支持 key$ 这种功能符 !只支持 key, key!, key<, key{} 等比较运算 和 @column, @having !"); } @@ -2639,15 +2713,15 @@ public String getSearchString(String key, Object value, String rawSQL) throws Il return ""; } - Logic logic = new Logic(key); - key = logic.getKey(); - Log.i(TAG, "getSearchString key = " + key); + Logic logic = new Logic(column); + column = logic.getKey(); + Log.i(TAG, "getSearchString column = " + column); JSONArray arr = newJSONArray(value); if (arr.isEmpty()) { return ""; } - return getSearchString(key, arr.toArray(), logic.getType()); + return getSearchString(key, column, arr.toArray(), logic.getType()); } /**search key match values * @param in @@ -2655,7 +2729,7 @@ public String getSearchString(String key, Object value, String rawSQL) throws Il * @throws IllegalArgumentException */ @JSONField(serialize = false) - public String getSearchString(String key, Object[] values, int type) throws IllegalArgumentException { + public String getSearchString(String key, String column, Object[] values, int type) throws IllegalArgumentException { if (values == null || values.length <= 0) { return ""; } @@ -2664,16 +2738,16 @@ public String getSearchString(String key, Object[] values, int type) throws Ille for (int i = 0; i < values.length; i++) { Object v = values[i]; if (v instanceof String == false) { - throw new IllegalArgumentException(key + "$:value 中 value 的类型只能为 String 或 String[]!"); + throw new IllegalArgumentException(key + ":value 中 value 的类型只能为 String 或 String[]!"); } if (((String) v).isEmpty()) { // 允许查空格 StringUtil.isEmpty((String) v, true) - throw new IllegalArgumentException(key + "$:value 中 value 值 " + v + "是空字符串,没有意义,不允许这样传!"); + throw new IllegalArgumentException(key + ":value 中 value 值 " + v + "是空字符串,没有意义,不允许这样传!"); } // if (((String) v).contains("%%")) { // 需要通过 %\%% 来模糊搜索 % // throw new IllegalArgumentException(key + "$:value 中 value 值 " + v + " 中包含 %% !不允许有连续的 % !"); // } - condition += (i <= 0 ? "" : (Logic.isAnd(type) ? AND : OR)) + getLikeString(key, v); + condition += (i <= 0 ? "" : (Logic.isAnd(type) ? AND : OR)) + getLikeString(key, column, v); } return getCondition(Logic.isNot(type), condition); @@ -2681,12 +2755,13 @@ public String getSearchString(String key, Object[] values, int type) throws Ille /**WHERE key LIKE 'value' * @param key + * @param column * @param value * @return key LIKE 'value' */ @JSONField(serialize = false) - public String getLikeString(String key, Object value) { - return getKey(key) + " LIKE " + getValue(value); + public String getLikeString(String key, String column, Object value) { + return getKey(column) + " LIKE " + getValue(key, column, value); } //$ search >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> @@ -2696,13 +2771,14 @@ public String getLikeString(String key, Object value) { //~ regexp <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< /**search key match RegExp values * @param key + * @param column * @param value * @param ignoreCase * @return {@link #getRegExpString(String, Object[], int, boolean)} * @throws IllegalArgumentException */ @JSONField(serialize = false) - public String getRegExpString(String key, Object value, boolean ignoreCase, String rawSQL) throws IllegalArgumentException { + public String getRegExpString(String key, String column, Object value, boolean ignoreCase, String rawSQL) throws IllegalArgumentException { if (rawSQL != null) { throw new UnsupportedOperationException("@raw:value 中 " + key + " 不合法!@raw 不支持 key~ 这种功能符 !只支持 key, key!, key<, key{} 等比较运算 和 @column, @having !"); } @@ -2710,15 +2786,15 @@ public String getRegExpString(String key, Object value, boolean ignoreCase, Stri return ""; } - Logic logic = new Logic(key); - key = logic.getKey(); - Log.i(TAG, "getRegExpString key = " + key); + Logic logic = new Logic(column); + column = logic.getKey(); + Log.i(TAG, "getRegExpString column = " + column); JSONArray arr = newJSONArray(value); if (arr.isEmpty()) { return ""; } - return getRegExpString(key, arr.toArray(), logic.getType(), ignoreCase); + return getRegExpString(key, column, arr.toArray(), logic.getType(), ignoreCase); } /**search key match RegExp values * @param key @@ -2729,7 +2805,7 @@ public String getRegExpString(String key, Object value, boolean ignoreCase, Stri * @throws IllegalArgumentException */ @JSONField(serialize = false) - public String getRegExpString(String key, Object[] values, int type, boolean ignoreCase) throws IllegalArgumentException { + public String getRegExpString(String key, String column, Object[] values, int type, boolean ignoreCase) throws IllegalArgumentException { if (values == null || values.length <= 0) { return ""; } @@ -2737,9 +2813,9 @@ public String getRegExpString(String key, Object[] values, int type, boolean ign String condition = ""; for (int i = 0; i < values.length; i++) { if (values[i] instanceof String == false) { - throw new IllegalArgumentException(key + "$:value 中value的类型只能为String或String[]!"); + throw new IllegalArgumentException(key + ":value 中value的类型只能为String或String[]!"); } - condition += (i <= 0 ? "" : (Logic.isAnd(type) ? AND : OR)) + getRegExpString(key, (String) values[i], ignoreCase); + condition += (i <= 0 ? "" : (Logic.isAnd(type) ? AND : OR)) + getRegExpString(key, column, (String) values[i], ignoreCase); } return getCondition(Logic.isNot(type), condition); @@ -2752,20 +2828,22 @@ public String getRegExpString(String key, Object[] values, int type, boolean ign * @return key REGEXP 'value' */ @JSONField(serialize = false) - public String getRegExpString(String key, String value, boolean ignoreCase) { + public String getRegExpString(String key, String column, String value, boolean ignoreCase) { if (isPostgreSQL()) { - return getKey(key) + " ~" + (ignoreCase ? "* " : " ") + getValue(value); + return getKey(column) + " ~" + (ignoreCase ? "* " : " ") + getValue(key, column, value); } if (isOracle()) { - return "regexp_like(" + getKey(key) + ", " + getValue(value) + (ignoreCase ? ", 'i'" : ", 'c'") + ")"; + return "regexp_like(" + getKey(column) + ", " + getValue(key, column, value) + (ignoreCase ? ", 'i'" : ", 'c'") + ")"; } if (isClickHouse()) { - return "match(" + (ignoreCase ? "lower(" : "") + getKey(key) + (ignoreCase ? ")" : "") + ", " + (ignoreCase ? "lower(" : "") + getValue(value) + (ignoreCase ? ")" : "") + ")"; + return "match(" + (ignoreCase ? "lower(" : "") + getKey(column) + (ignoreCase ? ")" : "") + + ", " + (ignoreCase ? "lower(" : "") + getValue(key, column, value) + (ignoreCase ? ")" : "") + ")"; } if (isHive()) { - return (ignoreCase ? "lower(" : "") + getKey(key) + (ignoreCase ? ")" : "") + " REGEXP " + (ignoreCase ? "lower(" : "") + getValue(value) + (ignoreCase ? ")" : ""); + return (ignoreCase ? "lower(" : "") + getKey(column) + (ignoreCase ? ")" : "") + + " REGEXP " + (ignoreCase ? "lower(" : "") + getValue(key, column, value) + (ignoreCase ? ")" : ""); } - return getKey(key) + " REGEXP " + (ignoreCase ? "" : "BINARY ") + getValue(value); + return getKey(column) + " REGEXP " + (ignoreCase ? "" : "BINARY ") + getValue(key, column, value); } //~ regexp >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> @@ -2780,7 +2858,7 @@ public String getRegExpString(String key, String value, boolean ignoreCase) { * @throws IllegalArgumentException */ @JSONField(serialize = false) - public String getBetweenString(String key, Object value, String rawSQL) throws IllegalArgumentException { + public String getBetweenString(String key, String column, Object value, String rawSQL) throws IllegalArgumentException { if (rawSQL != null) { throw new UnsupportedOperationException("@raw:value 中 " + key + " 不合法!@raw 不支持 key% 这种功能符 !只支持 key, key!, key<, key{} 等比较运算 和 @column, @having !"); } @@ -2788,15 +2866,15 @@ public String getBetweenString(String key, Object value, String rawSQL) throws I return ""; } - Logic logic = new Logic(key); - key = logic.getKey(); - Log.i(TAG, "getBetweenString key = " + key); + Logic logic = new Logic(column); + column = logic.getKey(); + Log.i(TAG, "getBetweenString column = " + column); JSONArray arr = newJSONArray(value); if (arr.isEmpty()) { return ""; } - return getBetweenString(key, arr.toArray(), logic.getType()); + return getBetweenString(key, column, arr.toArray(), logic.getType()); } /**WHERE key BETWEEN 'start' AND 'end' @@ -2806,7 +2884,7 @@ public String getBetweenString(String key, Object value, String rawSQL) throws I * @throws IllegalArgumentException */ @JSONField(serialize = false) - public String getBetweenString(String key, Object[] values, int type) throws IllegalArgumentException { + public String getBetweenString(String key, String column, Object[] values, int type) throws IllegalArgumentException { if (values == null || values.length <= 0) { return ""; } @@ -2815,15 +2893,15 @@ public String getBetweenString(String key, Object[] values, int type) throws Ill String[] vs; for (int i = 0; i < values.length; i++) { if (values[i] instanceof String == false) { - throw new IllegalArgumentException(key + "%:value 中 value 的类型只能为 String 或 String[] !"); + throw new IllegalArgumentException(key + ":value 中 value 的类型只能为 String 或 String[] !"); } vs = StringUtil.split((String) values[i]); if (vs == null || vs.length != 2) { - throw new IllegalArgumentException(key + "%:value 中 value 不合法!类型为 String 时必须包括1个逗号 , 且左右两侧都有值!类型为 String[] 里面每个元素要符合前面类型为 String 的规则 !"); + throw new IllegalArgumentException(key + ":value 中 value 不合法!类型为 String 时必须包括1个逗号 , 且左右两侧都有值!类型为 String[] 里面每个元素要符合前面类型为 String 的规则 !"); } - condition += (i <= 0 ? "" : (Logic.isAnd(type) ? AND : OR)) + "(" + getBetweenString(key, (Object) vs[0], (Object) vs[1]) + ")"; + condition += (i <= 0 ? "" : (Logic.isAnd(type) ? AND : OR)) + "(" + getBetweenString(key, column, (Object) vs[0], (Object) vs[1]) + ")"; } return getCondition(Logic.isNot(type), condition); @@ -2836,11 +2914,11 @@ public String getBetweenString(String key, Object[] values, int type) throws Ill * @throws IllegalArgumentException */ @JSONField(serialize = false) - public String getBetweenString(String key, Object start, Object end) throws IllegalArgumentException { + public String getBetweenString(String key, String column, Object start, Object end) throws IllegalArgumentException { if (JSON.isBooleanOrNumberOrString(start) == false || JSON.isBooleanOrNumberOrString(end) == false) { - throw new IllegalArgumentException(key + "%:value 中 value 不合法!类型为 String 时必须包括1个逗号 , 且左右两侧都有值!类型为 String[] 里面每个元素要符合前面类型为 String 的规则 !"); + throw new IllegalArgumentException(key + ":value 中 value 不合法!类型为 String 时必须包括1个逗号 , 且左右两侧都有值!类型为 String[] 里面每个元素要符合前面类型为 String 的规则 !"); } - return getKey(key) + " BETWEEN " + getValue(start) + AND + getValue(end); + return getKey(column) + " BETWEEN " + getValue(key, column, start) + AND + getValue(key, column, end); } @@ -2858,20 +2936,19 @@ public String getBetweenString(String key, Object start, Object end) throws Ille * @throws Exception */ @JSONField(serialize = false) - public String getRangeString(String key, Object range, String rawSQL) throws Exception { - Log.i(TAG, "getRangeString key = " + key); + public String getRangeString(String key, String column, Object range, String rawSQL) throws Exception { + Log.i(TAG, "getRangeString column = " + column); if (range == null) {//依赖的对象都没有给出有效值,这个存在无意义。如果是客户端传的,那就能在客户端确定了。 - throw new NotExistException(TAG + "getRangeString(" + key + ", " + range - + ") range == null"); + throw new NotExistException(TAG + "getRangeString(" + column + ", " + range + ") range == null"); } - Logic logic = new Logic(key); + Logic logic = new Logic(column); String k = logic.getKey(); Log.i(TAG, "getRangeString k = " + k); if (range instanceof List) { if (rawSQL != null) { - throw new UnsupportedOperationException("@raw:value 的 value 中 " + key + "{} 不合法!" + throw new UnsupportedOperationException("@raw:value 的 value 中 " + key + " 不合法!" + "Raw SQL 不支持 key{}:[] 这种键值对!"); } @@ -2880,9 +2957,9 @@ public String getRangeString(String key, Object range, String rawSQL) throws Exc if (logic.isNot() && l.isEmpty()) { return ""; // key!{}: [] 这个条件无效,加到 SQL 语句中 key IN() 会报错,getInString 里不好处理 } - return getKey(k) + getInString(k, l.toArray(), logic.isNot()); + return getKey(k) + getInString(k, column, l.toArray(), logic.isNot()); } - throw new IllegalArgumentException(key + "{}\":[] 中 {} 前面的逻辑运算符错误!只能用'|','!'中的一种 !"); + throw new IllegalArgumentException(key + ":[] 中 {} 前面的逻辑运算符错误!只能用'|','!'中的一种 !"); } else if (range instanceof String) {//非Number类型需要客户端拼接成 < 'value0', >= 'value1'这种 String condition = ""; @@ -2929,7 +3006,7 @@ else if ("!=null".equals(c)) { c = SQL.isNull(false); } else if (isPrepared() && (c.contains("--") || PATTERN_RANGE.matcher(c).matches() == false)) { - throw new UnsupportedOperationException(key + "{}:value 的 value 中 " + c + " 不合法!" + throw new UnsupportedOperationException(key + ":value 的 value 中 " + c + " 不合法!" + "预编译模式下 key{}:\"condition\" 中 condition 必须 为 =null 或 !=null 或 符合正则表达式 " + PATTERN_RANGE + " !不允许连续减号 -- !不允许空格!"); } @@ -2949,7 +3026,7 @@ else if (range instanceof Subquery) { //如果在 Parser 解析成 SQL 字符串 return getKey(k) + (logic.isNot() ? NOT : "") + " IN " + getSubqueryString((Subquery) range); } - throw new IllegalArgumentException(key + "{}:range 类型为" + range.getClass().getSimpleName() + throw new IllegalArgumentException(key + ":range 类型为" + range.getClass().getSimpleName() + "!range 只能是 用','分隔条件的字符串 或者 可取选项JSONArray!"); } /**WHERE key IN ('key0', 'key1', ... ) @@ -2958,16 +3035,15 @@ else if (range instanceof Subquery) { //如果在 Parser 解析成 SQL 字符串 * @throws NotExistException */ @JSONField(serialize = false) - public String getInString(String key, Object[] in, boolean not) throws NotExistException { + public String getInString(String key, String column, Object[] in, boolean not) throws NotExistException { String condition = ""; if (in != null) {//返回 "" 会导致 id:[] 空值时效果和没有筛选id一样! for (int i = 0; i < in.length; i++) { - condition += ((i > 0 ? "," : "") + getValue(in[i])); + condition += ((i > 0 ? "," : "") + getValue(key, column, in[i])); } } if (condition.isEmpty()) {//条件如果存在必须执行,不能忽略。条件为空会导致出错,又很难保证条件不为空(@:条件),所以还是这样好 - throw new NotExistException(TAG + ".getInString(" + key + ", [], " + not - + ") >> condition.isEmpty() >> IN()"); + throw new NotExistException(TAG + ".getInString(" + key + "," + column + ", [], " + not + ") >> condition.isEmpty() >> IN()"); } return (not ? NOT : "") + " IN (" + condition + ")"; } @@ -2983,7 +3059,7 @@ public String getInString(String key, Object[] in, boolean not) throws NotExistE * @throws NotExistException */ @JSONField(serialize = false) - public String getExistsString(String key, Object value, String rawSQL) throws Exception { + public String getExistsString(String key, String column, Object value, String rawSQL) throws Exception { if (rawSQL != null) { throw new UnsupportedOperationException("@raw:value 中 " + key + " 不合法!@raw 不支持 key}{ 这种功能符 !只支持 key, key!, key<, key{} 等比较运算 和 @column, @having !"); } @@ -2991,13 +3067,13 @@ public String getExistsString(String key, Object value, String rawSQL) throws Ex return ""; } if (value instanceof Subquery == false) { - throw new IllegalArgumentException(key + "}{:subquery 类型为" + value.getClass().getSimpleName() + throw new IllegalArgumentException(key + ":subquery 类型为" + value.getClass().getSimpleName() + "!subquery 只能是 子查询JSONObejct!"); } - Logic logic = new Logic(key); - key = logic.getKey(); - Log.i(TAG, "getExistsString key = " + key); + Logic logic = new Logic(column); + column = logic.getKey(); + Log.i(TAG, "getExistsString column = " + column); return (logic.isNot() ? NOT : "") + " EXISTS " + getSubqueryString((Subquery) value); } @@ -3011,21 +3087,18 @@ public String getExistsString(String key, Object value, String rawSQL) throws Ex * @throws NotExistException */ @JSONField(serialize = false) - public String getContainString(String key, Object value, String rawSQL) throws IllegalArgumentException { + public String getContainString(String key, String column, Object value, String rawSQL) throws IllegalArgumentException { if (rawSQL != null) { throw new UnsupportedOperationException("@raw:value 中 " + key + " 不合法!@raw 不支持 key<> 这种功能符 !只支持 key, key!, key<, key{} 等比较运算 和 @column, @having !"); } - if (value == null) { - return ""; - } - Logic logic = new Logic(key); - key = logic.getKey(); - Log.i(TAG, "getContainString key = " + key); + Logic logic = new Logic(column); + column = logic.getKey(); + Log.i(TAG, "getContainString column = " + column); - return getContainString(key, newJSONArray(value).toArray(), logic.getType()); + return getContainString(key, column, newJSONArray(value).toArray(), logic.getType()); } - /**WHERE key contains childs + /**WHERE key contains childs TODO 支持 key<>: { "path":"$[0].name", "value": 82001 } 或者 key<$[0].name>:82001 或者 key$[0].name<>:82001 ? 还是前者好,key 一旦复杂了,包含 , ; : / [] 等就容易和 @combine 其它功能等冲突 * @param key * @param childs null ? "" : (empty ? no child : contains childs) * @param type |, &, ! @@ -3034,42 +3107,55 @@ public String getContainString(String key, Object value, String rawSQL) throws I * @throws IllegalArgumentException */ @JSONField(serialize = false) - public String getContainString(String key, Object[] childs, int type) throws IllegalArgumentException { + public String getContainString(String key, String column, Object[] childs, int type) throws IllegalArgumentException { boolean not = Logic.isNot(type); String condition = ""; if (childs != null) { for (int i = 0; i < childs.length; i++) { Object c = childs[i]; - if (c != null) { - if (c instanceof JSON) { - throw new IllegalArgumentException(key + "<>:value 中value类型不能为JSON!"); + if (c instanceof Collection) { + throw new IllegalArgumentException(key + ":value 中 value 类型不能为 [JSONArray, Collection] 中的任何一个 !"); + } + + Object path = ""; + if (c instanceof Map) { + path = ((Map) c).get("path"); + if (path != null && path instanceof String == false) { + throw new IllegalArgumentException(key + ":{ path:path, value:value } 中 path 类型错误,只能是 $, $.key1, $[0].key2 等符合 SQL 中 JSON 路径的 String !"); } - - condition += (i <= 0 ? "" : (Logic.isAnd(type) ? AND : OR)); - if (isPostgreSQL()) { - condition += (getKey(key) + " @> " + getValue(newJSONArray(c))); //operator does not exist: jsonb @> character varying "[" + c + "]"); + + c = ((Map) c).get("value"); + if (c instanceof Collection || c instanceof Map) { + throw new IllegalArgumentException(key + ":{ path:path, value:value } 中 value 类型不能为 [JSONObject, JSONArray, Collection, Map] 中的任何一个 !"); } - else if (isOracle()) { - condition += ("json_textcontains(" + getKey(key) + ", '$', " + getValue(c.toString()) + ")"); + } + + condition += (i <= 0 ? "" : (Logic.isAnd(type) ? AND : OR)); + if (isPostgreSQL()) { + condition += (getKey(column) + " @> " + getValue(key, column, newJSONArray(c))); //operator does not exist: jsonb @> character varying "[" + c + "]"); + } + else if (isOracle()) { + condition += ("json_textcontains(" + getKey(column) + ", " + (StringUtil.isEmpty(path, true) ? "'$'" : getValue(key, column, path)) + ", " + getValue(key, column, c == null ? null : c.toString()) + ")"); + } + else { + String v = c == null ? "null" : (c instanceof Boolean || c instanceof Number ? c.toString() : "\"" + c + "\""); + if (isClickHouse()) { + condition += (condition + "has(JSONExtractArrayRaw(assumeNotNull(" + getKey(column) + "))" + ", " + getValue(key, column, v) + (StringUtil.isEmpty(path, true) ? "" : ", " + getValue(key, column, path)) + ")"); } else { - boolean isNum = c instanceof Number; - String v = (isNum ? "" : "\"") + childs[i] + (isNum ? "" : "\""); - if (isClickHouse()) { - condition += condition + "has(JSONExtractArrayRaw(assumeNotNull(" + getKey(key) + "))" + ", " + getValue(v) + ")"; - } - else { - condition += ("json_contains(" + getKey(key) + ", " + getValue(v) + ")"); - } + condition += ("json_contains(" + getKey(column) + ", " + getValue(key, column, v) + (StringUtil.isEmpty(path, true) ? "" : ", " + getValue(key, column, path)) + ")"); } } } + if (condition.isEmpty()) { - condition = (getKey(key) + SQL.isNull(true) + OR + getLikeString(key, "[]")); // key = '[]' 无结果! - } else { - condition = (getKey(key) + SQL.isNull(false) + AND + "(" + condition + ")"); + condition = getKey(column) + SQL.isNull(true) + OR + getLikeString(key, column, "[]"); // key = '[]' 无结果! + } + else { + condition = getKey(column) + SQL.isNull(false) + AND + "(" + condition + ")"; } } + if (condition.isEmpty()) { return ""; } @@ -3083,6 +3169,10 @@ else if (isOracle()) { @Override public String getSubqueryString(Subquery subquery) throws Exception { + if (subquery == null) { + return ""; + } + String range = subquery.getRange(); SQLConfig cfg = subquery.getConfig(); @@ -3171,10 +3261,10 @@ public String getSetString(RequestMethod method, Map content, bo keyType = 0; //注意重置类型,不然不该加减的字段会跟着加减 } value = entry.getValue(); - key = getRealKey(method, key, false, true, verifyName); + String column = getRealKey(method, key, false, true, verifyName); - setString += (isFirst ? "" : ", ") + (getKey(key) + " = " + (keyType == 1 ? getAddString(key, value) : (keyType == 2 - ? getRemoveString(key, value) : getValue(value)) ) ); + setString += (isFirst ? "" : ", ") + (getKey(column) + " = " + (keyType == 1 ? getAddString(key, column, value) : (keyType == 2 + ? getRemoveString(key, column, value) : getValue(key, column, value)) ) ); isFirst = false; } @@ -3193,14 +3283,14 @@ public String getSetString(RequestMethod method, Map content, bo * @throws IllegalArgumentException */ @JSONField(serialize = false) - public String getAddString(String key, Object value) throws IllegalArgumentException { + public String getAddString(String key, String column, Object value) throws IllegalArgumentException { if (value instanceof Number) { - return getKey(key) + " + " + value; + return getKey(column) + " + " + value; } if (value instanceof String) { - return SQL.concat(getKey(key), (String) getValue(value)); + return SQL.concat(getKey(column), (String) getValue(key, column, value)); } - throw new IllegalArgumentException(key + "+ 对应的值 " + value + " 不是Number,String,Array中的任何一种!"); + throw new IllegalArgumentException(key + ":value 中 value 类型错误,必须是 Number,String,Array 中的任何一种!"); } /**SET key = replace(key, 'value', '') * @param key @@ -3209,14 +3299,14 @@ public String getAddString(String key, Object value) throws IllegalArgumentExcep * @throws IllegalArgumentException */ @JSONField(serialize = false) - public String getRemoveString(String key, Object value) throws IllegalArgumentException { + public String getRemoveString(String key, String column, Object value) throws IllegalArgumentException { if (value instanceof Number) { - return getKey(key) + " - " + value; + return getKey(column) + " - " + value; } if (value instanceof String) { - return SQL.replace(getKey(key), (String) getValue(value), "''");// " replace(" + key + ", '" + value + "', '') "; + return SQL.replace(getKey(column), (String) getValue(key, column, value), "''");// " replace(" + column + ", '" + value + "', '') "; } - throw new IllegalArgumentException(key + "- 对应的值 " + value + " 不是Number,String,Array中的任何一种!"); + throw new IllegalArgumentException(key + ":value 中 value 类型错误,必须是 Number,String,Array 中的任何一种!"); } //SET >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> @@ -3359,6 +3449,7 @@ private static String getConditionString(String column, String table, AbstractSQ // return table + " AS t0 INNER JOIN (SELECT id FROM " + condition + ") AS t1 ON t0.id = t1.id"; } + private boolean keyPrefix; @Override public boolean isKeyPrefix() { @@ -3371,7 +3462,6 @@ public AbstractSQLConfig setKeyPrefix(boolean keyPrefix) { } - public String getJoinString() throws Exception { String joinOns = ""; @@ -3604,9 +3694,11 @@ else if (id instanceof Subquery) {} String role = request.getString(KEY_ROLE); String cache = request.getString(KEY_CACHE); - String combine = request.getString(KEY_COMBINE); Subquery from = (Subquery) request.get(KEY_FROM); String column = request.getString(KEY_COLUMN); + String nulls = request.getString(KEY_NULL); + String cast = request.getString(KEY_CAST); + String combine = request.getString(KEY_COMBINE); String group = request.getString(KEY_GROUP); String having = request.getString(KEY_HAVING); String order = request.getString(KEY_ORDER); @@ -3624,14 +3716,52 @@ else if (id instanceof Subquery) {} request.remove(KEY_DATASOURCE); request.remove(KEY_DATABASE); request.remove(KEY_SCHEMA); - request.remove(KEY_COMBINE); request.remove(KEY_FROM); request.remove(KEY_COLUMN); + request.remove(KEY_NULL); + request.remove(KEY_CAST); + request.remove(KEY_COMBINE); request.remove(KEY_GROUP); request.remove(KEY_HAVING); request.remove(KEY_ORDER); request.remove(KEY_RAW); request.remove(KEY_JSON); + + String[] nullKeys = StringUtil.split(nulls); + if (nullKeys != null && nullKeys.length > 0) { + for (String nk : nullKeys) { + if (StringUtil.isEmpty(nk, true)) { + throw new IllegalArgumentException(table + ":{} 里的 @null: value 中的字符 '" + nk + "' 不合法!不允许为空!"); + } + if (request.get(nk) != null) { + throw new IllegalArgumentException(table + ":{} 里的 @null: value 中的字符 '" + nk + "' 已在当前对象有非 null 值!不允许对同一个 JSON key 设置不同值!"); + } + + request.put(nk, null); + } + } + + String[] casts = StringUtil.split(cast); + Map castMap = null; + if (casts != null && casts.length > 0) { + castMap = new HashMap<>(casts.length); + for (String c : casts) { + apijson.orm.Entry p = Pair.parseEntry(c); + + if (StringUtil.isEmpty(p.getKey(), true)) { + throw new IllegalArgumentException(table + ":{} 里的 @cast: 'key0:type0,key1:type1..' 中 '" + c + "' 对应的 key 的字符 '" + p.getKey() + "' 不合法!不允许为空!"); + } + if (StringUtil.isName(p.getValue()) == false) { + throw new IllegalArgumentException(table + ":{} 里的 @cast: 'key0:type0,key1:type1..' 中 '" + c + "' 对应的 type 的字符 '" + p.getValue() + "' 不合法!必须符合类型名称格式!"); + } + if (castMap.get(p.getKey()) != null) { + throw new IllegalArgumentException(table + ":{} 里的 @cast: 'key0:type0,key1:type1..' 中 '" + c + "' 对应的 key 的字符 '" + p.getKey() + "' 已存在!不允许重复设置类型!"); + } + + castMap.put(p.getKey(), p.getValue()); + } + } + String[] rawArr = StringUtil.split(raw); config.setRaw(rawArr == null || rawArr.length <= 0 ? null : new ArrayList<>(Arrays.asList(rawArr))); @@ -3642,7 +3772,7 @@ else if (id instanceof Subquery) {} Set set = request.keySet(); //前面已经判断request是否为空 if (method == POST) { //POST操作 if (idIn != null) { - throw new IllegalArgumentException("POST 请求中不允许传 " + idInKey + " !"); + throw new IllegalArgumentException(table + ":{} 里的 " + idInKey + ": value 不合法!POST 请求中不允许传 " + idInKey + " !"); } if (set != null && set.isEmpty() == false) { //不能直接return,要走完下面的流程 @@ -3841,26 +3971,27 @@ else if (whereList != null && whereList.contains(key)) { } } } + config.setExplain(explain); config.setCache(getCache(cache)); - config.setFrom(from); config.setDistinct(distinct); config.setColumn(column == null ? null : cs); //解决总是 config.column != null,总是不能得到 * - config.setWhere(tableWhere); + config.setFrom(from); + config.setRole(role); config.setId(id); //在 tableWhere 第0个 config.setIdIn(idIn); - config.setRole(role); + config.setNull(nullKeys == null || nullKeys.length <= 0 ? null : new ArrayList<>(Arrays.asList(nullKeys))); + config.setCast(castMap); + config.setWhere(tableWhere); config.setGroup(group); config.setHaving(having); config.setOrder(order); - String[] jsonArr = StringUtil.split(json); - config.setJson(jsonArr == null || jsonArr.length <= 0 ? null : new ArrayList<>(Arrays.asList(jsonArr))); - - //TODO 解析JOIN,包括 @column,@group 等要合并 + String[] jsons = StringUtil.split(json); + config.setJson(jsons == null || jsons.length <= 0 ? null : new ArrayList<>(Arrays.asList(jsons))); } finally {//后面还可能用到,要还原 @@ -3876,9 +4007,11 @@ else if (whereList != null && whereList.contains(key)) { request.put(KEY_CACHE, cache); request.put(KEY_DATASOURCE, datasource); request.put(KEY_SCHEMA, schema); - request.put(KEY_COMBINE, combine); request.put(KEY_FROM, from); request.put(KEY_COLUMN, column); + request.put(KEY_NULL, nulls); + request.put(KEY_CAST, cast); + request.put(KEY_COMBINE, combine); request.put(KEY_GROUP, group); request.put(KEY_HAVING, having); request.put(KEY_ORDER, order); diff --git a/APIJSONORM/src/main/java/apijson/orm/SQLConfig.java b/APIJSONORM/src/main/java/apijson/orm/SQLConfig.java index 83eddb301..aca544111 100755 --- a/APIJSONORM/src/main/java/apijson/orm/SQLConfig.java +++ b/APIJSONORM/src/main/java/apijson/orm/SQLConfig.java @@ -131,6 +131,9 @@ public interface SQLConfig { String getQuote(); + List getJson(); + SQLConfig setJson(List json); + /**请求传进来的Table名 * @return * @see {@link #getSQLTable()} @@ -150,7 +153,6 @@ public interface SQLConfig { List getRaw(); SQLConfig setRaw(List raw); - String getGroup(); SQLConfig setGroup(String group); @@ -160,9 +162,6 @@ public interface SQLConfig { String getOrder(); SQLConfig setOrder(String order); - List getJson(); - SQLConfig setJson(List json); - Subquery getFrom(); SQLConfig setFrom(Subquery from); @@ -175,13 +174,17 @@ public interface SQLConfig { Map getContent(); SQLConfig setContent(Map content); - Map getWhere(); - SQLConfig setWhere(Map where); - Map> getCombine(); SQLConfig setCombine(Map> combine); - - + + Map getCast(); + SQLConfig setCast(Map cast); + + List getNull(); + SQLConfig setNull(List nulls); + + Map getWhere(); + SQLConfig setWhere(Map where); /** * exactMatch = false From 2cc13dab41f658f729eb34684bd6c8f64d8fd0c2 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Sun, 6 Mar 2022 02:41:09 +0800 Subject: [PATCH 0575/1181] =?UTF-8?q?=E6=9D=A1=E4=BB=B6=E7=BB=84=E5=90=88?= =?UTF-8?q?=EF=BC=9A=E8=A7=A3=E5=86=B3=20@combine:"name*~,tag&$"=20?= =?UTF-8?q?=E8=A7=A3=E6=9E=90=E5=BC=82=E5=B8=B8=EF=BC=8C=E8=A7=A3=E5=86=B3?= =?UTF-8?q?=20@combine:"name*~=20|=20tag&$"=20=E8=BF=99=E7=A7=8D=E6=9C=80?= =?UTF-8?q?=E5=90=8E=E6=B2=A1=E6=9C=89=E6=8B=AC=E5=8F=B7=E7=9A=84=E8=A7=A3?= =?UTF-8?q?=E6=9E=90=E5=90=8E=E7=BC=BA=E5=B0=91=E6=9C=80=E5=90=8E=E7=9A=84?= =?UTF-8?q?=E6=9D=A1=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/apijson/orm/AbstractSQLConfig.java | 461 +++++++++++++++--- .../src/main/java/apijson/orm/SQLConfig.java | 3 + 2 files changed, 392 insertions(+), 72 deletions(-) diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java index 3f0aafcdd..cb4b0a981 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java @@ -6,10 +6,9 @@ package apijson.orm; import static apijson.JSONObject.KEY_CACHE; +import static apijson.JSONObject.KEY_CAST; import static apijson.JSONObject.KEY_COLUMN; import static apijson.JSONObject.KEY_COMBINE; -import static apijson.JSONObject.KEY_NULL; -import static apijson.JSONObject.KEY_CAST; import static apijson.JSONObject.KEY_DATABASE; import static apijson.JSONObject.KEY_DATASOURCE; import static apijson.JSONObject.KEY_EXPLAIN; @@ -18,6 +17,7 @@ import static apijson.JSONObject.KEY_HAVING; import static apijson.JSONObject.KEY_ID; import static apijson.JSONObject.KEY_JSON; +import static apijson.JSONObject.KEY_NULL; import static apijson.JSONObject.KEY_ORDER; import static apijson.JSONObject.KEY_RAW; import static apijson.JSONObject.KEY_ROLE; @@ -34,16 +34,14 @@ import static apijson.SQL.NOT; import static apijson.SQL.OR; -import java.math.BigDecimal; -import java.sql.Array; -import java.sql.Date; -import java.sql.Time; -import java.sql.Timestamp; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; +import java.util.Deque; import java.util.HashMap; +import java.util.HashSet; import java.util.LinkedHashMap; +import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Map.Entry; @@ -750,7 +748,7 @@ public String getUserIdKey() { private Map content; //Request内容,key:value形式,column = content.keySet(),values = content.values() private Map where; //筛选条件,key:value形式 private Map> combine; //条件组合,{ "&":[key], "|":[key], "!":[key] } - + private String combineExpression; //array item <<<<<<<<<< private int count; //Table数量 @@ -2142,6 +2140,16 @@ public SQLConfig setCast(Map cast) { //WHERE <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< + @Override + public String getCombineExpression() { + return combineExpression; + } + @Override + public AbstractSQLConfig setCombineExpression(String combineExpression) { + this.combineExpression = combineExpression; + return this; + } + @NotNull @Override public Map> getCombine() { @@ -2300,7 +2308,11 @@ else if (key.equals(userIdInKey)) { @JSONField(serialize = false) @Override public String getWhereString(boolean hasPrefix) throws Exception { - return getWhereString(hasPrefix, getMethod(), getWhere(), getCombine(), getJoinList(), ! isTest()); + String ce = getCombineExpression(); + if (StringUtil.isEmpty(ce, true)) { + return getWhereString(hasPrefix, getMethod(), getWhere(), getCombine(), getJoinList(), ! isTest()); + } + return getWhereString(hasPrefix, getMethod(), getWhere(), ce, getJoinList(), ! isTest()); } /**获取WHERE * @param method @@ -2309,6 +2321,301 @@ public String getWhereString(boolean hasPrefix) throws Exception { * @throws Exception */ @JSONField(serialize = false) + public String getWhereString(boolean hasPrefix, RequestMethod method, Map where, String combine, List joinList, boolean verifyName) throws Exception { + String s = StringUtil.getString(combine); + if (s.startsWith(" ") || s.endsWith(" ") ) { + throw new IllegalArgumentException(table + ":{ @combine: '" + combine + "' } 中字符 '" + s + + "' 不合法!不允许首尾有空格,也不允许连续空格!空格不能多也不能少!" + + "逻辑连接符 & | 左右必须各一个相邻空格!左括号 ( 右边和右括号 ) 左边都不允许有相邻空格!"); + } + + + String whereString = ""; + + int depth = 0; + int n = s.length(); + int i = 0; + + char lastLogic = 0; + char last = 0; + boolean first = true; + + String key = ""; + Set usedKeySet = new HashSet<>(where.size()); + while (i < n) { // "date> | (contactIdList<> & (name*~ | tag&$))" + char c = s.charAt(i); + boolean isLast = i >= n - 1; + boolean isBlankOrRightParenthesis = c == ' ' || c == ')'; + if (isLast || isBlankOrRightParenthesis) { + if (isBlankOrRightParenthesis == false) { + key += c; + } + + boolean isEmpty = StringUtil.isEmpty(key, true); + if (isEmpty && last != ')') { + throw new IllegalArgumentException(table + ":{ @combine: '" + combine + "' } 中字符 '" + s.substring(i) + + "' 不合法!" + (c == ' ' ? "空格 ' ' " : "右括号 ')'") + " 左边缺少条件 key !逻辑连接符 & | 左右必须各一个相邻空格!" + + "空格不能多也不能少!不允许首尾有空格,也不允许连续空格!左括号 ( 的右边 和 右括号 ) 的左边 都不允许有相邻空格!"); + } + + if (isEmpty == false) { + if (first == false && lastLogic <= 0) { + throw new IllegalArgumentException(table + ":{ @combine: '" + combine + "' } 中字符 '" + s.substring(i - key.length()) + "' 不合法!左边缺少 & | 其中一个逻辑连接符!"); + } + + Object value = where.get(key); + if (value == null) { + throw new IllegalArgumentException(table + ":{ @combine: '" + combine + "' } 中字符 '" + key + "' 对应的条件键值对 " + key + ":value 不存在!"); + } + + String wi = getWhereItem(key, value, method, verifyName); + if (StringUtil.isEmpty(wi, true)) { // 转成 1=1 ? + throw new IllegalArgumentException(table + ":{ @combine: '" + combine + "' } 中字符 '" + key + "' 对应的 " + key + ":value 不是有效条件键值对!"); + } + + usedKeySet.add(key); + whereString += "( " + wi + " )"; + first = false; + } + + key = ""; + + if (isLast) { + break; + } + } + + if (c == ' ') { + } + else if (c == '&') { + if (last == ' ') { + if (i >= n || s.charAt(i + 1) != ' ') { + throw new IllegalArgumentException(table + ":{ @combine: '" + combine + "' } 中字符 '" + s.substring(0, i + 1) + + "' 不合法!逻辑连接符 & 右边缺少一个空格 !逻辑连接符 & | 左右必须各一个相邻空格!空格不能多也不能少!" + + "不允许首尾有空格,也不允许连续空格!左括号 ( 的右边 和 右括号 ) 的左边 都不允许有相邻空格!"); + } + + whereString += SQL.AND; + lastLogic = c; + i ++; + } + else if (isLast == false) { + key += c; + } + } + else if (c == '|') { + if (last == ' ') { + if (i >= n || s.charAt(i + 1) != ' ') { + throw new IllegalArgumentException(table + ":{ @combine: '" + combine + "' } 中字符 '" + s.substring(0, i + 1) + + "' 不合法!逻辑连接符 | 右边缺少一个空格 !逻辑连接符 & | 左右必须各一个相邻空格!空格不能多也不能少!" + + "不允许首尾有空格,也不允许连续空格!左括号 ( 右边和右括号 ) 左边都不允许有相邻空格!"); + } + + whereString += SQL.OR; + lastLogic = c; + i ++; + } + else if (isLast == false) { + key += c; + } + } + else if (c == '(') { + if (key.isEmpty() == false || (i > 0 && lastLogic <= 0)) { + throw new IllegalArgumentException(table + ":{ @combine: '" + combine + "' } 中字符 '" + s.substring(i) + "' 不合法!左边缺少 & | 逻辑连接符!"); + } + + depth ++; + whereString += c; + lastLogic = 0; + first = true; + } + else if (c == ')') { + depth --; + if (depth < 0) { + throw new IllegalArgumentException(table + ":{ @combine: '" + combine + "' } 中字符 '" + s.substring(0, i + 1) + "' 不合法!左括号 ( 比 右括号 ) 少!数量必须相等从而完整闭合 (...) !"); + } + + whereString += c; + lastLogic = 0; + } + else if (isLast == false) { + key += c; + } + + last = c; + i ++; + + if (i >= n) { + i = n - 1; + } + } + + if (depth != 0) { + throw new IllegalArgumentException(table + ":{ @combine: '" + combine + "' } 中字符 '" + s + "' 不合法!左括号 ( 比 右括号 ) 多!数量必须相等从而完整闭合 (...) !"); + } + + Set> set = where.entrySet(); + + String andWhere = ""; + boolean isItemFirst = true; + + for (Entry entry : set) { + key = entry == null ? null : entry.getKey(); + if (key == null || usedKeySet.contains(key)) { + continue; + } + + String wi = getWhereItem(key, where.get(key), method, verifyName); + if (StringUtil.isEmpty(wi, true)) {//避免SQL条件连接错误 + continue; + } + + andWhere += (isItemFirst ? "" : AND) + "(" + wi + ")"; + isItemFirst = false; + } + + if (StringUtil.isEmpty(whereString, true)) { + whereString = andWhere; + } + else if (StringUtil.isNotEmpty(andWhere, true)) { + whereString = andWhere + AND + "( " + whereString + " )"; + } + + if (joinList != null) { + + String newWs = ""; + String ws = whereString; + + List newPvl = new ArrayList<>(); + List pvl = new ArrayList<>(preparedValueList); + + SQLConfig jc; + String js; + + boolean changed = false; + //各种 JOIN 没办法统一用 & | !连接,只能按优先级,和 @combine 一样? + for (Join j : joinList) { + String jt = j.getJoinType(); + + switch (jt) { + case "*": // CROSS JOIN + case "@": // APP JOIN + case "<": // LEFT JOIN + case ">": // RIGHT JOIN + break; + + case "&": // INNER JOIN: A & B + case "": // FULL JOIN: A | B + case "|": // FULL JOIN: A | B + case "!": // OUTER JOIN: ! (A | B) + case "^": // SIDE JOIN: ! (A & B) + case "(": // ANTI JOIN: A & ! B + case ")": // FOREIGN JOIN: B & ! A + jc = j.getJoinConfig(); + boolean isMain = jc.isMain(); + jc.setMain(false).setPrepared(isPrepared()).setPreparedValueList(new ArrayList()); + js = jc.getWhereString(false); + jc.setMain(isMain); + + boolean isOuterJoin = "!".equals(jt); + boolean isSideJoin = "^".equals(jt); + boolean isAntiJoin = "(".equals(jt); + boolean isForeignJoin = ")".equals(jt); + boolean isWsEmpty = StringUtil.isEmpty(ws, true); + + if (isWsEmpty) { + if (isOuterJoin) { // ! OUTER JOIN: ! (A | B) + throw new NotExistException("no result for ! OUTER JOIN( ! (A | B) ) when A or B is empty!"); + } + if (isForeignJoin) { // ) FOREIGN JOIN: B & ! A + throw new NotExistException("no result for ) FOREIGN JOIN( B & ! A ) when A is empty!"); + } + } + + if (StringUtil.isEmpty(js, true)) { + if (isOuterJoin) { // ! OUTER JOIN: ! (A | B) + throw new NotExistException("no result for ! OUTER JOIN( ! (A | B) ) when A or B is empty!"); + } + if (isAntiJoin) { // ( ANTI JOIN: A & ! B + throw new NotExistException("no result for ( ANTI JOIN( A & ! B ) when B is empty!"); + } + + if (isWsEmpty) { + if (isSideJoin) { + throw new NotExistException("no result for ^ SIDE JOIN( ! (A & B) ) when both A and B are empty!"); + } + } + else { + if (isSideJoin || isForeignJoin) { + newWs += " ( " + getCondition(true, ws) + " ) "; + + newPvl.addAll(pvl); + newPvl.addAll(jc.getPreparedValueList()); + changed = true; + } + } + + continue; + } + + if (StringUtil.isEmpty(newWs, true) == false) { + newWs += AND; + } + + if (isAntiJoin) { // ( ANTI JOIN: A & ! B + newWs += " ( " + ( isWsEmpty ? "" : ws + AND ) + NOT + " ( " + js + " ) " + " ) "; + } + else if (isForeignJoin) { // ) FOREIGN JOIN: (! A) & B // preparedValueList.add 不好反过来 B & ! A + newWs += " ( " + NOT + " ( " + ws + " ) ) " + AND + " ( " + js + " ) "; + } + else if (isSideJoin) { // ^ SIDE JOIN: ! (A & B) + //MySQL 因为 NULL 值处理问题,(A & ! B) | (B & ! A) 与 ! (A & B) 返回结果不一样,后者往往更多 + newWs += " ( " + getCondition( + true, + ( isWsEmpty ? "" : ws + AND ) + " ( " + js + " ) " + ) + " ) "; + } + else { // & INNER JOIN: A & B; | FULL JOIN: A | B; OUTER JOIN: ! (A | B) + int logic = Logic.getType(jt); + newWs += " ( " + + getCondition( + Logic.isNot(logic), + ws + + ( isWsEmpty ? "" : (Logic.isAnd(logic) ? AND : OR) ) + + " ( " + js + " ) " + ) + + " ) "; + } + + newPvl.addAll(pvl); + newPvl.addAll(jc.getPreparedValueList()); + + changed = true; + break; + default: + throw new UnsupportedOperationException( + "join:value 中 value 里的 " + jt + "/" + j.getPath() + + "错误!不支持 " + jt + " 等 [ @ APP, < LEFT, > RIGHT, * CROSS" + + ", & INNER, | FULL, ! OUTER, ^ SIDE, ( ANTI, ) FOREIGN ] 之外的 JOIN 类型 !" + ); + } + } + + if (changed) { + whereString = newWs; + preparedValueList = newPvl; + } + } + + String result = StringUtil.isEmpty(whereString, true) ? "" : (hasPrefix ? " WHERE " : "") + whereString; + + if (result.isEmpty() && RequestMethod.isQueryMethod(method) == false) { + throw new UnsupportedOperationException("写操作请求必须带条件!!!"); + } + + return result; + } + public String getWhereString(boolean hasPrefix, RequestMethod method, Map where, Map> combine, List joinList, boolean verifyName) throws Exception { Set>> combineSet = combine == null ? null : combine.entrySet(); if (combineSet == null || combineSet.isEmpty()) { @@ -2353,7 +2660,6 @@ else if ("!".equals(ce.getKey())) { } cs += (isItemFirst ? "" : (Logic.isAnd(logic) ? AND : OR)) + "(" + c + ")"; - isItemFirst = false; } @@ -3813,74 +4119,80 @@ else if (id instanceof Subquery) {} //条件<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< List whereList = null; - Map> combineMap = new LinkedHashMap<>(); - List andList = new ArrayList<>(); - List orList = new ArrayList<>(); - List notList = new ArrayList<>(); - - //强制作为条件且放在最前面优化性能 - if (id != null) { - tableWhere.put(idKey, id); - andList.add(idKey); - } - if (idIn != null) { - tableWhere.put(idInKey, idIn); - andList.add(idInKey); - } - String[] ws = StringUtil.split(combine); - if (ws != null) { - if (method == DELETE || method == GETS || method == HEADS) { - throw new IllegalArgumentException("DELETE,GETS,HEADS 请求不允许传 @combine:value !"); + String combineExpression = ws == null || ws.length != 1 ? null : ws[0]; + + Map> combineMap = StringUtil.isNotEmpty(combineExpression, true) ? null : new LinkedHashMap<>(); + List andList = combineMap == null ? null : new ArrayList<>(); + List orList = combineMap == null ? null : new ArrayList<>(); + List notList = combineMap == null ? null : new ArrayList<>(); + + if (combineMap != null) { + //强制作为条件且放在最前面优化性能 + if (id != null) { + tableWhere.put(idKey, id); + andList.add(idKey); } - whereList = new ArrayList<>(); - - String w; - for (int i = 0; i < ws.length; i++) { //去除 &,|,! 前缀 - w = ws[i]; - if (w != null) { - if (w.startsWith("&")) { - w = w.substring(1); - andList.add(w); - } - else if (w.startsWith("|")) { - if (method == PUT) { - throw new IllegalArgumentException(table + ":{} 里的 @combine:value 中的value里条件 " + ws[i] + " 不合法!" - + "PUT请求的 @combine:\"key0,key1,...\" 不允许传 |key 或 !key !"); + if (idIn != null) { + tableWhere.put(idInKey, idIn); + andList.add(idInKey); + } + + + if (ws != null) { + if (method == DELETE || method == GETS || method == HEADS) { + throw new IllegalArgumentException("DELETE,GETS,HEADS 请求不允许传 @combine:value !"); + } + whereList = new ArrayList<>(); + + String w; + for (int i = 0; i < ws.length; i++) { //去除 &,|,! 前缀 + w = ws[i]; + if (w != null) { + if (w.startsWith("&")) { + w = w.substring(1); + andList.add(w); } - w = w.substring(1); - orList.add(w); - } - else if (w.startsWith("!")) { - if (method == PUT) { - throw new IllegalArgumentException(table + ":{} 里的 @combine:value 中的value里条件 " + ws[i] + " 不合法!" - + "PUT请求的 @combine:\"key0,key1,...\" 不允许传 |key 或 !key !"); + else if (w.startsWith("|")) { + if (method == PUT) { + throw new IllegalArgumentException(table + ":{} 里的 @combine:value 中的value里条件 " + ws[i] + " 不合法!" + + "PUT请求的 @combine:\"key0,key1,...\" 不允许传 |key 或 !key !"); + } + w = w.substring(1); + orList.add(w); + } + else if (w.startsWith("!")) { + if (method == PUT) { + throw new IllegalArgumentException(table + ":{} 里的 @combine:value 中的value里条件 " + ws[i] + " 不合法!" + + "PUT请求的 @combine:\"key0,key1,...\" 不允许传 |key 或 !key !"); + } + w = w.substring(1); + notList.add(w); + } + else { + orList.add(w); } - w = w.substring(1); - notList.add(w); - } - else { - orList.add(w); - } - if (w.isEmpty()) { - throw new IllegalArgumentException(table + ":{} 里的 @combine:value 中的value里条件 " + ws[i] + " 不合法!不允许为空值!"); - } - else { - if (idKey.equals(w) || idInKey.equals(w) || userIdKey.equals(w) || userIdInKey.equals(w)) { - throw new UnsupportedOperationException(table + ":{} 里的 @combine:value 中的value里 " + ws[i] + " 不合法!" - + "不允许传 [" + idKey + ", " + idInKey + ", " + userIdKey + ", " + userIdInKey + "] 其中任何一个!"); + if (w.isEmpty()) { + throw new IllegalArgumentException(table + ":{} 里的 @combine:value 中的value里条件 " + ws[i] + " 不合法!不允许为空值!"); + } + else { + if (idKey.equals(w) || idInKey.equals(w) || userIdKey.equals(w) || userIdInKey.equals(w)) { + throw new UnsupportedOperationException(table + ":{} 里的 @combine:value 中的value里 " + ws[i] + " 不合法!" + + "不允许传 [" + idKey + ", " + idInKey + ", " + userIdKey + ", " + userIdInKey + "] 其中任何一个!"); + } } + + whereList.add(w); } - whereList.add(w); + // 可重写回调方法自定义处理 // 动态设置的场景似乎很少,而且去掉后不方便用户排错!//去掉判断,有时候不在没关系,如果是对增删改等非开放请求强制要求传对应的条件,可以用 Operation.NECESSARY + if (request.containsKey(w) == false) { //和 request.get(w) == null 没区别,前面 Parser 已经过滤了 null + // throw new IllegalArgumentException(table + ":{} 里的 @combine:value 中的value里 " + ws[i] + " 对应的 " + w + " 不在它里面!"); + callback.onMissingKey4Combine(table, request, combine, ws[i], w); + } } - // 可重写回调方法自定义处理 // 动态设置的场景似乎很少,而且去掉后不方便用户排错!//去掉判断,有时候不在没关系,如果是对增删改等非开放请求强制要求传对应的条件,可以用 Operation.NECESSARY - if (request.containsKey(w) == false) { //和 request.get(w) == null 没区别,前面 Parser 已经过滤了 null - // throw new IllegalArgumentException(table + ":{} 里的 @combine:value 中的value里 " + ws[i] + " 对应的 " + w + " 不在它里面!"); - callback.onMissingKey4Combine(table, request, combine, ws[i], w); - } } } @@ -3900,7 +4212,9 @@ else if (w.startsWith("!")) { if (isWhere || (StringUtil.isName(key.replaceFirst("[+-]$", "")) == false)) { tableWhere.put(key, value); if (whereList == null || whereList.contains(key) == false) { - andList.add(key); + if (andList != null) { + andList.add(key); + } } } else if (whereList != null && whereList.contains(key)) { @@ -3911,10 +4225,13 @@ else if (whereList != null && whereList.contains(key)) { } } - combineMap.put("&", andList); - combineMap.put("|", orList); - combineMap.put("!", notList); + if (combineMap != null) { + combineMap.put("&", andList); + combineMap.put("|", orList); + combineMap.put("!", notList); + } config.setCombine(combineMap); + config.setCombineExpression(combineExpression); config.setContent(tableContent); } diff --git a/APIJSONORM/src/main/java/apijson/orm/SQLConfig.java b/APIJSONORM/src/main/java/apijson/orm/SQLConfig.java index aca544111..259788770 100755 --- a/APIJSONORM/src/main/java/apijson/orm/SQLConfig.java +++ b/APIJSONORM/src/main/java/apijson/orm/SQLConfig.java @@ -176,6 +176,9 @@ public interface SQLConfig { Map> getCombine(); SQLConfig setCombine(Map> combine); + + String getCombineExpression(); + SQLConfig setCombineExpression(String combineExpression); Map getCast(); SQLConfig setCast(Map cast); From d29d079ab867843f4fbedb73dea737663167a024 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Sun, 6 Mar 2022 03:27:39 +0800 Subject: [PATCH 0576/1181] =?UTF-8?q?=E6=9D=A1=E4=BB=B6=E7=BB=84=E5=90=88?= =?UTF-8?q?=EF=BC=9A=E8=A7=A3=E5=86=B3=20@combine:"(date>=20|=20tag&$)=20&?= =?UTF-8?q?=20name*~"=20=E8=A7=A3=E6=9E=90=E5=BC=82=E5=B8=B8=EF=BC=8C?= =?UTF-8?q?=E8=A7=A3=E5=86=B3=20@combine:"id=20|=20userId{}"=20=E5=8F=AF?= =?UTF-8?q?=E7=BB=95=E8=BF=87=E6=9D=83=E9=99=90=E6=8E=A7=E5=88=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/apijson/orm/AbstractSQLConfig.java | 62 +++++++++++-------- 1 file changed, 36 insertions(+), 26 deletions(-) diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java index cb4b0a981..608676769 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java @@ -2342,25 +2342,22 @@ public String getWhereString(boolean hasPrefix, RequestMethod method, Map usedKeySet = new HashSet<>(where.size()); - while (i < n) { // "date> | (contactIdList<> & (name*~ | tag&$))" - char c = s.charAt(i); - boolean isLast = i >= n - 1; + while (i <= n) { // "date> | (contactIdList<> & (name*~ | tag&$))" + boolean isOver = i >= n; + char c = isOver ? 0 : s.charAt(i); boolean isBlankOrRightParenthesis = c == ' ' || c == ')'; - if (isLast || isBlankOrRightParenthesis) { - if (isBlankOrRightParenthesis == false) { - key += c; - } - + if (isOver || isBlankOrRightParenthesis) { boolean isEmpty = StringUtil.isEmpty(key, true); if (isEmpty && last != ')') { - throw new IllegalArgumentException(table + ":{ @combine: '" + combine + "' } 中字符 '" + s.substring(i) + throw new IllegalArgumentException(table + ":{ @combine: '" + combine + "' } 中字符 '" + (isOver ? s : s.substring(i)) + "' 不合法!" + (c == ' ' ? "空格 ' ' " : "右括号 ')'") + " 左边缺少条件 key !逻辑连接符 & | 左右必须各一个相邻空格!" + "空格不能多也不能少!不允许首尾有空格,也不允许连续空格!左括号 ( 的右边 和 右括号 ) 的左边 都不允许有相邻空格!"); } if (isEmpty == false) { if (first == false && lastLogic <= 0) { - throw new IllegalArgumentException(table + ":{ @combine: '" + combine + "' } 中字符 '" + s.substring(i - key.length()) + "' 不合法!左边缺少 & | 其中一个逻辑连接符!"); + throw new IllegalArgumentException(table + ":{ @combine: '" + combine + "' } 中字符 '" + s.substring(i - key.length() - (isOver ? 1 : 0)) + + "' 不合法!左边缺少 & | 其中一个逻辑连接符!"); } Object value = where.get(key); @@ -2380,7 +2377,7 @@ public String getWhereString(boolean hasPrefix, RequestMethod method, Map= n || s.charAt(i + 1) != ' ') { - throw new IllegalArgumentException(table + ":{ @combine: '" + combine + "' } 中字符 '" + s.substring(0, i + 1) + if (i >= n - 1 || s.charAt(i + 1) != ' ') { + throw new IllegalArgumentException(table + ":{ @combine: '" + combine + "' } 中字符 '" + (i >= n - 1 ? s : s.substring(0, i + 1)) + "' 不合法!逻辑连接符 & 右边缺少一个空格 !逻辑连接符 & | 左右必须各一个相邻空格!空格不能多也不能少!" + "不允许首尾有空格,也不允许连续空格!左括号 ( 的右边 和 右括号 ) 的左边 都不允许有相邻空格!"); } @@ -2399,14 +2396,14 @@ else if (c == '&') { lastLogic = c; i ++; } - else if (isLast == false) { + else { key += c; } } else if (c == '|') { if (last == ' ') { - if (i >= n || s.charAt(i + 1) != ' ') { - throw new IllegalArgumentException(table + ":{ @combine: '" + combine + "' } 中字符 '" + s.substring(0, i + 1) + if (i >= n - 1 || s.charAt(i + 1) != ' ') { + throw new IllegalArgumentException(table + ":{ @combine: '" + combine + "' } 中字符 '" + (i >= n - 1 ? s : s.substring(0, i + 1)) + "' 不合法!逻辑连接符 | 右边缺少一个空格 !逻辑连接符 & | 左右必须各一个相邻空格!空格不能多也不能少!" + "不允许首尾有空格,也不允许连续空格!左括号 ( 右边和右括号 ) 左边都不允许有相邻空格!"); } @@ -2415,7 +2412,7 @@ else if (c == '|') { lastLogic = c; i ++; } - else if (isLast == false) { + else { key += c; } } @@ -2438,16 +2435,12 @@ else if (c == ')') { whereString += c; lastLogic = 0; } - else if (isLast == false) { + else { key += c; } last = c; i ++; - - if (i >= n) { - i = n - 1; - } } if (depth != 0) { @@ -2477,8 +2470,8 @@ else if (isLast == false) { if (StringUtil.isEmpty(whereString, true)) { whereString = andWhere; } - else if (StringUtil.isNotEmpty(andWhere, true)) { - whereString = andWhere + AND + "( " + whereString + " )"; + else if (StringUtil.isNotEmpty(andWhere, true)) { // andWhere 必须放后面,否则 prepared 值顺序错误 + whereString = "( " + whereString + " )" + AND + andWhere; } if (joinList != null) { @@ -4127,7 +4120,24 @@ else if (id instanceof Subquery) {} List orList = combineMap == null ? null : new ArrayList<>(); List notList = combineMap == null ? null : new ArrayList<>(); - if (combineMap != null) { + if (combineMap == null) { + if (StringUtil.isNotEmpty(combineExpression, true)) { + List banKeyList = Arrays.asList(idKey, idInKey, userIdKey, userIdInKey); + + for (String key : banKeyList) { + int index = combineExpression.indexOf(key); + if (index >= 0) { + char left = index <= 0 ? ' ' : combineExpression.charAt(index - 1); + char right = index >= combineExpression.length() - key.length() ? ' ' : combineExpression.charAt(index + key.length()); + if ((left == ' ' || left == '(') && (right == ' ' || right == ')')) { + throw new UnsupportedOperationException(table + ":{} 里的 @combine:value 中的 value 里 " + key + " 不合法!" + + "不允许传 [" + idKey + ", " + idInKey + ", " + userIdKey + ", " + userIdInKey + "] 其中任何一个!"); + } + } + } + } + } + else { //强制作为条件且放在最前面优化性能 if (id != null) { tableWhere.put(idKey, id); @@ -4178,7 +4188,7 @@ else if (w.startsWith("!")) { } else { if (idKey.equals(w) || idInKey.equals(w) || userIdKey.equals(w) || userIdInKey.equals(w)) { - throw new UnsupportedOperationException(table + ":{} 里的 @combine:value 中的value里 " + ws[i] + " 不合法!" + throw new UnsupportedOperationException(table + ":{} 里的 @combine:value 中的 value 里 " + ws[i] + " 不合法!" + "不允许传 [" + idKey + ", " + idInKey + ", " + userIdKey + ", " + userIdInKey + "] 其中任何一个!"); } } From 795c8e9ccb0e4ccc82f3bd5e01d1c865b879c8a1 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Sun, 6 Mar 2022 04:38:35 +0800 Subject: [PATCH 0577/1181] =?UTF-8?q?=E6=9D=A1=E4=BB=B6=E7=BB=84=E5=90=88?= =?UTF-8?q?=EF=BC=9A=E9=99=90=E5=88=B6=20@combine:value=20=E4=B8=AD?= =?UTF-8?q?=E7=9A=84=20value=20=E7=9A=84=E6=8B=AC=E5=8F=B7=E5=B5=8C?= =?UTF-8?q?=E5=A5=97=E6=B7=B1=E5=BA=A6=E3=80=81key=20=E6=95=B0=E9=87=8F?= =?UTF-8?q?=E3=80=81key=20=E9=87=8D=E5=A4=8D=E6=AC=A1=E6=95=B0=E7=AD=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/apijson/orm/AbstractSQLConfig.java | 77 +++++++++++++++++-- 1 file changed, 70 insertions(+), 7 deletions(-) diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java index 608676769..1541a6be1 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java @@ -37,11 +37,8 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; -import java.util.Deque; import java.util.HashMap; -import java.util.HashSet; import java.util.LinkedHashMap; -import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Map.Entry; @@ -81,7 +78,13 @@ */ public abstract class AbstractSQLConfig implements SQLConfig { private static final String TAG = "AbstractSQLConfig"; - + + public static int MAX_COMBINE_DEPTH = 2; + public static int MAX_WHERE_COUNT = 3; + public static int MAX_COMBINE_COUNT = 5; + public static int MAX_COMBINE_KEY_COUNT = 2; + public static float MAX_COMBINE_RATIO = 1.0f; + public static String DEFAULT_DATABASE = DATABASE_MYSQL; public static String DEFAULT_SCHEMA = "sys"; public static String PREFFIX_DISTINCT = "DISTINCT "; @@ -102,6 +105,7 @@ public abstract class AbstractSQLConfig implements SQLConfig { // 允许调用的 SQL 函数:当 substring 为 null 时忽略;当 substring 为 "" 时整个 value 是 raw SQL;其它情况则只是 substring 这段为 raw SQL public static final Map SQL_FUNCTION_MAP; + static { // 凡是 SQL 边界符、分隔符、注释符 都不允许,例如 ' " ` ( ) ; # -- /**/ ,以免拼接 SQL 时被注入意外可执行指令 PATTERN_RANGE = Pattern.compile("^[0-9%,!=\\<\\>/\\.\\+\\-\\*\\^]+$"); // ^[a-zA-Z0-9_*%!=<>(),"]+$ 导致 exists(select*from(Comment)) 通过! PATTERN_FUNCTION = Pattern.compile("^[A-Za-z0-9%,:_@&~`!=\\<\\>\\|\\[\\]\\{\\} /\\.\\+\\-\\*\\^\\?\\(\\)\\$]+$"); //TODO 改成更好的正则,校验前面为单词,中间为操作符,后面为值 @@ -2140,6 +2144,23 @@ public SQLConfig setCast(Map cast) { //WHERE <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< + protected int getMaxWhereCount() { + return MAX_WHERE_COUNT; + } + protected int getMaxCombineDepth() { + return MAX_COMBINE_DEPTH; + } + protected int getMaxCombineCount() { + return MAX_COMBINE_COUNT; + } + protected int getMaxCombineKeyCount() { + return MAX_COMBINE_KEY_COUNT; + } + protected float getMaxCombineRatio() { + return MAX_COMBINE_RATIO; + } + + @Override public String getCombineExpression() { return combineExpression; @@ -2329,10 +2350,26 @@ public String getWhereString(boolean hasPrefix, RequestMethod method, Map(); + } + int whereSize = where.size(); + + int maxWhereCount = getMaxWhereCount(); + if (maxWhereCount > 0 && whereSize > maxWhereCount) { + throw new IllegalArgumentException(table + ":{ key0:value0, key1:value1... } 中条件 key:value 数量 " + whereSize + " 已超过最大数量,必须在 0-" + maxWhereCount + " 内!"); + } String whereString = ""; + int maxDepth = getMaxCombineDepth(); + int maxCombineCount = getMaxCombineCount(); + int maxCombineKeyCount = getMaxCombineKeyCount(); + float maxCombineRatio = getMaxCombineRatio(); + int depth = 0; + int allCount = 0; + int n = s.length(); int i = 0; @@ -2341,7 +2378,7 @@ public String getWhereString(boolean hasPrefix, RequestMethod method, Map usedKeySet = new HashSet<>(where.size()); + Map usedKeyCountMap = new HashMap<>(whereSize); while (i <= n) { // "date> | (contactIdList<> & (name*~ | tag&$))" boolean isOver = i >= n; char c = isOver ? 0 : s.charAt(i); @@ -2359,6 +2396,17 @@ public String getWhereString(boolean hasPrefix, RequestMethod method, Map maxCombineCount && maxCombineCount > 0) { + throw new IllegalArgumentException(table + ":{ @combine: '" + combine + "' } 中字符 '" + s + "' 不合法!" + + "其中 key 数量 " + allCount + " 已超过最大值,必须在条件键值对数量 0-" + maxCombineCount + " 内!"); + } + if (1.0f*allCount/whereSize > maxCombineRatio && maxCombineRatio > 0) { + throw new IllegalArgumentException(table + ":{ @combine: '" + combine + "' } 中字符 '" + s + "' 不合法!" + + "其中 key 数量 " + allCount + " / 条件键值对数量 " + whereSize + " = " + (1.0f*allCount/whereSize) + + " 已超过 最大倍数,必须在条件键值对数量 0-" + maxCombineRatio + " 倍内!"); + } Object value = where.get(key); if (value == null) { @@ -2370,7 +2418,16 @@ public String getWhereString(boolean hasPrefix, RequestMethod method, Map maxCombineKeyCount && maxCombineKeyCount > 0) { + throw new IllegalArgumentException(table + ":{ @combine: '" + combine + "' } 中字符 '" + s + "' 不合法!其中 '" + key + + "' 重复引用,次数 " + count + " 已超过最大值,必须在 0-" + maxCombineKeyCount + " 内!"); + } + + usedKeyCountMap.put(key, count); + + whereString += "( " + wi + " )"; first = false; } @@ -2422,6 +2479,10 @@ else if (c == '(') { } depth ++; + if (depth > maxDepth && maxDepth > 0) { + throw new IllegalArgumentException(table + ":{ @combine: '" + combine + "' } 中字符 '" + s.substring(0, i + 1) + "' 不合法!括号 (()) 嵌套层级 " + depth + " 已超过最大值,必须在 0-" + maxDepth + " 内!"); + } + whereString += c; lastLogic = 0; first = true; @@ -2454,7 +2515,7 @@ else if (c == ')') { for (Entry entry : set) { key = entry == null ? null : entry.getKey(); - if (key == null || usedKeySet.contains(key)) { + if (key == null || usedKeyCountMap.containsKey(key)) { continue; } @@ -2609,6 +2670,8 @@ else if (isSideJoin) { // ^ SIDE JOIN: ! (A & B) return result; } + + public String getWhereString(boolean hasPrefix, RequestMethod method, Map where, Map> combine, List joinList, boolean verifyName) throws Exception { Set>> combineSet = combine == null ? null : combine.entrySet(); if (combineSet == null || combineSet.isEmpty()) { From 66000f747f4bc10e8053b9267e5394eb11f7a050 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Sun, 6 Mar 2022 04:42:04 +0800 Subject: [PATCH 0578/1181] =?UTF-8?q?=E8=B0=83=E6=95=B4=E6=9D=A1=E4=BB=B6?= =?UTF-8?q?=E9=94=AE=E5=80=BC=E5=AF=B9=E7=9A=84=E9=BB=98=E8=AE=A4=E6=9C=80?= =?UTF-8?q?=E5=A4=A7=E6=95=B0=E9=87=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java index 1541a6be1..5aa4c9f8e 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java @@ -80,7 +80,7 @@ public abstract class AbstractSQLConfig implements SQLConfig { private static final String TAG = "AbstractSQLConfig"; public static int MAX_COMBINE_DEPTH = 2; - public static int MAX_WHERE_COUNT = 3; + public static int MAX_WHERE_COUNT = 10; public static int MAX_COMBINE_COUNT = 5; public static int MAX_COMBINE_KEY_COUNT = 2; public static float MAX_COMBINE_RATIO = 1.0f; From 4cf7d985a56f93fc6f9766ae138fca159ae63810 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Sun, 6 Mar 2022 17:23:10 +0800 Subject: [PATCH 0579/1181] =?UTF-8?q?=E6=9D=A1=E4=BB=B6=E7=BB=84=E5=90=88?= =?UTF-8?q?=EF=BC=9A@combine:value=20=E4=B8=AD=E7=9A=84=20value=20?= =?UTF-8?q?=E6=94=AF=E6=8C=81=E9=9D=9E=E9=80=BB=E8=BE=91=E7=AC=A6=20=20!?= =?UTF-8?q?=20=EF=BC=8C=E8=A7=A3=E5=86=B3=E4=B8=8D=E5=85=81=E8=AE=B8?= =?UTF-8?q?=E8=BF=9E=E7=BB=AD=E5=B7=A6=E6=8B=AC=E5=8F=B7=20((?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/apijson/orm/AbstractSQLConfig.java | 42 +++++++++++++------ 1 file changed, 29 insertions(+), 13 deletions(-) diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java index 5aa4c9f8e..4332b3069 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java @@ -2376,6 +2376,7 @@ public String getWhereString(boolean hasPrefix, RequestMethod method, Map usedKeyCountMap = new HashMap<>(whereSize); @@ -2394,7 +2395,7 @@ public String getWhereString(boolean hasPrefix, RequestMethod method, Map maxCombineKeyCount && maxCombineKeyCount > 0) { - throw new IllegalArgumentException(table + ":{ @combine: '" + combine + "' } 中字符 '" + s + "' 不合法!其中 '" + key + throw new IllegalArgumentException(table + ":{ @combine: '" + combine + "' } 中字符 '" + s + "' 不合法!其中 '" + column + "' 重复引用,次数 " + count + " 已超过最大值,必须在 0-" + maxCombineKeyCount + " 内!"); } + usedKeyCountMap.put(column, count); - usedKeyCountMap.put(key, count); - - - whereString += "( " + wi + " )"; + whereString += "( " + getCondition(isNot, wi) + " )"; + isNot = false; first = false; } @@ -2473,8 +2475,22 @@ else if (c == '|') { key += c; } } + else if (c == '!') { + last = i < 1 ? 0 : s.charAt(i - 1); // & | 后面跳过了空格 + if (i < n - 1 && s.charAt(i + 1) == '(') { + whereString += SQL.NOT; + lastLogic = c; + } + else if (last <= 0 || last == ' ' || last == '(') { + isNot = true; +// lastLogic = c; + } + else { + key += c; + } + } else if (c == '(') { - if (key.isEmpty() == false || (i > 0 && lastLogic <= 0)) { + if (key.isEmpty() == false || (i > 0 && lastLogic <= 0 && last != '(')) { throw new IllegalArgumentException(table + ":{ @combine: '" + combine + "' } 中字符 '" + s.substring(i) + "' 不合法!左边缺少 & | 逻辑连接符!"); } @@ -3555,7 +3571,7 @@ public String getSubqueryString(Subquery subquery) throws Exception { * @param condition * @return */ - private static String getCondition(boolean not, String condition) { + public static String getCondition(boolean not, String condition) { return not ? NOT + "(" + condition + ")" : condition; } From bff0d44c35c91754d7768c32d8683d6f8696f116 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Sun, 6 Mar 2022 19:41:25 +0800 Subject: [PATCH 0580/1181] =?UTF-8?q?@combine:value=20=E5=A4=8D=E6=9D=82?= =?UTF-8?q?=E6=9D=A1=E4=BB=B6=E7=BB=84=E5=90=88=EF=BC=9A=E8=A7=A3=E5=86=B3?= =?UTF-8?q?=E6=9C=80=E7=BB=88=E6=9D=A1=E4=BB=B6=E4=B8=A2=E5=A4=B1=20id,=20?= =?UTF-8?q?id{}=EF=BC=8C=E8=A7=A3=E5=86=B3=E5=8F=AF=E4=BB=A5=E9=80=9A?= =?UTF-8?q?=E8=BF=87=20!id,=20!id{}=20=E7=BB=95=E8=BF=87=E6=9D=83=E9=99=90?= =?UTF-8?q?=E6=A0=A1=E9=AA=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/apijson/orm/AbstractSQLConfig.java | 90 +++++++++++++------ 1 file changed, 62 insertions(+), 28 deletions(-) diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java index 4332b3069..5a4675471 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java @@ -2357,7 +2357,8 @@ public String getWhereString(boolean hasPrefix, RequestMethod method, Map 0 && whereSize > maxWhereCount) { - throw new IllegalArgumentException(table + ":{ key0:value0, key1:value1... } 中条件 key:value 数量 " + whereSize + " 已超过最大数量,必须在 0-" + maxWhereCount + " 内!"); + throw new IllegalArgumentException(table + ":{ key0:value0, key1:value1... } 中条件 key:value 数量 " + whereSize + + " 已超过最大数量,必须在 0-" + maxWhereCount + " 内!"); } String whereString = ""; @@ -2367,6 +2368,9 @@ public String getWhereString(boolean hasPrefix, RequestMethod method, Map prepreadValues = getPreparedValueList(); + setPreparedValueList(new ArrayList<>()); + int depth = 0; int allCount = 0; @@ -2394,8 +2398,8 @@ public String getWhereString(boolean hasPrefix, RequestMethod method, Map maxCombineKeyCount && maxCombineKeyCount > 0) { - throw new IllegalArgumentException(table + ":{ @combine: '" + combine + "' } 中字符 '" + s + "' 不合法!其中 '" + column - + "' 重复引用,次数 " + count + " 已超过最大值,必须在 0-" + maxCombineKeyCount + " 内!"); + throw new IllegalArgumentException(table + ":{ @combine: '" + combine + "' } 中字符 '" + s + "' 不合法!" + + "其中 '" + column + "' 重复引用,次数 " + count + " 已超过最大值,必须在 0-" + maxCombineKeyCount + " 内!"); } usedKeyCountMap.put(column, count); @@ -2476,8 +2482,24 @@ else if (c == '|') { } } else if (c == '!') { - last = i < 1 ? 0 : s.charAt(i - 1); // & | 后面跳过了空格 - if (i < n - 1 && s.charAt(i + 1) == '(') { + char next = i >= n - 1 ? 0 : s.charAt(i + 1); + if (next == ' ') { + throw new IllegalArgumentException(table + ":{ @combine: '" + combine + "' } 中字符 '" + s.substring(0, i + 1) + + "' 不合法!非逻辑符 '!' 右边多了一个空格 ' ' !非逻辑符 '!' 右边不允许任何相邻空格 ' ',也不允许 ')' '&' '|' 中任何一个!"); + } + if (next == ')' || next == '&' || next == '!') { + throw new IllegalArgumentException(table + ":{ @combine: '" + combine + "' } 中字符 '" + s.substring(0, i + 1) + + "' 不合法!非逻辑符 '!' 右边多了一个字符 '" + next + "' !非逻辑符 '!' 右边不允许任何相邻空格 ' ',也不允许 ')' '&' '|' 中任何一个!"); + } + + last = i <= 0 ? 0 : s.charAt(i - 1); // & | 后面跳过了空格 + if (i > 0 && lastLogic <= 0 && last != '(') { + throw new IllegalArgumentException(table + ":{ @combine: '" + combine + "' } 中字符 '" + s.substring(i) + + "' 不合法!左边缺少 & | 逻辑连接符!逻辑连接符 & | 左右必须各一个相邻空格!空格不能多也不能少!" + + "不允许首尾有空格,也不允许连续空格!左括号 ( 的右边 和 右括号 ) 的左边 都不允许有相邻空格!"); + } + + if (next == '(') { whereString += SQL.NOT; lastLogic = c; } @@ -2491,12 +2513,15 @@ else if (last <= 0 || last == ' ' || last == '(') { } else if (c == '(') { if (key.isEmpty() == false || (i > 0 && lastLogic <= 0 && last != '(')) { - throw new IllegalArgumentException(table + ":{ @combine: '" + combine + "' } 中字符 '" + s.substring(i) + "' 不合法!左边缺少 & | 逻辑连接符!"); + throw new IllegalArgumentException(table + ":{ @combine: '" + combine + "' } 中字符 '" + s.substring(i) + + "' 不合法!左边缺少 & | 逻辑连接符!逻辑连接符 & | 左右必须各一个相邻空格!空格不能多也不能少!" + + "不允许首尾有空格,也不允许连续空格!左括号 ( 的右边 和 右括号 ) 的左边 都不允许有相邻空格!"); } depth ++; if (depth > maxDepth && maxDepth > 0) { - throw new IllegalArgumentException(table + ":{ @combine: '" + combine + "' } 中字符 '" + s.substring(0, i + 1) + "' 不合法!括号 (()) 嵌套层级 " + depth + " 已超过最大值,必须在 0-" + maxDepth + " 内!"); + throw new IllegalArgumentException(table + ":{ @combine: '" + combine + "' } 中字符 '" + s.substring(0, i + 1) + + "' 不合法!括号 (()) 嵌套层级 " + depth + " 已超过最大值,必须在 0-" + maxDepth + " 内!"); } whereString += c; @@ -2506,7 +2531,8 @@ else if (c == '(') { else if (c == ')') { depth --; if (depth < 0) { - throw new IllegalArgumentException(table + ":{ @combine: '" + combine + "' } 中字符 '" + s.substring(0, i + 1) + "' 不合法!左括号 ( 比 右括号 ) 少!数量必须相等从而完整闭合 (...) !"); + throw new IllegalArgumentException(table + ":{ @combine: '" + combine + "' } 中字符 '" + s.substring(0, i + 1) + + "' 不合法!左括号 ( 比 右括号 ) 少!数量必须相等从而完整闭合 (...) !"); } whereString += c; @@ -2521,7 +2547,8 @@ else if (c == ')') { } if (depth != 0) { - throw new IllegalArgumentException(table + ":{ @combine: '" + combine + "' } 中字符 '" + s + "' 不合法!左括号 ( 比 右括号 ) 多!数量必须相等从而完整闭合 (...) !"); + throw new IllegalArgumentException(table + ":{ @combine: '" + combine + "' } 中字符 '" + s + + "' 不合法!左括号 ( 比 右括号 ) 多!数量必须相等从而完整闭合 (...) !"); } Set> set = where.entrySet(); @@ -2548,7 +2575,11 @@ else if (c == ')') { whereString = andWhere; } else if (StringUtil.isNotEmpty(andWhere, true)) { // andWhere 必须放后面,否则 prepared 值顺序错误 - whereString = "( " + whereString + " )" + AND + andWhere; +// whereString = "( " + whereString + " )" + AND + andWhere; + + whereString = andWhere + AND + "( " + whereString + " )"; // 先暂存之前的 prepared 值,然后反向整合 + prepreadValues.addAll(getPreparedValueList()); + setPreparedValueList(prepreadValues); } if (joinList != null) { @@ -2557,7 +2588,7 @@ else if (StringUtil.isNotEmpty(andWhere, true)) { // andWhere 必须放后面 String ws = whereString; List newPvl = new ArrayList<>(); - List pvl = new ArrayList<>(preparedValueList); + List pvl = new ArrayList<>(getPreparedValueList()); SQLConfig jc; String js; @@ -2673,7 +2704,7 @@ else if (isSideJoin) { // ^ SIDE JOIN: ! (A & B) if (changed) { whereString = newWs; - preparedValueList = newPvl; + setPreparedValueList(newPvl); } } @@ -4199,6 +4230,20 @@ else if (id instanceof Subquery) {} List orList = combineMap == null ? null : new ArrayList<>(); List notList = combineMap == null ? null : new ArrayList<>(); + //强制作为条件且放在最前面优化性能 + if (id != null) { + tableWhere.put(idKey, id); + if (andList != null) { + andList.add(idKey); + } + } + if (idIn != null) { + tableWhere.put(idInKey, idIn); + if (andList != null) { + andList.add(idInKey); + } + } + if (combineMap == null) { if (StringUtil.isNotEmpty(combineExpression, true)) { List banKeyList = Arrays.asList(idKey, idInKey, userIdKey, userIdInKey); @@ -4208,7 +4253,7 @@ else if (id instanceof Subquery) {} if (index >= 0) { char left = index <= 0 ? ' ' : combineExpression.charAt(index - 1); char right = index >= combineExpression.length() - key.length() ? ' ' : combineExpression.charAt(index + key.length()); - if ((left == ' ' || left == '(') && (right == ' ' || right == ')')) { + if ((left == ' ' || left == '(' || left == '&' || left == '|' || left == '!') && (right == ' ' || right == ')')) { throw new UnsupportedOperationException(table + ":{} 里的 @combine:value 中的 value 里 " + key + " 不合法!" + "不允许传 [" + idKey + ", " + idInKey + ", " + userIdKey + ", " + userIdInKey + "] 其中任何一个!"); } @@ -4217,17 +4262,6 @@ else if (id instanceof Subquery) {} } } else { - //强制作为条件且放在最前面优化性能 - if (id != null) { - tableWhere.put(idKey, id); - andList.add(idKey); - } - if (idIn != null) { - tableWhere.put(idInKey, idIn); - andList.add(idInKey); - } - - if (ws != null) { if (method == DELETE || method == GETS || method == HEADS) { throw new IllegalArgumentException("DELETE,GETS,HEADS 请求不允许传 @combine:value !"); From cf7bdd74e4b25bed7b19eff65c1ec4a5051b86d2 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Sun, 6 Mar 2022 20:04:26 +0800 Subject: [PATCH 0581/1181] =?UTF-8?q?@combine:value=20=E5=A4=8D=E6=9D=82?= =?UTF-8?q?=E6=9D=A1=E4=BB=B6=E7=BB=84=E5=90=88=EF=BC=9A=E8=A7=A3=E5=86=B3?= =?UTF-8?q?=20key!=20=E6=8A=A5=E9=94=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/apijson/orm/AbstractSQLConfig.java | 31 ++++++++++--------- 1 file changed, 17 insertions(+), 14 deletions(-) diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java index 5a4675471..2bed0dda0 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java @@ -2441,6 +2441,7 @@ public String getWhereString(boolean hasPrefix, RequestMethod method, Map= n - 1 ? 0 : s.charAt(i + 1); - if (next == ' ') { - throw new IllegalArgumentException(table + ":{ @combine: '" + combine + "' } 中字符 '" + s.substring(0, i + 1) - + "' 不合法!非逻辑符 '!' 右边多了一个空格 ' ' !非逻辑符 '!' 右边不允许任何相邻空格 ' ',也不允许 ')' '&' '|' 中任何一个!"); - } - if (next == ')' || next == '&' || next == '!') { - throw new IllegalArgumentException(table + ":{ @combine: '" + combine + "' } 中字符 '" + s.substring(0, i + 1) - + "' 不合法!非逻辑符 '!' 右边多了一个字符 '" + next + "' !非逻辑符 '!' 右边不允许任何相邻空格 ' ',也不允许 ')' '&' '|' 中任何一个!"); - } - last = i <= 0 ? 0 : s.charAt(i - 1); // & | 后面跳过了空格 - if (i > 0 && lastLogic <= 0 && last != '(') { - throw new IllegalArgumentException(table + ":{ @combine: '" + combine + "' } 中字符 '" + s.substring(i) - + "' 不合法!左边缺少 & | 逻辑连接符!逻辑连接符 & | 左右必须各一个相邻空格!空格不能多也不能少!" - + "不允许首尾有空格,也不允许连续空格!左括号 ( 的右边 和 右括号 ) 的左边 都不允许有相邻空格!"); + + char next = i >= n - 1 ? 0 : s.charAt(i + 1); + if (last == ' ' || last == '(') { + if (next == ' ') { + throw new IllegalArgumentException(table + ":{ @combine: '" + combine + "' } 中字符 '" + s.substring(0, i + 1) + + "' 不合法!非逻辑符 '!' 右边多了一个空格 ' ' !非逻辑符 '!' 右边不允许任何相邻空格 ' ',也不允许 ')' '&' '|' 中任何一个!"); + } + if (next == ')' || next == '&' || next == '!') { + throw new IllegalArgumentException(table + ":{ @combine: '" + combine + "' } 中字符 '" + s.substring(0, i + 1) + + "' 不合法!非逻辑符 '!' 右边多了一个字符 '" + next + "' !非逻辑符 '!' 右边不允许任何相邻空格 ' ',也不允许 ')' '&' '|' 中任何一个!"); + } + if (i > 0 && lastLogic <= 0 && last != '(') { + throw new IllegalArgumentException(table + ":{ @combine: '" + combine + "' } 中字符 '" + s.substring(i) + + "' 不合法!左边缺少 & | 逻辑连接符!逻辑连接符 & | 左右必须各一个相邻空格!空格不能多也不能少!" + + "不允许首尾有空格,也不允许连续空格!左括号 ( 的右边 和 右括号 ) 的左边 都不允许有相邻空格!"); + } } if (next == '(') { From 3ea3e6121552965b21e3e79b4bde1bf0f0b1ae2f Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Sun, 6 Mar 2022 20:53:29 +0800 Subject: [PATCH 0582/1181] =?UTF-8?q?=E4=BC=98=E5=8C=96=20=20where=20?= =?UTF-8?q?=E5=92=8C=20JOIN=20=E8=A7=A3=E6=9E=90=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/apijson/orm/AbstractSQLConfig.java | 157 +++--------------- 1 file changed, 20 insertions(+), 137 deletions(-) diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java index 2bed0dda0..83e0d85c1 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java @@ -2585,132 +2585,8 @@ else if (StringUtil.isNotEmpty(andWhere, true)) { // andWhere 必须放后面 setPreparedValueList(prepreadValues); } - if (joinList != null) { - - String newWs = ""; - String ws = whereString; - - List newPvl = new ArrayList<>(); - List pvl = new ArrayList<>(getPreparedValueList()); - - SQLConfig jc; - String js; - - boolean changed = false; - //各种 JOIN 没办法统一用 & | !连接,只能按优先级,和 @combine 一样? - for (Join j : joinList) { - String jt = j.getJoinType(); - - switch (jt) { - case "*": // CROSS JOIN - case "@": // APP JOIN - case "<": // LEFT JOIN - case ">": // RIGHT JOIN - break; - - case "&": // INNER JOIN: A & B - case "": // FULL JOIN: A | B - case "|": // FULL JOIN: A | B - case "!": // OUTER JOIN: ! (A | B) - case "^": // SIDE JOIN: ! (A & B) - case "(": // ANTI JOIN: A & ! B - case ")": // FOREIGN JOIN: B & ! A - jc = j.getJoinConfig(); - boolean isMain = jc.isMain(); - jc.setMain(false).setPrepared(isPrepared()).setPreparedValueList(new ArrayList()); - js = jc.getWhereString(false); - jc.setMain(isMain); - - boolean isOuterJoin = "!".equals(jt); - boolean isSideJoin = "^".equals(jt); - boolean isAntiJoin = "(".equals(jt); - boolean isForeignJoin = ")".equals(jt); - boolean isWsEmpty = StringUtil.isEmpty(ws, true); - - if (isWsEmpty) { - if (isOuterJoin) { // ! OUTER JOIN: ! (A | B) - throw new NotExistException("no result for ! OUTER JOIN( ! (A | B) ) when A or B is empty!"); - } - if (isForeignJoin) { // ) FOREIGN JOIN: B & ! A - throw new NotExistException("no result for ) FOREIGN JOIN( B & ! A ) when A is empty!"); - } - } - - if (StringUtil.isEmpty(js, true)) { - if (isOuterJoin) { // ! OUTER JOIN: ! (A | B) - throw new NotExistException("no result for ! OUTER JOIN( ! (A | B) ) when A or B is empty!"); - } - if (isAntiJoin) { // ( ANTI JOIN: A & ! B - throw new NotExistException("no result for ( ANTI JOIN( A & ! B ) when B is empty!"); - } - - if (isWsEmpty) { - if (isSideJoin) { - throw new NotExistException("no result for ^ SIDE JOIN( ! (A & B) ) when both A and B are empty!"); - } - } - else { - if (isSideJoin || isForeignJoin) { - newWs += " ( " + getCondition(true, ws) + " ) "; - - newPvl.addAll(pvl); - newPvl.addAll(jc.getPreparedValueList()); - changed = true; - } - } - - continue; - } - - if (StringUtil.isEmpty(newWs, true) == false) { - newWs += AND; - } - - if (isAntiJoin) { // ( ANTI JOIN: A & ! B - newWs += " ( " + ( isWsEmpty ? "" : ws + AND ) + NOT + " ( " + js + " ) " + " ) "; - } - else if (isForeignJoin) { // ) FOREIGN JOIN: (! A) & B // preparedValueList.add 不好反过来 B & ! A - newWs += " ( " + NOT + " ( " + ws + " ) ) " + AND + " ( " + js + " ) "; - } - else if (isSideJoin) { // ^ SIDE JOIN: ! (A & B) - //MySQL 因为 NULL 值处理问题,(A & ! B) | (B & ! A) 与 ! (A & B) 返回结果不一样,后者往往更多 - newWs += " ( " + getCondition( - true, - ( isWsEmpty ? "" : ws + AND ) + " ( " + js + " ) " - ) + " ) "; - } - else { // & INNER JOIN: A & B; | FULL JOIN: A | B; OUTER JOIN: ! (A | B) - int logic = Logic.getType(jt); - newWs += " ( " - + getCondition( - Logic.isNot(logic), - ws - + ( isWsEmpty ? "" : (Logic.isAnd(logic) ? AND : OR) ) - + " ( " + js + " ) " - ) - + " ) "; - } - - newPvl.addAll(pvl); - newPvl.addAll(jc.getPreparedValueList()); - - changed = true; - break; - default: - throw new UnsupportedOperationException( - "join:value 中 value 里的 " + jt + "/" + j.getPath() - + "错误!不支持 " + jt + " 等 [ @ APP, < LEFT, > RIGHT, * CROSS" - + ", & INNER, | FULL, ! OUTER, ^ SIDE, ( ANTI, ) FOREIGN ] 之外的 JOIN 类型 !" - ); - } - } - - if (changed) { - whereString = newWs; - setPreparedValueList(newPvl); - } - } - + whereString = concatJoinWhereString(whereString); + String result = StringUtil.isEmpty(whereString, true) ? "" : (hasPrefix ? " WHERE " : "") + whereString; if (result.isEmpty() && RequestMethod.isQueryMethod(method) == false) { @@ -2721,7 +2597,6 @@ else if (isSideJoin) { // ^ SIDE JOIN: ! (A & B) } - public String getWhereString(boolean hasPrefix, RequestMethod method, Map where, Map> combine, List joinList, boolean verifyName) throws Exception { Set>> combineSet = combine == null ? null : combine.entrySet(); if (combineSet == null || combineSet.isEmpty()) { @@ -2776,15 +2651,28 @@ else if ("!".equals(ce.getKey())) { whereString += (isCombineFirst ? "" : AND) + (Logic.isNot(logic) ? NOT : "") + " ( " + cs + " ) "; isCombineFirst = false; } + + whereString = concatJoinWhereString(whereString); + String s = StringUtil.isEmpty(whereString, true) ? "" : (hasPrefix ? " WHERE " : "") + whereString; + if (s.isEmpty() && RequestMethod.isQueryMethod(method) == false) { + throw new UnsupportedOperationException("写操作请求必须带条件!!!"); + } + + return s; + } + + + protected String concatJoinWhereString(String whereString) throws Exception { + List joinList = getJoinList(); if (joinList != null) { String newWs = ""; String ws = whereString; List newPvl = new ArrayList<>(); - List pvl = new ArrayList<>(preparedValueList); + List pvl = new ArrayList<>(getPreparedValueList()); SQLConfig jc; String js; @@ -2873,7 +2761,7 @@ else if (isSideJoin) { // ^ SIDE JOIN: ! (A & B) ) + " ) "; } else { // & INNER JOIN: A & B; | FULL JOIN: A | B; OUTER JOIN: ! (A | B) - logic = Logic.getType(jt); + int logic = Logic.getType(jt); newWs += " ( " + getCondition( Logic.isNot(logic), @@ -2900,19 +2788,14 @@ else if (isSideJoin) { // ^ SIDE JOIN: ! (A & B) if (changed) { whereString = newWs; - preparedValueList = newPvl; + setPreparedValueList(newPvl); } } - String s = StringUtil.isEmpty(whereString, true) ? "" : (hasPrefix ? " WHERE " : "") + whereString; - - if (s.isEmpty() && RequestMethod.isQueryMethod(method) == false) { - throw new UnsupportedOperationException("写操作请求必须带条件!!!"); - } - - return s; + return whereString; } + /** * @param key * @param value From 0063721354b60ae62244b36429ddaf5c3674b389 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Sun, 6 Mar 2022 21:57:26 +0800 Subject: [PATCH 0583/1181] =?UTF-8?q?JOIN=20ON=20=E6=96=B0=E5=A2=9E?= =?UTF-8?q?=E6=94=AF=E6=8C=81=20{}=20IN=20=E5=92=8C=20<>=20json=5Fcontains?= =?UTF-8?q?=20=E4=B8=A4=E7=A7=8D=E5=85=B3=E8=81=94=E6=96=B9=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- APIJSONORM/src/main/java/apijson/SQL.java | 2 + .../java/apijson/orm/AbstractSQLConfig.java | 44 +++++++++++++++++-- .../java/apijson/orm/AbstractSQLExecutor.java | 6 ++- 3 files changed, 46 insertions(+), 6 deletions(-) diff --git a/APIJSONORM/src/main/java/apijson/SQL.java b/APIJSONORM/src/main/java/apijson/SQL.java index 397c2915f..e8b7bda6f 100755 --- a/APIJSONORM/src/main/java/apijson/SQL.java +++ b/APIJSONORM/src/main/java/apijson/SQL.java @@ -10,6 +10,8 @@ */ public class SQL { + public static final String JOIN = " JOIN "; + public static final String ON = " ON "; public static final String OR = " OR "; public static final String AND = " AND "; public static final String NOT = " NOT "; diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java index 83e0d85c1..94b65a484 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java @@ -33,6 +33,7 @@ import static apijson.SQL.AND; import static apijson.SQL.NOT; import static apijson.SQL.OR; +import static apijson.SQL.ON; import java.util.ArrayList; import java.util.Arrays; @@ -3803,8 +3804,25 @@ public String getJoinString() throws Exception { if (onList != null) { boolean first = true; for (On on : onList) { - sql += (first ? " ON " : " AND ") + quote + jt + quote + "." + quote + on.getKey() + quote + " = " - + quote + on.getTargetTable() + quote + "." + quote + on.getTargetKey() + quote; + String rt = on.getRelateType(); + if (StringUtil.isEmpty(rt, false)) { + sql += (first ? ON : AND) + quote + jt + quote + "." + quote + on.getKey() + quote + " = " + + quote + on.getTargetTable() + quote + "." + quote + on.getTargetKey() + quote; + } + else if ("{}".equals(rt)) { + sql += (first ? ON : AND) + "json_contains(" + quote + on.getTargetTable() + quote + "." + quote + on.getTargetKey() + quote + // + ", concat('\\'', " + quote + jt + quote + "." + quote + on.getKey() + quote + ", '\\''), '$')"; + + ", cast(" + quote + jt + quote + "." + quote + on.getKey() + quote + " AS CHAR), '$')"; + } + else if ("<>".equals(rt)) { + sql += (first ? ON : AND) + "json_contains(" + quote + jt + quote + "." + quote + on.getKey() + quote + // + ", concat('\\'', " + quote + on.getTargetTable() + quote + "." + quote + on.getTargetKey() + quote + ", '\\''), '$')"; + + ", cast(" + quote + on.getTargetTable() + quote + "." + quote + on.getTargetKey() + quote + " AS CHAR), '$')"; + } + else { + throw new IllegalArgumentException("join:value 中 value 里的 " + jt + "/" + j.getPath() + + " 中 JOIN ON 条件关联类型 " + rt + " 不合法!只支持 =, {}, <> 这几种!"); + } first = false; } } @@ -3826,8 +3844,26 @@ public String getJoinString() throws Exception { if (onList != null) { boolean first = true; for (On on : onList) { - sql += (first ? " ON " : " AND ") + quote + jt + quote + "." + quote + on.getKey() + quote + " = " - + quote + on.getTargetTable() + quote + "." + quote + on.getTargetKey() + quote; + String rt = on.getRelateType(); + if (StringUtil.isEmpty(rt, false)) { + sql += (first ? ON : AND) + quote + jt + quote + "." + quote + on.getKey() + quote + " = " + + quote + on.getTargetTable() + quote + "." + quote + on.getTargetKey() + quote; + } + else if ("{}".equals(rt)) { + sql += (first ? ON : AND) + "json_contains(" + quote + on.getTargetTable() + quote + "." + quote + on.getTargetKey() + quote + // + ", concat('\\'', " + quote + jt + quote + "." + quote + on.getKey() + quote + ", '\\''), '$')"; + + ", cast(" + quote + jt + quote + "." + quote + on.getKey() + quote + " AS CHAR), '$')"; + } + else if ("<>".equals(rt)) { + sql += (first ? ON : AND) + "json_contains(" + quote + jt + quote + "." + quote + on.getKey() + quote + // + ", concat('\\'', " + quote + on.getTargetTable() + quote + "." + quote + on.getTargetKey() + quote + ", '\\''), '$')"; + + ", cast(" + quote + on.getTargetTable() + quote + "." + quote + on.getTargetKey() + quote + " AS CHAR), '$')"; + } + else { + throw new IllegalArgumentException("join:value 中 value 里的 " + jt + "/" + j.getPath() + + " 中 JOIN ON 条件关联类型 " + rt + " 不合法!只支持 =, {}, <> 这几种!"); + } + first = false; } } diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java index 51705dca4..13edbfc26 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java @@ -23,7 +23,6 @@ import java.util.Arrays; import java.util.Collection; import java.util.HashMap; -import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Map.Entry; @@ -537,7 +536,8 @@ else if (config.isClickHouse() && (sqlTable.startsWith("`") || sqlTable.startsWi if (onList != null) { for (On on : onList) { if (on != null) { - viceConfig.putWhere(on.getKey(), item.get(on.getTargetKey()), true); + String ok = on.getOriginKey(); + viceConfig.putWhere(ok.substring(0, ok.length() - 1), item.get(on.getTargetKey()), true); } } } @@ -743,6 +743,8 @@ protected void executeAppJoin(SQLConfig config, List resultList, Map //缓存到 childMap if (onList != null) { for (On on : onList) { + String ok = on.getOriginKey(); + String vk = ok.substring(0, ok.length() - 1); cc.putWhere(on.getKey(), result.get(on.getKey()), true); } } From 0dc96b4681a74ebeb35435ac131f5a2978a5e4ed Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Mon, 7 Mar 2022 00:48:54 +0800 Subject: [PATCH 0584/1181] =?UTF-8?q?JOIN=20ON=20=E6=96=B0=E5=A2=9E?= =?UTF-8?q?=E6=94=AF=E6=8C=81=E6=AF=94=E8=BE=83=E8=BF=90=E7=AE=97=E7=AC=A6?= =?UTF-8?q?=20>,=20<,=20>=3D,=20<=3D=20=E5=92=8C=E5=AD=97=E7=AC=A6?= =?UTF-8?q?=E5=8C=B9=E9=85=8D=20$=20LIKE,=20~=20REGEXP?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- APIJSONORM/src/main/java/apijson/SQL.java | 11 +- .../java/apijson/orm/AbstractSQLConfig.java | 203 +++++++++++++----- .../java/apijson/orm/AbstractSQLExecutor.java | 1 + .../src/main/java/apijson/orm/Join.java | 60 +++++- .../src/main/java/apijson/orm/Logic.java | 5 + 5 files changed, 219 insertions(+), 61 deletions(-) diff --git a/APIJSONORM/src/main/java/apijson/SQL.java b/APIJSONORM/src/main/java/apijson/SQL.java index e8b7bda6f..4f2a1ccb4 100755 --- a/APIJSONORM/src/main/java/apijson/SQL.java +++ b/APIJSONORM/src/main/java/apijson/SQL.java @@ -388,7 +388,16 @@ public static String search(String s, int type, boolean ignoreCase) { return "%" + s + "%"; } } - + //search>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> + + public static boolean isBooleanOrNumber(String type) { + type = StringUtil.toUpperCase(type, true); + return type.isEmpty() || (type.endsWith("INT") && type.endsWith("POINT") == false) + || type.endsWith("BOOLEAN") || type.endsWith("ENUM") + || type.endsWith("FLOAT") || type.endsWith("DOUBLE") || type.endsWith("DECIMAL"); + } + + } diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java index 94b65a484..9dfc7d9eb 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java @@ -430,6 +430,7 @@ public abstract class AbstractSQLConfig implements SQLConfig { SQL_FUNCTION_MAP.put("nullif", ""); // NULLIF(expr1, expr2) 比较两个字符串,如果字符串 expr1 与 expr2 相等 返回 NULL,否则返回 expr1 SQL_FUNCTION_MAP.put("group_concat", ""); // GROUP_CONCAT([DISTINCT], s1, s2...) 聚合拼接字符串 SQL_FUNCTION_MAP.put("match", ""); // MATCH (name,tag) AGAINST ('a b' IN NATURAL LANGUAGE MODE) 全文检索 + SQL_FUNCTION_MAP.put("any_value", ""); // any_value(userId) 解决 ONLY_FULL_GROUP_BY 报错 @@ -3439,7 +3440,7 @@ else if (isOracle()) { condition += (condition + "has(JSONExtractArrayRaw(assumeNotNull(" + getKey(column) + "))" + ", " + getValue(key, column, v) + (StringUtil.isEmpty(path, true) ? "" : ", " + getValue(key, column, path)) + ")"); } else { - condition += ("json_contains(" + getKey(column) + ", " + getValue(key, column, v) + (StringUtil.isEmpty(path, true) ? "" : ", " + getValue(key, column, path)) + ")"); + condition += ("json_contains(" + getKey(column) + ", " + getValue(key, column, v) + (StringUtil.isEmpty(path, true) ? "" : ", " + getValue(key, column, path)) + ")"); } } } @@ -3490,7 +3491,17 @@ public String getSubqueryString(Subquery subquery) throws Exception { * @return */ public static String getCondition(boolean not, String condition) { - return not ? NOT + "(" + condition + ")" : condition; + return getCondition(not, condition, false); + } + /**拼接条件 + * @param not + * @param condition + * @param outerBreaket + * @return + */ + public static String getCondition(boolean not, String condition, boolean addOuterBracket) { + String s = not ? NOT + "(" + condition + ")" : condition; + return addOuterBracket ? "( " + s + " )" : s; } @@ -3800,32 +3811,7 @@ public String getJoinString() throws Exception { jc.setMain(true).setKeyPrefix(false); sql = ( "<".equals(type) ? " LEFT" : (">".equals(type) ? " RIGHT" : " CROSS") ) + " JOIN ( " + jc.getSQL(isPrepared()) + " ) AS " + quote + jt + quote; - - if (onList != null) { - boolean first = true; - for (On on : onList) { - String rt = on.getRelateType(); - if (StringUtil.isEmpty(rt, false)) { - sql += (first ? ON : AND) + quote + jt + quote + "." + quote + on.getKey() + quote + " = " - + quote + on.getTargetTable() + quote + "." + quote + on.getTargetKey() + quote; - } - else if ("{}".equals(rt)) { - sql += (first ? ON : AND) + "json_contains(" + quote + on.getTargetTable() + quote + "." + quote + on.getTargetKey() + quote - // + ", concat('\\'', " + quote + jt + quote + "." + quote + on.getKey() + quote + ", '\\''), '$')"; - + ", cast(" + quote + jt + quote + "." + quote + on.getKey() + quote + " AS CHAR), '$')"; - } - else if ("<>".equals(rt)) { - sql += (first ? ON : AND) + "json_contains(" + quote + jt + quote + "." + quote + on.getKey() + quote - // + ", concat('\\'', " + quote + on.getTargetTable() + quote + "." + quote + on.getTargetKey() + quote + ", '\\''), '$')"; - + ", cast(" + quote + on.getTargetTable() + quote + "." + quote + on.getTargetKey() + quote + " AS CHAR), '$')"; - } - else { - throw new IllegalArgumentException("join:value 中 value 里的 " + jt + "/" + j.getPath() - + " 中 JOIN ON 条件关联类型 " + rt + " 不合法!只支持 =, {}, <> 这几种!"); - } - first = false; - } - } + sql = concatJoinOn(sql, quote, j, jt, onList); jc.setMain(false).setKeyPrefix(true); @@ -3841,32 +3827,7 @@ else if ("<>".equals(rt)) { case "(": // ANTI JOIN: A & ! B case ")": // FOREIGN JOIN: B & ! A sql = " INNER JOIN " + jc.getTablePath(); - if (onList != null) { - boolean first = true; - for (On on : onList) { - String rt = on.getRelateType(); - if (StringUtil.isEmpty(rt, false)) { - sql += (first ? ON : AND) + quote + jt + quote + "." + quote + on.getKey() + quote + " = " - + quote + on.getTargetTable() + quote + "." + quote + on.getTargetKey() + quote; - } - else if ("{}".equals(rt)) { - sql += (first ? ON : AND) + "json_contains(" + quote + on.getTargetTable() + quote + "." + quote + on.getTargetKey() + quote - // + ", concat('\\'', " + quote + jt + quote + "." + quote + on.getKey() + quote + ", '\\''), '$')"; - + ", cast(" + quote + jt + quote + "." + quote + on.getKey() + quote + " AS CHAR), '$')"; - } - else if ("<>".equals(rt)) { - sql += (first ? ON : AND) + "json_contains(" + quote + jt + quote + "." + quote + on.getKey() + quote - // + ", concat('\\'', " + quote + on.getTargetTable() + quote + "." + quote + on.getTargetKey() + quote + ", '\\''), '$')"; - + ", cast(" + quote + on.getTargetTable() + quote + "." + quote + on.getTargetKey() + quote + " AS CHAR), '$')"; - } - else { - throw new IllegalArgumentException("join:value 中 value 里的 " + jt + "/" + j.getPath() - + " 中 JOIN ON 条件关联类型 " + rt + " 不合法!只支持 =, {}, <> 这几种!"); - } - - first = false; - } - } + sql = concatJoinOn(sql, quote, j, jt, onList); break; default: throw new UnsupportedOperationException( @@ -3902,6 +3863,140 @@ else if ("<>".equals(rt)) { return StringUtil.isEmpty(joinOns, true) ? "" : joinOns + " \n"; } + + protected String concatJoinOn(@NotNull String sql, @NotNull String quote, @NotNull Join j, @NotNull String jt, List onList) { + if (onList != null) { + boolean first = true; + for (On on : onList) { + Logic logic = on.getLogic(); + boolean isNot = logic == null ? false : logic.isNot(); + if (isNot) { + onJoinNotRelation(sql, quote, j, jt, onList, on); + } + + String rt = on.getRelateType(); + if (StringUtil.isEmpty(rt, false)) { + sql += (first ? ON : AND) + quote + jt + quote + "." + quote + on.getKey() + quote + (isNot ? " != " : " = ") + + quote + on.getTargetTable() + quote + "." + quote + on.getTargetKey() + quote; + } + else { + onJoinComplextRelation(sql, quote, j, jt, onList, on); + + if (">=".equals(rt) || "<=".equals(rt) || ">".equals(rt) || "<".equals(rt)) { + if (isNot) { + throw new IllegalArgumentException("join:value 中 value 里的 " + jt + "/" + j.getPath() + + " 中 JOIN ON 条件关联逻辑符 " + rt + " 不合法! >, <, >=, <= 不支持与或非逻辑符 & | ! !"); + } + + sql += (first ? ON : AND) + quote + jt + quote + "." + quote + on.getKey() + quote + " " + rt + " " + + quote + on.getTargetTable() + quote + "." + quote + on.getTargetKey() + quote; + } + else if ("$".equals(rt)) { + sql += (first ? ON : AND) + quote + jt + quote + "." + quote + on.getKey() + quote + (isNot ? NOT : "") + + " LIKE concat('%', " + quote + on.getTargetTable() + quote + "." + quote + on.getTargetKey() + quote + ", '%')"; + } + else if (rt.endsWith("~")) { + boolean ignoreCase = "*~".equals(rt); + if (isPostgreSQL()) { + sql += (first ? ON : AND) + quote + jt + quote + "." + quote + on.getKey() + quote + (isNot ? NOT : "") + + " ~" + (ignoreCase ? "* " : " ") + quote + on.getTargetTable() + quote + "." + quote + on.getTargetKey() + quote; + } + else if (isOracle()) { + sql += (first ? ON : AND) + "regexp_like(" + quote + jt + quote + "." + quote + on.getKey() + quote + + ", " + quote + on.getTargetTable() + quote + "." + quote + on.getTargetKey() + quote + (ignoreCase ? ", 'i'" : ", 'c'") + ")"; + } + else if (isClickHouse()) { + sql += (first ? ON : AND) + "match(" + (ignoreCase ? "lower(" : "") + quote + jt + quote + "." + quote + on.getKey() + quote + (ignoreCase ? ")" : "") + + ", " + (ignoreCase ? "lower(" : "") + quote + on.getTargetTable() + quote + "." + quote + on.getTargetKey() + quote + (ignoreCase ? ")" : "") + ")"; + } + else if (isHive()) { + sql += (first ? ON : AND) + (ignoreCase ? "lower(" : "") + quote + jt + quote + "." + quote + on.getKey() + quote + (ignoreCase ? ")" : "") + + " REGEXP " + (ignoreCase ? "lower(" : "") + quote + on.getTargetTable() + quote + "." + quote + on.getTargetKey() + quote + (ignoreCase ? ")" : ""); + } + else { + sql += (first ? ON : AND) + quote + jt + quote + "." + quote + on.getKey() + quote + (isNot ? NOT : "") + + " REGEXP " + (ignoreCase ? "" : "BINARY ") + quote + on.getTargetTable() + quote + "." + quote + on.getTargetKey() + quote; + } + } + else if ("{}".equals(rt) || "<>".equals(rt)) { + String tt = on.getTargetTable(); + String ta = on.getTargetAlias(); + + Map cast = null; + if (tt.equals(getTable()) && ((ta == null && getAlias() == null) || ta.equals(getAlias()))) { + cast = getCast(); + } + else { + boolean find = false; + for (Join jn : joinList) { + if (tt.equals(jn.getTable()) && ((ta == null && jn.getAlias() == null) || ta.equals(jn.getAlias()))) { + cast = getCast(); + find = true; + break; + } + } + + if (find == false) { + throw new IllegalArgumentException("join:value 中 value 里的 " + jt + "/" + j.getPath() + + " 中 JOIN ON 条件中找不到对应的 " + rt + " 不合法!只支持 =, {}, <> 这几种!"); + } + } + + boolean isBoolOrNum = SQL.isBooleanOrNumber(cast == null ? null : cast.get(on.getTargetKey())); + + String arrKeyPath; + String itemKeyPath; + if ("{}".equals(rt)) { + arrKeyPath = quote + on.getTargetTable() + quote + "." + quote + on.getTargetKey() + quote; + itemKeyPath = quote + jt + quote + "." + quote + on.getKey() + quote; + } + else { + arrKeyPath = quote + jt + quote + "." + quote + on.getKey() + quote; + itemKeyPath = quote + on.getTargetTable() + quote + "." + quote + on.getTargetKey() + quote; + } + + if (isPostgreSQL()) { //operator does not exist: jsonb @> character varying "[" + c + "]"); + sql += (first ? ON : AND) + (isNot ? "( " : "") + getCondition(isNot, arrKeyPath + + " IS NOT NULL AND " + arrKeyPath + " @> " + itemKeyPath) + (isNot ? ") " : ""); + } + else if (isOracle()) { + sql += (first ? ON : AND) + (isNot ? "( " : "") + getCondition(isNot, arrKeyPath + + " IS NOT NULL AND json_textcontains(" + arrKeyPath + + ", '$', " + itemKeyPath + ")") + (isNot ? ") " : ""); + } + else if (isClickHouse()) { + sql += (first ? ON : AND) + (isNot ? "( " : "") + getCondition(isNot, arrKeyPath + + " IS NOT NULL AND has(JSONExtractArrayRaw(assumeNotNull(" + arrKeyPath + "))" + + ", " + itemKeyPath + ")") + (isNot ? ") " : ""); + } + else { + sql += (first ? ON : AND) + (isNot ? "( " : "") + getCondition(isNot, arrKeyPath + + " IS NOT NULL AND json_contains(" + arrKeyPath + + (isBoolOrNum ? ", cast(" + itemKeyPath + " AS CHAR), '$')" + : ", concat('\"', " + itemKeyPath + ", '\"'), '$')" + ) + ) + (isNot ? ") " : ""); + } + } + else { + throw new IllegalArgumentException("join:value 中 value 里的 " + jt + "/" + j.getPath() + + " 中 JOIN ON 条件关联类型 " + rt + " 不合法!只支持 =, >, <, >=, <=, !=, $, ~, {}, <> 这几种!"); + } + } + + first = false; + } + } + + return sql; + } + + protected void onJoinNotRelation(String sql, String quote, Join j, String jt, List onList, On on) { +// throw new UnsupportedOperationException("JOIN 已禁用 '!' 非逻辑连接符 !性能很差、需求极少,如要取消禁用可在后端重写相关方法!"); + } + protected void onJoinComplextRelation(String sql, String quote, Join j, String jt, List onList, On on) { +// throw new UnsupportedOperationException("JOIN 已禁用 {} 和 <> 等复杂关联 !性能很差、需求极少,默认只允许等价关联,如要取消禁用可在后端重写相关方法!"); + } protected void onGetCrossJoinString(Join j) throws UnsupportedOperationException { throw new UnsupportedOperationException("已禁用 * CROSS JOIN !性能很差、需求极少,如要取消禁用可在后端重写相关方法!"); } diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java index 13edbfc26..bd5c0cf00 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java @@ -745,6 +745,7 @@ protected void executeAppJoin(SQLConfig config, List resultList, Map for (On on : onList) { String ok = on.getOriginKey(); String vk = ok.substring(0, ok.length() - 1); + //TODO 兼容复杂关联 cc.putWhere(on.getKey(), result.get(on.getKey()), true); } } diff --git a/APIJSONORM/src/main/java/apijson/orm/Join.java b/APIJSONORM/src/main/java/apijson/orm/Join.java index 24ad36ec8..bd10ec8d7 100644 --- a/APIJSONORM/src/main/java/apijson/orm/Join.java +++ b/APIJSONORM/src/main/java/apijson/orm/Join.java @@ -10,6 +10,7 @@ import com.alibaba.fastjson.JSONObject; import apijson.NotNull; +import apijson.StringUtil; /**连表 配置 * @author Lemon @@ -163,7 +164,8 @@ public static class On { private String originKey; private String originValue; - private String relateType; // "" - 一对一, "{}" - 一对多, "<>" - 多对一 + private Logic logic; // & | ! + private String relateType; // "" - 一对一, "{}" - 一对多, "<>" - 多对一, > , <= , != private String key; // id private String targetTable; // Moment private String targetAlias; // main @@ -183,6 +185,12 @@ public void setOriginValue(String originValue) { } + public Logic getLogic() { + return logic; + } + public void setLogic(Logic logic) { + this.logic = logic; + } public String getRelateType() { return relateType; } @@ -190,7 +198,6 @@ public void setRelateType(String relateType) { this.relateType = relateType; } - public String getKey() { return key; } @@ -222,22 +229,63 @@ public void setKeyAndType(String joinType, String table, @NotNull String originK originKey = originKey.substring(0, originKey.length() - 1); } else { //TODO 暂时只允许 User.id = Moment.userId 字段关联,不允许 User.id = 82001 这种 - throw new IllegalArgumentException(joinType + "/.../" + table + "/" + originKey + " 不合法!join:'.../refKey'" + " 中 refKey 必须以 @ 结尾!"); + throw new IllegalArgumentException(joinType + "/.../" + table + "/" + originKey + " 中字符 " + originKey + " 不合法!join:'.../refKey'" + " 中 refKey 必须以 @ 结尾!"); } + String k; + if (originKey.endsWith("{}")) { setRelateType("{}"); - setKey(originKey.substring(0, originKey.length() - 2)); + k = originKey.substring(0, originKey.length() - 2); } else if (originKey.endsWith("<>")) { setRelateType("<>"); - setKey(originKey.substring(0, originKey.length() - 2)); + k = originKey.substring(0, originKey.length() - 2); + } + else if (originKey.endsWith("$")) { + setRelateType("$"); + k = originKey.substring(0, originKey.length() - 1); + } + else if (originKey.endsWith("~")) { + boolean ignoreCase = originKey.endsWith("*~"); + setRelateType(ignoreCase ? "*~" : "~"); + k = originKey.substring(0, originKey.length() - (ignoreCase ? 2 : 1)); + } + else if (originKey.endsWith(">=")) { + setRelateType(">="); + k = originKey.substring(0, originKey.length() - 2); + } + else if (originKey.endsWith("<=")) { + setRelateType("<="); + k = originKey.substring(0, originKey.length() - 2); + } + else if (originKey.endsWith(">")) { + setRelateType(">"); + k = originKey.substring(0, originKey.length() - 1); + } + else if (originKey.endsWith("<")) { + setRelateType("<"); + k = originKey.substring(0, originKey.length() - 1); } else { setRelateType(""); - setKey(originKey); + k = originKey; } + + if (k != null && (k.contains("&") || k.contains("|"))) { + throw new UnsupportedOperationException(joinType + "/.../" + table + "/" + originKey + " 中字符 " + k + " 不合法!与或非逻辑符仅支持 '!' 非逻辑符 !"); + } + + Logic l = new Logic(k); + setLogic(l); + + if (StringUtil.isName(l.getKey()) == false) { + throw new IllegalArgumentException(joinType + "/.../" + table + "/" + originKey + " 中字符 " + l.getKey() + " 不合法!必须符合字段命名格式!"); + } + + setKey(l.getKey()); } + } diff --git a/APIJSONORM/src/main/java/apijson/orm/Logic.java b/APIJSONORM/src/main/java/apijson/orm/Logic.java index b795ae961..cfc08d016 100755 --- a/APIJSONORM/src/main/java/apijson/orm/Logic.java +++ b/APIJSONORM/src/main/java/apijson/orm/Logic.java @@ -30,6 +30,7 @@ public class Logic { private int type; private String key; + private String originKey; public Logic() { super(); @@ -40,6 +41,7 @@ public Logic(int type) { this.type = type; } public Logic(String key) { + this.originKey = key; key = StringUtil.getString(key); int type = getType(key.isEmpty() ? "" : key.substring(key.length() - 1)); @@ -71,6 +73,9 @@ public String getKey() { public void setKey(String key) { this.key = key; } + public String getOriginKey() { + return originKey; + } public boolean isOr() { From 895917ba987e656d7866dd2f9a35b218e43758fb Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Mon, 7 Mar 2022 00:51:24 +0800 Subject: [PATCH 0585/1181] =?UTF-8?q?JOIN=20=E9=BB=98=E8=AE=A4=E7=A6=81?= =?UTF-8?q?=E7=94=A8=20!=20=E9=9D=9E=E9=80=BB=E8=BE=91=E7=AC=A6=E5=92=8C?= =?UTF-8?q?=E5=A4=8D=E6=9D=82=E5=85=B3=E8=81=94=E6=96=B9=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java index 9dfc7d9eb..8dfcc425e 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java @@ -3992,10 +3992,10 @@ else if (isClickHouse()) { } protected void onJoinNotRelation(String sql, String quote, Join j, String jt, List onList, On on) { -// throw new UnsupportedOperationException("JOIN 已禁用 '!' 非逻辑连接符 !性能很差、需求极少,如要取消禁用可在后端重写相关方法!"); + throw new UnsupportedOperationException("JOIN 已禁用 '!' 非逻辑连接符 !性能很差、需求极少,如要取消禁用可在后端重写相关方法!"); } protected void onJoinComplextRelation(String sql, String quote, Join j, String jt, List onList, On on) { -// throw new UnsupportedOperationException("JOIN 已禁用 {} 和 <> 等复杂关联 !性能很差、需求极少,默认只允许等价关联,如要取消禁用可在后端重写相关方法!"); + throw new UnsupportedOperationException("JOIN 已禁用 $, ~, {}, <>, >, <, >=, <= 等复杂关联 !性能很差、需求极少,默认只允许等价关联,如要取消禁用可在后端重写相关方法!"); } protected void onGetCrossJoinString(Join j) throws UnsupportedOperationException { throw new UnsupportedOperationException("已禁用 * CROSS JOIN !性能很差、需求极少,如要取消禁用可在后端重写相关方法!"); From 3c8058ee27113963c56aba968c9815d9e32affd9 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Sun, 13 Mar 2022 15:52:29 +0800 Subject: [PATCH 0586/1181] Update README.md --- README.md | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 8e8f72995..e0d4110cf 100644 --- a/README.md +++ b/README.md @@ -155,22 +155,21 @@ https://www.bilibili.com/video/BV1yv411p7Y4 前后端 关于接口的 开发、文档、联调 等 10 大痛点解析
https://github.com/Tencent/APIJSON/wiki -* **解决十大痛点** (APIJSON 可帮助用户 提振开发效率、杜绝联调扯皮、规避文档缺陷、节省流量带宽 等) -* **开发提速很大** (CRUD 零代码热更新自动化,APIJSONBoot 对比 SSM、SSH 等保守估计可提速 20 倍以上) +* **解决十大痛点** (可帮前后端开发大幅提振开发效率、强力杜绝联调扯皮、巧妙规避文档缺陷、非常节省流量带宽等) +* **开发提速很大** (CRUD 零代码热更新全自动,APIJSONBoot 对比 SSM、SSH 等保守估计可提速 20 倍以上) * **腾讯官方开源** (使用 GitHub、Gitee、工蜂 等平台的官方账号开源,微信公众号、腾讯云+社区 等官方公告) * **社区影响力大** (GitHub 1W+ Star 在 350W Java 项目中排名前 120,远超 FLAG, BAT 等国内外绝大部分开源项目) * **多样用户案例** (腾讯内部用户包含 互娱、音乐、云与智慧,外部用户包含 500 强上市公司、数千亿资本国企 等) * **适用场景广泛** (社交聊天、阅读资讯、影音视频、办公学习 等各种 App、网站、公众号、小程序 等非金融类项目) * **周边生态丰富** (Android, iOS, Web 等各种 Demo、继承 JSON 的海量生态、零代码 接口测试 和 单元测试 工具等) * **文档视频齐全** (项目介绍、快速上手、安装部署 等后端、前端、客户端的 图文解说、视频教程、代码注释 等) -* **功能丰富强大** (增删改查、分页排序、分组聚合、各种 JOIN、各种子查询、跨库跨表、性能分析 等零代码实现) -* **使用安全简单** (自动增删改查、自动生成文档、自动管理版本、自动控制权限、自动校验参数、自动防SQL注入等) +* **功能丰富强大** (增删改查、分页排序、分组聚合、各种条件、各种 JOIN、各种子查询、跨库连表 等零代码实现) +* **使用安全简单** (自动增删改查、自动生成文档、自动管理版本、自动控制权限、自动校验参数、自动防 SQL 注入等) * **灵活定制业务** (在后端编写 远程函数,可以拿到 session、version、当前 JSON 对象 等,然后自定义处理) * **高质可靠代码** (代码严谨规范,商业分析软件源伞 Pinpoint 代码扫描报告平均每行代码 Bug 率低至 0.15%) * **兼容各种项目** (协议不限 HTTP,与其它库无冲突,对各类 Web 框架集成友好且提供 SpringBoot, JFinal 的示例) * **工程轻量小巧** (仅依赖 fastjson,Jar 仅 280KB,Java 文件仅 59 个共 13719 行代码,例如 APIJSONORM 4.3.1) -* **多年持续迭代** (自 2016 年开源至今已连续维护 5 年,累计 2000+ Commits、80+ Releases,不断更新迭代中...) - +* **多年持续迭代** (自 2016 年开源至今已连续维护 5 年多,累计 2000+ Commits、80+ Releases,不断更新迭代中...) ### 常见问题 #### 1.如何定制业务逻辑? From 29d8d1ef1f0848bb49dd73a2dc7d5fcf5daa97e5 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Sun, 13 Mar 2022 21:50:57 +0800 Subject: [PATCH 0587/1181] =?UTF-8?q?JOIN=20ON=20=E5=8F=8A=E6=99=AE?= =?UTF-8?q?=E9=80=9A=E6=9D=A1=E4=BB=B6=E6=96=B0=E5=A2=9E=E6=94=AF=E6=8C=81?= =?UTF-8?q?=E5=9C=A8=20key$:value=20=E7=9A=84=20key=20=E4=B8=AD=E5=AE=9A?= =?UTF-8?q?=E5=88=B6=E5=8D=A0=E4=BD=8D=E7=AC=A6=20%,=20=5F=20=E4=B8=8E=20v?= =?UTF-8?q?alue=20=E7=9A=84=E6=8B=BC=E6=8E=A5=E6=96=B9=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/apijson/orm/AbstractSQLConfig.java | 80 +++++++++++++++++-- .../src/main/java/apijson/orm/Join.java | 27 ++++++- 2 files changed, 99 insertions(+), 8 deletions(-) diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java index 8dfcc425e..9e4a328bd 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java @@ -3891,9 +3891,55 @@ protected String concatJoinOn(@NotNull String sql, @NotNull String quote, @NotNu sql += (first ? ON : AND) + quote + jt + quote + "." + quote + on.getKey() + quote + " " + rt + " " + quote + on.getTargetTable() + quote + "." + quote + on.getTargetKey() + quote; } - else if ("$".equals(rt)) { - sql += (first ? ON : AND) + quote + jt + quote + "." + quote + on.getKey() + quote + (isNot ? NOT : "") - + " LIKE concat('%', " + quote + on.getTargetTable() + quote + "." + quote + on.getTargetKey() + quote + ", '%')"; + else if (rt.endsWith("$")) { + String t = rt.substring(0, rt.length() - 1); + char r = t.isEmpty() ? 0 : t.charAt(t.length() - 1); + + char l; + if (r == '%' || r == '_' || r == '?') { + t = t.substring(0, t.length() - 1); + + if (t.isEmpty()) { + if (r == '?') { + throw new IllegalArgumentException(on.getOriginKey() + ":value 中字符 " + on.getOriginKey() + " 不合法!key$:value 中不允许只有单独的 '?',必须和 '%', '_' 之一配合使用 !"); + } + + l = r; + } + else { + l = t.charAt(t.length() - 1); + if (l == '%' || l == '_' || l == '?') { + if (l == r) { + throw new IllegalArgumentException(on.getOriginKey() + ":value 中字符 " + t + " 不合法!key$:value 中不允许 key 中有连续相同的占位符!"); + } + + t = t.substring(0, t.length() - 1); + } + else if (l > 0 && StringUtil.isName(String.valueOf(l))) { + l = r; + } + } + + if (l == '?') { + l = 0; + } + if (r == '?') { + r = 0; + } + } + else { + l = r = 0; + } + + if (l <= 0 && r <= 0) { + sql += (first ? ON : AND) + quote + jt + quote + "." + quote + on.getKey() + quote + (isNot ? NOT : "") + + " LIKE " + quote + on.getTargetTable() + quote + "." + quote + on.getTargetKey() + quote; + } + else { + sql += (first ? ON : AND) + quote + jt + quote + "." + quote + on.getKey() + quote + (isNot ? NOT : "") + + (l <= 0 ? " LIKE concat(" : " LIKE concat('" + l + "', ") + quote + on.getTargetTable() + quote + + "." + quote + on.getTargetKey() + quote + (r <= 0 ? ")" : ", '" + r + "')"); + } } else if (rt.endsWith("~")) { boolean ignoreCase = "*~".equals(rt); @@ -3992,10 +4038,10 @@ else if (isClickHouse()) { } protected void onJoinNotRelation(String sql, String quote, Join j, String jt, List onList, On on) { - throw new UnsupportedOperationException("JOIN 已禁用 '!' 非逻辑连接符 !性能很差、需求极少,如要取消禁用可在后端重写相关方法!"); +// throw new UnsupportedOperationException("JOIN 已禁用 '!' 非逻辑连接符 !性能很差、需求极少,如要取消禁用可在后端重写相关方法!"); } protected void onJoinComplextRelation(String sql, String quote, Join j, String jt, List onList, On on) { - throw new UnsupportedOperationException("JOIN 已禁用 $, ~, {}, <>, >, <, >=, <= 等复杂关联 !性能很差、需求极少,默认只允许等价关联,如要取消禁用可在后端重写相关方法!"); +// throw new UnsupportedOperationException("JOIN 已禁用 $, ~, {}, <>, >, <, >=, <= 等复杂关联 !性能很差、需求极少,默认只允许等价关联,如要取消禁用可在后端重写相关方法!"); } protected void onGetCrossJoinString(Join j) throws UnsupportedOperationException { throw new UnsupportedOperationException("已禁用 * CROSS JOIN !性能很差、需求极少,如要取消禁用可在后端重写相关方法!"); @@ -4599,7 +4645,27 @@ public static String getRealKey(RequestMethod method, String originKey String key = new String(originKey); if (key.endsWith("$")) {//搜索 LIKE,查询时处理 - key = key.substring(0, key.length() - 1); + String k = key.substring(0, key.length() - 1); + // key%$:"a" -> key LIKE '%a%'; key?%$:"a" -> key LIKE 'a%'; key_?$:"a" -> key LIKE '_a'; key_%$:"a" -> key LIKE '_a%' + char c = k.isEmpty() ? 0 : k.charAt(k.length() - 1); + + if (c == '%' || c == '_' || c == '?') { + k = k.substring(0, k.length() - 1); + + char c2 = k.isEmpty() ? 0 : k.charAt(k.length() - 1); + if (c2 == '%' || c2 == '_' || c2 == '?') { + if (c2 == c) { + throw new IllegalArgumentException(originKey + ":value 中字符 " + k + " 不合法!key$:value 中不允许 key 中有连续相同的占位符!"); + } + + k = k.substring(0, k.length() - 1); + } + else if (c == '?') { + throw new IllegalArgumentException(originKey + ":value 中字符 " + originKey + " 不合法!key$:value 中不允许只有单独的 '?',必须和 '%', '_' 之一配合使用 !"); + } + } + + key = k; } else if (key.endsWith("~")) {//匹配正则表达式 REGEXP,查询时处理 key = key.substring(0, key.length() - 1); @@ -4648,6 +4714,8 @@ else if (key.endsWith("-")) {//缩减,PUT查询时处理 } } + //TODO if (key.endsWith("-")) { // 表示 key 和 value 顺序反过来: value LIKE key + String last = null;//不用Logic优化代码,否则 key 可能变为 key| 导致 key=value 变成 key|=value 而出错 if (RequestMethod.isQueryMethod(method)) {//逻辑运算符仅供GET,HEAD方法使用 last = key.isEmpty() ? "" : key.substring(key.length() - 1); diff --git a/APIJSONORM/src/main/java/apijson/orm/Join.java b/APIJSONORM/src/main/java/apijson/orm/Join.java index bd10ec8d7..4fb34b0a9 100644 --- a/APIJSONORM/src/main/java/apijson/orm/Join.java +++ b/APIJSONORM/src/main/java/apijson/orm/Join.java @@ -242,9 +242,30 @@ else if (originKey.endsWith("<>")) { setRelateType("<>"); k = originKey.substring(0, originKey.length() - 2); } - else if (originKey.endsWith("$")) { - setRelateType("$"); + else if (originKey.endsWith("$")) { // key%$:"a" -> key LIKE '%a%'; key?%$:"a" -> key LIKE 'a%'; key_?$:"a" -> key LIKE '_a'; key_%$:"a" -> key LIKE '_a%' k = originKey.substring(0, originKey.length() - 1); + char c = k.isEmpty() ? 0 : k.charAt(k.length() - 1); + + String t = "$"; + if (c == '%' || c == '_' || c == '?') { + t = c + t; + k = k.substring(0, k.length() - 1); + + char c2 = k.isEmpty() ? 0 : k.charAt(k.length() - 1); + if (c2 == '%' || c2 == '_' || c2 == '?') { + if (c2 == c) { + throw new IllegalArgumentException(originKey + ":value 中字符 " + k + " 不合法!key$:value 中不允许 key 中有连续相同的占位符!"); + } + + t = c2 + t; + k = k.substring(0, k.length() - 1); + } + else if (c == '?') { + throw new IllegalArgumentException(originKey + ":value 中字符 " + originKey + " 不合法!key$:value 中不允许只有单独的 '?',必须和 '%', '_' 之一配合使用 !"); + } + } + + setRelateType(t); } else if (originKey.endsWith("~")) { boolean ignoreCase = originKey.endsWith("*~"); @@ -276,6 +297,8 @@ else if (originKey.endsWith("<")) { throw new UnsupportedOperationException(joinType + "/.../" + table + "/" + originKey + " 中字符 " + k + " 不合法!与或非逻辑符仅支持 '!' 非逻辑符 !"); } + //TODO if (c3 == '-') { // 表示 key 和 value 顺序反过来: value LIKE key + Logic l = new Logic(k); setLogic(l); From 96ee9dd23c5b0fdec988c377fdcc3f260718b3f7 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Sun, 13 Mar 2022 23:52:29 +0800 Subject: [PATCH 0588/1181] =?UTF-8?q?LIKE:=20=E6=94=AF=E6=8C=81=E9=9D=9E?= =?UTF-8?q?=20JOIN=20ON=20=E5=BC=95=E7=94=A8=E8=B5=8B=E5=80=BC=E4=B9=9F?= =?UTF-8?q?=E8=83=BD=E7=94=A8=20key%$:value=20=E6=A0=BC=E5=BC=8F=EF=BC=8C?= =?UTF-8?q?=E5=B9=B6=E4=B8=94=E7=BB=99=20key%$:"%"=20=E4=B8=AD=E7=9A=84?= =?UTF-8?q?=E7=89=B9=E6=AE=8A=E7=AC=A6=E5=8F=B7=E8=BD=AC=E4=B9=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/apijson/orm/AbstractSQLConfig.java | 52 +++++++++++++++++-- 1 file changed, 49 insertions(+), 3 deletions(-) diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java index 9e4a328bd..5598c9685 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java @@ -2982,7 +2982,7 @@ public Object getSQLValue(@NotNull Object value) { return SQL.NULL; } // return (value instanceof Number || value instanceof Boolean) && DATABASE_POSTGRESQL.equals(getDatabase()) ? value : "'" + value + "'"; - return (value instanceof Number || value instanceof Boolean) ? value : "'" + value.toString().replaceAll("'", "\\'") + "'"; //MySQL 隐式转换用不了索引 + return (value instanceof Number || value instanceof Boolean) ? value : "'" + value.toString().replaceAll("\\'", "\\\\'") + "'"; //MySQL 隐式转换用不了索引 } @Override @@ -3044,7 +3044,7 @@ public String getSearchString(String key, String column, Object[] values, int ty // throw new IllegalArgumentException(key + "$:value 中 value 值 " + v + " 中包含 %% !不允许有连续的 % !"); // } - condition += (i <= 0 ? "" : (Logic.isAnd(type) ? AND : OR)) + getLikeString(key, column, v); + condition += (i <= 0 ? "" : (Logic.isAnd(type) ? AND : OR)) + getLikeString(key, column, (String) v); } return getCondition(Logic.isNot(type), condition); @@ -3057,7 +3057,53 @@ public String getSearchString(String key, String column, Object[] values, int ty * @return key LIKE 'value' */ @JSONField(serialize = false) - public String getLikeString(String key, String column, Object value) { + public String getLikeString(@NotNull String key, @NotNull String column, String value) { + String k = key.substring(0, key.length() - 1); + char r = k.charAt(k.length() - 1); + + char l; + if (r == '%' || r == '_' || r == '?') { + k = k.substring(0, k.length() - 1); + + l = k.charAt(k.length() - 1); + if (l == '%' || l == '_' || l == '?') { + if (l == r) { + throw new IllegalArgumentException(key + ":value 中字符 " + k + " 不合法!key$:value 中不允许 key 中有连续相同的占位符!"); + } + + k = k.substring(0, k.length() - 1); + } + else if (l > 0 && StringUtil.isName(String.valueOf(l))) { + l = r; + } + + if (l == '?') { + l = 0; + } + if (r == '?') { + r = 0; + } + } + else { + l = r = 0; + } + + if (l > 0 || r > 0) { + if (value == null) { + throw new IllegalArgumentException(key + ":value 中 value 为 null!key$:value 中 value 不能为 null,且类型必须是 String !"); + } + + value = value.replaceAll("\\\\", "\\\\\\\\"); + value = value.replaceAll("\\%", "\\\\%"); + value = value.replaceAll("\\_", "\\\\_"); + if (l > 0) { + value = l + value; + } + if (r > 0) { + value = value + r; + } + } + return getKey(column) + " LIKE " + getValue(key, column, value); } From 38c19975ea67efb07c1ea71a52b4556614e242e5 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Mon, 14 Mar 2022 00:07:07 +0800 Subject: [PATCH 0589/1181] =?UTF-8?q?*=20CROSS=20JOIN=20=E5=85=81=E8=AE=B8?= =?UTF-8?q?=E6=B2=A1=E6=9C=89=20JOIN=20ON=20=E5=BC=95=E7=94=A8=E8=B5=8B?= =?UTF-8?q?=E5=80=BC=E5=85=B3=E8=81=94=E6=9D=A1=E4=BB=B6=EF=BC=9B=E9=BB=98?= =?UTF-8?q?=E8=AE=A4=E7=A6=81=E7=94=A8=20JOIN=20ON=20=E5=A4=8D=E6=9D=82?= =?UTF-8?q?=E5=85=B3=E8=81=94=E6=96=B9=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- APIJSONORM/src/main/java/apijson/orm/AbstractParser.java | 2 +- APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java b/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java index 5bfa7da93..733414049 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java @@ -1494,7 +1494,7 @@ else if (join != null){ } Set> refSet = refObj.entrySet(); - if (refSet.isEmpty()) { + if (refSet.isEmpty() && "*".equals(joinType) == false) { throw new IllegalArgumentException(JSONRequest.KEY_JOIN + ":value 中 value 的 alias 值 " + alias + " 不合法!" + "必须为 &/Table0, onList, On on) { -// throw new UnsupportedOperationException("JOIN 已禁用 '!' 非逻辑连接符 !性能很差、需求极少,如要取消禁用可在后端重写相关方法!"); + throw new UnsupportedOperationException("JOIN 已禁用 '!' 非逻辑连接符 !性能很差、需求极少,如要取消禁用可在后端重写相关方法!"); } protected void onJoinComplextRelation(String sql, String quote, Join j, String jt, List onList, On on) { -// throw new UnsupportedOperationException("JOIN 已禁用 $, ~, {}, <>, >, <, >=, <= 等复杂关联 !性能很差、需求极少,默认只允许等价关联,如要取消禁用可在后端重写相关方法!"); + throw new UnsupportedOperationException("JOIN 已禁用 $, ~, {}, <>, >, <, >=, <= 等复杂关联 !性能很差、需求极少,默认只允许 = 等价关联,如要取消禁用可在后端重写相关方法!"); } protected void onGetCrossJoinString(Join j) throws UnsupportedOperationException { throw new UnsupportedOperationException("已禁用 * CROSS JOIN !性能很差、需求极少,如要取消禁用可在后端重写相关方法!"); From c39cd1ec7c5b568cfbeac19e82e4688a9c26a679 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Mon, 14 Mar 2022 01:55:32 +0800 Subject: [PATCH 0590/1181] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E6=95=B0=E7=BB=84?= =?UTF-8?q?=E5=85=B3=E9=94=AE=E8=AF=8D=20compat=20=E8=A7=A3=E5=86=B3?= =?UTF-8?q?=E5=AF=B9=E8=81=9A=E5=90=88=E5=87=BD=E6=95=B0=E5=AD=97=E6=AE=B5?= =?UTF-8?q?=E9=80=9A=E8=BF=87=20query:2=20=E5=88=86=E9=A1=B5=E6=9F=A5?= =?UTF-8?q?=E6=80=BB=E6=95=B0=E8=BF=94=E5=9B=9E=E5=80=BC=E9=94=99=E8=AF=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/main/java/apijson/JSONRequest.java | 2 + APIJSONORM/src/main/java/apijson/SQL.java | 33 ++++---- .../main/java/apijson/orm/AbstractParser.java | 35 +++++++- .../java/apijson/orm/AbstractSQLConfig.java | 81 +++++++++++++++---- .../src/main/java/apijson/orm/SQLConfig.java | 3 + 5 files changed, 120 insertions(+), 34 deletions(-) diff --git a/APIJSONORM/src/main/java/apijson/JSONRequest.java b/APIJSONORM/src/main/java/apijson/JSONRequest.java index 5b49f608e..8707cc2c0 100755 --- a/APIJSONORM/src/main/java/apijson/JSONRequest.java +++ b/APIJSONORM/src/main/java/apijson/JSONRequest.java @@ -87,6 +87,7 @@ public JSONRequest setFormat(Boolean format) { public static final String SUBQUERY_RANGE_ANY = "ANY"; public static final String KEY_QUERY = "query"; + public static final String KEY_COMPAT = "compat"; public static final String KEY_COUNT = "count"; public static final String KEY_PAGE = "page"; public static final String KEY_JOIN = "join"; @@ -97,6 +98,7 @@ public JSONRequest setFormat(Boolean format) { static { ARRAY_KEY_LIST = new ArrayList(); ARRAY_KEY_LIST.add(KEY_QUERY); + ARRAY_KEY_LIST.add(KEY_COMPAT); ARRAY_KEY_LIST.add(KEY_COUNT); ARRAY_KEY_LIST.add(KEY_PAGE); ARRAY_KEY_LIST.add(KEY_JOIN); diff --git a/APIJSONORM/src/main/java/apijson/SQL.java b/APIJSONORM/src/main/java/apijson/SQL.java index 4f2a1ccb4..391d5db48 100755 --- a/APIJSONORM/src/main/java/apijson/SQL.java +++ b/APIJSONORM/src/main/java/apijson/SQL.java @@ -21,7 +21,7 @@ public class SQL { public static final String IS_NOT = " IS NOT "; public static final String IS_NULL = " IS NULL "; public static final String IS_NOT_NULL = " IS NOT NULL "; - + //括号必须紧跟函数名! count (...) 报错! public static final String COUNT = "count"; public static final String SUM = "sum"; @@ -191,7 +191,7 @@ public static String indexOf(String s, String c) { public static String replace(String s, String c1, String c2) { return "replace(" + s + ", " + c1 + ", " + c2 + ")"; } - + /** * @param s1 * @param s2 @@ -225,11 +225,11 @@ public static String toLowerCase(String s) { return "lower(" + s + ")"; } - - + + //column and function<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< - + /**字段 * @param column * @return column.isEmpty() ? "*" : column; @@ -245,15 +245,16 @@ public static String column(String column) { public static String columnAs(String column) { return count(column) + AS; } - + /**函数 * @param column if (StringUtil.isEmpty(column, true) || column.contains(",")) -> column = null; * @return " " + fun + "(" + {@link #column(String)} + ") "; */ public static String function(String fun, String column) { - if (StringUtil.isEmpty(column, true) || column.contains(",")) { - column = null; //解决 count(id,name) 这种多个字段导致的SQL异常 - } + // 支持 fun(col1,col2..) + // if (StringUtil.isEmpty(column, true) || column.contains(",")) { + // column = null; //解决 count(id,name) 这种多个字段导致的SQL异常 + // } return " " + fun + "(" + column(column) + ") "; } /**有别名的函数 @@ -263,7 +264,7 @@ public static String function(String fun, String column) { public static String functionAs(String fun, String column) { return function(fun, column) + AS + fun + " "; } - + /**计数 * column = null * @return {@link #count(String)} @@ -313,9 +314,9 @@ public static String avg(String column) { } //column and function>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> - - - + + + //search<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< public static final int SEARCH_TYPE_CONTAIN_FULL = 0; @@ -391,13 +392,13 @@ public static String search(String s, int type, boolean ignoreCase) { //search>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> - + public static boolean isBooleanOrNumber(String type) { type = StringUtil.toUpperCase(type, true); return type.isEmpty() || (type.endsWith("INT") && type.endsWith("POINT") == false) || type.endsWith("BOOLEAN") || type.endsWith("ENUM") || type.endsWith("FLOAT") || type.endsWith("DOUBLE") || type.endsWith("DECIMAL"); } - - + + } diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java b/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java index 733414049..2d0f7748c 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java @@ -42,6 +42,7 @@ import apijson.Log; import apijson.NotNull; import apijson.RequestMethod; +import apijson.SQL; import apijson.StringUtil; import apijson.orm.exception.ConditionErrorException; import apijson.orm.exception.ConflictException; @@ -1051,9 +1052,33 @@ public JSONObject onObjectParse(final JSONObject request //total 这里不能用arrayConfig.getType(),因为在createObjectParser.onChildParse传到onObjectParse时已被改掉 if (type == SQLConfig.TYPE_ITEM_CHILD_0 && query != JSONRequest.QUERY_TABLE && position == 0) { - RequestMethod method = op.getMethod(); - JSONObject rp = op.setMethod(RequestMethod.HEAD).setSQLConfig().executeSQL().getSqlReponse(); - op.setMethod(method); + JSONObject rp; + Boolean compat = arrayConfig.getCompat(); + if (compat != null && compat) { + // 解决对聚合函数字段通过 query:2 分页查总数返回值错误 + // 这里可能改变了内部的一些数据,下方通过 arrayConfig 还原 + SQLConfig cfg = op.setSQLConfig(0, 0, 0).getSQLConfig(); + boolean isExplain = cfg.isExplain(); + cfg.setExplain(false); + + Subquery subq = new Subquery(); + subq.setFrom(cfg.getTable()); + subq.setConfig(cfg); + + SQLConfig countSQLCfg = createSQLConfig(); + countSQLCfg.setColumn(Arrays.asList("count(*):count")); + countSQLCfg.setFrom(subq); + + rp = executeSQL(countSQLCfg, false); + + cfg.setExplain(isExplain); + } + else { + // 对聚合函数字段通过 query:2 分页查总数返回值错误 + RequestMethod method = op.getMethod(); + rp = op.setMethod(RequestMethod.HEAD).setSQLConfig().executeSQL().getSqlReponse(); + op.setMethod(method); + } if (rp != null) { int index = parentPath.lastIndexOf("]/"); @@ -1147,6 +1172,7 @@ public JSONArray onArrayParse(JSONObject request, String parentPath, String name //不能改变,因为后面可能继续用到,导致1以上都改变 []:{0:{Comment[]:{0:{Comment:{}},1:{...},...}},1:{...},...} final String query = request.getString(JSONRequest.KEY_QUERY); + final Boolean compat = request.getBoolean(JSONRequest.KEY_COMPAT); final Integer count = request.getInteger(JSONRequest.KEY_COUNT); //TODO 如果不想用默认数量可以改成 getIntValue(JSONRequest.KEY_COUNT); final Integer page = request.getInteger(JSONRequest.KEY_PAGE); final Object join = request.get(JSONRequest.KEY_JOIN); @@ -1189,6 +1215,7 @@ public JSONArray onArrayParse(JSONObject request, String parentPath, String name } request.remove(JSONRequest.KEY_QUERY); + request.remove(JSONRequest.KEY_COMPAT); request.remove(JSONRequest.KEY_COUNT); request.remove(JSONRequest.KEY_PAGE); request.remove(JSONRequest.KEY_JOIN); @@ -1227,6 +1254,7 @@ else if (childKeys.length == 1 && JSONRequest.isTableKey(childKeys[0])) { // .setCount(size) .setPage(page2) .setQuery(query2) + .setCompat(compat) .setTable(arrTableKey) .setJoinList(onJoinParse(join, request)); @@ -1301,6 +1329,7 @@ else if (childKeys.length == 1 && JSONRequest.isTableKey(childKeys[0])) { // } finally { //后面还可能用到,要还原 request.put(JSONRequest.KEY_QUERY, query); + request.put(JSONRequest.KEY_COMPAT, compat); request.put(JSONRequest.KEY_COUNT, count); request.put(JSONRequest.KEY_PAGE, page); request.put(JSONRequest.KEY_JOIN, join); diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java index 15482a889..2b5f1760f 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java @@ -104,6 +104,7 @@ public abstract class AbstractSQLConfig implements SQLConfig { // 自定义原始 SQL 片段 Map:当 substring 为 null 时忽略;当 substring 为 "" 时整个 value 是 raw SQL;其它情况则只是 substring 这段为 raw SQL public static final Map RAW_MAP; // 允许调用的 SQL 函数:当 substring 为 null 时忽略;当 substring 为 "" 时整个 value 是 raw SQL;其它情况则只是 substring 这段为 raw SQL + public static final Map SQL_AGGREGATE_FUNCTION_MAP; public static final Map SQL_FUNCTION_MAP; @@ -242,6 +243,13 @@ public abstract class AbstractSQLConfig implements SQLConfig { + SQL_AGGREGATE_FUNCTION_MAP = new LinkedHashMap<>(); // 保证顺序,避免配置冲突等意外情况 + SQL_AGGREGATE_FUNCTION_MAP.put("max", ""); + SQL_AGGREGATE_FUNCTION_MAP.put("min", ""); + SQL_AGGREGATE_FUNCTION_MAP.put("avg", ""); + SQL_AGGREGATE_FUNCTION_MAP.put("count", ""); + SQL_AGGREGATE_FUNCTION_MAP.put("sum", ""); + SQL_FUNCTION_MAP = new LinkedHashMap<>(); // 保证顺序,避免配置冲突等意外情况 //窗口函数 @@ -761,6 +769,7 @@ public String getUserIdKey() { private int page; //Table所在页码 private int position; //Table在[]中的位置 private int query; //JSONRequest.query + private Boolean compat; //JSONRequest.compat query total private int type; //ObjectParser.type private int cache; private boolean explain; @@ -1458,14 +1467,9 @@ public String getColumnString(boolean inSQLJoin) throws Exception { case HEAD: case HEADS: //StringUtil.isEmpty(column, true) || column.contains(",") 时SQL.count(column)会return "*" if (isPrepared() && column != null) { - List raw = getRaw(); boolean containRaw = raw != null && raw.contains(KEY_COLUMN); - - String origin; - String alias; - int index; - + for (String c : column) { if (containRaw) { // 由于 HashMap 对 key 做了 hash 处理,所以 get 比 containsValue 更快 @@ -1476,9 +1480,9 @@ public String getColumnString(boolean inSQLJoin) throws Exception { } } - index = c.lastIndexOf(":"); //StringUtil.split返回数组中,子项不会有null - origin = index < 0 ? c : c.substring(0, index); - alias = index < 0 ? null : c.substring(index + 1); + int index = c.lastIndexOf(":"); //StringUtil.split返回数组中,子项不会有null + String origin = index < 0 ? c : c.substring(0, index); + String alias = index < 0 ? null : c.substring(index + 1); if (alias != null && StringUtil.isName(alias) == false) { throw new IllegalArgumentException("HEAD请求: 字符 " + alias + " 不合法!预编译模式下 @column:value 中 value里面用 , 分割的每一项" @@ -1499,8 +1503,41 @@ public String getColumnString(boolean inSQLJoin) throws Exception { } } } + + boolean onlyOne = column != null && column.size() == 1; + String c0 = onlyOne ? column.get(0) : null; + + if (onlyOne) { + int index = c0 == null ? -1 : c0.lastIndexOf(":"); + if (index > 0) { + c0 = c0.substring(0, index); + } + + int start = c0 == null ? -1 : c0.indexOf("("); + int end = start <= 0 ? -1 : c0.lastIndexOf(")"); + if (start > 0 && end > start) { + String fun = c0.substring(0, start); + + // Invalid use of group function SELECT count(max(`id`)) AS count FROM `sys`.`Comment` + if (SQL_AGGREGATE_FUNCTION_MAP.containsKey(fun)) { + String group = getGroup(); // TODO 唯一 100% 兼容的可能只有 SELECT count(*) FROM (原语句) AS table + return StringUtil.isEmpty(group, true) ? "1" : "count(DISTINCT " + group + ")"; + } + + String[] args = start == end - 1 ? null : StringUtil.split(c0.substring(start + 1, end)); + if (args == null || args.length <= 0) { + return SQL.count(c0); + } - return SQL.count(column != null && column.size() == 1 && StringUtil.isName(column.get(0)) ? getKey(column.get(0)) : "*"); + List raw = getRaw(); + boolean containRaw = raw != null && raw.contains(KEY_COLUMN); + + return SQL.count(parseColumn(c0, containRaw)); + } + } + + return SQL.count(onlyOne ? getKey(c0) : "*"); + // return SQL.count(onlyOne && StringUtil.isName(column.get(0)) ? getKey(column.get(0)) : "*"); case POST: if (column == null || column.isEmpty()) { throw new IllegalArgumentException("POST 请求必须在Table内设置要保存的 key:value !"); @@ -1997,6 +2034,16 @@ public AbstractSQLConfig setQuery(int query) { this.query = query; return this; } + @Override + public Boolean getCompat() { + return compat; + } + @Override + public AbstractSQLConfig setCompat(Boolean compat) { + this.compat = compat; + return this; + } + @Override public int getType() { return type; @@ -3753,14 +3800,13 @@ private static String getConditionString(String column, String table, AbstractSQ //根据方法不同,聚合语句不同。GROUP BY 和 HAVING 可以加在 HEAD 上, HAVING 可以加在 PUT, DELETE 上,GET 全加,POST 全都不加 String aggregation = ""; - if (RequestMethod.isGetMethod(config.getMethod(), true)){ - aggregation = config.getGroupString(true) + config.getHavingString(true) + - config.getOrderString(true); + if (RequestMethod.isGetMethod(config.getMethod(), true)) { + aggregation = config.getGroupString(true) + config.getHavingString(true) + config.getOrderString(true); } - if (RequestMethod.isHeadMethod(config.getMethod(), true)){ + if (RequestMethod.isHeadMethod(config.getMethod(), true)) { // TODO 加参数 isPagenation 判断是 GET 内分页 query:2 查总数,不用加这些条件 aggregation = config.getGroupString(true) + config.getHavingString(true) ; } - if (config.getMethod() == PUT || config.getMethod() == DELETE){ + if (config.getMethod() == PUT || config.getMethod() == DELETE) { aggregation = config.getHavingString(true) ; } @@ -3825,6 +3871,8 @@ public String getJoinString() throws Exception { // 主表不用别名 String ta; for (Join j : joinList) { + onGetJoinString(j); + if (j.isAppJoin()) { // APP JOIN,只是作为一个标记,执行完主表的查询后自动执行副表的查询 User.id IN($commentIdList) continue; } @@ -4089,6 +4137,9 @@ protected void onJoinNotRelation(String sql, String quote, Join j, String jt, Li protected void onJoinComplextRelation(String sql, String quote, Join j, String jt, List onList, On on) { throw new UnsupportedOperationException("JOIN 已禁用 $, ~, {}, <>, >, <, >=, <= 等复杂关联 !性能很差、需求极少,默认只允许 = 等价关联,如要取消禁用可在后端重写相关方法!"); } + + protected void onGetJoinString(Join j) throws UnsupportedOperationException { + } protected void onGetCrossJoinString(Join j) throws UnsupportedOperationException { throw new UnsupportedOperationException("已禁用 * CROSS JOIN !性能很差、需求极少,如要取消禁用可在后端重写相关方法!"); } diff --git a/APIJSONORM/src/main/java/apijson/orm/SQLConfig.java b/APIJSONORM/src/main/java/apijson/orm/SQLConfig.java index 259788770..51a8df36b 100755 --- a/APIJSONORM/src/main/java/apijson/orm/SQLConfig.java +++ b/APIJSONORM/src/main/java/apijson/orm/SQLConfig.java @@ -98,6 +98,9 @@ public interface SQLConfig { int getQuery(); SQLConfig setQuery(int query); + Boolean getCompat(); + SQLConfig setCompat(Boolean compat); + int getPosition(); SQLConfig setPosition(int position); From fca75602b01a8deb5b3a88f6e39e96ba9806c296 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Mon, 14 Mar 2022 01:59:47 +0800 Subject: [PATCH 0591/1181] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E5=85=B3=E4=BA=8E?= =?UTF-8?q?=E5=88=86=E9=A1=B5=E6=9F=A5=E6=80=BB=E6=95=B0=E6=97=B6=20@colum?= =?UTF-8?q?n:"max(id)"=20=E8=BF=99=E7=A7=8D=E8=81=9A=E5=90=88=E5=87=BD?= =?UTF-8?q?=E6=95=B0=E7=9A=84=E4=BC=98=E5=8C=96=E6=B3=A8=E9=87=8A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- APIJSONORM/src/main/java/apijson/orm/AbstractParser.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java b/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java index 2d0f7748c..5e49d5c02 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java @@ -1052,6 +1052,8 @@ public JSONObject onObjectParse(final JSONObject request //total 这里不能用arrayConfig.getType(),因为在createObjectParser.onChildParse传到onObjectParse时已被改掉 if (type == SQLConfig.TYPE_ITEM_CHILD_0 && query != JSONRequest.QUERY_TABLE && position == 0) { + //TODO 应在这里判断 @column 中是否有聚合函数,而不是 AbstractSQLConfig.getColumnString + JSONObject rp; Boolean compat = arrayConfig.getCompat(); if (compat != null && compat) { From a3d9c90a8dc2ba2949fa824f08087ea67390952d Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Mon, 21 Mar 2022 00:26:22 +0800 Subject: [PATCH 0592/1181] =?UTF-8?q?=E5=8C=85=E5=90=AB=E9=80=89=E9=A1=B9?= =?UTF-8?q?=E8=8C=83=E5=9B=B4=E6=96=B0=E5=A2=9E=E6=94=AF=E6=8C=81=E4=BC=A0?= =?UTF-8?q?=E8=B7=AF=E5=BE=84=EF=BC=8C=E4=BE=8B=E5=A6=82=20key<>:{=20path:?= =?UTF-8?q?=20"$",=20value:82001=20}?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/main/java/apijson/orm/AbstractObjectParser.java | 2 +- APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractObjectParser.java b/APIJSONORM/src/main/java/apijson/orm/AbstractObjectParser.java index 590e016dc..003aee026 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractObjectParser.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractObjectParser.java @@ -255,7 +255,7 @@ public AbstractObjectParser parse(String name, boolean isReuse) throws Exception key = entry.getKey(); try { - if (key.startsWith("@") || key.endsWith("@")) { + if (key.startsWith("@") || key.endsWith("@") || (key.endsWith("<>") && value instanceof JSONObject)) { if (onParse(key, value) == false) { invalidate(); } diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java index 2b5f1760f..8cd68bacd 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java @@ -4487,8 +4487,8 @@ else if (w.startsWith("!")) { for (String key : set) { value = request.get(key); - if (value instanceof Map) {//只允许常规Object - throw new IllegalArgumentException("不允许 " + key + " 等任何key的value类型为 {JSONObject} !"); + if (key.endsWith("<>") == false && value instanceof Map) {//只允许常规Object + throw new IllegalArgumentException(table + ":{ " + key + ":value } 中 value 类型错误!除了 key<>:{} 外,不允许 " + key + " 等其它任何 key 对应 value 的类型为 JSONObject {} !"); } //解决AccessVerifier新增userId没有作为条件,而是作为内容,导致PUT,DELETE出错 From 9776408d63bc1d768cdd97d910f6b2243b2a94a8 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Mon, 21 Mar 2022 00:26:48 +0800 Subject: [PATCH 0593/1181] =?UTF-8?q?@having=20=E6=94=AF=E6=8C=81=E5=A4=8D?= =?UTF-8?q?=E6=9D=82=E6=9D=A1=E4=BB=B6=E7=BB=84=E5=90=88=EF=BC=8C=E4=B8=94?= =?UTF-8?q?=E6=96=B0=E5=A2=9E=20@having&=20=E7=AE=80=E5=8C=96=20AND=20?= =?UTF-8?q?=E8=BF=9E=E6=8E=A5=E7=9A=84=E5=86=99=E6=B3=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/main/java/apijson/JSONObject.java | 11 +- .../main/java/apijson/orm/AbstractParser.java | 9 +- .../java/apijson/orm/AbstractSQLConfig.java | 749 +++++++++++------- .../src/main/java/apijson/orm/SQLConfig.java | 7 +- 4 files changed, 468 insertions(+), 308 deletions(-) diff --git a/APIJSONORM/src/main/java/apijson/JSONObject.java b/APIJSONORM/src/main/java/apijson/JSONObject.java index 8000e231b..6825a02de 100755 --- a/APIJSONORM/src/main/java/apijson/JSONObject.java +++ b/APIJSONORM/src/main/java/apijson/JSONObject.java @@ -147,6 +147,7 @@ public JSONObject setUserIdIn(List list) { public static final String KEY_COMBINE = "@combine"; //条件组合,每个条件key前面可以放&,|,!逻辑关系 "id!{},&sex,!name&$" public static final String KEY_GROUP = "@group"; //分组方式 public static final String KEY_HAVING = "@having"; //聚合函数条件,一般和@group一起用 + public static final String KEY_HAVING_AND = "@having&"; //聚合函数条件,一般和@group一起用 public static final String KEY_ORDER = "@order"; //排序方式 public static final String KEY_RAW = "@raw"; // 自定义原始 SQL 片段 public static final String KEY_JSON = "@json"; //SQL Server 把字段转为 JSON 输出 @@ -167,6 +168,7 @@ public JSONObject setUserIdIn(List list) { TABLE_KEY_LIST.add(KEY_COMBINE); TABLE_KEY_LIST.add(KEY_GROUP); TABLE_KEY_LIST.add(KEY_HAVING); + TABLE_KEY_LIST.add(KEY_HAVING_AND); TABLE_KEY_LIST.add(KEY_ORDER); TABLE_KEY_LIST.add(KEY_RAW); TABLE_KEY_LIST.add(KEY_JSON); @@ -350,7 +352,14 @@ public JSONObject setHaving(String... keys) { * @return */ public JSONObject setHaving(String keys) { - return puts(KEY_HAVING, keys); + return setHaving(keys, false); + } + /**set keys for having + * @param keys "key0,key1,key2..." + * @return + */ + public JSONObject setHaving(String keys, boolean isAnd) { + return puts(isAnd ? KEY_HAVING_AND : KEY_HAVING, keys); } /**set keys for order by diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java b/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java index 5e49d5c02..6e407144e 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java @@ -1063,13 +1063,13 @@ public JSONObject onObjectParse(final JSONObject request boolean isExplain = cfg.isExplain(); cfg.setExplain(false); - Subquery subq = new Subquery(); - subq.setFrom(cfg.getTable()); - subq.setConfig(cfg); + Subquery subqy = new Subquery(); + subqy.setFrom(cfg.getTable()); + subqy.setConfig(cfg); SQLConfig countSQLCfg = createSQLConfig(); countSQLCfg.setColumn(Arrays.asList("count(*):count")); - countSQLCfg.setFrom(subq); + countSQLCfg.setFrom(subqy); rp = executeSQL(countSQLCfg, false); @@ -1358,6 +1358,7 @@ else if (childKeys.length == 1 && JSONRequest.isTableKey(childKeys[0])) { // JOIN_COPY_KEY_LIST.add(JSONRequest.KEY_COMBINE); JOIN_COPY_KEY_LIST.add(JSONRequest.KEY_GROUP); JOIN_COPY_KEY_LIST.add(JSONRequest.KEY_HAVING); + JOIN_COPY_KEY_LIST.add(JSONRequest.KEY_HAVING_AND); JOIN_COPY_KEY_LIST.add(JSONRequest.KEY_ORDER); JOIN_COPY_KEY_LIST.add(JSONRequest.KEY_RAW); } diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java index 8cd68bacd..c9c64b61e 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java @@ -15,6 +15,7 @@ import static apijson.JSONObject.KEY_FROM; import static apijson.JSONObject.KEY_GROUP; import static apijson.JSONObject.KEY_HAVING; +import static apijson.JSONObject.KEY_HAVING_AND; import static apijson.JSONObject.KEY_ID; import static apijson.JSONObject.KEY_JSON; import static apijson.JSONObject.KEY_NULL; @@ -32,8 +33,8 @@ import static apijson.RequestMethod.PUT; import static apijson.SQL.AND; import static apijson.SQL.NOT; -import static apijson.SQL.OR; import static apijson.SQL.ON; +import static apijson.SQL.OR; import java.util.ArrayList; import java.util.Arrays; @@ -80,8 +81,20 @@ public abstract class AbstractSQLConfig implements SQLConfig { private static final String TAG = "AbstractSQLConfig"; - public static int MAX_COMBINE_DEPTH = 2; + /** + * 为 true 则兼容 5.0 之前 @having:"toId>0;avg(id)<100000" 默认 AND 连接,为 HAVING toId>0 AND avg(id)<100000; + * 否则按 5.0+ 新版默认 OR 连接,为 HAVING toId>0 OR avg(id)<100000,使用 @having& 或 @having:{ @combine: null } 时才用 AND 连接 + */ + public static boolean IS_HAVING_DEFAULT_AND = false; + /** + * 为 true 则兼容 5.0 之前 @having:"toId>0" 这种不包含 SQL 函数的表达式; + * 否则按 5.0+ 新版不允许,可以用 @having:"(toId)>0" 替代 + */ + public static boolean IS_HAVING_ALLOW_NOT_FUNCTION = false; + + public static int MAX_HAVING_COUNT = 5; public static int MAX_WHERE_COUNT = 10; + public static int MAX_COMBINE_DEPTH = 2; public static int MAX_COMBINE_COUNT = 5; public static int MAX_COMBINE_KEY_COUNT = 2; public static float MAX_COMBINE_RATIO = 1.0f; @@ -750,7 +763,8 @@ public String getUserIdKey() { private String table; //表名 private String alias; //表别名 private String group; //分组方式的字符串数组,','分隔 - private String having; //聚合函数的字符串数组,','分隔 + private String havingCombine; //聚合函数的字符串数组,','分隔 + private Map having; //聚合函数的字符串数组,','分隔 private String order; //排序方式的字符串数组,','分隔 private List raw; //需要保留原始 SQL 的字段,','分隔 private List json; //需要转为 JSON 的字段,','分隔 @@ -1101,24 +1115,36 @@ public String getGroupString(boolean hasPrefix) { return (hasPrefix ? " GROUP BY " : "") + StringUtil.concat(StringUtil.getString(keys), joinGroup, ", "); } + + @Override + public String getHavingCombine() { + return havingCombine; + } + @Override + public SQLConfig setHavingCombine(String havingCombine) { + this.havingCombine = havingCombine; + return this; + } @Override - public String getHaving() { + public Map getHaving() { return having; } - public AbstractSQLConfig setHaving(String... conditions) { - return setHaving(StringUtil.getString(conditions)); - } @Override - public AbstractSQLConfig setHaving(String having) { + public SQLConfig setHaving(Map having) { this.having = having; return this; } + public AbstractSQLConfig setHaving(String... conditions) { + return setHaving(StringUtil.getString(conditions)); + } + /**TODO @having 改为默认 | 或连接,且支持 @having: { "key1>": 1, "key{}": "length(key2)>0", "@combine": "key1,key2" } * @return HAVING conditoin0 AND condition1 OR condition2 ... + * @throws Exception */ @JSONField(serialize = false) - public String getHavingString(boolean hasPrefix) { + public String getHavingString(boolean hasPrefix) throws Exception { //加上子表的 having String joinHaving = ""; if (joinList != null) { @@ -1146,122 +1172,120 @@ public String getHavingString(boolean hasPrefix) { } } - String[] keys = StringUtil.split(getHaving(), ";"); - if (keys == null || keys.length <= 0) { + Map map = getHaving(); + Set> set = map == null ? null : map.entrySet(); + if (set == null || set.isEmpty()) { return StringUtil.isEmpty(joinHaving, true) ? "" : (hasPrefix ? " HAVING " : "") + joinHaving; } - String quote = getQuote(); - String tableAlias = getAliasWithQuote(); - List raw = getRaw(); + // 提前把 @having& 转为 @having,或者干脆不允许 @raw:"@having&" boolean containRaw = raw != null && (raw.contains(KEY_HAVING) || raw.contains(KEY_HAVING_AND)); boolean containRaw = raw != null && raw.contains(KEY_HAVING); - - String expression; - String method; - //暂时不允许 String prefix; - String suffix; + + // 直接把 having 类型从 Map 定改为 Map,避免额外拷贝 + // Map newMap = new LinkedHashMap<>(map.size()); + // for (Entry entry : set) { + // newMap.put(entry.getKey(), entry.getValue()); + // } //fun0(arg0,arg1,...);fun1(arg0,arg1,...) - for (int i = 0; i < keys.length; i++) { + String havingString = parseCombineExpression(getMethod(), getQuote(), getTable(), getAliasWithQuote(), map, getHavingCombine(), true, containRaw, true); - //fun(arg0,arg1,...) - expression = keys[i]; - if (containRaw) { - try { - String rawSQL = getRawSQL(KEY_HAVING, expression); - if (rawSQL != null) { - keys[i] = rawSQL; - continue; - } - } catch (Exception e) { - Log.e(TAG, "newSQLConfig rawColumnSQL == null >> try { " - + " String rawSQL = ((AbstractSQLConfig) config).getRawSQL(KEY_COLUMN, fk); ... " - + "} catch (Exception e) = " + e.getMessage()); + return (hasPrefix ? " HAVING " : "") + StringUtil.concat(havingString, joinHaving, AND); + } + + protected String getHavingItem(String quote, String table, String alias, String key, String expression, boolean containRaw) { + //fun(arg0,arg1,...) + if (containRaw) { + try { + String rawSQL = getRawSQL(KEY_HAVING, expression); + if (rawSQL != null) { + return rawSQL; } + } catch (Exception e) { + Log.e(TAG, "newSQLConfig rawColumnSQL == null >> try { " + + " String rawSQL = ((AbstractSQLConfig) config).getRawSQL(KEY_COLUMN, fk); ... " + + "} catch (Exception e) = " + e.getMessage()); } + } - if (expression.length() > 50) { - throw new UnsupportedOperationException("@having:value 的 value 中字符串 " + expression + " 不合法!" - + "不允许传超过 50 个字符的函数或表达式!请用 @raw 简化传参!"); - } + if (expression.length() > 100) { + throw new UnsupportedOperationException("@having:value 的 value 中字符串 " + expression + " 不合法!" + + "不允许传超过 100 个字符的函数或表达式!请用 @raw 简化传参!"); + } - int start = expression.indexOf("("); - if (start < 0) { - if (isPrepared() && PATTERN_FUNCTION.matcher(expression).matches() == false) { - throw new UnsupportedOperationException("字符串 " + expression + " 不合法!" - + "预编译模式下 @having:\"column?value;function(arg0,arg1,...)?value...\"" - + " 中 column?value 必须符合正则表达式 " + PATTERN_FUNCTION + " 且不包含连续减号 -- !不允许空格!"); - } - continue; + int start = expression.indexOf("("); + if (start < 0) { + if (isPrepared() && PATTERN_FUNCTION.matcher(expression).matches() == false) { + throw new UnsupportedOperationException("字符串 " + expression + " 不合法!" + + "预编译模式下 @having:\"column?value;function(arg0,arg1,...)?value...\"" + + " 中 column?value 必须符合正则表达式 " + PATTERN_FUNCTION + " 且不包含连续减号 -- !不允许空格!"); } + return expression; + } - int end = expression.lastIndexOf(")"); - if (start >= end) { - throw new IllegalArgumentException("字符 " + expression + " 不合法!" - + "@having:value 中 value 里的 SQL函数必须为 function(arg0,arg1,...) 这种格式!"); - } + int end = expression.lastIndexOf(")"); + if (start >= end) { + throw new IllegalArgumentException("字符 " + expression + " 不合法!" + + "@having:value 中 value 里的 SQL函数必须为 function(arg0,arg1,...) 这种格式!"); + } - method = expression.substring(0, start); - if (method.isEmpty() == false) { - if (SQL_FUNCTION_MAP == null || SQL_FUNCTION_MAP.isEmpty()) { - if (StringUtil.isName(method) == false) { - throw new IllegalArgumentException("字符 " + method + " 不合法!" - + "预编译模式下 @having:\"column?value;function(arg0,arg1,...)?value...\"" - + " 中 function 必须符合小写英文单词的 SQL 函数名格式!"); - } - } - else if (SQL_FUNCTION_MAP.containsKey(method) == false) { + String method = expression.substring(0, start); + if (method.isEmpty() == false) { + if (SQL_FUNCTION_MAP == null || SQL_FUNCTION_MAP.isEmpty()) { + if (StringUtil.isName(method) == false) { throw new IllegalArgumentException("字符 " + method + " 不合法!" - + "预编译模式下 @column:\"column0,column1:alias;function0(arg0,arg1,...);function1(...):alias...\"" - + " 中 function 必须符合小写英文单词的 SQL 函数名格式!且必须是后端允许调用的 SQL 函数!"); + + "预编译模式下 @having:\"column?value;function(arg0,arg1,...)?value...\"" + + " 中 function 必须符合小写英文单词的 SQL 函数名格式!"); } } - - suffix = expression.substring(end + 1, expression.length()); - - if (isPrepared() && (((String) suffix).contains("--") || ((String) suffix).contains("/*") || PATTERN_RANGE.matcher((String) suffix).matches() == false)) { - throw new UnsupportedOperationException("字符串 " + suffix + " 不合法!" - + "预编译模式下 @having:\"column?value;function(arg0,arg1,...)?value...\"" - + " 中 ?value 必须符合正则表达式 " + PATTERN_RANGE + " 且不包含连续减号 -- 或注释符 /* !不允许多余的空格!"); + else if (SQL_FUNCTION_MAP.containsKey(method) == false) { + throw new IllegalArgumentException("字符 " + method + " 不合法!" + + "预编译模式下 @column:\"column0,column1:alias;function0(arg0,arg1,...);function1(...):alias...\"" + + " 中 function 必须符合小写英文单词的 SQL 函数名格式!且必须是后端允许调用的 SQL 函数!"); } + } + + String suffix = expression.substring(end + 1, expression.length()); - String[] ckeys = StringUtil.split(expression.substring(start + 1, end)); + if (isPrepared() && (((String) suffix).contains("--") || ((String) suffix).contains("/*") || PATTERN_RANGE.matcher((String) suffix).matches() == false)) { + throw new UnsupportedOperationException("字符串 " + suffix + " 不合法!" + + "预编译模式下 @having:\"column?value;function(arg0,arg1,...)?value...\"" + + " 中 ?value 必须符合正则表达式 " + PATTERN_RANGE + " 且不包含连续减号 -- 或注释符 /* !不允许多余的空格!"); + } - if (ckeys != null) { - for (int j = 0; j < ckeys.length; j++) { - String origin = ckeys[j]; + String[] ckeys = StringUtil.split(expression.substring(start + 1, end)); - if (isPrepared()) { - if (origin.startsWith("_") || origin.contains("--") || PATTERN_FUNCTION.matcher(origin).matches() == false) { - throw new IllegalArgumentException("字符 " + ckeys[j] + " 不合法!" - + "预编译模式下 @having:\"column?value;function(arg0,arg1,...)?value...\"" - + " 中所有 column, arg 都必须是1个不以 _ 开头的单词 或者 符合正则表达式 " + PATTERN_FUNCTION + " 且不包含连续减号 -- !不允许多余的空格!"); - } - } + if (ckeys != null) { + for (int j = 0; j < ckeys.length; j++) { + String origin = ckeys[j]; - //JOIN 副表不再在外层加副表名前缀 userId AS `Commet.userId`, 而是直接 userId AS `userId` - boolean isName = false; - if (StringUtil.isNumer(origin)) { - //do nothing - } - else if (StringUtil.isName(origin)) { - origin = quote + origin + quote; - isName = true; - } - else { - origin = getValue(origin).toString(); + if (isPrepared()) { + if (origin.startsWith("_") || origin.contains("--") || PATTERN_FUNCTION.matcher(origin).matches() == false) { + throw new IllegalArgumentException("字符 " + ckeys[j] + " 不合法!" + + "预编译模式下 @having:\"column?value;function(arg0,arg1,...)?value...\"" + + " 中所有 column, arg 都必须是1个不以 _ 开头的单词 或者 符合正则表达式 " + PATTERN_FUNCTION + " 且不包含连续减号 -- !不允许多余的空格!"); } + } - ckeys[j] = (isName && isKeyPrefix() ? tableAlias + "." : "") + origin; + //JOIN 副表不再在外层加副表名前缀 userId AS `Commet.userId`, 而是直接 userId AS `userId` + boolean isName = false; + if (StringUtil.isNumer(origin)) { + //do nothing + } + else if (StringUtil.isName(origin)) { + origin = quote + origin + quote; + isName = true; + } + else { + origin = getValue(origin).toString(); } - } - keys[i] = method + "(" + StringUtil.getString(ckeys) + ")" + suffix; + ckeys[j] = (isName && isKeyPrefix() ? alias + "." : "") + origin; + } } - //TODO 支持 OR, NOT 参考 @combine:"&key0,|key1,!key2" - return (hasPrefix ? " HAVING " : "") + StringUtil.concat(StringUtil.getString(keys, AND), joinHaving, AND); + return method + "(" + StringUtil.getString(ckeys) + ")" + suffix; } @Override @@ -2193,6 +2217,9 @@ public SQLConfig setCast(Map cast) { //WHERE <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< + protected int getMaxHavingCount() { + return MAX_HAVING_COUNT; + } protected int getMaxWhereCount() { return MAX_WHERE_COUNT; } @@ -2392,260 +2419,274 @@ public String getWhereString(boolean hasPrefix) throws Exception { */ @JSONField(serialize = false) public String getWhereString(boolean hasPrefix, RequestMethod method, Map where, String combine, List joinList, boolean verifyName) throws Exception { + String whereString = parseCombineExpression(method, getQuote(), getTable(), getAliasWithQuote(), where, combine, verifyName, false, false); + + whereString = concatJoinWhereString(whereString); + + String result = StringUtil.isEmpty(whereString, true) ? "" : (hasPrefix ? " WHERE " : "") + whereString; + + if (result.isEmpty() && RequestMethod.isQueryMethod(method) == false) { + throw new UnsupportedOperationException("写操作请求必须带条件!!!"); + } + + return result; + } + + + protected String parseCombineExpression(RequestMethod method, String quote, String table, String alias + , Map conditioinMap, String combine, boolean verifyName, boolean containRaw, boolean isHaving) throws Exception { + + String errPrefix = table + (isHaving ? ":{ @having:{ " : ":{ ") + "@combine:'" + combine + (isHaving ? "' } }" : "' }"); String s = StringUtil.getString(combine); if (s.startsWith(" ") || s.endsWith(" ") ) { - throw new IllegalArgumentException(table + ":{ @combine: '" + combine + "' } 中字符 '" + s + throw new IllegalArgumentException(errPrefix + " 中字符 '" + s + "' 不合法!不允许首尾有空格,也不允许连续空格!空格不能多也不能少!" + "逻辑连接符 & | 左右必须各一个相邻空格!左括号 ( 右边和右括号 ) 左边都不允许有相邻空格!"); } - if (where == null) { - where = new HashMap<>(); + if (conditioinMap == null) { + conditioinMap = new HashMap<>(); } - int whereSize = where.size(); + int size = conditioinMap.size(); - int maxWhereCount = getMaxWhereCount(); - if (maxWhereCount > 0 && whereSize > maxWhereCount) { - throw new IllegalArgumentException(table + ":{ key0:value0, key1:value1... } 中条件 key:value 数量 " + whereSize - + " 已超过最大数量,必须在 0-" + maxWhereCount + " 内!"); + int maxCount = isHaving ? getMaxHavingCount() : getMaxWhereCount(); + if (maxCount > 0 && size > maxCount) { + throw new IllegalArgumentException(table + (isHaving ? ":{ @having:{ " : ":{ ") + "key0:value0, key1:value1... " + combine + + (isHaving ? " } }" : " }") + " 中条件 key:value 数量 " + size + " 已超过最大数量,必须在 0-" + maxCount + " 内!"); } - - String whereString = ""; - - int maxDepth = getMaxCombineDepth(); - int maxCombineCount = getMaxCombineCount(); - int maxCombineKeyCount = getMaxCombineKeyCount(); - float maxCombineRatio = getMaxCombineRatio(); + String result = ""; + List prepreadValues = getPreparedValueList(); - setPreparedValueList(new ArrayList<>()); - - int depth = 0; - int allCount = 0; + Map usedKeyCountMap = new HashMap<>(size); + int n = s.length(); - int i = 0; - - char lastLogic = 0; - char last = 0; - boolean first = true; - boolean isNot = false; - - String key = ""; - Map usedKeyCountMap = new HashMap<>(whereSize); - while (i <= n) { // "date> | (contactIdList<> & (name*~ | tag&$))" - boolean isOver = i >= n; - char c = isOver ? 0 : s.charAt(i); - boolean isBlankOrRightParenthesis = c == ' ' || c == ')'; - if (isOver || isBlankOrRightParenthesis) { - boolean isEmpty = StringUtil.isEmpty(key, true); - if (isEmpty && last != ')') { - throw new IllegalArgumentException(table + ":{ @combine: '" + combine + "' } 中字符 '" + (isOver ? s : s.substring(i)) - + "' 不合法!" + (c == ' ' ? "空格 ' ' " : "右括号 ')'") + " 左边缺少条件 key !逻辑连接符 & | 左右必须各一个相邻空格!" - + "空格不能多也不能少!不允许首尾有空格,也不允许连续空格!左括号 ( 的右边 和 右括号 ) 的左边 都不允许有相邻空格!"); - } - - if (isEmpty == false) { - if (first == false && lastLogic <= 0) { - throw new IllegalArgumentException(table + ":{ @combine: '" + combine + "' } 中字符 " - + "'" + s.substring(i - key.length() - (isOver ? 1 : 0)) + "' 不合法!左边缺少 & | 其中一个逻辑连接符!"); - } - - allCount ++; - if (allCount > maxCombineCount && maxCombineCount > 0) { - throw new IllegalArgumentException(table + ":{ @combine: '" + combine + "' } 中字符 '" + s + "' 不合法!" - + "其中 key 数量 " + allCount + " 已超过最大值,必须在条件键值对数量 0-" + maxCombineCount + " 内!"); - } - if (1.0f*allCount/whereSize > maxCombineRatio && maxCombineRatio > 0) { - throw new IllegalArgumentException(table + ":{ @combine: '" + combine + "' } 中字符 '" + s + "' 不合法!" - + "其中 key 数量 " + allCount + " / 条件键值对数量 " + whereSize + " = " + (1.0f*allCount/whereSize) - + " 已超过 最大倍数,必须在条件键值对数量 0-" + maxCombineRatio + " 倍内!"); - } - - String column = key; - - Object value = where.get(column); - if (value == null) { - throw new IllegalArgumentException(table + ":{ @combine: '" + combine + "' } 中字符 '" + key - + "' 对应的条件键值对 " + column + ":value 不存在!"); + if (n > 0) { + setPreparedValueList(new ArrayList<>()); + + int maxDepth = getMaxCombineDepth(); + int maxCombineCount = getMaxCombineCount(); + int maxCombineKeyCount = getMaxCombineKeyCount(); + float maxCombineRatio = getMaxCombineRatio(); + + int depth = 0; + int allCount = 0; + + int i = 0; + + char lastLogic = 0; + char last = 0; + boolean first = true; + boolean isNot = false; + + String key = ""; + while (i <= n) { // "date> | (contactIdList<> & (name*~ | tag&$))" + boolean isOver = i >= n; + char c = isOver ? 0 : s.charAt(i); + boolean isBlankOrRightParenthesis = c == ' ' || c == ')'; + if (isOver || isBlankOrRightParenthesis) { + boolean isEmpty = StringUtil.isEmpty(key, true); + if (isEmpty && last != ')') { + throw new IllegalArgumentException(errPrefix + " 中字符 '" + (isOver ? s : s.substring(i)) + + "' 不合法!" + (c == ' ' ? "空格 ' ' " : "右括号 ')'") + " 左边缺少条件 key !逻辑连接符 & | 左右必须各一个相邻空格!" + + "空格不能多也不能少!不允许首尾有空格,也不允许连续空格!左括号 ( 的右边 和 右括号 ) 的左边 都不允许有相邻空格!"); } - - String wi = getWhereItem(column, value, method, verifyName); - if (StringUtil.isEmpty(wi, true)) { // 转成 1=1 ? - throw new IllegalArgumentException(table + ":{ @combine: '" + combine + "' } 中字符 '" + key - + "' 对应的 " + column + ":value 不是有效条件键值对!"); + + if (isEmpty == false) { + if (first == false && lastLogic <= 0) { + throw new IllegalArgumentException(errPrefix + " 中字符 " + + "'" + s.substring(i - key.length() - (isOver ? 1 : 0)) + "' 不合法!左边缺少 & | 其中一个逻辑连接符!"); + } + + allCount ++; + if (allCount > maxCombineCount && maxCombineCount > 0) { + throw new IllegalArgumentException(errPrefix + " 中字符 '" + s + "' 不合法!" + + "其中 key 数量 " + allCount + " 已超过最大值,必须在条件键值对数量 0-" + maxCombineCount + " 内!"); + } + if (1.0f*allCount/size > maxCombineRatio && maxCombineRatio > 0) { + throw new IllegalArgumentException(errPrefix + " 中字符 '" + s + "' 不合法!" + + "其中 key 数量 " + allCount + " / 条件键值对数量 " + size + " = " + (1.0f*allCount/size) + + " 已超过 最大倍数,必须在条件键值对数量 0-" + maxCombineRatio + " 倍内!"); + } + + String column = key; + + Object value = conditioinMap.get(column); + if (value == null) { + throw new IllegalArgumentException(errPrefix + " 中字符 '" + key + + "' 对应的条件键值对 " + column + ":value 不存在!"); + } + + String wi = isHaving ? getHavingItem(quote, table, alias, column, (String) value, containRaw) : getWhereItem(column, value, method, verifyName); + if (StringUtil.isEmpty(wi, true)) { // 转成 1=1 ? + throw new IllegalArgumentException(errPrefix + " 中字符 '" + key + + "' 对应的 " + column + ":value 不是有效条件键值对!"); + } + + Integer count = usedKeyCountMap.get(column); + count = count == null ? 1 : count + 1; + if (count > maxCombineKeyCount && maxCombineKeyCount > 0) { + throw new IllegalArgumentException(errPrefix + " 中字符 '" + s + "' 不合法!" + + "其中 '" + column + "' 重复引用,次数 " + count + " 已超过最大值,必须在 0-" + maxCombineKeyCount + " 内!"); + } + usedKeyCountMap.put(column, count); + + result += "( " + getCondition(isNot, wi) + " )"; + isNot = false; + first = false; } - - Integer count = usedKeyCountMap.get(column); - count = count == null ? 1 : count + 1; - if (count > maxCombineKeyCount && maxCombineKeyCount > 0) { - throw new IllegalArgumentException(table + ":{ @combine: '" + combine + "' } 中字符 '" + s + "' 不合法!" - + "其中 '" + column + "' 重复引用,次数 " + count + " 已超过最大值,必须在 0-" + maxCombineKeyCount + " 内!"); + + key = ""; + lastLogic = 0; + + if (isOver) { + break; } - usedKeyCountMap.put(column, count); - - whereString += "( " + getCondition(isNot, wi) + " )"; - isNot = false; - first = false; } - - key = ""; - lastLogic = 0; - - if (isOver) { - break; + + if (c == ' ') { } - } - - if (c == ' ') { - } - else if (c == '&') { - if (last == ' ') { - if (i >= n - 1 || s.charAt(i + 1) != ' ') { - throw new IllegalArgumentException(table + ":{ @combine: '" + combine + "' } 中字符 '" + (i >= n - 1 ? s : s.substring(0, i + 1)) - + "' 不合法!逻辑连接符 & 右边缺少一个空格 !逻辑连接符 & | 左右必须各一个相邻空格!空格不能多也不能少!" - + "不允许首尾有空格,也不允许连续空格!左括号 ( 的右边 和 右括号 ) 的左边 都不允许有相邻空格!"); + else if (c == '&') { + if (last == ' ') { + if (i >= n - 1 || s.charAt(i + 1) != ' ') { + throw new IllegalArgumentException(errPrefix + " 中字符 '" + (i >= n - 1 ? s : s.substring(0, i + 1)) + + "' 不合法!逻辑连接符 & 右边缺少一个空格 !逻辑连接符 & | 左右必须各一个相邻空格!空格不能多也不能少!" + + "不允许首尾有空格,也不允许连续空格!左括号 ( 的右边 和 右括号 ) 的左边 都不允许有相邻空格!"); + } + + result += SQL.AND; + lastLogic = c; + i ++; + } + else { + key += c; } - - whereString += SQL.AND; - lastLogic = c; - i ++; - } - else { - key += c; } - } - else if (c == '|') { - if (last == ' ') { - if (i >= n - 1 || s.charAt(i + 1) != ' ') { - throw new IllegalArgumentException(table + ":{ @combine: '" + combine + "' } 中字符 '" + (i >= n - 1 ? s : s.substring(0, i + 1)) - + "' 不合法!逻辑连接符 | 右边缺少一个空格 !逻辑连接符 & | 左右必须各一个相邻空格!空格不能多也不能少!" - + "不允许首尾有空格,也不允许连续空格!左括号 ( 右边和右括号 ) 左边都不允许有相邻空格!"); + else if (c == '|') { + if (last == ' ') { + if (i >= n - 1 || s.charAt(i + 1) != ' ') { + throw new IllegalArgumentException(table + ":{ @combine: '" + combine + "' } 中字符 '" + (i >= n - 1 ? s : s.substring(0, i + 1)) + + "' 不合法!逻辑连接符 | 右边缺少一个空格 !逻辑连接符 & | 左右必须各一个相邻空格!空格不能多也不能少!" + + "不允许首尾有空格,也不允许连续空格!左括号 ( 右边和右括号 ) 左边都不允许有相邻空格!"); + } + + result += SQL.OR; + lastLogic = c; + i ++; + } + else { + key += c; } - - whereString += SQL.OR; - lastLogic = c; - i ++; - } - else { - key += c; } - } - else if (c == '!') { - last = i <= 0 ? 0 : s.charAt(i - 1); // & | 后面跳过了空格 - - char next = i >= n - 1 ? 0 : s.charAt(i + 1); - if (last == ' ' || last == '(') { - if (next == ' ') { - throw new IllegalArgumentException(table + ":{ @combine: '" + combine + "' } 中字符 '" + s.substring(0, i + 1) - + "' 不合法!非逻辑符 '!' 右边多了一个空格 ' ' !非逻辑符 '!' 右边不允许任何相邻空格 ' ',也不允许 ')' '&' '|' 中任何一个!"); + else if (c == '!') { + last = i <= 0 ? 0 : s.charAt(i - 1); // & | 后面跳过了空格 + + char next = i >= n - 1 ? 0 : s.charAt(i + 1); + if (last == ' ' || last == '(') { + if (next == ' ') { + throw new IllegalArgumentException(errPrefix + " 中字符 '" + s.substring(0, i + 1) + + "' 不合法!非逻辑符 '!' 右边多了一个空格 ' ' !非逻辑符 '!' 右边不允许任何相邻空格 ' ',也不允许 ')' '&' '|' 中任何一个!"); + } + if (next == ')' || next == '&' || next == '!') { + throw new IllegalArgumentException(errPrefix + " 中字符 '" + s.substring(0, i + 1) + + "' 不合法!非逻辑符 '!' 右边多了一个字符 '" + next + "' !非逻辑符 '!' 右边不允许任何相邻空格 ' ',也不允许 ')' '&' '|' 中任何一个!"); + } + if (i > 0 && lastLogic <= 0 && last != '(') { + throw new IllegalArgumentException(errPrefix + " 中字符 '" + s.substring(i) + + "' 不合法!左边缺少 & | 逻辑连接符!逻辑连接符 & | 左右必须各一个相邻空格!空格不能多也不能少!" + + "不允许首尾有空格,也不允许连续空格!左括号 ( 的右边 和 右括号 ) 的左边 都不允许有相邻空格!"); + } + } + + if (next == '(') { + result += SQL.NOT; + lastLogic = c; } - if (next == ')' || next == '&' || next == '!') { - throw new IllegalArgumentException(table + ":{ @combine: '" + combine + "' } 中字符 '" + s.substring(0, i + 1) - + "' 不合法!非逻辑符 '!' 右边多了一个字符 '" + next + "' !非逻辑符 '!' 右边不允许任何相邻空格 ' ',也不允许 ')' '&' '|' 中任何一个!"); + else if (last <= 0 || last == ' ' || last == '(') { + isNot = true; + // lastLogic = c; } - if (i > 0 && lastLogic <= 0 && last != '(') { - throw new IllegalArgumentException(table + ":{ @combine: '" + combine + "' } 中字符 '" + s.substring(i) + else { + key += c; + } + } + else if (c == '(') { + if (key.isEmpty() == false || (i > 0 && lastLogic <= 0 && last != '(')) { + throw new IllegalArgumentException(errPrefix + " 中字符 '" + s.substring(i) + "' 不合法!左边缺少 & | 逻辑连接符!逻辑连接符 & | 左右必须各一个相邻空格!空格不能多也不能少!" + "不允许首尾有空格,也不允许连续空格!左括号 ( 的右边 和 右括号 ) 的左边 都不允许有相邻空格!"); } + + depth ++; + if (depth > maxDepth && maxDepth > 0) { + throw new IllegalArgumentException(errPrefix + " 中字符 '" + s.substring(0, i + 1) + + "' 不合法!括号 (()) 嵌套层级 " + depth + " 已超过最大值,必须在 0-" + maxDepth + " 内!"); + } + + result += c; + lastLogic = 0; + first = true; } - - if (next == '(') { - whereString += SQL.NOT; - lastLogic = c; - } - else if (last <= 0 || last == ' ' || last == '(') { - isNot = true; -// lastLogic = c; - } + else if (c == ')') { + depth --; + if (depth < 0) { + throw new IllegalArgumentException(errPrefix + " 中字符 '" + s.substring(0, i + 1) + + "' 不合法!左括号 ( 比 右括号 ) 少!数量必须相等从而完整闭合 (...) !"); + } + + result += c; + lastLogic = 0; + } else { key += c; } + + last = c; + i ++; } - else if (c == '(') { - if (key.isEmpty() == false || (i > 0 && lastLogic <= 0 && last != '(')) { - throw new IllegalArgumentException(table + ":{ @combine: '" + combine + "' } 中字符 '" + s.substring(i) - + "' 不合法!左边缺少 & | 逻辑连接符!逻辑连接符 & | 左右必须各一个相邻空格!空格不能多也不能少!" - + "不允许首尾有空格,也不允许连续空格!左括号 ( 的右边 和 右括号 ) 的左边 都不允许有相邻空格!"); - } - - depth ++; - if (depth > maxDepth && maxDepth > 0) { - throw new IllegalArgumentException(table + ":{ @combine: '" + combine + "' } 中字符 '" + s.substring(0, i + 1) - + "' 不合法!括号 (()) 嵌套层级 " + depth + " 已超过最大值,必须在 0-" + maxDepth + " 内!"); - } - - whereString += c; - lastLogic = 0; - first = true; - } - else if (c == ')') { - depth --; - if (depth < 0) { - throw new IllegalArgumentException(table + ":{ @combine: '" + combine + "' } 中字符 '" + s.substring(0, i + 1) - + "' 不合法!左括号 ( 比 右括号 ) 少!数量必须相等从而完整闭合 (...) !"); - } - - whereString += c; - lastLogic = 0; - } - else { - key += c; + + if (depth != 0) { + throw new IllegalArgumentException(errPrefix + " 中字符 '" + s + + "' 不合法!左括号 ( 比 右括号 ) 多!数量必须相等从而完整闭合 (...) !"); } - - last = c; - i ++; } - if (depth != 0) { - throw new IllegalArgumentException(table + ":{ @combine: '" + combine + "' } 中字符 '" + s - + "' 不合法!左括号 ( 比 右括号 ) 多!数量必须相等从而完整闭合 (...) !"); - } - - Set> set = where.entrySet(); + Set> set = conditioinMap.entrySet(); - String andWhere = ""; + String andCond = ""; boolean isItemFirst = true; for (Entry entry : set) { - key = entry == null ? null : entry.getKey(); + String key = entry == null ? null : entry.getKey(); if (key == null || usedKeyCountMap.containsKey(key)) { continue; } - String wi = getWhereItem(key, where.get(key), method, verifyName); + String wi = isHaving ? getHavingItem(quote, table, alias, key, (String) entry.getValue(), containRaw) : getWhereItem(key, entry.getValue(), method, verifyName); if (StringUtil.isEmpty(wi, true)) {//避免SQL条件连接错误 continue; } - andWhere += (isItemFirst ? "" : AND) + "(" + wi + ")"; + andCond += (isItemFirst ? "" : AND) + "(" + wi + ")"; isItemFirst = false; } - if (StringUtil.isEmpty(whereString, true)) { - whereString = andWhere; - } - else if (StringUtil.isNotEmpty(andWhere, true)) { // andWhere 必须放后面,否则 prepared 值顺序错误 -// whereString = "( " + whereString + " )" + AND + andWhere; - - whereString = andWhere + AND + "( " + whereString + " )"; // 先暂存之前的 prepared 值,然后反向整合 - prepreadValues.addAll(getPreparedValueList()); - setPreparedValueList(prepreadValues); + if (StringUtil.isEmpty(result, true)) { + result = andCond; + } + else if (StringUtil.isNotEmpty(andCond, true)) { // andWhere 必须放后面,否则 prepared 值顺序错误 + // result = "( " + result + " )" + AND + andCond; + result = andCond + AND + "( " + result + " )"; // 先暂存之前的 prepared 值,然后反向整合 + if (n > 0) { + prepreadValues.addAll(getPreparedValueList()); + setPreparedValueList(prepreadValues); + } } - - whereString = concatJoinWhereString(whereString); - String result = StringUtil.isEmpty(whereString, true) ? "" : (hasPrefix ? " WHERE " : "") + whereString; - - if (result.isEmpty() && RequestMethod.isQueryMethod(method) == false) { - throw new UnsupportedOperationException("写操作请求必须带条件!!!"); - } - return result; } - public String getWhereString(boolean hasPrefix, RequestMethod method, Map where, Map> combine, List joinList, boolean verifyName) throws Exception { Set>> combineSet = combine == null ? null : combine.entrySet(); if (combineSet == null || combineSet.isEmpty()) { @@ -4269,7 +4310,8 @@ else if (id instanceof Subquery) {} String cast = request.getString(KEY_CAST); String combine = request.getString(KEY_COMBINE); String group = request.getString(KEY_GROUP); - String having = request.getString(KEY_HAVING); + Object having = request.get(KEY_HAVING); + String havingAnd = request.getString(KEY_HAVING_AND); String order = request.getString(KEY_ORDER); String raw = request.getString(KEY_RAW); String json = request.getString(KEY_JSON); @@ -4292,24 +4334,29 @@ else if (id instanceof Subquery) {} request.remove(KEY_COMBINE); request.remove(KEY_GROUP); request.remove(KEY_HAVING); + request.remove(KEY_HAVING_AND); request.remove(KEY_ORDER); request.remove(KEY_RAW); request.remove(KEY_JSON); + + // @null <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< String[] nullKeys = StringUtil.split(nulls); if (nullKeys != null && nullKeys.length > 0) { for (String nk : nullKeys) { if (StringUtil.isEmpty(nk, true)) { - throw new IllegalArgumentException(table + ":{} 里的 @null: value 中的字符 '" + nk + "' 不合法!不允许为空!"); + throw new IllegalArgumentException(table + ":{ @null: value } 中的字符 '" + nk + "' 不合法!不允许为空!"); } if (request.get(nk) != null) { - throw new IllegalArgumentException(table + ":{} 里的 @null: value 中的字符 '" + nk + "' 已在当前对象有非 null 值!不允许对同一个 JSON key 设置不同值!"); + throw new IllegalArgumentException(table + ":{ @null: value } 中的字符 '" + nk + "' 已在当前对象有非 null 值!不允许对同一个 JSON key 设置不同值!"); } request.put(nk, null); } } + // @null >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> + // @cast <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< String[] casts = StringUtil.split(cast); Map castMap = null; if (casts != null && casts.length > 0) { @@ -4330,8 +4377,9 @@ else if (id instanceof Subquery) {} castMap.put(p.getKey(), p.getValue()); } } + // @cast >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> - + String[] rawArr = StringUtil.split(raw); config.setRaw(rawArr == null || rawArr.length <= 0 ? null : new ArrayList<>(Arrays.asList(rawArr))); @@ -4523,8 +4571,15 @@ else if (whereList != null && whereList.contains(key)) { List cs = new ArrayList<>(); List rawList = config.getRaw(); - boolean containColumnRaw = rawList != null && rawList.contains(KEY_COLUMN); + boolean containColumnHavingAnd = rawList != null && rawList.contains(KEY_HAVING_AND); + + if (containColumnHavingAnd) { + throw new IllegalArgumentException(table + ":{ @raw:value } 的 value 里字符 @having& 不合法!" + + "@raw 不支持 @having&,请用 @having 替代!"); + } + // TODO 这段是否必要?如果 @column 只支持分段后的 SQL 片段,也没问题 + boolean containColumnRaw = rawList != null && rawList.contains(KEY_COLUMN); String rawColumnSQL = null; if (containColumnRaw) { try { @@ -4572,6 +4627,96 @@ else if (whereList != null && whereList.contains(key)) { } } + + // @having, @haivng& <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< + Object newHaving = having; + boolean isHavingAnd = false; + + Map havingMap = new LinkedHashMap<>(); + if (havingAnd != null) { + if (having != null) { + throw new IllegalArgumentException(table + ":{ @having: value1, @having&: value2 } " + + "中 value1 与 value2 不合法!不允许同时传 @having 和 @having& ,两者最多传一个!"); + } + + newHaving = havingAnd; + isHavingAnd = true; + } + + String havingKey = (isHavingAnd ? KEY_HAVING_AND : KEY_HAVING); + String havingCombine = ""; + + if (newHaving instanceof String) { + String[] havingss = StringUtil.split((String) newHaving, ";"); + if (havingss != null) { + int ind = -1; + for (int i = 0; i < havingss.length; i++) { + + String havingsStr = havingss[i]; + int start = havingsStr == null ? -1 : havingsStr.indexOf("("); + int end = havingsStr == null ? -1 : havingsStr.lastIndexOf(")"); + if (IS_HAVING_ALLOW_NOT_FUNCTION == false && (start < 0 || start >= end)) { + throw new IllegalArgumentException(table + ":{ " + havingKey + ":value } 里的 value 中的第 " + i + + " 个字符 '" + havingsStr + "' 不合法!里面没有包含 SQL 函数!必须为 fun(col1,col2..)?val 格式!"); + } + + String[] havings = start >= 0 && end > start ? new String[]{havingsStr} : StringUtil.split(havingsStr); + if (havings != null) { + for (int j = 0; j < havings.length; j++) { + ind ++; + String h = havings[j]; + if (StringUtil.isEmpty(h, true)) { + throw new IllegalArgumentException(table + ":{ " + havingKey + ":value } 里的" + + " value 中的第 " + ind + " 个字符 '" + h + "' 不合法!不允许为空!"); + } + + havingMap.put("having" + ind, h); + + if (isHavingAnd == false && IS_HAVING_DEFAULT_AND == false) { + havingCombine += (ind <= 0 ? "" : " | ") + "having" + ind; + } + } + } + } + } + } + else if (newHaving instanceof JSONObject) { + if (isHavingAnd) { + throw new IllegalArgumentException(table + ":{ " + havingKey + ":value } 里的 value 类型不合法!" + + "@having&:value 中 value 只能是 String,@having:value 中 value 只能是 String 或 JSONObject !"); + } + + JSONObject havingObj = (JSONObject) newHaving; + Set> havingSet = havingObj.entrySet(); + for (Entry entry : havingSet) { + String k = entry == null ? null : entry.getKey(); + Object v = k == null ? null : entry.getValue(); + if (v == null) { + continue; + } + if (v instanceof String == false) { + throw new IllegalArgumentException(table + ":{ " + havingKey + ":{ " + k + ":value } } 里的" + + " value 不合法!类型只能是 String,且不允许为空!"); + } + + if (KEY_COMBINE.equals(k)) { + havingCombine = (String) v; + } + else if (StringUtil.isName(k) == false) { + throw new IllegalArgumentException(table + ":{ " + havingKey + ":{ " + k + ":value } } 里的" + + " key 对应字符 " + k + " 不合法!必须为 英文字母 开头,且只包含 英文字母、下划线、数字 的合法变量名!"); + } + else { + havingMap.put(k, (String) v); + } + } + } + else if (newHaving != null) { + throw new IllegalArgumentException(table + ":{ " + havingKey + ":value } 里的 value 类型不合法!" + + "@having:value 中 value 只能是 String 或 JSONObject,@having&:value 中 value 只能是 String !"); + } + // @having, @haivng& >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> + config.setExplain(explain); config.setCache(getCache(cache)); @@ -4587,7 +4732,8 @@ else if (whereList != null && whereList.contains(key)) { config.setCast(castMap); config.setWhere(tableWhere); config.setGroup(group); - config.setHaving(having); + config.setHaving(havingMap); + config.setHavingCombine(havingCombine); config.setOrder(order); String[] jsons = StringUtil.split(json); @@ -4614,6 +4760,7 @@ else if (whereList != null && whereList.contains(key)) { request.put(KEY_COMBINE, combine); request.put(KEY_GROUP, group); request.put(KEY_HAVING, having); + request.put(KEY_HAVING_AND, havingAnd); request.put(KEY_ORDER, order); request.put(KEY_RAW, raw); request.put(KEY_JSON, json); diff --git a/APIJSONORM/src/main/java/apijson/orm/SQLConfig.java b/APIJSONORM/src/main/java/apijson/orm/SQLConfig.java index 51a8df36b..22acf1461 100755 --- a/APIJSONORM/src/main/java/apijson/orm/SQLConfig.java +++ b/APIJSONORM/src/main/java/apijson/orm/SQLConfig.java @@ -159,8 +159,11 @@ public interface SQLConfig { String getGroup(); SQLConfig setGroup(String group); - String getHaving(); - SQLConfig setHaving(String having); + Map getHaving(); + SQLConfig setHaving(Map having); + + String getHavingCombine(); + SQLConfig setHavingCombine(String havingCombine); String getOrder(); SQLConfig setOrder(String order); From 12738bfb6bafb4bdcd91afd58016119f37bd5598 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Mon, 21 Mar 2022 00:46:20 +0800 Subject: [PATCH 0594/1181] =?UTF-8?q?=E8=A7=A3=E5=86=B3=20JOIN=20ON=20?= =?UTF-8?q?=E4=B8=AD=E7=94=A8=20@combine=20=E7=AD=89=E6=83=85=E5=86=B5?= =?UTF-8?q?=E4=B8=8B=E9=A2=84=E7=BC=96=E8=AF=91=E5=80=BC=E4=B8=8E=20SQL=20?= =?UTF-8?q?=E4=B8=AD=20=3F=20=E5=8D=A0=E4=BD=8D=E7=AC=A6=E9=A1=BA=E5=BA=8F?= =?UTF-8?q?=E5=AF=B9=E4=B8=8D=E4=B8=8A=E5=AF=BC=E8=87=B4=E7=9A=84=E5=BC=82?= =?UTF-8?q?=E5=B8=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/apijson/orm/AbstractSQLConfig.java | 24 ++++++++++++------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java index c9c64b61e..fd9631c2e 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java @@ -2463,8 +2463,10 @@ protected String parseCombineExpression(RequestMethod method, String quote, Stri int n = s.length(); if (n > 0) { - setPreparedValueList(new ArrayList<>()); - + if (isHaving == false) { + setPreparedValueList(new ArrayList<>()); // 必须反过来,否则 JOIN ON 内部 @combine 拼接后顺序错误 + } + int maxDepth = getMaxCombineDepth(); int maxCombineCount = getMaxCombineCount(); int maxCombineKeyCount = getMaxCombineKeyCount(); @@ -2675,13 +2677,17 @@ else if (c == ')') { if (StringUtil.isEmpty(result, true)) { result = andCond; } - else if (StringUtil.isNotEmpty(andCond, true)) { // andWhere 必须放后面,否则 prepared 值顺序错误 - // result = "( " + result + " )" + AND + andCond; - result = andCond + AND + "( " + result + " )"; // 先暂存之前的 prepared 值,然后反向整合 - if (n > 0) { - prepreadValues.addAll(getPreparedValueList()); - setPreparedValueList(prepreadValues); - } + else if (StringUtil.isNotEmpty(andCond, true)) { // andCond 必须放后面,否则 prepared 值顺序错误 + if (isHaving) { // HAVING 前 WHERE 已经有条件 ? 占位,不能反过来,想优化 AND 连接在最前,需要多遍历一次内部的 key,也可以 newSQLConfig 时存到 andList + result = "( " + result + " )" + AND + andCond; + } + else { + result = andCond + AND + "( " + result + " )"; // 先暂存之前的 prepared 值,然后反向整合 + if (n > 0) { + prepreadValues.addAll(getPreparedValueList()); + setPreparedValueList(prepreadValues); + } + } } return result; From be92b894b5beb6f2b1cdd8ffe71f9fbef11ae79a Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Sat, 26 Mar 2022 16:26:27 +0800 Subject: [PATCH 0595/1181] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index e0d4110cf..1aca30303 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ This source code is licensed under the Apache License Version 2.0
APIJSON -

零代码、热更新、全自动 ORM 库
🚀 后端接口和文档零代码,前端(客户端) 定制返回 JSON 的数据和结构

+

零代码、全自动、强安全 ORM 库
🚀 后端接口和文档零代码,前端(客户端) 定制返回 JSON 的数据和结构

From b248c698887728bd826febd77c4404dd1faecf06 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Sat, 26 Mar 2022 23:56:23 +0800 Subject: [PATCH 0596/1181] =?UTF-8?q?=E4=BC=98=E5=8C=96=20@combine=20?= =?UTF-8?q?=E5=AF=B9=E5=BA=94=E7=9A=84=E4=BB=A3=E7=A0=81=EF=BC=8C=E9=BB=98?= =?UTF-8?q?=E8=AE=A4=20combine=20=E4=B8=BA=E9=80=BB=E8=BE=91=E8=BF=90?= =?UTF-8?q?=E7=AE=97=E6=A8=A1=E6=9D=BF=EF=BC=8C=E5=8E=9F=E6=9D=A5=E7=9A=84?= =?UTF-8?q?=20combine=20=E9=87=8D=E5=91=BD=E5=90=8D=E4=B8=BA=20combineMap?= =?UTF-8?q?=EF=BC=8CcombineExpression=20=E9=87=8D=E5=91=BD=E5=90=8D?= =?UTF-8?q?=E4=B8=BA=20combine?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/apijson/orm/AbstractSQLConfig.java | 83 ++++++++++++------- .../java/apijson/orm/AbstractSQLExecutor.java | 2 +- .../src/main/java/apijson/orm/SQLConfig.java | 32 +++---- 3 files changed, 70 insertions(+), 47 deletions(-) diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java index fd9631c2e..17aebb59a 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java @@ -775,8 +775,8 @@ public String getUserIdKey() { private Map cast; private Map content; //Request内容,key:value形式,column = content.keySet(),values = content.values() private Map where; //筛选条件,key:value形式 - private Map> combine; //条件组合,{ "&":[key], "|":[key], "!":[key] } - private String combineExpression; + private String combine; //条件组合, a | (b & c & !(d | !e)) + private Map> combineMap; //条件组合,{ "&":[key], "|":[key], "!":[key] } //array item <<<<<<<<<< private int count; //Table数量 @@ -2238,31 +2238,31 @@ protected float getMaxCombineRatio() { @Override - public String getCombineExpression() { - return combineExpression; + public String getCombine() { + return combine; } @Override - public AbstractSQLConfig setCombineExpression(String combineExpression) { - this.combineExpression = combineExpression; + public AbstractSQLConfig setCombine(String combine) { + this.combine = combine; return this; } @NotNull @Override - public Map> getCombine() { - List andList = combine == null ? null : combine.get("&"); + public Map> getCombineMap() { + List andList = combineMap == null ? null : combineMap.get("&"); if (andList == null) { andList = where == null ? new ArrayList() : new ArrayList(where.keySet()); - if (combine == null) { - combine = new HashMap<>(); + if (combineMap == null) { + combineMap = new HashMap<>(); } - combine.put("&", andList); + combineMap.put("&", andList); } - return combine; + return combineMap; } @Override - public AbstractSQLConfig setCombine(Map> combine) { - this.combine = combine; + public AbstractSQLConfig setCombineMap(Map> combineMap) { + this.combineMap = combineMap; return this; } @@ -2329,8 +2329,8 @@ public AbstractSQLConfig putWhere(String key, Object value, boolean prior) { where.put(key, value); } - combine = getCombine(); - List andList = combine.get("&"); + Map> combineMap = getCombineMap(); + List andList = combineMap.get("&"); if (value == null) { if (andList != null) { andList.remove(key); @@ -2392,7 +2392,7 @@ else if (key.equals(userIdInKey)) { andList.add(key); //AbstractSQLExecutor.onPutColumn里getSQL,要保证缓存的SQL和查询的SQL里 where 的 key:value 顺序一致 } } - combine.put("&", andList); + combineMap.put("&", andList); } return this; @@ -2405,11 +2405,11 @@ else if (key.equals(userIdInKey)) { @JSONField(serialize = false) @Override public String getWhereString(boolean hasPrefix) throws Exception { - String ce = getCombineExpression(); - if (StringUtil.isEmpty(ce, true)) { - return getWhereString(hasPrefix, getMethod(), getWhere(), getCombine(), getJoinList(), ! isTest()); + String combineExpr = getCombine(); + if (StringUtil.isEmpty(combineExpr, true)) { + return getWhereString(hasPrefix, getMethod(), getWhere(), getCombineMap(), getJoinList(), ! isTest()); } - return getWhereString(hasPrefix, getMethod(), getWhere(), ce, getJoinList(), ! isTest()); + return getWhereString(hasPrefix, getMethod(), getWhere(), combineExpr, getJoinList(), ! isTest()); } /**获取WHERE * @param method @@ -2432,7 +2432,19 @@ public String getWhereString(boolean hasPrefix, RequestMethod method, Map conditioinMap, String combine, boolean verifyName, boolean containRaw, boolean isHaving) throws Exception { @@ -2693,6 +2705,17 @@ else if (StringUtil.isNotEmpty(andCond, true)) { // andCond 必须放后面, return result; } + /**已废弃,最快 6.0 删除,请尽快把前端/客户端 @combine:"a,b" 这种旧方式改为 @combine:"a | b" 这种新方式 + * @param hasPrefix + * @param method + * @param where + * @param combine + * @param joinList + * @param verifyName + * @return + * @throws Exception + */ + @Deprecated public String getWhereString(boolean hasPrefix, RequestMethod method, Map where, Map> combine, List joinList, boolean verifyName) throws Exception { Set>> combineSet = combine == null ? null : combine.entrySet(); if (combineSet == null || combineSet.isEmpty()) { @@ -4437,9 +4460,9 @@ else if (id instanceof Subquery) {} List whereList = null; String[] ws = StringUtil.split(combine); - String combineExpression = ws == null || ws.length != 1 ? null : ws[0]; + String combineExpr = ws == null || ws.length != 1 ? null : ws[0]; - Map> combineMap = StringUtil.isNotEmpty(combineExpression, true) ? null : new LinkedHashMap<>(); + Map> combineMap = StringUtil.isNotEmpty(combineExpr, true) ? null : new LinkedHashMap<>(); List andList = combineMap == null ? null : new ArrayList<>(); List orList = combineMap == null ? null : new ArrayList<>(); List notList = combineMap == null ? null : new ArrayList<>(); @@ -4459,14 +4482,14 @@ else if (id instanceof Subquery) {} } if (combineMap == null) { - if (StringUtil.isNotEmpty(combineExpression, true)) { + if (StringUtil.isNotEmpty(combineExpr, true)) { List banKeyList = Arrays.asList(idKey, idInKey, userIdKey, userIdInKey); for (String key : banKeyList) { - int index = combineExpression.indexOf(key); + int index = combineExpr.indexOf(key); if (index >= 0) { - char left = index <= 0 ? ' ' : combineExpression.charAt(index - 1); - char right = index >= combineExpression.length() - key.length() ? ' ' : combineExpression.charAt(index + key.length()); + char left = index <= 0 ? ' ' : combineExpr.charAt(index - 1); + char right = index >= combineExpr.length() - key.length() ? ' ' : combineExpr.charAt(index + key.length()); if ((left == ' ' || left == '(' || left == '&' || left == '|' || left == '!') && (right == ' ' || right == ')')) { throw new UnsupportedOperationException(table + ":{} 里的 @combine:value 中的 value 里 " + key + " 不合法!" + "不允许传 [" + idKey + ", " + idInKey + ", " + userIdKey + ", " + userIdInKey + "] 其中任何一个!"); @@ -4567,8 +4590,8 @@ else if (whereList != null && whereList.contains(key)) { combineMap.put("|", orList); combineMap.put("!", notList); } - config.setCombine(combineMap); - config.setCombineExpression(combineExpression); + config.setCombineMap(combineMap); + config.setCombine(combineExpr); config.setContent(tableContent); } diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java index bd5c0cf00..c99f1e5eb 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java @@ -335,7 +335,7 @@ public JSONObject execute(@NotNull SQLConfig config, boolean unknowType) throws capacity = config.getCount() <= 0 ? Parser.MAX_QUERY_COUNT : config.getCount(); if (capacity > 100) { // 有条件,条件越多过滤数据越多 - Map> combine = config.getCombine(); + Map> combine = config.getCombineMap(); List andList = combine == null ? null : combine.get("&"); int andCondCount = andList == null ? (config.getWhere() == null ? 0 : config.getWhere().size()) : andList.size(); diff --git a/APIJSONORM/src/main/java/apijson/orm/SQLConfig.java b/APIJSONORM/src/main/java/apijson/orm/SQLConfig.java index 22acf1461..43561fdd8 100755 --- a/APIJSONORM/src/main/java/apijson/orm/SQLConfig.java +++ b/APIJSONORM/src/main/java/apijson/orm/SQLConfig.java @@ -156,18 +156,6 @@ public interface SQLConfig { List getRaw(); SQLConfig setRaw(List raw); - String getGroup(); - SQLConfig setGroup(String group); - - Map getHaving(); - SQLConfig setHaving(Map having); - - String getHavingCombine(); - SQLConfig setHavingCombine(String havingCombine); - - String getOrder(); - SQLConfig setOrder(String order); - Subquery getFrom(); SQLConfig setFrom(Subquery from); @@ -180,11 +168,11 @@ public interface SQLConfig { Map getContent(); SQLConfig setContent(Map content); - Map> getCombine(); - SQLConfig setCombine(Map> combine); + Map> getCombineMap(); + SQLConfig setCombineMap(Map> combineMap); - String getCombineExpression(); - SQLConfig setCombineExpression(String combineExpression); + String getCombine(); + SQLConfig setCombine(String combine); Map getCast(); SQLConfig setCast(Map cast); @@ -195,6 +183,18 @@ public interface SQLConfig { Map getWhere(); SQLConfig setWhere(Map where); + String getGroup(); + SQLConfig setGroup(String group); + + Map getHaving(); + SQLConfig setHaving(Map having); + + String getHavingCombine(); + SQLConfig setHavingCombine(String havingCombine); + + String getOrder(); + SQLConfig setOrder(String order); + /** * exactMatch = false * @param key From b881541295e32f8cfce8c05391e7d70892e2bcd5 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Sat, 26 Mar 2022 23:56:50 +0800 Subject: [PATCH 0597/1181] =?UTF-8?q?=E4=BC=98=E5=8C=96=E4=BB=A3=E7=A0=81?= =?UTF-8?q?=EF=BC=8C=E5=8E=BB=E9=99=A4=E4=B8=8D=E5=BF=85=E8=A6=81=E7=9A=84?= =?UTF-8?q?=20synchonized?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../main/java/apijson/orm/AbstractParser.java | 2 +- .../java/apijson/orm/AbstractSQLConfig.java | 18 ++++++++---------- .../java/apijson/orm/AbstractSQLExecutor.java | 18 +++++++++++------- 3 files changed, 20 insertions(+), 18 deletions(-) diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java b/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java index 6e407144e..4e615ce08 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java @@ -1769,7 +1769,7 @@ public static String replaceArrayChildPath(String parentPath, String valuePath) * @param result 需要被关联的object */ @Override - public synchronized void putQueryResult(String path, Object result) { + public void putQueryResult(String path, Object result) { Log.i(TAG, "\n putQueryResult valuePath = " + path + "; result = " + result + "\n <<<<<<<<<<<<<<<<<<<<<<<"); // if (queryResultMap.containsKey(valuePath)) {//只保存被关联的value Log.d(TAG, "putQueryResult queryResultMap.containsKey(valuePath) >> queryResultMap.put(path, result);"); diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java index 17aebb59a..27689a5cb 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java @@ -2303,18 +2303,16 @@ public Object getWhere(String key, boolean exactMatch) { if (key == null || where == null){ return null; } - synchronized (where) { - if (where != null) { - int index; - for (Entry entry : where.entrySet()) { - String k = entry.getKey(); - index = k.indexOf(key); - if (index >= 0 && StringUtil.isName(k.substring(index)) == false) { - return entry.getValue(); - } - } + + int index; + for (Entry entry : where.entrySet()) { + String k = entry.getKey(); + index = k.indexOf(key); + if (index >= 0 && StringUtil.isName(k.substring(index, index + 1)) == false) { + return entry.getValue(); } } + return null; } @Override diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java index c99f1e5eb..2c36b35e6 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java @@ -1120,18 +1120,22 @@ public void close() { @Override public ResultSet executeQuery(@NotNull SQLConfig config) throws Exception { - PreparedStatement s = getStatement(config); -// 不准,getStatement 有时比 execute sql 更耗时 executedSQLStartTime = System.currentTimeMillis(); - ResultSet rs = s.executeQuery(); //PreparedStatement 不用传 SQL -// executedSQLEndTime = System.currentTimeMillis(); + PreparedStatement stt = getStatement(config); + // 不准,getStatement 有时比 execute sql 更耗时 executedSQLStartTime = System.currentTimeMillis(); + ResultSet rs = stt.executeQuery(); //PreparedStatement 不用传 SQL + // executedSQLEndTime = System.currentTimeMillis(); + // if (config.isExplain() && (config.isSQLServer() || config.isOracle())) { + // FIXME 返回的是 boolean 值 rs = stt.getMoreResults(Statement.CLOSE_CURRENT_RESULT); + // } + return rs; } @Override public int executeUpdate(@NotNull SQLConfig config) throws Exception { - PreparedStatement s = getStatement(config); + PreparedStatement stt = getStatement(config); // 不准,getStatement 有时比 execute sql 更耗时 executedSQLStartTime = System.currentTimeMillis(); - int count = s.executeUpdate(); // PreparedStatement 不用传 SQL + int count = stt.executeUpdate(); // PreparedStatement 不用传 SQL // executedSQLEndTime = System.currentTimeMillis(); if (count <= 0 && config.isHive()) { @@ -1139,7 +1143,7 @@ public int executeUpdate(@NotNull SQLConfig config) throws Exception { } if (config.getId() == null && config.getMethod() == RequestMethod.POST) { // 自增id - ResultSet rs = s.getGeneratedKeys(); + ResultSet rs = stt.getGeneratedKeys(); if (rs != null && rs.next()) { config.setId(rs.getLong(1)); //返回插入的主键id FIXME Oracle 拿不到 } From 2433f3b50fc9c84d54b85ca5023a6f132f5a8637 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Sun, 27 Mar 2022 02:38:47 +0800 Subject: [PATCH 0598/1181] =?UTF-8?q?=E5=AE=8C=E5=96=84=E5=AF=B9=20id,=20i?= =?UTF-8?q?d{},=20userId,=20userId{}=20=E7=9A=84=E6=9D=A1=E4=BB=B6?= =?UTF-8?q?=E5=BC=BA=E5=88=B6=E5=89=8D=E7=BD=AE=20AND=20=E5=A4=84=E7=90=86?= =?UTF-8?q?=E4=BB=A5=E5=8F=8A=20@combine=20=E4=B8=8D=E5=85=81=E8=AE=B8?= =?UTF-8?q?=E8=AE=BE=E7=BD=AE=E7=9A=84=E6=A0=A1=E9=AA=8C=EF=BC=8C=E9=81=BF?= =?UTF-8?q?=E5=85=8D=20id!=20|=20id=20=E8=BF=99=E7=A7=8D=E9=87=8D=E5=A4=8D?= =?UTF-8?q?=E5=AD=97=E7=AC=A6=E7=BB=95=E8=BF=87=E6=A0=A1=E9=AA=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/apijson/orm/AbstractSQLConfig.java | 309 ++++++++++++------ 1 file changed, 209 insertions(+), 100 deletions(-) diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java index 27689a5cb..851ec4e7c 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java @@ -748,10 +748,15 @@ public String getUserIdKey() { } - private Object id; //Table的id private RequestMethod method; //操作方法 private boolean prepared = true; //预编译 private boolean main = true; + + private Object id; // Table 的 id + private Object idIn; // User Table 的 id IN + private Object userId; // Table 的 userId + private Object userIdIn; // Table 的 userId IN + /** * TODO 被关联的表通过就忽略关联的表?(这个不行 User:{"sex@":"/Comment/toId"}) */ @@ -857,6 +862,37 @@ public AbstractSQLConfig setId(Object id) { this.id = id; return this; } + + @Override + public Object getIdIn() { + return idIn; + } + @Override + public AbstractSQLConfig setIdIn(Object idIn) { + this.idIn = idIn; + return this; + } + + + @Override + public Object getUserId() { + return userId; + } + @Override + public AbstractSQLConfig setUserId(Object userId) { + this.userId = userId; + return this; + } + + @Override + public Object getUserIdIn() { + return userIdIn; + } + @Override + public AbstractSQLConfig setUserIdIn(Object userIdIn) { + this.userIdIn = userIdIn; + return this; + } @Override public String getRole() { @@ -2348,18 +2384,22 @@ else if (prior && andList.isEmpty() == false) { int lastIndex; if (key.equals(idKey)) { + setId(value); lastIndex = -1; } else if (key.equals(idInKey)) { + setIdIn(value); lastIndex = andList.lastIndexOf(idKey); } else if (key.equals(userIdKey)) { + setUserId(value); lastIndex = andList.lastIndexOf(idInKey); if (lastIndex < 0) { lastIndex = andList.lastIndexOf(idKey); } } else if (key.equals(userIdInKey)) { + setUserIdIn(value); lastIndex = andList.lastIndexOf(userIdKey); if (lastIndex < 0) { lastIndex = andList.lastIndexOf(idInKey); @@ -2404,7 +2444,7 @@ else if (key.equals(userIdInKey)) { @Override public String getWhereString(boolean hasPrefix) throws Exception { String combineExpr = getCombine(); - if (StringUtil.isEmpty(combineExpr, true)) { + if (StringUtil.isEmpty(combineExpr, false)) { return getWhereString(hasPrefix, getMethod(), getWhere(), getCombineMap(), getJoinList(), ! isTest()); } return getWhereString(hasPrefix, getMethod(), getWhere(), combineExpr, getJoinList(), ! isTest()); @@ -2417,10 +2457,9 @@ public String getWhereString(boolean hasPrefix) throws Exception { */ @JSONField(serialize = false) public String getWhereString(boolean hasPrefix, RequestMethod method, Map where, String combine, List joinList, boolean verifyName) throws Exception { + String whereString = parseCombineExpression(method, getQuote(), getTable(), getAliasWithQuote(), where, combine, verifyName, false, false); - whereString = concatJoinWhereString(whereString); - String result = StringUtil.isEmpty(whereString, true) ? "" : (hasPrefix ? " WHERE " : "") + whereString; if (result.isEmpty() && RequestMethod.isQueryMethod(method) == false) { @@ -4256,23 +4295,22 @@ public static SQLConfig newSQLConfig(RequestMethod method, String table, String return config; //request.remove(key); 前都可以直接return,之后必须保证 put 回去 } + //对 id, id{}, userId, userId{} 处理,这些只要不为 null 就一定会作为 AND 条件 <<<<<<<<<<<<<<<<<<<<<<<<< String idKey = callback.getIdKey(datasource, database, schema, table); String idInKey = idKey + "{}"; String userIdKey = callback.getUserIdKey(datasource, database, schema, table); String userIdInKey = userIdKey + "{}"; - //对id和id{}处理,这两个一定会作为条件 - Object idIn = request.get(idInKey); //可能是 id{}:">0" - if (idIn instanceof List) { // 排除掉 0, 负数, 空字符串 等无效 id 值 - List ids = ((List) idIn); + if (idIn instanceof Collection) { // 排除掉 0, 负数, 空字符串 等无效 id 值 + Collection ids = (Collection) idIn; List newIdIn = new ArrayList<>(); - Object d; - for (int i = 0; i < ids.size(); i++) { //不用 idIn.contains(id) 因为 idIn 里存到很可能是 Integer,id 又是 Long! - d = ids.get(i); + for (Object d : ids) { //不用 idIn.contains(id) 因为 idIn 里存到很可能是 Integer,id 又是 Long! if ((d instanceof Number && ((Number) d).longValue() > 0) || (d instanceof String && StringUtil.isNotEmpty(d, true))) { - newIdIn.add(d); + if (newIdIn.contains(d) == false) { + newIdIn.add(d); + } } } if (newIdIn.isEmpty()) { @@ -4286,8 +4324,7 @@ public static SQLConfig newSQLConfig(RequestMethod method, String table, String } Object id = request.get(idKey); - boolean hasId = id != null; - if (method == POST && hasId == false) { + if (id == null && method == POST) { id = callback.newId(method, database, schema, table); // null 表示数据库自增 id } @@ -4307,12 +4344,10 @@ else if (id instanceof Subquery) {} throw new IllegalArgumentException(idKey + ":value 中 value 的类型只能是 Long , String 或 Subquery !"); } - if (idIn instanceof List) { //共用idIn场景少性能差 + if (idIn instanceof Collection) { //共用idIn场景少性能差 boolean contains = false; - List ids = ((List) idIn); - Object d; - for (int i = 0; i < ids.size(); i++) { //不用 idIn.contains(id) 因为 idIn 里存到很可能是 Integer,id 又是 Long! - d = ids.get(i); + Collection idList = ((Collection) idIn); + for (Object d : idList) { //不用 idIn.contains(id) 因为 idIn 里存到很可能是 Integer,id 又是 Long! if (d != null && id.toString().equals(d.toString())) { contains = true; break; @@ -4328,7 +4363,59 @@ else if (id instanceof Subquery) {} } } - + //对 userId 和 userId{} 处理,这两个一定会作为条件 + Object userIdIn = request.get(userIdInKey); //可能是 userId{}:">0" + if (userIdIn instanceof Collection) { // 排除掉 0, 负数, 空字符串 等无效 userId 值 + Collection userIds = (Collection) userIdIn; + List newUserIdIn = new ArrayList<>(); + for (Object d : userIds) { //不用 userIdIn.contains(userId) 因为 userIdIn 里存到很可能是 Integer,userId 又是 Long! + if ((d instanceof Number && ((Number) d).longValue() > 0) || (d instanceof String && StringUtil.isNotEmpty(d, true))) { + if (newUserIdIn.contains(d) == false) { + newUserIdIn.add(d); + } + } + } + if (newUserIdIn.isEmpty()) { + throw new NotExistException(TAG + ": newSQLConfig userIdIn instanceof List >> 去掉无效 userId 后 newIdIn.isEmpty()"); + } + userIdIn = newUserIdIn; + } + + Object userId = request.get(userIdKey); + if (userId != null) { //null无效 + if (userId instanceof Number) { + if (((Number) userId).longValue() <= 0) { //一定没有值 + throw new NotExistException(TAG + ": newSQLConfig " + table + ".userId <= 0"); + } + } + else if (userId instanceof String) { + if (StringUtil.isEmpty(userId, true)) { //一定没有值 + throw new NotExistException(TAG + ": newSQLConfig StringUtil.isEmpty(" + table + ".userId, true)"); + } + } + else if (userId instanceof Subquery) {} + else { + throw new IllegalArgumentException(userIdKey + ":value 中 value 的类型只能是 Long , String 或 Subquery !"); + } + + if (userIdIn instanceof Collection) { //共用userIdIn场景少性能差 + boolean contains = false; + Collection userIds = (Collection) userIdIn; + for (Object d : userIds) { //不用 userIdIn.contains(userId) 因为 userIdIn 里存到很可能是 Integer,userId 又是 Long! + if (d != null && userId.toString().equals(d.toString())) { + contains = true; + break; + } + } + if (contains == false) {//empty有效 BaseModel.isEmpty(userIdIn) == false) { + throw new NotExistException(TAG + ": newSQLConfig userIdIn != null && (((List) userIdIn).contains(userId) == false"); + } + } + } + + //对 id, id{}, userId, userId{} 处理,这些只要不为 null 就一定会作为 AND 条件 >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> + + String role = request.getString(KEY_ROLE); String cache = request.getString(KEY_CACHE); Subquery from = (Subquery) request.get(KEY_FROM); @@ -4347,6 +4434,8 @@ else if (id instanceof Subquery) {} //强制作为条件且放在最前面优化性能 request.remove(idKey); request.remove(idInKey); + request.remove(userIdKey); + request.remove(userIdInKey); //关键词 request.remove(KEY_ROLE); request.remove(KEY_EXPLAIN); @@ -4452,107 +4541,116 @@ else if (id instanceof Subquery) {} } } else { //非POST操作 - final boolean isWhere = method != PUT;//除了POST,PUT,其它全是条件!!! + final boolean isWhere = method != PUT; //除了POST,PUT,其它全是条件!!! //条件<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< List whereList = null; String[] ws = StringUtil.split(combine); + if (ws != null && (method == DELETE || method == GETS || method == HEADS)) { + throw new IllegalArgumentException(table + ":{} 里的 @combine:value 不合法!DELETE,GETS,HEADS 请求不允许传 @combine:value !"); + } + String combineExpr = ws == null || ws.length != 1 ? null : ws[0]; - Map> combineMap = StringUtil.isNotEmpty(combineExpr, true) ? null : new LinkedHashMap<>(); - List andList = combineMap == null ? null : new ArrayList<>(); - List orList = combineMap == null ? null : new ArrayList<>(); - List notList = combineMap == null ? null : new ArrayList<>(); + Map> combineMap = new LinkedHashMap<>(); + List andList = new ArrayList<>(); + List orList = new ArrayList<>(); + List notList = new ArrayList<>(); //强制作为条件且放在最前面优化性能 if (id != null) { tableWhere.put(idKey, id); - if (andList != null) { - andList.add(idKey); - } + andList.add(idKey); } if (idIn != null) { tableWhere.put(idInKey, idIn); - if (andList != null) { - andList.add(idInKey); - } + andList.add(idInKey); } - - if (combineMap == null) { - if (StringUtil.isNotEmpty(combineExpr, true)) { - List banKeyList = Arrays.asList(idKey, idInKey, userIdKey, userIdInKey); - - for (String key : banKeyList) { - int index = combineExpr.indexOf(key); - if (index >= 0) { - char left = index <= 0 ? ' ' : combineExpr.charAt(index - 1); - char right = index >= combineExpr.length() - key.length() ? ' ' : combineExpr.charAt(index + key.length()); - if ((left == ' ' || left == '(' || left == '&' || left == '|' || left == '!') && (right == ' ' || right == ')')) { - throw new UnsupportedOperationException(table + ":{} 里的 @combine:value 中的 value 里 " + key + " 不合法!" - + "不允许传 [" + idKey + ", " + idInKey + ", " + userIdKey + ", " + userIdInKey + "] 其中任何一个!"); - } + if (userId != null) { + tableWhere.put(userIdKey, userId); + andList.add(userIdKey); + } + if (userIdIn != null) { + tableWhere.put(userIdInKey, userIdIn); + andList.add(userIdInKey); + } + + if (StringUtil.isNotEmpty(combineExpr, true)) { + List banKeyList = Arrays.asList(idKey, idInKey, userIdKey, userIdInKey); + for (String key : banKeyList) { + String str = combineExpr; + while (str.isEmpty() == false) { + int index = str.indexOf(key); + if (index < 0) { + break; + } + + char left = index <= 0 ? ' ' : str.charAt(index - 1); + char right = index >= str.length() - key.length() ? ' ' : str.charAt(index + key.length()); + if ((left == ' ' || left == '(' || left == '&' || left == '|' || left == '!') && (right == ' ' || right == ')')) { + throw new UnsupportedOperationException(table + ":{} 里的 @combine:value 中的 value 里 " + key + " 不合法!" + + "不允许传 [" + idKey + ", " + idInKey + ", " + userIdKey + ", " + userIdInKey + "] 其中任何一个!"); } + + int newIndex = index + key.length() + 1; + if (str.length() <= newIndex) { + break; + } + str = str.substring(newIndex); } } - } - else { - if (ws != null) { - if (method == DELETE || method == GETS || method == HEADS) { - throw new IllegalArgumentException("DELETE,GETS,HEADS 请求不允许传 @combine:value !"); - } - whereList = new ArrayList<>(); - - String w; - for (int i = 0; i < ws.length; i++) { //去除 &,|,! 前缀 - w = ws[i]; - if (w != null) { - if (w.startsWith("&")) { - w = w.substring(1); - andList.add(w); - } - else if (w.startsWith("|")) { - if (method == PUT) { - throw new IllegalArgumentException(table + ":{} 里的 @combine:value 中的value里条件 " + ws[i] + " 不合法!" - + "PUT请求的 @combine:\"key0,key1,...\" 不允许传 |key 或 !key !"); - } - w = w.substring(1); - orList.add(w); - } - else if (w.startsWith("!")) { - if (method == PUT) { - throw new IllegalArgumentException(table + ":{} 里的 @combine:value 中的value里条件 " + ws[i] + " 不合法!" - + "PUT请求的 @combine:\"key0,key1,...\" 不允许传 |key 或 !key !"); - } - w = w.substring(1); - notList.add(w); + } + else if (ws != null) { + whereList = new ArrayList<>(); + + String w; + for (int i = 0; i < ws.length; i++) { //去除 &,|,! 前缀 + w = ws[i]; + if (w != null) { + if (w.startsWith("&")) { + w = w.substring(1); + andList.add(w); + } + else if (w.startsWith("|")) { + if (method == PUT) { + throw new IllegalArgumentException(table + ":{} 里的 @combine:value 中的value里条件 " + ws[i] + " 不合法!" + + "PUT请求的 @combine:\"key0,key1,...\" 不允许传 |key 或 !key !"); } - else { - orList.add(w); + w = w.substring(1); + orList.add(w); + } + else if (w.startsWith("!")) { + if (method == PUT) { + throw new IllegalArgumentException(table + ":{} 里的 @combine:value 中的value里条件 " + ws[i] + " 不合法!" + + "PUT请求的 @combine:\"key0,key1,...\" 不允许传 |key 或 !key !"); } + w = w.substring(1); + notList.add(w); + } + else { + orList.add(w); + } - if (w.isEmpty()) { - throw new IllegalArgumentException(table + ":{} 里的 @combine:value 中的value里条件 " + ws[i] + " 不合法!不允许为空值!"); - } - else { - if (idKey.equals(w) || idInKey.equals(w) || userIdKey.equals(w) || userIdInKey.equals(w)) { - throw new UnsupportedOperationException(table + ":{} 里的 @combine:value 中的 value 里 " + ws[i] + " 不合法!" - + "不允许传 [" + idKey + ", " + idInKey + ", " + userIdKey + ", " + userIdInKey + "] 其中任何一个!"); - } + if (w.isEmpty()) { + throw new IllegalArgumentException(table + ":{} 里的 @combine:value 中的value里条件 " + ws[i] + " 不合法!不允许为空值!"); + } + else { + if (idKey.equals(w) || idInKey.equals(w) || userIdKey.equals(w) || userIdInKey.equals(w)) { + throw new UnsupportedOperationException(table + ":{} 里的 @combine:value 中的 value 里 " + ws[i] + " 不合法!" + + "不允许传 [" + idKey + ", " + idInKey + ", " + userIdKey + ", " + userIdInKey + "] 其中任何一个!"); } - - whereList.add(w); } - // 可重写回调方法自定义处理 // 动态设置的场景似乎很少,而且去掉后不方便用户排错!//去掉判断,有时候不在没关系,如果是对增删改等非开放请求强制要求传对应的条件,可以用 Operation.NECESSARY - if (request.containsKey(w) == false) { //和 request.get(w) == null 没区别,前面 Parser 已经过滤了 null - // throw new IllegalArgumentException(table + ":{} 里的 @combine:value 中的value里 " + ws[i] + " 对应的 " + w + " 不在它里面!"); - callback.onMissingKey4Combine(table, request, combine, ws[i], w); - } + whereList.add(w); } + // 可重写回调方法自定义处理 // 动态设置的场景似乎很少,而且去掉后不方便用户排错!//去掉判断,有时候不在没关系,如果是对增删改等非开放请求强制要求传对应的条件,可以用 Operation.NECESSARY + if (request.containsKey(w) == false) { //和 request.get(w) == null 没区别,前面 Parser 已经过滤了 null + // throw new IllegalArgumentException(table + ":{} 里的 @combine:value 中的value里 " + ws[i] + " 对应的 " + w + " 不在它里面!"); + callback.onMissingKey4Combine(table, request, combine, ws[i], w); + } } - } //条件>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> @@ -4753,7 +4851,9 @@ else if (newHaving != null) { config.setRole(role); config.setId(id); - //在 tableWhere 第0个 config.setIdIn(idIn); + config.setIdIn(idIn); + config.setUserId(userId); + config.setUserIdIn(userIdIn); config.setNull(nullKeys == null || nullKeys.length <= 0 ? null : new ArrayList<>(Arrays.asList(nullKeys))); config.setCast(castMap); @@ -4767,13 +4867,22 @@ else if (newHaving != null) { config.setJson(jsons == null || jsons.length <= 0 ? null : new ArrayList<>(Arrays.asList(jsons))); } - finally {//后面还可能用到,要还原 - //id或id{}条件 - if (hasId) { + finally { // 后面还可能用到,要还原 + // id, id{}, userId, userIdIn 条件 + if (id != null) { request.put(idKey, id); } - request.put(idInKey, idIn); - //关键词 + if (idIn != null) { + request.put(idInKey, idIn); + } + if (userId != null) { + request.put(userIdKey, userId); + } + if (userIdIn != null) { + request.put(userIdInKey, userIdIn); + } + + // 关键词 request.put(KEY_DATABASE, database); request.put(KEY_ROLE, role); request.put(KEY_EXPLAIN, explain); From 544a8694165bde0f858d67678dcc784d24f04167 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Sun, 27 Mar 2022 02:39:36 +0800 Subject: [PATCH 0599/1181] =?UTF-8?q?=E9=A2=84=E4=BC=B0=E5=AE=B9=E9=87=8F?= =?UTF-8?q?=E6=96=B0=E5=A2=9E=E5=AF=B9=20HAVING=20=E8=81=9A=E5=90=88?= =?UTF-8?q?=E5=87=BD=E6=95=B0=E7=9A=84=E5=A4=84=E7=90=86=EF=BC=9B=E4=BC=98?= =?UTF-8?q?=E5=8C=96=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/main/java/apijson/StringUtil.java | 56 ++++++++++++++++--- .../main/java/apijson/orm/AbstractParser.java | 3 - .../java/apijson/orm/AbstractSQLExecutor.java | 32 +++++++---- .../src/main/java/apijson/orm/SQLConfig.java | 9 +++ 4 files changed, 79 insertions(+), 21 deletions(-) diff --git a/APIJSONORM/src/main/java/apijson/StringUtil.java b/APIJSONORM/src/main/java/apijson/StringUtil.java index 0d473a6b0..ecc64a083 100755 --- a/APIJSONORM/src/main/java/apijson/StringUtil.java +++ b/APIJSONORM/src/main/java/apijson/StringUtil.java @@ -225,13 +225,27 @@ public static int getLength(String s, boolean trim) { //判断字符是否为空 <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< + /**判断字符是否为空 trim = true + * @param obj + * @return + */ + public static boolean isEmpty(Object obj) { + return isEmpty(obj, true); + } /**判断字符是否为空 - * @param object + * @param obj * @param trim * @return */ - public static boolean isEmpty(Object object, boolean trim) { - return isEmpty(getString(object), trim); + public static boolean isEmpty(Object obj, boolean trim) { + return isEmpty(getString(obj), trim); + } + /**判断字符是否为空 trim = true + * @param cs + * @return + */ + public static boolean isEmpty(CharSequence cs) { + return isEmpty(cs, true); } /**判断字符是否为空 * @param cs @@ -241,6 +255,13 @@ public static boolean isEmpty(Object object, boolean trim) { public static boolean isEmpty(CharSequence cs, boolean trim) { return isEmpty(getString(cs), trim); } + /**判断字符是否为空 trim = true + * @param s + * @return + */ + public static boolean isEmpty(String s) { + return isEmpty(s, true); + } /**判断字符是否为空 * @param s * @param trim @@ -267,13 +288,27 @@ public static boolean isEmpty(String s, boolean trim) { //判断字符是否非空 <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< - /**判断字符是否非空 + /**判断字符是否非空 trim = true * @param object + * @return + */ + public static boolean isNotEmpty(Object obj) { + return ! isEmpty(obj); + } + /**判断字符是否非空 + * @param obj * @param trim * @return */ - public static boolean isNotEmpty(Object object, boolean trim) { - return isNotEmpty(getString(object), trim); + public static boolean isNotEmpty(Object obj, boolean trim) { + return ! isEmpty(obj, trim); + } + /**判断字符是否非空 trim = true + * @param cs + * @return + */ + public static boolean isNotEmpty(CharSequence cs) { + return ! isEmpty(cs); } /**判断字符是否非空 * @param cs @@ -281,7 +316,14 @@ public static boolean isNotEmpty(Object object, boolean trim) { * @return */ public static boolean isNotEmpty(CharSequence cs, boolean trim) { - return isNotEmpty(getString(cs), trim); + return ! isEmpty(cs, trim); + } + /**判断字符是否非空 trim = true + * @param s + * @return + */ + public static boolean isNotEmpty(String s) { + return ! isEmpty(s); } /**判断字符是否非空 * @param s diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java b/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java index 4e615ce08..25e851bf0 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java @@ -18,8 +18,6 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; -import java.util.LinkedHashMap; -import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Map.Entry; @@ -42,7 +40,6 @@ import apijson.Log; import apijson.NotNull; import apijson.RequestMethod; -import apijson.SQL; import apijson.StringUtil; import apijson.orm.exception.ConditionErrorException; import apijson.orm.exception.ConflictException; diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java index 2c36b35e6..3606c5043 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java @@ -264,11 +264,15 @@ public JSONObject execute(@NotNull SQLConfig config, boolean unknowType) throws //id,id{}至少一个会有,一定会返回,不用抛异常来阻止关联写操作时前面错误导致后面无条件执行! result.put(JSONResponse.KEY_COUNT, updateCount);//返回修改的记录数 + + String idKey = config.getIdKey(); if (config.getId() != null) { - result.put(config.getIdKey(), config.getId()); - } else { - result.put(config.getIdKey() + "[]", config.getWhere(config.getIdKey() + "{}", true)); + result.put(idKey, config.getId()); + } + if (config.getIdIn() != null) { + result.put(idKey + "[]", config.getIdIn()); } + return result; case GET: @@ -327,14 +331,14 @@ public JSONObject execute(@NotNull SQLConfig config, boolean unknowType) throws capacity = 1; } else { - Object idIn = config.getWhere(config.getIdKey() + "{}", true); + Object idIn = config.getIdIn(); if (idIn instanceof Collection) { // id{}:[] 一定是 AND 条件,最终返回数据最多就这么多 capacity = ((Collection) idIn).size(); } else { // 预估容量 capacity = config.getCount() <= 0 ? Parser.MAX_QUERY_COUNT : config.getCount(); if (capacity > 100) { - // 有条件,条件越多过滤数据越多 + // 有 WHERE 条件,条件越多过滤数据越多,暂时不考虑 @combine:"a | (b & !c)" 里面 | OR 和 ! NOT 条件,太复杂也不是很必要 Map> combine = config.getCombineMap(); List andList = combine == null ? null : combine.get("&"); @@ -346,23 +350,29 @@ public JSONObject execute(@NotNull SQLConfig config, boolean unknowType) throws List notList = combine == null ? null : combine.get("|"); int notCondCount = notList == null ? 0 : notList.size(); - - // 有分组,分组字段越少过滤数据越多 + // 有 GROUP BY 分组,字段越少过滤数据越多 String[] group = StringUtil.split(config.getGroup()); int groupCount = group == null ? 0 : group.length; if (groupCount > 0 && Arrays.asList(group).contains(config.getIdKey())) { groupCount = 0; } - capacity /= Math.pow(1.5, Math.log10(capacity) + andCondCount - + (orCondCount <= 0 ? 0 : 2/orCondCount) // 1: 2.3, 2: 1.5, 3: 1.3, 4: 1.23, 5: 1.18 - + (notCondCount/5) // 1: 1.08, 2: 1.18, 3: 1.28, 4: 1.38, 1.50 - + (groupCount <= 0 ? 0 : 10/groupCount) // 1: 57.7, 7.6, 3: 3.9, 4: 2.8, 5: 2.3 + // 有 HAVING 聚合函数,字段越多过滤数据越多,暂时不考虑 @combine:"a | (b & !c)" 里面 | OR 和 ! NOT 条件,太复杂也不是很必要 + Map having = config.getHaving(); + int havingCount = having == null ? 0 : having.size(); + + capacity /= Math.pow(1.5, Math.log10(capacity) + + andCondCount + + ((orCondCount <= 0 ? 0 : 2.0d/orCondCount) // 1: 2.3, 2: 1.5, 3: 1.3, 4: 1.23, 5: 1.18 + + (notCondCount/5.0d) // 1: 1.08, 2: 1.18, 3: 1.28, 4: 1.38, 1.50 + + (groupCount <= 0 ? 0 : 10.0d/groupCount)) // 1: 57.7, 7.6, 3: 3.9, 4: 2.8, 5: 2.3 + + havingCount ); capacity += 1; // 避免正好比需要容量少一点点导致多一次扩容,大量数据 System.arrayCopy } } } + resultList = new ArrayList<>(capacity); } diff --git a/APIJSONORM/src/main/java/apijson/orm/SQLConfig.java b/APIJSONORM/src/main/java/apijson/orm/SQLConfig.java index 43561fdd8..57e282d51 100755 --- a/APIJSONORM/src/main/java/apijson/orm/SQLConfig.java +++ b/APIJSONORM/src/main/java/apijson/orm/SQLConfig.java @@ -116,6 +116,15 @@ public interface SQLConfig { Object getId(); SQLConfig setId(Object id); + + Object getIdIn(); + SQLConfig setIdIn(Object idIn); + + Object getUserId(); + SQLConfig setUserId(Object userId); + + Object getUserIdIn(); + SQLConfig setUserIdIn(Object userIdIn); String getRole(); SQLConfig setRole(String role); From 46ccadec084742ea5bc7beaf061e2e1560115faf Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Sun, 27 Mar 2022 02:42:33 +0800 Subject: [PATCH 0600/1181] =?UTF-8?q?=E6=9D=83=E9=99=90=E6=8E=A7=E5=88=B6?= =?UTF-8?q?=EF=BC=9A=E5=88=86=E6=8B=86=E5=AF=B9=E8=A7=92=E8=89=B2=E7=9A=84?= =?UTF-8?q?=E6=A0=A1=E9=AA=8C=E7=9A=84=E4=BB=A3=E7=A0=81=E4=B8=BA=E5=A4=9A?= =?UTF-8?q?=E4=B8=AA=E6=96=B9=E6=B3=95=EF=BC=8C=E6=96=B9=E4=BE=BF=E7=81=B5?= =?UTF-8?q?=E6=B4=BB=E9=87=8D=E5=86=99=E9=83=A8=E5=88=86=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/apijson/orm/AbstractVerifier.java | 98 ++++++++++++------- .../src/main/java/apijson/orm/Verifier.java | 7 +- 2 files changed, 67 insertions(+), 38 deletions(-) diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractVerifier.java b/APIJSONORM/src/main/java/apijson/orm/AbstractVerifier.java index 1f366d0c0..feda82ac1 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractVerifier.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractVerifier.java @@ -240,6 +240,7 @@ public AbstractVerifier setVisitor(Visitor visitor) { * @return * @throws Exception */ + @Override public boolean verifyAccess(SQLConfig config) throws Exception { String table = config == null ? null : config.getTable(); if (table == null) { @@ -249,7 +250,7 @@ public boolean verifyAccess(SQLConfig config) throws Exception { String role = config.getRole(); if (role == null) { role = UNKNOWN; - } + } else { if (ROLE_MAP.containsKey(role) == false) { Set NAMES = ROLE_MAP.keySet(); @@ -262,14 +263,72 @@ public boolean verifyAccess(SQLConfig config) throws Exception { } RequestMethod method = config.getMethod(); + verifyRole(config, table, method, role); + + return true; + } + + @Override + public void verifyRole(SQLConfig config, String table, RequestMethod method, String role) throws Exception { + verifyAllowRole(config, table, method, role); //验证允许的角色 + verifyUseRole(config, table, method, role); //验证使用的角色 + } - verifyRole(table, method, role);//验证允许的角色 + /**允许请求使用的所以可能角色 + * @param config + * @param table + * @param method + * @param role + * @return + * @throws Exception + * @see {@link apijson.JSONObject#KEY_ROLE} + */ + public void verifyAllowRole(SQLConfig config, String table, RequestMethod method, String role) throws Exception { + Log.d(TAG, "verifyAllowRole table = " + table + "; method = " + method + "; role = " + role); + if (table == null) { + table = config == null ? null : config.getTable(); + } + + if (table != null) { + if (method == null) { + method = config == null ? GET : config.getMethod(); + } + if (role == null) { + role = config == null ? UNKNOWN : config.getRole(); + } + + Map map = ACCESS_MAP.get(table); + if (map == null || Arrays.asList(map.get(method)).contains(role) == false) { + throw new IllegalAccessException(table + " 不允许 " + role + " 用户的 " + method.name() + " 请求!"); + } + } + } + /**校验请求使用的角色,角色不好判断,让访问者发过来角色名,OWNER,CONTACT,ADMIN等 + * @param config + * @param table + * @param method + * @param role + * @return + * @throws Exception + * @see {@link apijson.JSONObject#KEY_ROLE} + */ + public void verifyUseRole(SQLConfig config, String table, RequestMethod method, String role) throws Exception { + Log.d(TAG, "verifyUseRole table = " + table + "; method = " + method + "; role = " + role); //验证角色,假定真实强制匹配<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< String visitorIdKey = getVisitorIdKey(config); - + if (table == null) { + table = config == null ? null : config.getTable(); + } + if (method == null) { + method = config == null ? GET : config.getMethod(); + } + if (role == null) { + role = config == null ? UNKNOWN : config.getRole(); + } + Object requestId; switch (role) { case LOGIN://verifyRole通过就行 @@ -366,39 +425,6 @@ public boolean verifyAccess(SQLConfig config) throws Exception { } //验证角色,假定真实强制匹配>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> - - - return true; - } - - - - - - /**允许请求,角色不好判断,让访问者发过来角色名,OWNER,CONTACT,ADMIN等 - * @param table - * @param method - * @param role - * @return - * @throws Exception - * @see {@link apijson.JSONObject#KEY_ROLE} - */ - public void verifyRole(String table, RequestMethod method, String role) throws Exception { - Log.d(TAG, "verifyRole table = " + table + "; method = " + method + "; role = " + role); - if (table != null) { - if (method == null) { - method = GET; - } - if (role == null) { - role = UNKNOWN; - } - - Map map = ACCESS_MAP.get(table); - - if (map == null || Arrays.asList(map.get(method)).contains(role) == false) { - throw new IllegalAccessException(table + " 不允许 " + role + " 用户的 " + method.name() + " 请求!"); - } - } } diff --git a/APIJSONORM/src/main/java/apijson/orm/Verifier.java b/APIJSONORM/src/main/java/apijson/orm/Verifier.java index 903b69530..76f142fc5 100755 --- a/APIJSONORM/src/main/java/apijson/orm/Verifier.java +++ b/APIJSONORM/src/main/java/apijson/orm/Verifier.java @@ -24,7 +24,9 @@ public interface Verifier { */ boolean verifyAccess(SQLConfig config) throws Exception; - /**允许请求,角色不好判断,让访问者发过来角色名,OWNER,CONTACT,ADMIN等 + + /**校验请求使用的角色,角色不好判断,让访问者发过来角色名,OWNER,CONTACT,ADMIN等 + * @param config * @param table * @param method * @param role @@ -32,7 +34,7 @@ public interface Verifier { * @throws Exception * @see {@link apijson.JSONObject#KEY_ROLE} */ - void verifyRole(String table, RequestMethod method, String role) throws Exception; + void verifyRole(SQLConfig config, String table, RequestMethod method, String role) throws Exception; /**登录校验 * @param config @@ -94,4 +96,5 @@ JSONObject verifyResponse(RequestMethod method, String name, JSONObject target, String getVisitorIdKey(SQLConfig config); + } From 5340749bea823820cf201200a61c6deaece48e0a Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Sun, 27 Mar 2022 05:47:00 +0800 Subject: [PATCH 0601/1181] =?UTF-8?q?=E5=AF=B9=20@having:"=E8=A1=A8?= =?UTF-8?q?=E8=BE=BE=E5=BC=8F"=20=E5=92=8C=20key{}:"=E8=A1=A8=E8=BE=BE?= =?UTF-8?q?=E5=BC=8F"=20=E6=96=B0=E5=A2=9E=E6=94=AF=E6=8C=81=E5=8D=95?= =?UTF-8?q?=E5=BC=95=E5=8F=B7=E3=80=81=E5=8F=8D=E5=BC=95=E5=8F=B7=E3=80=81?= =?UTF-8?q?=E5=90=84=E7=A7=8D=E5=85=B3=E9=94=AE=E8=AF=8D=E7=AD=89=EF=BC=9B?= =?UTF-8?q?=E8=A7=A3=E5=86=B3=E5=BD=93=20idKey=20=E5=92=8C=20idInKey=20?= =?UTF-8?q?=E4=B8=80=E6=A0=B7=E6=97=B6=E9=87=8D=E5=A4=8D=E6=8B=BC=E6=8E=A5?= =?UTF-8?q?=E6=9D=A1=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/apijson/orm/AbstractSQLConfig.java | 684 +++++++++--------- 1 file changed, 362 insertions(+), 322 deletions(-) diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java index 851ec4e7c..21fe2efce 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java @@ -80,7 +80,7 @@ */ public abstract class AbstractSQLConfig implements SQLConfig { private static final String TAG = "AbstractSQLConfig"; - + /** * 为 true 则兼容 5.0 之前 @having:"toId>0;avg(id)<100000" 默认 AND 连接,为 HAVING toId>0 AND avg(id)<100000; * 否则按 5.0+ 新版默认 OR 连接,为 HAVING toId>0 OR avg(id)<100000,使用 @having& 或 @having:{ @combine: null } 时才用 AND 连接 @@ -91,14 +91,14 @@ public abstract class AbstractSQLConfig implements SQLConfig { * 否则按 5.0+ 新版不允许,可以用 @having:"(toId)>0" 替代 */ public static boolean IS_HAVING_ALLOW_NOT_FUNCTION = false; - + public static int MAX_HAVING_COUNT = 5; public static int MAX_WHERE_COUNT = 10; public static int MAX_COMBINE_DEPTH = 2; public static int MAX_COMBINE_COUNT = 5; public static int MAX_COMBINE_KEY_COUNT = 2; public static float MAX_COMBINE_RATIO = 1.0f; - + public static String DEFAULT_DATABASE = DATABASE_MYSQL; public static String DEFAULT_SCHEMA = "sys"; public static String PREFFIX_DISTINCT = "DISTINCT "; @@ -153,7 +153,7 @@ public abstract class AbstractSQLConfig implements SQLConfig { RAW_MAP = new LinkedHashMap<>(); // 保证顺序,避免配置冲突等意外情况 - + RAW_MAP.put("+", ""); RAW_MAP.put("-", ""); RAW_MAP.put("*", ""); @@ -200,8 +200,8 @@ public abstract class AbstractSQLConfig implements SQLConfig { RAW_MAP.put("MONTH", ""); RAW_MAP.put("QUARTER", ""); RAW_MAP.put("YEAR", ""); -// RAW_MAP.put("json", ""); -// RAW_MAP.put("unit", ""); + // RAW_MAP.put("json", ""); + // RAW_MAP.put("unit", ""); //MYSQL 数据类型 BINARY,CHAR,DATETIME,TIME,DECIMAL,SIGNED,UNSIGNED RAW_MAP.put("BINARY", ""); @@ -254,7 +254,7 @@ public abstract class AbstractSQLConfig implements SQLConfig { RAW_MAP.put("LANGUAGE", ""); RAW_MAP.put("MODE", ""); - + SQL_AGGREGATE_FUNCTION_MAP = new LinkedHashMap<>(); // 保证顺序,避免配置冲突等意外情况 SQL_AGGREGATE_FUNCTION_MAP.put("max", ""); @@ -262,7 +262,7 @@ public abstract class AbstractSQLConfig implements SQLConfig { SQL_AGGREGATE_FUNCTION_MAP.put("avg", ""); SQL_AGGREGATE_FUNCTION_MAP.put("count", ""); SQL_AGGREGATE_FUNCTION_MAP.put("sum", ""); - + SQL_FUNCTION_MAP = new LinkedHashMap<>(); // 保证顺序,避免配置冲突等意外情况 //窗口函数 @@ -756,7 +756,7 @@ public String getUserIdKey() { private Object idIn; // User Table 的 id IN private Object userId; // Table 的 userId private Object userIdIn; // Table 的 userId IN - + /** * TODO 被关联的表通过就忽略关联的表?(这个不行 User:{"sex@":"/Comment/toId"}) */ @@ -862,7 +862,7 @@ public AbstractSQLConfig setId(Object id) { this.id = id; return this; } - + @Override public Object getIdIn() { return idIn; @@ -872,8 +872,8 @@ public AbstractSQLConfig setIdIn(Object idIn) { this.idIn = idIn; return this; } - - + + @Override public Object getUserId() { return userId; @@ -1151,7 +1151,7 @@ public String getGroupString(boolean hasPrefix) { return (hasPrefix ? " GROUP BY " : "") + StringUtil.concat(StringUtil.getString(keys), joinGroup, ", "); } - + @Override public String getHavingCombine() { return havingCombine; @@ -1174,7 +1174,7 @@ public SQLConfig setHaving(Map having) { public AbstractSQLConfig setHaving(String... conditions) { return setHaving(StringUtil.getString(conditions)); } - + /**TODO @having 改为默认 | 或连接,且支持 @having: { "key1>": 1, "key{}": "length(key2)>0", "@combine": "key1,key2" } * @return HAVING conditoin0 AND condition1 OR condition2 ... * @throws Exception @@ -1192,7 +1192,7 @@ public String getHavingString(boolean hasPrefix) throws Exception { SQLConfig ocfg = j.getOuterConfig(); SQLConfig cfg = (ocfg != null && ocfg.getHaving() != null) || j.isLeftOrRightJoin() ? ocfg : j.getJoinConfig(); - + if (cfg != null) { cfg.setMain(false).setKeyPrefix(true); if (StringUtil.isEmpty(cfg.getAlias(), true)) { @@ -1217,7 +1217,7 @@ public String getHavingString(boolean hasPrefix) throws Exception { List raw = getRaw(); // 提前把 @having& 转为 @having,或者干脆不允许 @raw:"@having&" boolean containRaw = raw != null && (raw.contains(KEY_HAVING) || raw.contains(KEY_HAVING_AND)); boolean containRaw = raw != null && raw.contains(KEY_HAVING); - + // 直接把 having 类型从 Map 定改为 Map,避免额外拷贝 // Map newMap = new LinkedHashMap<>(map.size()); // for (Entry entry : set) { @@ -1282,46 +1282,48 @@ else if (SQL_FUNCTION_MAP.containsKey(method) == false) { } } - String suffix = expression.substring(end + 1, expression.length()); - - if (isPrepared() && (((String) suffix).contains("--") || ((String) suffix).contains("/*") || PATTERN_RANGE.matcher((String) suffix).matches() == false)) { - throw new UnsupportedOperationException("字符串 " + suffix + " 不合法!" - + "预编译模式下 @having:\"column?value;function(arg0,arg1,...)?value...\"" - + " 中 ?value 必须符合正则表达式 " + PATTERN_RANGE + " 且不包含连续减号 -- 或注释符 /* !不允许多余的空格!"); - } - - String[] ckeys = StringUtil.split(expression.substring(start + 1, end)); - - if (ckeys != null) { - for (int j = 0; j < ckeys.length; j++) { - String origin = ckeys[j]; - - if (isPrepared()) { - if (origin.startsWith("_") || origin.contains("--") || PATTERN_FUNCTION.matcher(origin).matches() == false) { - throw new IllegalArgumentException("字符 " + ckeys[j] + " 不合法!" - + "预编译模式下 @having:\"column?value;function(arg0,arg1,...)?value...\"" - + " 中所有 column, arg 都必须是1个不以 _ 开头的单词 或者 符合正则表达式 " + PATTERN_FUNCTION + " 且不包含连续减号 -- !不允许多余的空格!"); - } - } - - //JOIN 副表不再在外层加副表名前缀 userId AS `Commet.userId`, 而是直接 userId AS `userId` - boolean isName = false; - if (StringUtil.isNumer(origin)) { - //do nothing - } - else if (StringUtil.isName(origin)) { - origin = quote + origin + quote; - isName = true; - } - else { - origin = getValue(origin).toString(); - } - - ckeys[j] = (isName && isKeyPrefix() ? alias + "." : "") + origin; - } - } + // String suffix = expression.substring(end + 1, expression.length()); + // + // if (isPrepared() && (((String) suffix).contains("--") || ((String) suffix).contains("/*") || PATTERN_RANGE.matcher((String) suffix).matches() == false)) { + // throw new UnsupportedOperationException("字符串 " + suffix + " 不合法!" + // + "预编译模式下 @having:\"column?value;function(arg0,arg1,...)?value...\"" + // + " 中 ?value 必须符合正则表达式 " + PATTERN_RANGE + " 且不包含连续减号 -- 或注释符 /* !不允许多余的空格!"); + // } + // + // String[] ckeys = StringUtil.split(expression.substring(start + 1, end)); + // + // if (ckeys != null) { + // for (int j = 0; j < ckeys.length; j++) { + // String origin = ckeys[j]; + // + // if (isPrepared()) { + // if (origin.startsWith("_") || origin.contains("--") || PATTERN_FUNCTION.matcher(origin).matches() == false) { + // throw new IllegalArgumentException("字符 " + ckeys[j] + " 不合法!" + // + "预编译模式下 @having:\"column?value;function(arg0,arg1,...)?value...\"" + // + " 中所有 column, arg 都必须是1个不以 _ 开头的单词 或者 符合正则表达式 " + PATTERN_FUNCTION + " 且不包含连续减号 -- !不允许多余的空格!"); + // } + // } + // + // //JOIN 副表不再在外层加副表名前缀 userId AS `Commet.userId`, 而是直接 userId AS `userId` + // boolean isName = false; + // if (StringUtil.isNumer(origin)) { + // //do nothing + // } + // else if (StringUtil.isName(origin)) { + // origin = quote + origin + quote; + // isName = true; + // } + // else { + // origin = getValue(origin).toString(); + // } + // + // ckeys[j] = (isName && isKeyPrefix() ? alias + "." : "") + origin; + // } + // } + // + // return method + "(" + StringUtil.getString(ckeys) + ")" + suffix; - return method + "(" + StringUtil.getString(ckeys) + ")" + suffix; + return method + parseSQLExpression(KEY_HAVING, expression.substring(start), containRaw, false, null); } @Override @@ -1461,7 +1463,7 @@ public String getRawSQL(String key, Object value) throws Exception { if (value == null) { return null; } - + List rawList = getRaw(); boolean containRaw = rawList != null && rawList.contains(key); if (containRaw && value instanceof String == false) { @@ -1529,7 +1531,7 @@ public String getColumnString(boolean inSQLJoin) throws Exception { if (isPrepared() && column != null) { List raw = getRaw(); boolean containRaw = raw != null && raw.contains(KEY_COLUMN); - + for (String c : column) { if (containRaw) { // 由于 HashMap 对 key 做了 hash 处理,所以 get 比 containsValue 更快 @@ -1563,10 +1565,10 @@ public String getColumnString(boolean inSQLJoin) throws Exception { } } } - + boolean onlyOne = column != null && column.size() == 1; String c0 = onlyOne ? column.get(0) : null; - + if (onlyOne) { int index = c0 == null ? -1 : c0.lastIndexOf(":"); if (index > 0) { @@ -1577,13 +1579,13 @@ public String getColumnString(boolean inSQLJoin) throws Exception { int end = start <= 0 ? -1 : c0.lastIndexOf(")"); if (start > 0 && end > start) { String fun = c0.substring(0, start); - + // Invalid use of group function SELECT count(max(`id`)) AS count FROM `sys`.`Comment` if (SQL_AGGREGATE_FUNCTION_MAP.containsKey(fun)) { String group = getGroup(); // TODO 唯一 100% 兼容的可能只有 SELECT count(*) FROM (原语句) AS table return StringUtil.isEmpty(group, true) ? "1" : "count(DISTINCT " + group + ")"; } - + String[] args = start == end - 1 ? null : StringUtil.split(c0.substring(start + 1, end)); if (args == null || args.length <= 0) { return SQL.count(c0); @@ -1592,7 +1594,7 @@ public String getColumnString(boolean inSQLJoin) throws Exception { List raw = getRaw(); boolean containRaw = raw != null && raw.contains(KEY_COLUMN); - return SQL.count(parseColumn(c0, containRaw)); + return SQL.count(parseSQLExpression(KEY_COLUMN, c0, containRaw, false, null)); } } @@ -1624,11 +1626,11 @@ public String getColumnString(boolean inSQLJoin) throws Exception { if (j.isAppJoin()) { continue; } - + SQLConfig ocfg = j.getOuterConfig(); boolean isEmpty = ocfg == null || ocfg.getColumn() == null; boolean isLeftOrRightJoin = j.isLeftOrRightJoin(); - + if (isEmpty && isLeftOrRightJoin) { // 改为 SELECT ViceTable.* 解决 SELECT sum(ViceTable.id) LEFT/RIGHT JOIN (SELECT sum(id) FROM ViceTable...) AS ViceTable // 不仅导致 SQL 函数重复计算,还有时导致 SQL 报错或对应字段未返回 @@ -1650,7 +1652,7 @@ public String getColumnString(boolean inSQLJoin) throws Exception { } } } - + inSQLJoin = true; } } @@ -1696,7 +1698,7 @@ public String getColumnString(boolean inSQLJoin) throws Exception { throw new UnsupportedOperationException("@column:value 的 value 中字符串 " + expression + " 不合法!" + "不允许传超过 100 个字符的函数或表达式!请用 @raw 简化传参!"); } - keys[i] = parseColumn(expression, containRaw); + keys[i] = parseSQLExpression(KEY_COLUMN, expression, containRaw, true, "@column:\"column0,column1:alias1;function0(arg0,arg1,...);function1(...):alias2...\""); } String c = StringUtil.getString(keys); @@ -1710,23 +1712,49 @@ public String getColumnString(boolean inSQLJoin) throws Exception { } } - /** - * 解析@column 中以“;”分隔的表达式("@column":"expression1;expression2;expression2;....")中的expression - * + /**解析@column 中以“;”分隔的表达式("@column":"expression1;expression2;expression2;....")中的expression + * @param key * @param expression + * @param containRaw + * @param allowAlias + * @param columnPrefix * @return */ - public String parseColumn(String expression, boolean containRaw) { + public String parseSQLExpression(String key, String expression, boolean containRaw, boolean allowAlias) { + return parseSQLExpression(key, expression, containRaw, allowAlias, null); + } + /**解析@column 中以“;”分隔的表达式("@column":"expression1;expression2;expression2;....")中的expression + * @param key + * @param expression + * @param containRaw + * @param allowAlias + * @param columnPrefix + * @param example + * @return + */ + public String parseSQLExpression(String key, String expression, boolean containRaw, boolean allowAlias, String example) { String quote = getQuote(); int start = expression.indexOf('('); if (start < 0) { //没有函数 ,可能是字段,也可能是 DISTINCT xx - String[] cks = parseArgsSplitWithComma(expression, true, containRaw); + String[] cks = parseArgsSplitWithComma(expression, true, containRaw, allowAlias); expression = StringUtil.getString(cks); } else { // FIXME 用括号断开? 如果少的话,用关键词加括号断开,例如 )OVER( 和 )AGAINST( // 窗口函数 rank() OVER (PARTITION BY id ORDER BY userId ASC) // 全文索引 math(name,tag) AGAINST ('a b +c -d' IN NATURALE LANGUAGE MODE) // IN BOOLEAN MODE + if (StringUtil.isEmpty(example)) { + if (KEY_COLUMN.equals(key)) { + example = key + ":\"column0,column1:alias1;function0(arg0,arg1,...);function1(...):alias2...\""; + } + // 和 key{}:"" 一样 else if (KEY_HAVING.equals(key) || KEY_HAVING_AND.equals(key)) { + // exeptionExample = key + ":\"function0(arg0,arg1,...)>1;function1(...)%5<=3...\""; + // } + else { + example = key + ":\"column0!=0;column1+3*2<=10;function0(arg0,arg1,...)>1;function1(...)%5<=3...\""; + } + } + //有函数,但不是窗口函数 int overIndex = expression.indexOf(")OVER("); // 传参不传空格,拼接带空格 ") OVER ("); int againstIndex = expression.indexOf(")AGAINST("); // 传参不传空格,拼接带空格 ") AGAINST ("); @@ -1734,28 +1762,25 @@ public String parseColumn(String expression, boolean containRaw) { boolean containAgainst = againstIndex > 0 && againstIndex < expression.length() - ")AGAINST(".length(); if (containOver && containAgainst) { - throw new IllegalArgumentException("字符 " + expression + " 不合法!" - + "预编译模式下 @column:\"column0,column1:alias;function0(arg0,arg1,...);function1(...):alias...\"" + throw new IllegalArgumentException("字符 " + expression + " 不合法!预编译模式下 " + example + " 中 function 必须符合小写英文单词的 SQL 函数名格式!不能同时存在窗口函数关键词 OVER 和全文索引关键词 AGAINST!"); } if (containOver == false && containAgainst == false) { - int end = expression.lastIndexOf(")"); + int end = expression.lastIndexOf(')'); if (start >= end) { throw new IllegalArgumentException("字符 " + expression + " 不合法!" - + "@column:value 中 value 里的 SQL函数必须为 function(arg0,arg1,...) 这种格式!"); + + key + ":value 中 value 里的 SQL函数必须为 function(arg0,arg1,...) 这种格式!"); } String fun = expression.substring(0, start); if (fun.isEmpty() == false) { if (SQL_FUNCTION_MAP == null || SQL_FUNCTION_MAP.isEmpty()) { if (StringUtil.isName(fun) == false) { - throw new IllegalArgumentException("字符 " + fun + " 不合法!" - + "预编译模式下 @column:\"column0,column1:alias;function0(arg0,arg1,...);function1(...):alias...\"" + throw new IllegalArgumentException("字符 " + fun + " 不合法!预编译模式下 " + example + " 中 function 必须符合小写英文单词的 SQL 函数名格式!"); } } else if (SQL_FUNCTION_MAP.containsKey(fun) == false) { - throw new IllegalArgumentException("字符 " + fun + " 不合法!" - + "预编译模式下 @column:\"column0,column1:alias;function0(arg0,arg1,...);function1(...):alias...\"" + throw new IllegalArgumentException("字符 " + fun + " 不合法!预编译模式下 " + example + " 中 function 必须符合小写英文单词的 SQL 函数名格式!且必须是后端允许调用的 SQL 函数!"); } } @@ -1767,28 +1792,32 @@ public String parseColumn(String expression, boolean containRaw) { } // 解析函数内的参数 - String ckeys[] = parseArgsSplitWithComma(s, false, containRaw); + String ckeys[] = parseArgsSplitWithComma(s, false, containRaw, allowAlias); String suffix = expression.substring(end + 1, expression.length()); //:contactCount - int index = suffix.lastIndexOf(":"); - String alias = index < 0 ? "" : suffix.substring(index + 1); //contactCount - suffix = index < 0 ? suffix : suffix.substring(0, index); - if (alias.isEmpty() == false && StringUtil.isName(alias) == false) { - throw new IllegalArgumentException("字符串 " + alias + " 不合法!" - + "预编译模式下 @column:value 中 value里面用 ; 分割的每一项" - + " function(arg0,arg1,...):alias 中 alias 必须是1个单词!并且不要有多余的空格!"); + String alias = null; + if (allowAlias) { + int index = suffix.lastIndexOf(":"); + alias = index < 0 ? "" : suffix.substring(index + 1); //contactCount + suffix = index < 0 ? suffix : suffix.substring(0, index); + if (alias.isEmpty() == false && StringUtil.isName(alias) == false) { + throw new IllegalArgumentException("字符串 " + alias + " 不合法!预编译模式下 " + + key + ":value 中 value里面用 ; 分割的每一项" + + " function(arg0,arg1,...):alias 中 alias 必须是1个单词!并且不要有多余的空格!"); + } } - if (suffix.isEmpty() == false && (((String) suffix).contains("--") || ((String) suffix).contains("/*") || PATTERN_RANGE.matcher((String) suffix).matches() == false)) { - throw new UnsupportedOperationException("字符串 " + suffix + " 不合法!" - + "预编译模式下 @column:\"column?value;function(arg0,arg1,...)?value...\"" + if (suffix.isEmpty() == false && (((String) suffix).contains("--") || ((String) suffix).contains("/*") + || PATTERN_RANGE.matcher((String) suffix).matches() == false)) { + throw new UnsupportedOperationException("字符串 " + suffix + " 不合法!预编译模式下 " + key + + ":\"column?value;function(arg0,arg1,...)?value...\"" + " 中 ?value 必须符合正则表达式 " + PATTERN_RANGE + " 且不包含连续减号 -- 或注释符 /* !不允许多余的空格!"); } String origin = fun + "(" + (distinct ? PREFFIX_DISTINCT : "") + StringUtil.getString(ckeys) + ")" + suffix; expression = origin + (StringUtil.isEmpty(alias, true) ? "" : " AS " + quote + alias + quote); - - } else { + } + else { //是窗口函数 fun(arg0,agr1) OVER (agr0 agr1 ...) int keyIndex = containOver ? overIndex : againstIndex; String s1 = expression.substring(0, keyIndex + 1); // OVER 前半部分 @@ -1800,31 +1829,31 @@ public String parseColumn(String expression, boolean containRaw) { if (index1 >= end) { throw new IllegalArgumentException("字符 " + expression + " 不合法!" - + "@column:value 中 value 里的 SQL函数必须为 function(arg0,arg1,...) 这种格式!"); + + key + ":value 中 value 里的 SQL 函数必须为 function(arg0,arg1,...) 这种格式!"); } + if (fun.isEmpty() == false) { if (SQL_FUNCTION_MAP == null || SQL_FUNCTION_MAP.isEmpty()) { if (StringUtil.isName(fun) == false) { - throw new IllegalArgumentException("字符 " + fun + " 不合法!" - + "预编译模式下 @column:\"column0,column1:alias;function0(arg0,arg1,...);function1(...):alias...\"" + throw new IllegalArgumentException("字符 " + fun + " 不合法!预编译模式下 " + example + " 中 function 必须符合小写英文单词的 SQL 函数名格式!"); } - } else if (SQL_FUNCTION_MAP.containsKey(fun) == false) { - throw new IllegalArgumentException("字符 " + fun + " 不合法!" - + "预编译模式下 @column:\"column0,column1:alias;function0(arg0,arg1,...);function1(...):alias...\"" + } + else if (SQL_FUNCTION_MAP.containsKey(fun) == false) { + throw new IllegalArgumentException("字符 " + fun + " 不合法!预编译模式下 " + example + " 中 function 必须符合小写英文单词的 SQL 函数名格式!且必须是后端允许调用的 SQL 函数!"); } } // 获取前半部分函数的参数解析 fun(arg0,agr1) - String agrsString1[] = parseArgsSplitWithComma(s1.substring(index1 + 1, s1.lastIndexOf(")")), false, containRaw); + String agrsString1[] = parseArgsSplitWithComma(s1.substring(index1 + 1, s1.lastIndexOf(")")), false, containRaw, allowAlias); int index2 = s2.indexOf("("); // 后半部分 “(”的起始位置 String argString2 = s2.substring(index2 + 1, end); // 后半部分的参数 // 别名 - String alias = s2.lastIndexOf(":") < s2.lastIndexOf(")") ? null : s2.substring(s2.lastIndexOf(":") + 1); + String alias = allowAlias == false || s2.lastIndexOf(":") < s2.lastIndexOf(")") ? null : s2.substring(s2.lastIndexOf(":") + 1); // 获取后半部分的参数解析 (agr0 agr1 ...) - String argsString2[] = parseArgsSplitWithComma(argString2, false, containRaw); + String argsString2[] = parseArgsSplitWithComma(argString2, false, containRaw, allowAlias); expression = fun + "(" + StringUtil.getString(agrsString1) + (containOver ? ") OVER (" : ") AGAINST (") // 传参不传空格,拼接带空格 + StringUtil.getString(argsString2) + ")" + (StringUtil.isEmpty(alias, true) ? "" : " AS " + quote + alias + quote); } } @@ -1832,25 +1861,27 @@ public String parseColumn(String expression, boolean containRaw) { return expression; } - /** - * 解析函数参数或者字段,此函数对于解析字段 和 函数内参数通用 - * + + /**解析函数参数或者字段,此函数对于解析字段 和 函数内参数通用 * @param param * @param isColumn true:不是函数参数。false:是函数参数 + * @param containRaw + * @param allowAlias * @return */ - private String[] parseArgsSplitWithComma(String param, boolean isColumn, boolean containRaw) { + private String[] parseArgsSplitWithComma(String param, boolean isColumn, boolean containRaw, boolean allowAlias) { // 以"," 分割参数 String quote = getQuote(); String tableAlias = getAliasWithQuote(); String ckeys[] = StringUtil.split(param); // 以","分割参数 if (ckeys != null && ckeys.length > 0) { - String origin; - String alias; - int index; + for (int i = 0; i < ckeys.length; i++) { String ck = ckeys[i]; + String origin; + String alias; + // 如果参数包含 "'" ,解析字符串 if (ck.startsWith("`") && ck.endsWith("`")) { origin = ck.substring(1, ck.length() - 1); @@ -1861,7 +1892,7 @@ private String[] parseArgsSplitWithComma(String param, boolean isColumn, boolean + " 中所有字符串 column 都必须必须为1个单词 !"); } - ckeys[i] = getKey(origin).toString(); + origin = getKey(origin).toString(); } else if (ck.startsWith("'") && ck.endsWith("'")) { origin = ck.substring(1, ck.length() - 1); @@ -1870,69 +1901,82 @@ else if (ck.startsWith("'") && ck.endsWith("'")) { + "预编译模式下 @column:\"column0,column1:alias;function0(arg0,arg1,...);function1(...):alias...\"" + " 中字符串参数不合法,必须以 ' 开头, ' 结尾,字符串中不能包含 ' "); } - + // 1.字符串不是字段也没有别名,所以不解析别名 2. 是字符串,进行预编译,使用getValue() ,对字符串进行截取 - ckeys[i] = getValue(origin).toString(); + origin = getValue(origin).toString(); } else { // 参数不包含",",即不是字符串 // 解析参数:1. 字段 ,2. 是以空格分隔的参数 eg: cast(now() as date) - index = isColumn ? ck.lastIndexOf(":") : -1; //StringUtil.split返回数组中,子项不会有null - origin = index < 0 ? ck : ck.substring(0, index); //获取 : 之前的 - alias = index < 0 ? null : ck.substring(index + 1); - if (isPrepared()) { - if (isColumn) { - if (StringUtil.isName(origin) == false || (alias != null && StringUtil.isName(alias) == false)) { - throw new IllegalArgumentException("字符 " + ck + " 不合法!" - + "预编译模式下 @column:value 中 value里面用 , 分割的每一项" - + " column:alias 中 column 必须是1个单词!如果有alias,则alias也必须为1个单词!" - + "关键字必须全大写,且以空格分隔的参数,空格必须只有 1 个!其它情况不允许空格!"); - } - } else { - if (origin.startsWith("_") || origin.contains("--")) { // || PATTERN_FUNCTION.matcher(origin).matches() == false) { - throw new IllegalArgumentException("字符 " + ck + " 不合法!" - + "预编译模式下 @column:\"column0,column1:alias;function0(arg0,arg1,...);function1(...):alias...\"" - + " 中所有 arg 都必须是1个不以 _ 开头的单词 或者符合正则表达式 " + PATTERN_FUNCTION + " 且不包含连续减号 -- !DISTINCT 必须全大写,且后面必须有且只有 1 个空格!其它情况不允许空格!"); + if ("=null".equals(ck)) { + origin = SQL.isNull(); + } + else if ("!=null".equals(ck)) { + origin = SQL.isNull(false); + } + else { + origin = ck; + alias = null; + if (allowAlias) { + int index = isColumn ? ck.lastIndexOf(":") : -1; //StringUtil.split返回数组中,子项不会有null + origin = index < 0 ? ck : ck.substring(0, index); //获取 : 之前的 + alias = index < 0 ? null : ck.substring(index + 1); + if (isPrepared()) { + if (isColumn) { + if (StringUtil.isName(origin) == false || (alias != null && StringUtil.isName(alias) == false)) { + throw new IllegalArgumentException("字符 " + ck + " 不合法!" + + "预编译模式下 @column:value 中 value里面用 , 分割的每一项" + + " column:alias 中 column 必须是1个单词!如果有alias,则alias也必须为1个单词!" + + "关键字必须全大写,且以空格分隔的参数,空格必须只有 1 个!其它情况不允许空格!"); + } + } else { + if (origin.startsWith("_") || origin.contains("--")) { // || PATTERN_FUNCTION.matcher(origin).matches() == false) { + throw new IllegalArgumentException("字符 " + ck + " 不合法!" + + "预编译模式下 @column:\"column0,column1:alias;function0(arg0,arg1,...);function1(...):alias...\"" + + " 中所有 arg 都必须是1个不以 _ 开头的单词 或者符合正则表达式 " + PATTERN_FUNCTION + " 且不包含连续减号 -- !DISTINCT 必须全大写,且后面必须有且只有 1 个空格!其它情况不允许空格!"); + } + } } } - } - // 以空格分割参数 - String[] mkes = containRaw ? StringUtil.split(ck, " ", true) : new String[]{ ck }; - //如果参数中含有空格(少数情况) 比如 fun(arg1 arg2 arg3 ,arg4) 中的 arg1 arg2 arg3,比如 DISTINCT id - if (mkes != null && mkes.length >= 2) { - ckeys[i] = praseArgsSplitWithSpace(mkes); - } else { - boolean isName = false; + // 以空格分割参数 + String[] mkes = containRaw ? StringUtil.split(ck, " ", true) : new String[]{ ck }; - String mk = RAW_MAP.get(origin); - if (mk != null) { // newSQLConfig 提前处理好的 - if (mk.length() > 0) { - origin = mk; - } - } else if (StringUtil.isNumer(origin)) { - //do nothing - } else if (StringUtil.isName(origin)) { - origin = quote + origin + quote; - isName = true; + //如果参数中含有空格(少数情况) 比如 fun(arg1 arg2 arg3 ,arg4) 中的 arg1 arg2 arg3,比如 DISTINCT id + if (mkes != null && mkes.length >= 2) { + origin = praseArgsSplitWithSpace(mkes); } else { - origin = getValue(origin).toString(); - } + boolean isName = false; - if (isName && isKeyPrefix()) { - origin = tableAlias + "." + origin; - } + String mk = RAW_MAP.get(origin); + if (mk != null) { // newSQLConfig 提前处理好的 + if (mk.length() > 0) { + origin = mk; + } + } else if (StringUtil.isNumer(origin)) { + //do nothing + } else if (StringUtil.isName(origin)) { + origin = quote + origin + quote; + isName = true; + } else { + origin = getValue(origin).toString(); + } - if (isColumn && StringUtil.isEmpty(alias, true) == false) { - origin += " AS " + quote + alias + quote; - } + if (isName && isKeyPrefix()) { + origin = tableAlias + "." + origin; + } - ckeys[i] = origin; + if (isColumn && StringUtil.isEmpty(alias, true) == false) { + origin += " AS " + quote + alias + quote; + } + } } - } + + ckeys[i] = origin; } } + return ckeys; } @@ -2103,7 +2147,7 @@ public AbstractSQLConfig setCompat(Boolean compat) { this.compat = compat; return this; } - + @Override public int getType() { return type; @@ -2230,7 +2274,7 @@ public static String getLimitString(int page, int count, boolean isTSQL, boolean return " LIMIT " + count + (offset <= 0 ? "" : " OFFSET " + offset); // DELETE, UPDATE 不支持 OFFSET } - + @Override public List getNull() { return nulls; @@ -2250,9 +2294,9 @@ public SQLConfig setCast(Map cast) { this.cast = cast; return this; } - + //WHERE <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< - + protected int getMaxHavingCount() { return MAX_HAVING_COUNT; } @@ -2282,7 +2326,7 @@ public AbstractSQLConfig setCombine(String combine) { this.combine = combine; return this; } - + @NotNull @Override public Map> getCombineMap() { @@ -2301,7 +2345,7 @@ public AbstractSQLConfig setCombineMap(Map> combineMap) { this.combineMap = combineMap; return this; } - + @Override public Map getWhere() { return where; @@ -2339,7 +2383,7 @@ public Object getWhere(String key, boolean exactMatch) { if (key == null || where == null){ return null; } - + int index; for (Entry entry : where.entrySet()) { String k = entry.getKey(); @@ -2348,7 +2392,7 @@ public Object getWhere(String key, boolean exactMatch) { return entry.getValue(); } } - + return null; } @Override @@ -2420,7 +2464,7 @@ else if (key.equals(userIdInKey)) { lastIndex = andList.lastIndexOf(idKey); } } - + i = lastIndex + 1; } @@ -2432,7 +2476,7 @@ else if (key.equals(userIdInKey)) { } combineMap.put("&", andList); } - + return this; } @@ -2457,7 +2501,7 @@ public String getWhereString(boolean hasPrefix) throws Exception { */ @JSONField(serialize = false) public String getWhereString(boolean hasPrefix, RequestMethod method, Map where, String combine, List joinList, boolean verifyName) throws Exception { - + String whereString = parseCombineExpression(method, getQuote(), getTable(), getAliasWithQuote(), where, combine, verifyName, false, false); whereString = concatJoinWhereString(whereString); String result = StringUtil.isEmpty(whereString, true) ? "" : (hasPrefix ? " WHERE " : "") + whereString; @@ -2484,30 +2528,30 @@ public String getWhereString(boolean hasPrefix, RequestMethod method, Map conditioinMap, String combine, boolean verifyName, boolean containRaw, boolean isHaving) throws Exception { - + String errPrefix = table + (isHaving ? ":{ @having:{ " : ":{ ") + "@combine:'" + combine + (isHaving ? "' } }" : "' }"); String s = StringUtil.getString(combine); if (s.startsWith(" ") || s.endsWith(" ") ) { throw new IllegalArgumentException(errPrefix + " 中字符 '" + s - + "' 不合法!不允许首尾有空格,也不允许连续空格!空格不能多也不能少!" - + "逻辑连接符 & | 左右必须各一个相邻空格!左括号 ( 右边和右括号 ) 左边都不允许有相邻空格!"); + + "' 不合法!不允许首尾有空格,也不允许连续空格!空格不能多也不能少!" + + "逻辑连接符 & | 左右必须各一个相邻空格!左括号 ( 右边和右括号 ) 左边都不允许有相邻空格!"); } - + if (conditioinMap == null) { conditioinMap = new HashMap<>(); } int size = conditioinMap.size(); - + int maxCount = isHaving ? getMaxHavingCount() : getMaxWhereCount(); if (maxCount > 0 && size > maxCount) { throw new IllegalArgumentException(table + (isHaving ? ":{ @having:{ " : ":{ ") + "key0:value0, key1:value1... " + combine + (isHaving ? " } }" : " }") + " 中条件 key:value 数量 " + size + " 已超过最大数量,必须在 0-" + maxCount + " 内!"); } - + String result = ""; List prepreadValues = getPreparedValueList(); - + Map usedKeyCountMap = new HashMap<>(size); int n = s.length(); @@ -2707,13 +2751,13 @@ else if (c == ')') { String andCond = ""; boolean isItemFirst = true; - + for (Entry entry : set) { String key = entry == null ? null : entry.getKey(); if (key == null || usedKeyCountMap.containsKey(key)) { continue; } - + String wi = isHaving ? getHavingItem(quote, table, alias, key, (String) entry.getValue(), containRaw) : getWhereItem(key, entry.getValue(), method, verifyName); if (StringUtil.isEmpty(wi, true)) {//避免SQL条件连接错误 continue; @@ -2722,23 +2766,23 @@ else if (c == ')') { andCond += (isItemFirst ? "" : AND) + "(" + wi + ")"; isItemFirst = false; } - + if (StringUtil.isEmpty(result, true)) { result = andCond; } else if (StringUtil.isNotEmpty(andCond, true)) { // andCond 必须放后面,否则 prepared 值顺序错误 - if (isHaving) { // HAVING 前 WHERE 已经有条件 ? 占位,不能反过来,想优化 AND 连接在最前,需要多遍历一次内部的 key,也可以 newSQLConfig 时存到 andList - result = "( " + result + " )" + AND + andCond; + if (isHaving) { // HAVING 前 WHERE 已经有条件 ? 占位,不能反过来,想优化 AND 连接在最前,需要多遍历一次内部的 key,也可以 newSQLConfig 时存到 andList + result = "( " + result + " )" + AND + andCond; } - else { - result = andCond + AND + "( " + result + " )"; // 先暂存之前的 prepared 值,然后反向整合 - if (n > 0) { - prepreadValues.addAll(getPreparedValueList()); - setPreparedValueList(prepreadValues); - } - } - } - + else { + result = andCond + AND + "( " + result + " )"; // 先暂存之前的 prepared 值,然后反向整合 + if (n > 0) { + prepreadValues.addAll(getPreparedValueList()); + setPreparedValueList(prepreadValues); + } + } + } + return result; } @@ -2807,7 +2851,7 @@ else if ("!".equals(ce.getKey())) { whereString += (isCombineFirst ? "" : AND) + (Logic.isNot(logic) ? NOT : "") + " ( " + cs + " ) "; isCombineFirst = false; } - + whereString = concatJoinWhereString(whereString); String s = StringUtil.isEmpty(whereString, true) ? "" : (hasPrefix ? " WHERE " : "") + whereString; @@ -2818,7 +2862,7 @@ else if ("!".equals(ce.getKey())) { return s; } - + protected String concatJoinWhereString(String whereString) throws Exception { List joinList = getJoinList(); @@ -3052,7 +3096,7 @@ public String getEqualString(String key, String column, Object value, String raw if (StringUtil.isName(column) == false) { throw new IllegalArgumentException(key + ":value 中key不合法!不支持 ! 以外的逻辑符 !"); } - + String logic = value == null && rawSQL == null ? (not ? SQL.IS_NOT : SQL.IS) : (not ? " != " : " = "); return getKey(column) + logic + (value instanceof Subquery ? getSubqueryString((Subquery) value) : (rawSQL != null ? rawSQL : getValue(key, column, value))); } @@ -3097,34 +3141,34 @@ protected Object getValue(String key, String column, Object value) { if (value == null) { return null; } - + Map castMap = getCast(); String type = key == null || castMap == null ? null : castMap.get(key); - -// if ("DATE".equalsIgnoreCase(type) && value instanceof Date == false) { -// value = value instanceof Number ? new Date(((Number) value).longValue()) : Date.valueOf((String) value); -// } -// else if ("TIME".equalsIgnoreCase(type) && value instanceof Time == false) { -// value = value instanceof Number ? new Time(((Number) value).longValue()) : Time.valueOf((String) value); -// } -// else if ("TIMESTAMP".equalsIgnoreCase(type) && value instanceof Timestamp == false) { -// value = value instanceof Number ? new Timestamp(((Number) value).longValue()) : Timestamp.valueOf((String) value); -// } -// else if ("ARRAY".equalsIgnoreCase(type) && value instanceof Array == false) { -// value = ((Collection) value).toArray(); -// } -// else if (StringUtil.isEmpty(type, true) == false) { -// preparedValueList.add(value); -// return "cast(?" + SQL.AS + type + ")"; -// } - + + // if ("DATE".equalsIgnoreCase(type) && value instanceof Date == false) { + // value = value instanceof Number ? new Date(((Number) value).longValue()) : Date.valueOf((String) value); + // } + // else if ("TIME".equalsIgnoreCase(type) && value instanceof Time == false) { + // value = value instanceof Number ? new Time(((Number) value).longValue()) : Time.valueOf((String) value); + // } + // else if ("TIMESTAMP".equalsIgnoreCase(type) && value instanceof Timestamp == false) { + // value = value instanceof Number ? new Timestamp(((Number) value).longValue()) : Timestamp.valueOf((String) value); + // } + // else if ("ARRAY".equalsIgnoreCase(type) && value instanceof Array == false) { + // value = ((Collection) value).toArray(); + // } + // else if (StringUtil.isEmpty(type, true) == false) { + // preparedValueList.add(value); + // return "cast(?" + SQL.AS + type + ")"; + // } + preparedValueList.add(value); return StringUtil.isEmpty(type, true) ? "?" : "cast(?" + SQL.AS + type + ")"; } - + return key == null ? getSQLValue(value) : getSQLValue(key, column, value); } - + public Object getSQLValue(String key, String column, @NotNull Object value) { Map castMap = getCast(); String type = key == null || castMap == null ? null : castMap.get(key); @@ -3214,7 +3258,7 @@ public String getSearchString(String key, String column, Object[] values, int ty public String getLikeString(@NotNull String key, @NotNull String column, String value) { String k = key.substring(0, key.length() - 1); char r = k.charAt(k.length() - 1); - + char l; if (r == '%' || r == '_' || r == '?') { k = k.substring(0, k.length() - 1); @@ -3230,7 +3274,7 @@ public String getLikeString(@NotNull String key, @NotNull String column, String else if (l > 0 && StringUtil.isName(String.valueOf(l))) { l = r; } - + if (l == '?') { l = 0; } @@ -3241,12 +3285,12 @@ else if (l > 0 && StringUtil.isName(String.valueOf(l))) { else { l = r = 0; } - + if (l > 0 || r > 0) { if (value == null) { throw new IllegalArgumentException(key + ":value 中 value 为 null!key$:value 中 value 不能为 null,且类型必须是 String !"); } - + value = value.replaceAll("\\\\", "\\\\\\\\"); value = value.replaceAll("\\%", "\\\\%"); value = value.replaceAll("\\_", "\\\\_"); @@ -3257,7 +3301,7 @@ else if (l > 0 && StringUtil.isName(String.valueOf(l))) { value = value + r; } } - + return getKey(column) + " LIKE " + getValue(key, column, value); } @@ -3460,59 +3504,56 @@ public String getRangeString(String key, String column, Object range, String raw } else if (range instanceof String) {//非Number类型需要客户端拼接成 < 'value0', >= 'value1'这种 String condition = ""; - String[] cs = rawSQL != null ? null : StringUtil.split((String) range, false); + String[] cs = rawSQL != null ? null : StringUtil.split((String) range, ";", false); if (rawSQL != null) { - int index = rawSQL == null ? -1 : rawSQL.indexOf("("); + int index = rawSQL.indexOf("("); condition = (index >= 0 && index < rawSQL.lastIndexOf(")") ? "" : getKey(k) + " ") + rawSQL; } - // 还是只支持整段为 Raw SQL 比较好 - // boolean appendRaw = false; - // if ("".equals(rawSQL)) { - // condition = rawSQL; - // cs = null; - // } - // else { - // if (rawSQL != null) { //先找出所有 rawSQL 的位置,然后去掉,再最后按原位置来拼接 - // String[] rs = StringUtil.split((String) range, rawSQL, false); - // - // if (rs != null && rs.length > 0) { - // String cond = ""; - // for (int i = 0; i < rs.length; i++) { - // cond += rs[i]; - // } - // range = cond; - // appendRaw = true; - // } - // } - // - // cs = StringUtil.split((String) range, false); - // } - if (cs != null) { - String c; - int index; + List raw = getRaw(); + boolean containRaw = raw == null ? false : raw.contains(key); + String lk = logic.isAnd() ? AND : OR; + for (int i = 0; i < cs.length; i++) {//对函数条件length(key)<=5这种不再在开头加key - c = cs[i]; - - if ("=null".equals(c)) { - c = SQL.isNull(); - } - else if ("!=null".equals(c)) { - c = SQL.isNull(false); + String expr = cs[i]; + + if (expr.length() > 100) { + throw new UnsupportedOperationException(key + ":value 的 value 中字符串 " + expr + " 不合法!" + + "不允许传超过 100 个字符的函数或表达式!请用 @raw 简化传参!"); } - else if (isPrepared() && (c.contains("--") || PATTERN_RANGE.matcher(c).matches() == false)) { - throw new UnsupportedOperationException(key + ":value 的 value 中 " + c + " 不合法!" - + "预编译模式下 key{}:\"condition\" 中 condition 必须 为 =null 或 !=null 或 符合正则表达式 " + PATTERN_RANGE + " !不允许连续减号 -- !不允许空格!"); + + int index = expr == null ? -1 : expr.indexOf("("); + if (index >= 0) { + expr = parseSQLExpression(key, expr, containRaw, false, key + ":\"!=null;+3*2<=10;function0(arg0,arg1,...)>1;function1(...)%5<=3...\""); + } + else { + String fk = getKey(k) + " "; + String[] ccs = StringUtil.split(expr, false); + expr = ""; + + for (int j = 0; j < ccs.length; j++) { + String c = ccs[j]; + if ("=null".equals(c)) { + c = SQL.isNull(); + } + else if ("!=null".equals(c)) { + c = SQL.isNull(false); + } + else if (isPrepared() && (c.contains("--") || PATTERN_RANGE.matcher(c).matches() == false)) { + throw new UnsupportedOperationException(key + ":value 的 value 中 " + c + " 不合法!" + + "预编译模式下 key{}:\"condition\" 中 condition 必须 为 =null 或 !=null 或 符合正则表达式 " + PATTERN_RANGE + " !不允许连续减号 -- !不允许空格!"); + } + + expr += (j <= 0 ? "" : lk) + fk + c; + } } - index = c == null ? -1 : c.indexOf("("); - condition += ((i <= 0 ? "" : (logic.isAnd() ? AND : OR)) //连接方式 - + (index >= 0 && index < c.lastIndexOf(")") ? "" : getKey(k) + " ") //函数和非函数条件 - + c); // 还是只支持整段为 Raw SQL 比较好 (appendRaw && index > 0 ? rawSQL : "") + c); //单个条件,如果有 Raw SQL 则按原来位置拼接 + condition += ((i <= 0 ? "" : lk) + expr); } } + if (condition.isEmpty()) { return ""; } @@ -3613,14 +3654,14 @@ public String getContainString(String key, String column, Object[] childs, int t if (c instanceof Collection) { throw new IllegalArgumentException(key + ":value 中 value 类型不能为 [JSONArray, Collection] 中的任何一个 !"); } - + Object path = ""; if (c instanceof Map) { path = ((Map) c).get("path"); if (path != null && path instanceof String == false) { throw new IllegalArgumentException(key + ":{ path:path, value:value } 中 path 类型错误,只能是 $, $.key1, $[0].key2 等符合 SQL 中 JSON 路径的 String !"); } - + c = ((Map) c).get("value"); if (c instanceof Collection || c instanceof Map) { throw new IllegalArgumentException(key + ":{ path:path, value:value } 中 value 类型不能为 [JSONObject, JSONArray, Collection, Map] 中的任何一个 !"); @@ -3644,7 +3685,7 @@ else if (isOracle()) { } } } - + if (condition.isEmpty()) { condition = getKey(column) + SQL.isNull(true) + OR + getLikeString(key, column, "[]"); // key = '[]' 无结果! } @@ -3652,7 +3693,7 @@ else if (isOracle()) { condition = getKey(column) + SQL.isNull(false) + AND + "(" + condition + ")"; } } - + if (condition.isEmpty()) { return ""; } @@ -3669,7 +3710,7 @@ public String getSubqueryString(Subquery subquery) throws Exception { if (subquery == null) { return ""; } - + String range = subquery.getRange(); SQLConfig cfg = subquery.getConfig(); @@ -3955,7 +3996,7 @@ private static String getConditionString(String column, String table, AbstractSQ // return table + " AS t0 INNER JOIN (SELECT id FROM " + condition + ") AS t1 ON t0.id = t1.id"; } - + private boolean keyPrefix; @Override public boolean isKeyPrefix() { @@ -3979,7 +4020,7 @@ public String getJoinString() throws Exception { // 主表不用别名 String ta; for (Join j : joinList) { onGetJoinString(j); - + if (j.isAppJoin()) { // APP JOIN,只是作为一个标记,执行完主表的查询后自动执行副表的查询 User.id IN($commentIdList) continue; } @@ -3998,9 +4039,9 @@ public String getJoinString() throws Exception { // jt = jt.toLowerCase(); // tn = tn.toLowerCase(); // } - + String sql; - + switch (type) { //前面已跳过 case "@": // APP JOIN // continue; @@ -4013,7 +4054,7 @@ public String getJoinString() throws Exception { sql = ( "<".equals(type) ? " LEFT" : (">".equals(type) ? " RIGHT" : " CROSS") ) + " JOIN ( " + jc.getSQL(isPrepared()) + " ) AS " + quote + jt + quote; sql = concatJoinOn(sql, quote, j, jt, onList); - + jc.setMain(false).setKeyPrefix(true); pvl.addAll(jc.getPreparedValueList()); @@ -4037,7 +4078,7 @@ public String getJoinString() throws Exception { + ", & INNER, | FULL, ! OUTER, ^ SIDE, ( ANTI, ) FOREIGN ] 之外的 JOIN 类型 !" ); } - + SQLConfig oc = j.getOuterConfig(); String ow = null; if (oc != null) { @@ -4045,7 +4086,7 @@ public String getJoinString() throws Exception { oc.setPreparedValueList(new ArrayList<>()); oc.setMain(false).setKeyPrefix(true); ow = oc.getWhereString(false); - + pvl.addAll(oc.getPreparedValueList()); changed = true; } @@ -4064,7 +4105,7 @@ public String getJoinString() throws Exception { return StringUtil.isEmpty(joinOns, true) ? "" : joinOns + " \n"; } - + protected String concatJoinOn(@NotNull String sql, @NotNull String quote, @NotNull Join j, @NotNull String jt, List onList) { if (onList != null) { boolean first = true; @@ -4074,7 +4115,7 @@ protected String concatJoinOn(@NotNull String sql, @NotNull String quote, @NotNu if (isNot) { onJoinNotRelation(sql, quote, j, jt, onList, on); } - + String rt = on.getRelateType(); if (StringUtil.isEmpty(rt, false)) { sql += (first ? ON : AND) + quote + jt + quote + "." + quote + on.getKey() + quote + (isNot ? " != " : " = ") @@ -4082,29 +4123,29 @@ protected String concatJoinOn(@NotNull String sql, @NotNull String quote, @NotNu } else { onJoinComplextRelation(sql, quote, j, jt, onList, on); - + if (">=".equals(rt) || "<=".equals(rt) || ">".equals(rt) || "<".equals(rt)) { if (isNot) { throw new IllegalArgumentException("join:value 中 value 里的 " + jt + "/" + j.getPath() + " 中 JOIN ON 条件关联逻辑符 " + rt + " 不合法! >, <, >=, <= 不支持与或非逻辑符 & | ! !"); } - + sql += (first ? ON : AND) + quote + jt + quote + "." + quote + on.getKey() + quote + " " + rt + " " + quote + on.getTargetTable() + quote + "." + quote + on.getTargetKey() + quote; } else if (rt.endsWith("$")) { String t = rt.substring(0, rt.length() - 1); char r = t.isEmpty() ? 0 : t.charAt(t.length() - 1); - + char l; if (r == '%' || r == '_' || r == '?') { t = t.substring(0, t.length() - 1); - + if (t.isEmpty()) { if (r == '?') { throw new IllegalArgumentException(on.getOriginKey() + ":value 中字符 " + on.getOriginKey() + " 不合法!key$:value 中不允许只有单独的 '?',必须和 '%', '_' 之一配合使用 !"); } - + l = r; } else { @@ -4120,7 +4161,7 @@ else if (l > 0 && StringUtil.isName(String.valueOf(l))) { l = r; } } - + if (l == '?') { l = 0; } @@ -4131,7 +4172,7 @@ else if (l > 0 && StringUtil.isName(String.valueOf(l))) { else { l = r = 0; } - + if (l <= 0 && r <= 0) { sql += (first ? ON : AND) + quote + jt + quote + "." + quote + on.getKey() + quote + (isNot ? NOT : "") + " LIKE " + quote + on.getTargetTable() + quote + "." + quote + on.getTargetKey() + quote; @@ -4201,7 +4242,7 @@ else if ("{}".equals(rt) || "<>".equals(rt)) { arrKeyPath = quote + jt + quote + "." + quote + on.getKey() + quote; itemKeyPath = quote + on.getTargetTable() + quote + "." + quote + on.getTargetKey() + quote; } - + if (isPostgreSQL()) { //operator does not exist: jsonb @> character varying "[" + c + "]"); sql += (first ? ON : AND) + (isNot ? "( " : "") + getCondition(isNot, arrKeyPath + " IS NOT NULL AND " + arrKeyPath + " @> " + itemKeyPath) + (isNot ? ") " : ""); @@ -4230,11 +4271,11 @@ else if (isClickHouse()) { + " 中 JOIN ON 条件关联类型 " + rt + " 不合法!只支持 =, >, <, >=, <=, !=, $, ~, {}, <> 这几种!"); } } - + first = false; } } - + return sql; } @@ -4244,7 +4285,7 @@ protected void onJoinNotRelation(String sql, String quote, Join j, String jt, Li protected void onJoinComplextRelation(String sql, String quote, Join j, String jt, List onList, On on) { throw new UnsupportedOperationException("JOIN 已禁用 $, ~, {}, <>, >, <, >=, <= 等复杂关联 !性能很差、需求极少,默认只允许 = 等价关联,如要取消禁用可在后端重写相关方法!"); } - + protected void onGetJoinString(Join j) throws UnsupportedOperationException { } protected void onGetCrossJoinString(Join j) throws UnsupportedOperationException { @@ -4364,7 +4405,7 @@ else if (id instanceof Subquery) {} } //对 userId 和 userId{} 处理,这两个一定会作为条件 - Object userIdIn = request.get(userIdInKey); //可能是 userId{}:">0" + Object userIdIn = userIdInKey.equals(idInKey) ? null : request.get(userIdInKey); //可能是 userId{}:">0" if (userIdIn instanceof Collection) { // 排除掉 0, 负数, 空字符串 等无效 userId 值 Collection userIds = (Collection) userIdIn; List newUserIdIn = new ArrayList<>(); @@ -4380,8 +4421,8 @@ else if (id instanceof Subquery) {} } userIdIn = newUserIdIn; } - - Object userId = request.get(userIdKey); + + Object userId = userIdKey.equals(idKey) ? null : request.get(userIdKey); if (userId != null) { //null无效 if (userId instanceof Number) { if (((Number) userId).longValue() <= 0) { //一定没有值 @@ -4397,7 +4438,7 @@ else if (userId instanceof Subquery) {} else { throw new IllegalArgumentException(userIdKey + ":value 中 value 的类型只能是 Long , String 或 Subquery !"); } - + if (userIdIn instanceof Collection) { //共用userIdIn场景少性能差 boolean contains = false; Collection userIds = (Collection) userIdIn; @@ -4412,10 +4453,10 @@ else if (userId instanceof Subquery) {} } } } - + //对 id, id{}, userId, userId{} 处理,这些只要不为 null 就一定会作为 AND 条件 >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> - - + + String role = request.getString(KEY_ROLE); String cache = request.getString(KEY_CACHE); Subquery from = (Subquery) request.get(KEY_FROM); @@ -4454,8 +4495,8 @@ else if (userId instanceof Subquery) {} request.remove(KEY_ORDER); request.remove(KEY_RAW); request.remove(KEY_JSON); - - + + // @null <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< String[] nullKeys = StringUtil.split(nulls); if (nullKeys != null && nullKeys.length > 0) { @@ -4466,12 +4507,12 @@ else if (userId instanceof Subquery) {} if (request.get(nk) != null) { throw new IllegalArgumentException(table + ":{ @null: value } 中的字符 '" + nk + "' 已在当前对象有非 null 值!不允许对同一个 JSON key 设置不同值!"); } - + request.put(nk, null); } } // @null >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> - + // @cast <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< String[] casts = StringUtil.split(cast); Map castMap = null; @@ -4479,7 +4520,7 @@ else if (userId instanceof Subquery) {} castMap = new HashMap<>(casts.length); for (String c : casts) { apijson.orm.Entry p = Pair.parseEntry(c); - + if (StringUtil.isEmpty(p.getKey(), true)) { throw new IllegalArgumentException(table + ":{} 里的 @cast: 'key0:type0,key1:type1..' 中 '" + c + "' 对应的 key 的字符 '" + p.getKey() + "' 不合法!不允许为空!"); } @@ -4489,13 +4530,13 @@ else if (userId instanceof Subquery) {} if (castMap.get(p.getKey()) != null) { throw new IllegalArgumentException(table + ":{} 里的 @cast: 'key0:type0,key1:type1..' 中 '" + c + "' 对应的 key 的字符 '" + p.getKey() + "' 已存在!不允许重复设置类型!"); } - + castMap.put(p.getKey(), p.getValue()); } } // @cast >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> - + String[] rawArr = StringUtil.split(raw); config.setRaw(rawArr == null || rawArr.length <= 0 ? null : new ArrayList<>(Arrays.asList(rawArr))); @@ -4544,36 +4585,40 @@ else if (userId instanceof Subquery) {} final boolean isWhere = method != PUT; //除了POST,PUT,其它全是条件!!! //条件<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< - List whereList = null; - String[] ws = StringUtil.split(combine); if (ws != null && (method == DELETE || method == GETS || method == HEADS)) { throw new IllegalArgumentException(table + ":{} 里的 @combine:value 不合法!DELETE,GETS,HEADS 请求不允许传 @combine:value !"); } - + String combineExpr = ws == null || ws.length != 1 ? null : ws[0]; - + Map> combineMap = new LinkedHashMap<>(); List andList = new ArrayList<>(); List orList = new ArrayList<>(); List notList = new ArrayList<>(); + List whereList = new ArrayList<>(); + //强制作为条件且放在最前面优化性能 if (id != null) { tableWhere.put(idKey, id); andList.add(idKey); + whereList.add(idKey); } if (idIn != null) { tableWhere.put(idInKey, idIn); andList.add(idInKey); + whereList.add(idInKey); } if (userId != null) { tableWhere.put(userIdKey, userId); andList.add(userIdKey); + whereList.add(userIdKey); } if (userIdIn != null) { tableWhere.put(userIdInKey, userIdIn); andList.add(userIdInKey); + whereList.add(userIdInKey); } if (StringUtil.isNotEmpty(combineExpr, true)) { @@ -4585,7 +4630,7 @@ else if (userId instanceof Subquery) {} if (index < 0) { break; } - + char left = index <= 0 ? ' ' : str.charAt(index - 1); char right = index >= str.length() - key.length() ? ' ' : str.charAt(index + key.length()); if ((left == ' ' || left == '(' || left == '&' || left == '|' || left == '!') && (right == ' ' || right == ')')) { @@ -4602,11 +4647,8 @@ else if (userId instanceof Subquery) {} } } else if (ws != null) { - whereList = new ArrayList<>(); - - String w; for (int i = 0; i < ws.length; i++) { //去除 &,|,! 前缀 - w = ws[i]; + String w = ws[i]; if (w != null) { if (w.startsWith("&")) { w = w.substring(1); @@ -4667,17 +4709,15 @@ else if (w.startsWith("!")) { //解决AccessVerifier新增userId没有作为条件,而是作为内容,导致PUT,DELETE出错 if (isWhere || (StringUtil.isName(key.replaceFirst("[+-]$", "")) == false)) { tableWhere.put(key, value); - if (whereList == null || whereList.contains(key) == false) { - if (andList != null) { - andList.add(key); - } + if (whereList.contains(key) == false) { + andList.add(key); } } - else if (whereList != null && whereList.contains(key)) { + else if (whereList.contains(key)) { tableWhere.put(key, value); } else { - tableContent.put(key, value);//一样 instanceof JSONArray ? JSON.toJSONString(value) : value); + tableContent.put(key, value); //一样 instanceof JSONArray ? JSON.toJSONString(value) : value); } } @@ -4751,19 +4791,19 @@ else if (whereList != null && whereList.contains(key)) { } } } - - + + // @having, @haivng& <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< Object newHaving = having; boolean isHavingAnd = false; - + Map havingMap = new LinkedHashMap<>(); if (havingAnd != null) { if (having != null) { throw new IllegalArgumentException(table + ":{ @having: value1, @having&: value2 } " + "中 value1 与 value2 不合法!不允许同时传 @having 和 @having& ,两者最多传一个!"); } - + newHaving = havingAnd; isHavingAnd = true; } @@ -4776,7 +4816,7 @@ else if (whereList != null && whereList.contains(key)) { if (havingss != null) { int ind = -1; for (int i = 0; i < havingss.length; i++) { - + String havingsStr = havingss[i]; int start = havingsStr == null ? -1 : havingsStr.indexOf("("); int end = havingsStr == null ? -1 : havingsStr.lastIndexOf(")"); @@ -4784,7 +4824,7 @@ else if (whereList != null && whereList.contains(key)) { throw new IllegalArgumentException(table + ":{ " + havingKey + ":value } 里的 value 中的第 " + i + " 个字符 '" + havingsStr + "' 不合法!里面没有包含 SQL 函数!必须为 fun(col1,col2..)?val 格式!"); } - + String[] havings = start >= 0 && end > start ? new String[]{havingsStr} : StringUtil.split(havingsStr); if (havings != null) { for (int j = 0; j < havings.length; j++) { @@ -4841,7 +4881,7 @@ else if (newHaving != null) { + "@having:value 中 value 只能是 String 或 JSONObject,@having&:value 中 value 只能是 String !"); } // @having, @haivng& >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> - + config.setExplain(explain); config.setCache(getCache(cache)); @@ -4881,7 +4921,7 @@ else if (newHaving != null) { if (userIdIn != null) { request.put(userIdInKey, userIdIn); } - + // 关键词 request.put(KEY_DATABASE, database); request.put(KEY_ROLE, role); @@ -4974,7 +5014,7 @@ LEFT JOIN ( SELECT count(*) AS count FROM sys.Comment ) AS Comment ON Comment.m column.add(on.getKey()); } } - + joinConfig.setMethod(GET); // 子查询不能为 SELECT count(*) ,而应该是 SELECT momentId joinConfig.setColumn(column); // 优化性能,不取非必要的字段 @@ -5044,7 +5084,7 @@ else if (c == '?') { throw new IllegalArgumentException(originKey + ":value 中字符 " + originKey + " 不合法!key$:value 中不允许只有单独的 '?',必须和 '%', '_' 之一配合使用 !"); } } - + key = k; } else if (key.endsWith("~")) {//匹配正则表达式 REGEXP,查询时处理 @@ -5095,7 +5135,7 @@ else if (key.endsWith("-")) {//缩减,PUT查询时处理 } //TODO if (key.endsWith("-")) { // 表示 key 和 value 顺序反过来: value LIKE key - + String last = null;//不用Logic优化代码,否则 key 可能变为 key| 导致 key=value 变成 key|=value 而出错 if (RequestMethod.isQueryMethod(method)) {//逻辑运算符仅供GET,HEAD方法使用 last = key.isEmpty() ? "" : key.substring(key.length() - 1); From 1e5e587eaa48d5d356b80de1790fae3eef77d675 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Sun, 27 Mar 2022 06:32:20 +0800 Subject: [PATCH 0602/1181] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E6=94=AF=E6=8C=81?= =?UTF-8?q?=20@having:"match(arg0..)AGAINST(..)%2=3D1"=20=E5=85=A8?= =?UTF-8?q?=E6=96=87=E6=A3=80=E7=B4=A2=E7=AD=89=E5=87=BD=E6=95=B0=E5=90=8E?= =?UTF-8?q?=E5=B8=A6=E6=95=B0=E5=AD=A6=E8=A1=A8=E8=BE=BE=E5=BC=8F=EF=BC=9B?= =?UTF-8?q?=E5=AF=B9=20key{}:">0;length(key)<=3D5"=20=E6=96=B0=E5=A2=9E?= =?UTF-8?q?=E6=94=AF=E6=8C=81=E9=83=A8=E5=88=86=E4=B8=BA=20RAW=20SQL?= =?UTF-8?q?=EF=BC=9B=E7=A6=81=E6=AD=A2=20@having:"fun(arg0..):alias"=20?= =?UTF-8?q?=E8=BF=99=E6=A0=B7=E4=BD=BF=E7=94=A8=E5=88=AB=E5=90=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/apijson/orm/AbstractSQLConfig.java | 88 +++++++++++-------- .../src/main/java/apijson/orm/SQLConfig.java | 1 + 2 files changed, 50 insertions(+), 39 deletions(-) diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java index 21fe2efce..310e75dc6 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java @@ -1230,18 +1230,12 @@ public String getHavingString(boolean hasPrefix) throws Exception { return (hasPrefix ? " HAVING " : "") + StringUtil.concat(havingString, joinHaving, AND); } - protected String getHavingItem(String quote, String table, String alias, String key, String expression, boolean containRaw) { + protected String getHavingItem(String quote, String table, String alias, String key, String expression, boolean containRaw) throws Exception { //fun(arg0,arg1,...) if (containRaw) { - try { - String rawSQL = getRawSQL(KEY_HAVING, expression); - if (rawSQL != null) { - return rawSQL; - } - } catch (Exception e) { - Log.e(TAG, "newSQLConfig rawColumnSQL == null >> try { " - + " String rawSQL = ((AbstractSQLConfig) config).getRawSQL(KEY_COLUMN, fk); ... " - + "} catch (Exception e) = " + e.getMessage()); + String rawSQL = getRawSQL(KEY_HAVING, expression); + if (rawSQL != null) { + return rawSQL; } } @@ -1460,6 +1454,17 @@ public SQLConfig setRaw(List raw) { */ @Override public String getRawSQL(String key, Object value) throws Exception { + return getRawSQL(key, value, false); + } + /**获取原始 SQL 片段 + * @param key + * @param value + * @param throwWhenMissing + * @return + * @throws Exception + */ + @Override + public String getRawSQL(String key, Object value, boolean throwWhenMissing) throws Exception { if (value == null) { return null; } @@ -1474,11 +1479,12 @@ public String getRawSQL(String key, Object value) throws Exception { String rawSQL = containRaw ? RAW_MAP.get(value) : null; if (containRaw) { if (rawSQL == null) { - throw new UnsupportedOperationException("@raw:value 的 value 中 " + key + " 不合法!" - + "对应的 " + key + ":value 中 value 值 " + value + " 未在后端 RAW_MAP 中配置 !"); + if (throwWhenMissing) { + throw new UnsupportedOperationException("@raw:value 的 value 中 " + key + " 不合法!" + + "对应的 " + key + ":value 中 value 值 " + value + " 未在后端 RAW_MAP 中配置 !"); + } } - - if (rawSQL.isEmpty()) { + else if (rawSQL.isEmpty()) { return (String) value; } } @@ -1824,14 +1830,14 @@ public String parseSQLExpression(String key, String expression, boolean containR String s2 = expression.substring(keyIndex + 1); // OVER 后半部分 int index1 = s1.indexOf("("); // 函数 "(" 的起始位置 - String fun = s1.substring(0, index1); // 函数名称 int end = s2.lastIndexOf(")"); // 后半部分 “)” 的位置 - if (index1 >= end) { + if (index1 >= end + s1.length()) { throw new IllegalArgumentException("字符 " + expression + " 不合法!" + key + ":value 中 value 里的 SQL 函数必须为 function(arg0,arg1,...) 这种格式!"); } + String fun = s1.substring(0, index1); // 函数名称 if (fun.isEmpty() == false) { if (SQL_FUNCTION_MAP == null || SQL_FUNCTION_MAP.isEmpty()) { if (StringUtil.isName(fun) == false) { @@ -1851,11 +1857,26 @@ else if (SQL_FUNCTION_MAP.containsKey(fun) == false) { int index2 = s2.indexOf("("); // 后半部分 “(”的起始位置 String argString2 = s2.substring(index2 + 1, end); // 后半部分的参数 // 别名 - String alias = allowAlias == false || s2.lastIndexOf(":") < s2.lastIndexOf(")") ? null : s2.substring(s2.lastIndexOf(":") + 1); + int aliasIndex = allowAlias == false ? -1 : s2.lastIndexOf(":"); + String alias = aliasIndex < 0 ? "" : s2.substring(aliasIndex + 1); + if (alias.isEmpty() == false && StringUtil.isName(alias) == false) { + throw new IllegalArgumentException("字符串 " + alias + " 不合法!预编译模式下 " + + key + ":value 中 value里面用 ; 分割的每一项" + + " function(arg0,arg1,...):alias 中 alias 必须是1个单词!并且不要有多余的空格!"); + } + + String suffix = s2.substring(end + 1, aliasIndex < 0 ? s2.length() : aliasIndex); + if (suffix.isEmpty() == false && (((String) suffix).contains("--") || ((String) suffix).contains("/*") + || PATTERN_RANGE.matcher((String) suffix).matches() == false)) { + throw new UnsupportedOperationException("字符串 " + suffix + " 不合法!预编译模式下 " + key + + ":\"column?value;function(arg0,arg1,...)?value...\"" + + " 中 ?value 必须符合正则表达式 " + PATTERN_RANGE + " 且不包含连续减号 -- 或注释符 /* !不允许多余的空格!"); + } + // 获取后半部分的参数解析 (agr0 agr1 ...) String argsString2[] = parseArgsSplitWithComma(argString2, false, containRaw, allowAlias); expression = fun + "(" + StringUtil.getString(agrsString1) + (containOver ? ") OVER (" : ") AGAINST (") // 传参不传空格,拼接带空格 - + StringUtil.getString(argsString2) + ")" + (StringUtil.isEmpty(alias, true) ? "" : " AS " + quote + alias + quote); } + + StringUtil.getString(argsString2) + ")" + suffix + (StringUtil.isEmpty(alias, true) ? "" : " AS " + quote + alias + quote); } } return expression; @@ -3016,8 +3037,6 @@ protected String getWhereItem(String key, Object value, RequestMethod method, bo throw new IllegalArgumentException(TAG + ".getWhereItem: 字符 " + key + " 不合法!"); } - // 原始 SQL 片段 - String rawSQL = getRawSQL(key, value); int keyType; if (key.endsWith("$")) { @@ -3054,6 +3073,9 @@ else if (key.endsWith("<")) { } String column = getRealKey(method, key, false, true, verifyName); + + // 原始 SQL 片段 + String rawSQL = getRawSQL(key, value, keyType != 4 || value instanceof String == false); switch (keyType) { case 1: @@ -4747,15 +4769,9 @@ else if (whereList.contains(key)) { boolean containColumnRaw = rawList != null && rawList.contains(KEY_COLUMN); String rawColumnSQL = null; if (containColumnRaw) { - try { - rawColumnSQL = config.getRawSQL(KEY_COLUMN, column); - if (rawColumnSQL != null) { - cs.add(rawColumnSQL); - } - } catch (Exception e) { - Log.e(TAG, "newSQLConfig config instanceof AbstractSQLConfig >> try { " - + " rawColumnSQL = ((AbstractSQLConfig) config).getRawSQL(KEY_COLUMN, column); " - + "} catch (Exception e) = " + e.getMessage()); + rawColumnSQL = config.getRawSQL(KEY_COLUMN, column); + if (rawColumnSQL != null) { + cs.add(rawColumnSQL); } } @@ -4766,16 +4782,10 @@ else if (whereList.contains(key)) { String[] ks; for (String fk : fks) { if (containColumnRaw) { - try { - String rawSQL = config.getRawSQL(KEY_COLUMN, fk); - if (rawSQL != null) { - cs.add(rawSQL); - continue; - } - } catch (Exception e) { - Log.e(TAG, "newSQLConfig rawColumnSQL == null >> try { " - + " String rawSQL = ((AbstractSQLConfig) config).getRawSQL(KEY_COLUMN, fk); ... " - + "} catch (Exception e) = " + e.getMessage()); + String rawSQL = config.getRawSQL(KEY_COLUMN, fk); + if (rawSQL != null) { + cs.add(rawSQL); + continue; } } diff --git a/APIJSONORM/src/main/java/apijson/orm/SQLConfig.java b/APIJSONORM/src/main/java/apijson/orm/SQLConfig.java index 57e282d51..4db5a7520 100755 --- a/APIJSONORM/src/main/java/apijson/orm/SQLConfig.java +++ b/APIJSONORM/src/main/java/apijson/orm/SQLConfig.java @@ -244,6 +244,7 @@ public interface SQLConfig { String getWhereString(boolean hasPrefix) throws Exception; String getRawSQL(String key, Object value) throws Exception; + String getRawSQL(String key, Object value, boolean throwWhenMissing) throws Exception; boolean isKeyPrefix(); From d7c311554042620a91faf8f4a63ebcf9a2fd0be2 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Sun, 27 Mar 2022 20:34:02 +0800 Subject: [PATCH 0603/1181] =?UTF-8?q?=E6=8B=BC=E9=94=99=E5=8D=95=E8=AF=8D?= =?UTF-8?q?=20globle=20=E7=BA=A0=E6=AD=A3=E4=B8=BA=20global?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../apijson/orm/AbstractObjectParser.java | 20 ++-- .../main/java/apijson/orm/AbstractParser.java | 94 +++++++++---------- .../src/main/java/apijson/orm/Parser.java | 14 +-- .../src/main/java/apijson/orm/Verifier.java | 2 +- 4 files changed, 65 insertions(+), 65 deletions(-) diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractObjectParser.java b/APIJSONORM/src/main/java/apijson/orm/AbstractObjectParser.java index 003aee026..3f3a09a04 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractObjectParser.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractObjectParser.java @@ -332,22 +332,22 @@ else if (sqlConfig.isClickHouse()) { } if (isTable) { - if (parser.getGlobleDatabase() != null && sqlRequest.get(JSONRequest.KEY_DATABASE) == null) { - sqlRequest.put(JSONRequest.KEY_DATABASE, parser.getGlobleDatabase()); + if (parser.getGlobalDatabase() != null && sqlRequest.get(JSONRequest.KEY_DATABASE) == null) { + sqlRequest.put(JSONRequest.KEY_DATABASE, parser.getGlobalDatabase()); } - if (parser.getGlobleSchema() != null && sqlRequest.get(JSONRequest.KEY_SCHEMA) == null) { - sqlRequest.put(JSONRequest.KEY_SCHEMA, parser.getGlobleSchema()); + if (parser.getGlobalSchema() != null && sqlRequest.get(JSONRequest.KEY_SCHEMA) == null) { + sqlRequest.put(JSONRequest.KEY_SCHEMA, parser.getGlobalSchema()); } - if (parser.getGlobleDatasource() != null && sqlRequest.get(JSONRequest.KEY_DATASOURCE) == null) { - sqlRequest.put(JSONRequest.KEY_DATASOURCE, parser.getGlobleDatasource()); + if (parser.getGlobalDatasource() != null && sqlRequest.get(JSONRequest.KEY_DATASOURCE) == null) { + sqlRequest.put(JSONRequest.KEY_DATASOURCE, parser.getGlobalDatasource()); } if (isSubquery == false) { //解决 SQL 语法报错,子查询不能 EXPLAIN - if (parser.getGlobleExplain() != null && sqlRequest.get(JSONRequest.KEY_EXPLAIN) == null) { - sqlRequest.put(JSONRequest.KEY_EXPLAIN, parser.getGlobleExplain()); + if (parser.getGlobalExplain() != null && sqlRequest.get(JSONRequest.KEY_EXPLAIN) == null) { + sqlRequest.put(JSONRequest.KEY_EXPLAIN, parser.getGlobalExplain()); } - if (parser.getGlobleCache() != null && sqlRequest.get(JSONRequest.KEY_CACHE) == null) { - sqlRequest.put(JSONRequest.KEY_CACHE, parser.getGlobleCache()); + if (parser.getGlobalCache() != null && sqlRequest.get(JSONRequest.KEY_CACHE) == null) { + sqlRequest.put(JSONRequest.KEY_CACHE, parser.getGlobalCache()); } } } diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java b/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java index 25e851bf0..f56af8cc8 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java @@ -187,69 +187,69 @@ public AbstractParser setRequest(JSONObject request) { return this; } - protected Boolean globleFormat; - public AbstractParser setGlobleFormat(Boolean globleFormat) { - this.globleFormat = globleFormat; + protected Boolean globalFormat; + public AbstractParser setGlobalFormat(Boolean globalFormat) { + this.globalFormat = globalFormat; return this; } @Override - public Boolean getGlobleFormat() { - return globleFormat; + public Boolean getGlobalFormat() { + return globalFormat; } - protected String globleRole; - public AbstractParser setGlobleRole(String globleRole) { - this.globleRole = globleRole; + protected String globalRole; + public AbstractParser setGlobalRole(String globalRole) { + this.globalRole = globalRole; return this; } @Override - public String getGlobleRole() { - return globleRole; + public String getGlobalRole() { + return globalRole; } - protected String globleDatabase; - public AbstractParser setGlobleDatabase(String globleDatabase) { - this.globleDatabase = globleDatabase; + protected String globalDatabase; + public AbstractParser setGlobalDatabase(String globalDatabase) { + this.globalDatabase = globalDatabase; return this; } @Override - public String getGlobleDatabase() { - return globleDatabase; + public String getGlobalDatabase() { + return globalDatabase; } - protected String globleSchema; - public AbstractParser setGlobleSchema(String globleSchema) { - this.globleSchema = globleSchema; + protected String globalSchema; + public AbstractParser setGlobalSchema(String globalSchema) { + this.globalSchema = globalSchema; return this; } @Override - public String getGlobleSchema() { - return globleSchema; + public String getGlobalSchema() { + return globalSchema; } - protected String globleDatasource; + protected String globalDatasource; @Override - public String getGlobleDatasource() { - return globleDatasource; + public String getGlobalDatasource() { + return globalDatasource; } - public AbstractParser setGlobleDatasource(String globleDatasource) { - this.globleDatasource = globleDatasource; + public AbstractParser setGlobalDatasource(String globalDatasource) { + this.globalDatasource = globalDatasource; return this; } - protected Boolean globleExplain; - public AbstractParser setGlobleExplain(Boolean globleExplain) { - this.globleExplain = globleExplain; + protected Boolean globalExplain; + public AbstractParser setGlobalExplain(Boolean globalExplain) { + this.globalExplain = globalExplain; return this; } @Override - public Boolean getGlobleExplain() { - return globleExplain; + public Boolean getGlobalExplain() { + return globalExplain; } - protected String globleCache; - public AbstractParser setGlobleCache(String globleCache) { - this.globleCache = globleCache; + protected String globalCache; + public AbstractParser setGlobalCache(String globalCache) { + this.globalCache = globalCache; return this; } @Override - public String getGlobleCache() { - return globleCache; + public String getGlobalCache() { + return globalCache; } @Override @@ -383,9 +383,9 @@ public JSONObject parseResponse(JSONObject request) { } //必须在parseCorrectRequest后面,因为parseCorrectRequest可能会添加 @role - if (isNeedVerifyRole() && globleRole == null) { + if (isNeedVerifyRole() && globalRole == null) { try { - setGlobleRole(requestObject.getString(JSONRequest.KEY_ROLE)); + setGlobalRole(requestObject.getString(JSONRequest.KEY_ROLE)); requestObject.remove(JSONRequest.KEY_ROLE); } catch (Exception e) { return extendErrorResult(requestObject, e, requestMethod, getRequestURL(), isRoot); @@ -393,12 +393,12 @@ public JSONObject parseResponse(JSONObject request) { } try { - setGlobleFormat(requestObject.getBoolean(JSONRequest.KEY_FORMAT)); - setGlobleDatabase(requestObject.getString(JSONRequest.KEY_DATABASE)); - setGlobleSchema(requestObject.getString(JSONRequest.KEY_SCHEMA)); - setGlobleDatasource(requestObject.getString(JSONRequest.KEY_DATASOURCE)); - setGlobleExplain(requestObject.getBoolean(JSONRequest.KEY_EXPLAIN)); - setGlobleCache(requestObject.getString(JSONRequest.KEY_CACHE)); + setGlobalFormat(requestObject.getBoolean(JSONRequest.KEY_FORMAT)); + setGlobalDatabase(requestObject.getString(JSONRequest.KEY_DATABASE)); + setGlobalSchema(requestObject.getString(JSONRequest.KEY_SCHEMA)); + setGlobalDatasource(requestObject.getString(JSONRequest.KEY_DATASOURCE)); + setGlobalExplain(requestObject.getBoolean(JSONRequest.KEY_EXPLAIN)); + setGlobalCache(requestObject.getString(JSONRequest.KEY_CACHE)); requestObject.remove(JSONRequest.KEY_FORMAT); requestObject.remove(JSONRequest.KEY_DATABASE); @@ -435,7 +435,7 @@ public JSONObject parseResponse(JSONObject request) { requestObject = error == null ? extendSuccessResult(requestObject, isRoot) : extendErrorResult(requestObject, error, requestMethod, getRequestURL(), isRoot); - JSONObject res = (globleFormat != null && globleFormat) && JSONResponse.isSuccess(requestObject) ? new JSONResponse(requestObject) : requestObject; + JSONObject res = (globalFormat != null && globalFormat) && JSONResponse.isSuccess(requestObject) ? new JSONResponse(requestObject) : requestObject; long endTime = System.currentTimeMillis(); long duration = endTime - startTime; @@ -493,8 +493,8 @@ public void onVerifyRole(@NotNull SQLConfig config) throws Exception { if (isNeedVerifyRole()) { if (config.getRole() == null) { - if (globleRole != null) { - config.setRole(globleRole); + if (globalRole != null) { + config.setRole(globalRole); } else { config.setRole(getVisitor().getId() == null ? AbstractVerifier.UNKNOWN : AbstractVerifier.LOGIN); } @@ -548,7 +548,7 @@ public JSONObject parseCorrectRequest(RequestMethod method, String tag, int vers JSONObject target = wrapRequest(method, tag, object, true); //JSONObject clone 浅拷贝没用,Structure.parse 会导致 structure 里面被清空,第二次从缓存里取到的就是 {} - return getVerifier().verifyRequest(method, name, target, request, maxUpdateCount, getGlobleDatabase(), getGlobleSchema(), creator); + return getVerifier().verifyRequest(method, name, target, request, maxUpdateCount, getGlobalDatabase(), getGlobalSchema(), creator); } diff --git a/APIJSONORM/src/main/java/apijson/orm/Parser.java b/APIJSONORM/src/main/java/apijson/orm/Parser.java index 135dc1c7a..db98ea1f5 100755 --- a/APIJSONORM/src/main/java/apijson/orm/Parser.java +++ b/APIJSONORM/src/main/java/apijson/orm/Parser.java @@ -118,13 +118,13 @@ JSONObject parseCorrectRequest(RequestMethod method, String tag, int version, St Verifier getVerifier(); - Boolean getGlobleFormat(); - String getGlobleRole(); - String getGlobleDatabase(); - String getGlobleSchema(); - String getGlobleDatasource(); - Boolean getGlobleExplain(); - String getGlobleCache(); + Boolean getGlobalFormat(); + String getGlobalRole(); + String getGlobalDatabase(); + String getGlobalSchema(); + String getGlobalDatasource(); + Boolean getGlobalExplain(); + String getGlobalCache(); int getTransactionIsolation(); diff --git a/APIJSONORM/src/main/java/apijson/orm/Verifier.java b/APIJSONORM/src/main/java/apijson/orm/Verifier.java index 76f142fc5..366adb070 100755 --- a/APIJSONORM/src/main/java/apijson/orm/Verifier.java +++ b/APIJSONORM/src/main/java/apijson/orm/Verifier.java @@ -74,7 +74,7 @@ public interface Verifier { * @throws Exception */ JSONObject verifyRequest(RequestMethod method, String name, JSONObject target, JSONObject request, - int maxUpdateCount, String globleDatabase, String globleSchema, SQLCreator creator) throws Exception; + int maxUpdateCount, String globalDatabase, String globalSchema, SQLCreator creator) throws Exception; /**验证返回结果的数据和结构 * @param table From d803766fc4880c59375218819356c20b3d4b86f7 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Sun, 27 Mar 2022 22:50:21 +0800 Subject: [PATCH 0604/1181] =?UTF-8?q?=E5=8D=87=E7=BA=A7=E8=87=AA=E8=BA=AB,?= =?UTF-8?q?=20fastjson=20=E7=89=88=E6=9C=AC=E5=88=86=E5=88=AB=E4=B8=BA=205?= =?UTF-8?q?.0.0,=201.2.79?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- APIJSONORM/pom.xml | 5 ++--- APIJSONORM/src/main/java/apijson/Log.java | 2 +- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/APIJSONORM/pom.xml b/APIJSONORM/pom.xml index 377ecc644..edd10dfb9 100755 --- a/APIJSONORM/pom.xml +++ b/APIJSONORM/pom.xml @@ -5,7 +5,7 @@ apijson.orm apijson-orm - 4.9.0 + 5.0.0 jar APIJSONORM @@ -18,11 +18,10 @@ - com.alibaba fastjson - 1.2.74 + 1.2.79 javax.activation diff --git a/APIJSONORM/src/main/java/apijson/Log.java b/APIJSONORM/src/main/java/apijson/Log.java index f97fae342..9c2c60a98 100755 --- a/APIJSONORM/src/main/java/apijson/Log.java +++ b/APIJSONORM/src/main/java/apijson/Log.java @@ -14,7 +14,7 @@ public class Log { public static boolean DEBUG = true; - public static final String VERSION = "4.9.0"; + public static final String VERSION = "5.0.0"; public static final String KEY_SYSTEM_INFO_DIVIDER = "---|-----APIJSON SYSTEM INFO-----|---"; //默认的时间格式 From e609c8682cedbb9b39c5cec6b252cda8cfc2cdb4 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Tue, 5 Apr 2022 16:56:48 +0800 Subject: [PATCH 0605/1181] =?UTF-8?q?=E8=A7=A3=E5=86=B3=E5=85=B3=E9=97=AD?= =?UTF-8?q?=E6=9D=83=E9=99=90=E6=A0=A1=E9=AA=8C=E6=97=B6=20POST=20?= =?UTF-8?q?=E8=AF=B7=E6=B1=82=E4=BC=A0=20userId=20=E6=97=A0=E6=95=88?= =?UTF-8?q?=EF=BC=8C=E5=8A=A0=E5=BC=BA=E5=AF=B9=20POST=20=E8=AF=B7?= =?UTF-8?q?=E6=B1=82=E5=86=85=E5=AD=97=E6=AE=B5=E6=A0=BC=E5=BC=8F=E7=9A=84?= =?UTF-8?q?=E6=A0=A1=E9=AA=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/apijson/orm/AbstractSQLConfig.java | 41 ++++++++++++------- 1 file changed, 26 insertions(+), 15 deletions(-) diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java index 310e75dc6..6698e4f4b 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java @@ -4568,10 +4568,20 @@ else if (userId instanceof Subquery) {} Set set = request.keySet(); //前面已经判断request是否为空 if (method == POST) { //POST操作 if (idIn != null) { - throw new IllegalArgumentException(table + ":{} 里的 " + idInKey + ": value 不合法!POST 请求中不允许传 " + idInKey + " !"); - } + throw new IllegalArgumentException(table + ":{" + idInKey + ": value} 里的 key 不合法!POST 请求中不允许传 " + idInKey + + " 这种非字段命名 key !必须为 英文字母 开头且只包含 英文字母、数字、下划线的 字段命名!"); } + if (userIdIn != null) { + throw new IllegalArgumentException(table + ":{" + userIdInKey + ": value} 里的 key 不合法!POST 请求中不允许传 " + userIdInKey + + " 这种非字段命名 key !必须为 英文字母 开头且只包含 英文字母、数字、下划线的 字段命名!"); } if (set != null && set.isEmpty() == false) { //不能直接return,要走完下面的流程 + for (String k : set) { + if (StringUtil.isName(k) == false) { + throw new IllegalArgumentException(table + ":{" + k + ": value} 里的 key 不合法!POST 请求中不允许传 " + k + + " 这种非字段命名 key !必须为 英文字母 开头且只包含 英文字母、数字、下划线的 字段命名!"); + } + } + String[] columns = set.toArray(new String[]{}); Collection valueCollection = request.values(); @@ -4581,24 +4591,25 @@ else if (userId instanceof Subquery) {} throw new Exception("服务器内部错误:\n" + TAG + " newSQLConfig values == null || values.length != columns.length !"); } - column = (id == null ? "" : idKey + ",") + StringUtil.getString(columns); //set已经判断过不为空 + + column = (id == null ? "" : idKey + ",") + (userId == null ? "" : userIdKey + ",") + StringUtil.getString(columns); //set已经判断过不为空 - List> valuess = new ArrayList<>(1); - List items; //(item0, item1, ...) - if (id == null) { //数据库自增 id - items = Arrays.asList(values); //FIXME 是否还需要进行 add 或 remove 操作?Arrays.ArrayList 不允许修改,会抛异常 - } - else { - int size = columns.length + (id == null ? 0 : 1); //以key数量为准 + int idCount = id == null ? (userId == null ? 0 : 1) : (userId == null ? 1 : 2); + int size = idCount + columns.length; // 以 key 数量为准 - items = new ArrayList<>(size); - items.add(id); //idList.get(i)); //第0个就是id + List items = new ArrayList<>(size); // VALUES(item0, item1, ...) + if (id != null) { + items.add(id); // idList.get(i)); // 第 0 个就是 id + } + if (userId != null) { + items.add(userId); // idList.get(i)); // 第 1 个就是 userId + } - for (int j = 1; j < size; j++) { - items.add(values[j-1]); //从第1个开始,允许"null" - } + for (int j = 0; j < values.length; j++) { + items.add(values[j]); // 从第 1 个开始,允许 "null" } + List> valuess = new ArrayList<>(1); valuess.add(items); config.setValues(valuess); } From eb3e5189f6c72b408902b0de25974d62f746fdb6 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Tue, 5 Apr 2022 17:17:17 +0800 Subject: [PATCH 0606/1181] =?UTF-8?q?=E6=96=87=E6=A1=A3=EF=BC=9A=E6=9B=B4?= =?UTF-8?q?=E6=96=B0=20@combine=20=E6=9D=A1=E4=BB=B6=E7=BB=84=E5=90=88?= =?UTF-8?q?=E8=AF=B4=E6=98=8E=E4=B8=BA=205.0+=20=E7=9A=84=E6=9D=A1?= =?UTF-8?q?=E4=BB=B6=E4=BB=BB=E6=84=8F=E7=BB=84=E5=90=88=E6=A0=BC=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Document.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Document.md b/Document.md index 46c090233..09d88f847 100644 --- a/Document.md +++ b/Document.md @@ -418,6 +418,6 @@ DELETE:
删除数据 | base_url/delete/ | {
   TableName:{< 比较运算 | >, <, >=, <= 比较运算符,用于
① 提供 "id{}":"<=90000" 这种条件范围的简化写法

② 实现子查询相关比较运算

不支持 "key=":Object 和 "key!=":Object 这两种写法,直接用更简单的 "key":Object 和 "key!":Object 替代。 | ① ["id<=":90000](http://apijson.cn:8080/get/{"[]":{"User":{"id<=":90000}}}),对应SQL是`id<=90000`,查询符合id<=90000的一个User数组

② ["id>@":{
   "from":"Comment",
   "Comment":{
      "@column":"min(userId)"
   }
}](http://apijson.cn:8080/get/{"User":{"id>@":{"from":"Comment","Comment":{"@column":"min(userId)"}}}})
WHERE id>(SELECT min(userId) FROM Comment) 逻辑运算 | &, \|, ! 逻辑运算符,对应数据库 SQL 中的 AND, OR, NOT。
横或纵与:同一键值对的值内条件默认 \| 或连接,可以在 key 后加逻辑运算符来具体指定;不同键值对的条件默认 & 与连接,可以用下面说明的对象关键词 @combine 来具体指定。

① & 可用于"key&{}":"条件"等

② \| 可用于"key\|{}":"条件", "key\|{}":[]等,一般可省略

③ ! 可单独使用,如"key!":Object,也可像&,\|一样配合其他功能符使用
"key!":null 无效,null 值会导致整个键值对被忽略解析,可以用 "key{}":"!=null" 替代,
"key":null 同理,用 "key{}":"=null" 替代。 | ① ["id&{}":">80000,<=90000"](http://apijson.cn:8080/head/{"User":{"id&{}":">80000,<=90000"}}),对应SQL是`id>80000 AND id<=90000`,即id满足id>80000 & id<=90000

② ["id\|{}":">90000,<=80000"](http://apijson.cn:8080/head/{"User":{"id\|{}":">90000,<=80000"}}),同"id{}":">90000,<=80000",对应SQL是`id>80000 OR id<=90000`,即id满足id>90000 \| id<=80000

③ ["id!{}":[82001,38710]](http://apijson.cn:8080/head/{"User":{"id!{}":[82001,38710]}}),对应SQL是`id NOT IN(82001,38710)`,即id满足 ! (id=82001 \| id=38710),可过滤黑名单的消息 数组关键词,可自定义 | "key":Object,key为 "[]":{} 中{}内的关键词,Object的类型由key指定

① "count":Integer,查询数量,0 表示最大值,默认最大值为100

② "page":Integer,查询页码,从0开始,默认最大值为100,一般和count一起用

③ "query":Integer,查询内容
0-对象,1-总数和分页详情,2-数据、总数和分页详情
总数关键词为 total,分页详情关键词为 info,
它们都和 query 同级,通过引用赋值得到,例如
"total@":"/[]/total", "info@":"/[]/info"
这里query及total仅为GET类型的请求提供方便,
一般可直接用HEAD类型的请求获取总数

④ "join":"&/Table0/key0@,\多表连接方式:
"\<" - LEFT JOIN
">" - RIGHT JOIN
"&" - INNER JOIN
"\|" - FULL JOIN
"!" - OUTER JOIN
"@" - APP JOIN
其中 @ APP JOIN 为应用层连表,会从已查出的主表里取得所有副表 key@ 关联的主表内的 refKey 作为一个数组 refKeys: [value0, value1...],然后把原来副表 count 次查询 key=$refKey 的 SQL 用 key IN($refKeys) 的方式合并为一条 SQL 来优化性能;
其它 JOIN 都是 SQL JOIN,具体功能和 MySQL,PostgreSQL 等数据库的 JOIN 一一对应
`"join":"`"MainTable":{},`
`"ViceTable":{"key@":"/MainTable/refKey"}`
会对应生成
`MainTable LEFT JOIN ViceTable`
`ON ViceTable.key=MainTable.refKey`

⑤ "otherKey":Object,自定义关键词,名称和以上系统关键词不一样,且原样返回上传的值 | ① 查询User数组,最多5个:
["count":5](http://apijson.cn:8080/get/{"[]":{"count":5,"User":{}}})
对应SQL是`LIMIT 5`

② 查询第3页的User数组,每页5个:
["count":5,
"page":3](http://apijson.cn:8080/get/{"[]":{"count":5,"page":3,"User":{}}})
对应SQL是`LIMIT 5 OFFSET 15`

③ 查询User数组和对应的User总数:
["[]":{
   "query":2,
   "User":{}
},
"total@":"/[]/total",
"info@":"/[]/info"](http://apijson.cn:8080/get/{"[]":{"query":2,"count":5,"User":{}},"total@":"%252F[]%252Ftotal","info@":"%252F[]%252Finfo"})
返回的数据中,总数及分页详情结构为:
"total":139, //总数
"info":{ //分页详情
   "total":139, //总数
   "count":5, //每页数量
   "page":0, //当前页码
   "max":27, //最大页码
   "more":true, //是否还有更多
   "first":true, //是否为首页
   "last":false //是否为尾页
}

④ Moment INNER JOIN User LEFT JOIN Comment:
["[]":{
   "join":"&/User/id@,\    "Moment":{
     "@group":"id" //主副表不是一对一,要去除重复数据
   },
   "User":{
     "name~":"t",
     "id@":"/Moment/userId"
   },
   "Comment":{
     "momentId@":"/Moment/id"
   }
}](http://apijson.cn:8080/get/{"[]":{"count":5,"join":"&%252FUser%252Fid@,\<%252FComment%252FmomentId@","Moment":{"@column":"id,userId,content","@group":"id"},"User":{"name~":"t","id@":"%252FMoment%252FuserId","@column":"id,name,head"},"Comment":{"momentId@":"%252FMoment%252Fid","@column":"id,momentId,content"}}})

⑤ 每一层都加当前用户名:
["User":{},
"[]":{
   "name@":"User/name", //自定义关键词
   "Moment":{}
}](http://apijson.cn:8080/get/{"User":{},"[]":{"name@":"User%252Fname","Moment":{}}}) - 对象关键词,可自定义 | "@key":Object,@key为 Table:{} 中{}内的关键词,Object的类型由@key指定

① "@combine":"&key0,&key1,\|key2,key3,
!key4,!key5,&key6,key7...",条件组合方式,\| 可省略。会自动把同类的合并,外层按照 & \| ! 顺序,内层的按传参顺序组合成
(key0 & key1 & key6 & 其它key) & (key2 \| key3 \| key7) & !(key4 \| key5)
这种连接方式,其中 "其它key" 是指与 @combine 在同一对象,且未被它声明的条件 key,默认都是 & 连接

② "@column":"column;function(arg)...",返回字段

③ "@order":"column0+,column1-...",排序方式

④ "@group":"column0,column1...",分组方式。如果@column里声明了Table的id,则id也必须在@group中声明;其它情况下必须满足至少一个条件:
1.分组的key在@column里声明
2.Table主键在@group中声明

⑤ "@having":"function0(...)?value0;function1(...)?value1;function2(...)?value2...",SQL函数条件,一般和@group一起用,函数一般在@column里声明

⑥ "@schema":"sys",集合空间(数据库名/模式),非默认的值可通过它来指定,可以在最外层作为全局默认配置

⑦ "@database":"POSTGRESQL",数据库类型,非默认的值可通过它来指定,可以在最外层作为全局默认配置

⑧ "@datasource":"DRUID",跨数据源,非默认的值可通过它来指定,可以在最外层作为全局默认配置

⑨ "@json":"key0,key1...",转为 JSON 格式返回,符合 JSONObject 则转为 {...},符合 JSONArray 则转为 \[...]

⑩ "@role":"OWNER",来访角色,包括
UNKNOWN,LOGIN,CONTACT,CIRCLE,OWNER,ADMIN,
可以在最外层作为全局默认配置,
可自定义其它角色并重写 Verifier.verify 等相关方法来自定义校验

⑪ "@explain":true,性能分析,可以在最外层作为全局默认配置

⑫ "@raw":"key0,key1...",其中 key0, key1 都对应有键值对
"key0":"SQL片段或SQL片段的别名",
"key1":"SQL片段或SQL片段的别名"
自定义原始SQL片段,可扩展嵌套SQL函数等复杂语句,必须是后端已配置的,只有其它功能符都做不到才考虑,谨慎使用,注意防SQL注入

⑬ "@otherKey":Object,自定义关键词,名称和以上系统关键词不一样,且原样返回上传的值 | ① 搜索name或tag任何一个字段包含字符a的User列表:
["name~":"a",
"tag~":"a",
"@combine":"name~,tag~"](http://apijson.cn:8080/get/{"User[]":{"count":10,"User":{"@column":"id,name,tag","name~":"a","tag~":"a","@combine":"name~,tag~"}}})
对应SQL是`name REGEXP 'a' OR tag REGEXP 'a'`

② 只查询id,sex,name这几列并且请求结果也按照这个顺序:
["@column":"id,sex,name"](http://apijson.cn:8080/get/{"User":{"@column":"id,sex,name","id":38710}})
对应SQL是`SELECT id,sex,name`

③ 查询按 name降序、id默认顺序 排序的User数组:
["@order":"name-,id"](http://apijson.cn:8080/get/{"[]":{"count":10,"User":{"@column":"name,id","@order":"name-,id"}}})
对应SQL是`ORDER BY name DESC,id`

④ 查询按userId分组的Moment数组:
["@group":"userId,id"](http://apijson.cn:8080/get/{"[]":{"count":10,"Moment":%7B"@column":"userId,id","@group":"userId,id"}}})
对应SQL是`GROUP BY userId,id`

⑤ 查询 按userId分组、id最大值>=100 的Moment数组:
["@column":"userId;max(id)",
"@group":"userId",
"@having":"max(id)>=100"](http://apijson.cn:8080/get/{"[]":{"count":10,"Moment":{"@column":"userId%253Bmax(id)","@group":"userId","@having":"max(id)>=100"}}})
对应SQL是`SELECT userId,max(id) ... GROUP BY userId HAVING max(id)>=100`
还可以指定函数返回名:
["@column":"userId;max(id):maxId",
"@group":"userId",
"@having":"maxId>=100"](http://apijson.cn:8080/get/{"[]":{"count":10,"Moment":{"@column":"userId%253Bmax(id):maxId","@group":"userId","@having":"maxId>=100"}}})
对应SQL是`SELECT userId,max(id) AS maxId ... GROUP BY userId HAVING maxId>=100`

⑥ 查询 sys 内的 User 表:
["@schema":"sys"](http://apijson.cn:8080/get/{"User":{"@schema":"sys"}})
对应SQL是`FROM sys.User`

⑦ 查询 PostgreSQL 数据库的 User 表:
["@database":"POSTGRESQL"](http://apijson.cn:8080/get/{"User":{"@database":"POSTGRESQL","@explain":true}})

⑧ 使用 Druid 连接池查询 User 表:
["@datasource":"DRUID"](http://apijson.cn:8080/get/{"User":{"@datasource":"DRUID"}})

⑨ 将 VARCHAR 字符串字段 get 转为 JSONArray 返回:
["@json":"get"](http://apijson.cn:8080/get/{"Access":{"@json":"get"}})

⑩ 查询当前用户的动态:
["@role":"OWNER"](http://apijson.cn:8080/get/{"[]":{"Moment":{"@role":"OWNER"}}})

⑪ 开启性能分析:
["@explain":true](http://apijson.cn:8080/get/{"[]":{"Moment":{"@explain":true}}})
对应SQL是`EXPLAIN`

⑫ 统计最近一周偶数userId的数量
["@column":"date;left(date,10):day;sum(if(userId%2=0,1,0))",
"@group":"day",
"@having":"to_days(now())-to_days(\`date\`)<=7",
"@raw":"@column,@having"](http://apijson.cn:8080/get/{"[]":{"Moment":{"@column":"date%3bleft(date,10):day%3bsum(if(userId%252=0,1,0))","@group":"day","@having":"to_days(now())-to_days(\`date\`)<=7","@raw":"@column,@having"}}})
对应SQL是``SELECT date, left(date,10) AS day, sum(if(userId%2=0,1,0)) ... GROUP BY day HAVING to_days(now())-to_days(`date`)<=7``

⑬ 从pictureList获取第0张图片:
["@position":0, //自定义关键词
"firstPicture()":"getFromArray(pictureList,@position)"](http://apijson.cn:8080/get/{"User":{"id":38710,"@position":0,"firstPicture()":"getFromArray(pictureList,@position)"}}) + 对象关键词,可自定义 | "@key":Object,@key为 Table:{} 中{}内的关键词,Object的类型由@key指定

① "@combine":"key0 \| (key1 & (key2 \| !key3))...",条件组合方式,最终按
(其它key条件 AND 连接) AND (key0条件 OR (key1条件 AND (key2条件 OR (NOT key3条件))))
这种方式连接,其中 "其它key" 是指与 @combine 在同一对象,且未被它声明的条件 key,默认都是 & 连接

② "@column":"column;function(arg)...",返回字段

③ "@order":"column0+,column1-...",排序方式

④ "@group":"column0,column1...",分组方式。如果@column里声明了Table的id,则id也必须在@group中声明;其它情况下必须满足至少一个条件:
1.分组的key在@column里声明
2.Table主键在@group中声明

⑤ "@having":"function0(...)?value0;function1(...)?value1;function2(...)?value2...",SQL函数条件,一般和@group一起用,函数一般在@column里声明

⑥ "@schema":"sys",集合空间(数据库名/模式),非默认的值可通过它来指定,可以在最外层作为全局默认配置

⑦ "@database":"POSTGRESQL",数据库类型,非默认的值可通过它来指定,可以在最外层作为全局默认配置

⑧ "@datasource":"DRUID",跨数据源,非默认的值可通过它来指定,可以在最外层作为全局默认配置

⑨ "@json":"key0,key1...",转为 JSON 格式返回,符合 JSONObject 则转为 {...},符合 JSONArray 则转为 \[...]

⑩ "@role":"OWNER",来访角色,包括
UNKNOWN,LOGIN,CONTACT,CIRCLE,OWNER,ADMIN,
可以在最外层作为全局默认配置,
可自定义其它角色并重写 Verifier.verify 等相关方法来自定义校验

⑪ "@explain":true,性能分析,可以在最外层作为全局默认配置

⑫ "@raw":"key0,key1...",其中 key0, key1 都对应有键值对
"key0":"SQL片段或SQL片段的别名",
"key1":"SQL片段或SQL片段的别名"
自定义原始SQL片段,可扩展嵌套SQL函数等复杂语句,必须是后端已配置的,只有其它功能符都做不到才考虑,谨慎使用,注意防SQL注入

⑬ "@otherKey":Object,自定义关键词,名称和以上系统关键词不一样,且原样返回上传的值 | ① 搜索name或tag任何一个字段包含字符a的User列表:
["name~":"a",
"tag~":"a",
"@combine":"name~ \| tag~"](http://apijson.cn:8080/get/{"User[]":{"count":10,"User":{"@column":"id,name,tag","name~":"a","tag~":"a","@combine":"name~%20%7C%20tag~"}}})
对应SQL是`name REGEXP 'a' OR tag REGEXP 'a'`

② 只查询id,sex,name这几列并且请求结果也按照这个顺序:
["@column":"id,sex,name"](http://apijson.cn:8080/get/{"User":{"@column":"id,sex,name","id":38710}})
对应SQL是`SELECT id,sex,name`

③ 查询按 name降序、id默认顺序 排序的User数组:
["@order":"name-,id"](http://apijson.cn:8080/get/{"[]":{"count":10,"User":{"@column":"name,id","@order":"name-,id"}}})
对应SQL是`ORDER BY name DESC,id`

④ 查询按userId分组的Moment数组:
["@group":"userId,id"](http://apijson.cn:8080/get/{"[]":{"count":10,"Moment":%7B"@column":"userId,id","@group":"userId,id"}}})
对应SQL是`GROUP BY userId,id`

⑤ 查询 按userId分组、id最大值>=100 的Moment数组:
["@column":"userId;max(id)",
"@group":"userId",
"@having":"max(id)>=100"](http://apijson.cn:8080/get/{"[]":{"count":10,"Moment":{"@column":"userId%253Bmax(id)","@group":"userId","@having":"max(id)>=100"}}})
对应SQL是`SELECT userId,max(id) ... GROUP BY userId HAVING max(id)>=100`
还可以指定函数返回名:
["@column":"userId;max(id):maxId",
"@group":"userId",
"@having":"maxId>=100"](http://apijson.cn:8080/get/{"[]":{"count":10,"Moment":{"@column":"userId%253Bmax(id):maxId","@group":"userId","@having":"maxId>=100"}}})
对应SQL是`SELECT userId,max(id) AS maxId ... GROUP BY userId HAVING maxId>=100`

⑥ 查询 sys 内的 User 表:
["@schema":"sys"](http://apijson.cn:8080/get/{"User":{"@schema":"sys"}})
对应SQL是`FROM sys.User`

⑦ 查询 PostgreSQL 数据库的 User 表:
["@database":"POSTGRESQL"](http://apijson.cn:8080/get/{"User":{"@database":"POSTGRESQL","@explain":true}})

⑧ 使用 Druid 连接池查询 User 表:
["@datasource":"DRUID"](http://apijson.cn:8080/get/{"User":{"@datasource":"DRUID"}})

⑨ 将 VARCHAR 字符串字段 get 转为 JSONArray 返回:
["@json":"get"](http://apijson.cn:8080/get/{"Access":{"@json":"get"}})

⑩ 查询当前用户的动态:
["@role":"OWNER"](http://apijson.cn:8080/get/{"[]":{"Moment":{"@role":"OWNER"}}})

⑪ 开启性能分析:
["@explain":true](http://apijson.cn:8080/get/{"[]":{"Moment":{"@explain":true}}})
对应SQL是`EXPLAIN`

⑫ 统计最近一周偶数userId的数量
["@column":"date;left(date,10):day;sum(if(userId%2=0,1,0))",
"@group":"day",
"@having":"to_days(now())-to_days(\`date\`)<=7",
"@raw":"@column,@having"](http://apijson.cn:8080/get/{"[]":{"Moment":{"@column":"date%3bleft(date,10):day%3bsum(if(userId%252=0,1,0))","@group":"day","@having":"to_days(now())-to_days(\`date\`)<=7","@raw":"@column,@having"}}})
对应SQL是``SELECT date, left(date,10) AS day, sum(if(userId%2=0,1,0)) ... GROUP BY day HAVING to_days(now())-to_days(`date`)<=7``

⑬ 从pictureList获取第0张图片:
["@position":0, //自定义关键词
"firstPicture()":"getFromArray(pictureList,@position)"](http://apijson.cn:8080/get/{"User":{"id":38710,"@position":0,"firstPicture()":"getFromArray(pictureList,@position)"}}) 全局关键词 | 为最外层对象 {} 内的关键词。其中 @database,@schema, @datasource, @role, @explain 基本同对象关键词,见上方说明,区别是全局关键词会每个表对象中没有时自动放入,作为默认值。

① "tag":String,后面的 tag 是非 GET、HEAD 请求中匹配请求的 JSON 结构的标识,一般是要查询的 Table 的名称或该名称对应的数组 Table[] 或 Table:[],由后端 Request 表中指定。

② "version":Integer,接口版本,version 不传、为 null 或 <=0 都会使用最高版本,传了其它有效值则会使用最接近它的最低版本,由后端 Request 表中指定。

③ "format":Boolean,格式化返回 Response JSON 的 key,一般是将 TableName 转为 tableName, TableName[] 转为 tableNameList, Table:alias 转为 alias, TableName-key[] 转为 tableNameKeyList 等小驼峰格式。 | ① 查隐私信息:
[{"tag":"Privacy","Privacy":{"id":82001}}](http://apijson.cn/api?url=http%3A%2F%2Fapijson.cn%3A8080%2Fgets&type=JSON&json={%22tag%22:%22Privacy%22,%22Privacy%22:{%22id%22:82001}})

② 使用第 1 版接口查隐私信息:
[{"version":1,"tag":"Privacy","Privacy":{"id":82001}}](http://apijson.cn/api?url=http%3A%2F%2Fapijson.cn%3A8080%2Fgets&type=JSON&json={%22version%22:1,%22tag%22:%22Privacy%22,%22Privacy%22:{%22id%22:82001}})

③ 格式化朋友圈接口返回 JSON 中的 key:
[{
   "format":true,
   "[]":{
     "page":0,
     "count":3,
     "Moment":{},
     "User":{
       "id@":"/Moment/userId"
     },
     "Comment[]":{
       "count":3,
       "Comment":{
         "momentId@":"[]/Moment/id"
       }
     }
   }
}](http://apijson.cn:8080/get/{"format":true,"[]":{"page":0,"count":3,"Moment":{},"User":{"id@":"%252FMoment%252FuserId"},"Comment[]":{"count":3,"Comment":{"momentId@":"[]%252FMoment%252Fid"}}}})
From df979db60bbdff1137d4835187797f0195901a87 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Tue, 5 Apr 2022 17:25:25 +0800 Subject: [PATCH 0607/1181] Update Document.md --- Document.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Document.md b/Document.md index 09d88f847..12bac1aa6 100644 --- a/Document.md +++ b/Document.md @@ -418,6 +418,6 @@ DELETE:
删除数据 | base_url/delete/ | {
   TableName:{< 比较运算 | >, <, >=, <= 比较运算符,用于
① 提供 "id{}":"<=90000" 这种条件范围的简化写法

② 实现子查询相关比较运算

不支持 "key=":Object 和 "key!=":Object 这两种写法,直接用更简单的 "key":Object 和 "key!":Object 替代。 | ① ["id<=":90000](http://apijson.cn:8080/get/{"[]":{"User":{"id<=":90000}}}),对应SQL是`id<=90000`,查询符合id<=90000的一个User数组

② ["id>@":{
   "from":"Comment",
   "Comment":{
      "@column":"min(userId)"
   }
}](http://apijson.cn:8080/get/{"User":{"id>@":{"from":"Comment","Comment":{"@column":"min(userId)"}}}})
WHERE id>(SELECT min(userId) FROM Comment) 逻辑运算 | &, \|, ! 逻辑运算符,对应数据库 SQL 中的 AND, OR, NOT。
横或纵与:同一键值对的值内条件默认 \| 或连接,可以在 key 后加逻辑运算符来具体指定;不同键值对的条件默认 & 与连接,可以用下面说明的对象关键词 @combine 来具体指定。

① & 可用于"key&{}":"条件"等

② \| 可用于"key\|{}":"条件", "key\|{}":[]等,一般可省略

③ ! 可单独使用,如"key!":Object,也可像&,\|一样配合其他功能符使用
"key!":null 无效,null 值会导致整个键值对被忽略解析,可以用 "key{}":"!=null" 替代,
"key":null 同理,用 "key{}":"=null" 替代。 | ① ["id&{}":">80000,<=90000"](http://apijson.cn:8080/head/{"User":{"id&{}":">80000,<=90000"}}),对应SQL是`id>80000 AND id<=90000`,即id满足id>80000 & id<=90000

② ["id\|{}":">90000,<=80000"](http://apijson.cn:8080/head/{"User":{"id\|{}":">90000,<=80000"}}),同"id{}":">90000,<=80000",对应SQL是`id>80000 OR id<=90000`,即id满足id>90000 \| id<=80000

③ ["id!{}":[82001,38710]](http://apijson.cn:8080/head/{"User":{"id!{}":[82001,38710]}}),对应SQL是`id NOT IN(82001,38710)`,即id满足 ! (id=82001 \| id=38710),可过滤黑名单的消息 数组关键词,可自定义 | "key":Object,key为 "[]":{} 中{}内的关键词,Object的类型由key指定

① "count":Integer,查询数量,0 表示最大值,默认最大值为100

② "page":Integer,查询页码,从0开始,默认最大值为100,一般和count一起用

③ "query":Integer,查询内容
0-对象,1-总数和分页详情,2-数据、总数和分页详情
总数关键词为 total,分页详情关键词为 info,
它们都和 query 同级,通过引用赋值得到,例如
"total@":"/[]/total", "info@":"/[]/info"
这里query及total仅为GET类型的请求提供方便,
一般可直接用HEAD类型的请求获取总数

④ "join":"&/Table0/key0@,\多表连接方式:
"\<" - LEFT JOIN
">" - RIGHT JOIN
"&" - INNER JOIN
"\|" - FULL JOIN
"!" - OUTER JOIN
"@" - APP JOIN
其中 @ APP JOIN 为应用层连表,会从已查出的主表里取得所有副表 key@ 关联的主表内的 refKey 作为一个数组 refKeys: [value0, value1...],然后把原来副表 count 次查询 key=$refKey 的 SQL 用 key IN($refKeys) 的方式合并为一条 SQL 来优化性能;
其它 JOIN 都是 SQL JOIN,具体功能和 MySQL,PostgreSQL 等数据库的 JOIN 一一对应
`"join":"`"MainTable":{},`
`"ViceTable":{"key@":"/MainTable/refKey"}`
会对应生成
`MainTable LEFT JOIN ViceTable`
`ON ViceTable.key=MainTable.refKey`

⑤ "otherKey":Object,自定义关键词,名称和以上系统关键词不一样,且原样返回上传的值 | ① 查询User数组,最多5个:
["count":5](http://apijson.cn:8080/get/{"[]":{"count":5,"User":{}}})
对应SQL是`LIMIT 5`

② 查询第3页的User数组,每页5个:
["count":5,
"page":3](http://apijson.cn:8080/get/{"[]":{"count":5,"page":3,"User":{}}})
对应SQL是`LIMIT 5 OFFSET 15`

③ 查询User数组和对应的User总数:
["[]":{
   "query":2,
   "User":{}
},
"total@":"/[]/total",
"info@":"/[]/info"](http://apijson.cn:8080/get/{"[]":{"query":2,"count":5,"User":{}},"total@":"%252F[]%252Ftotal","info@":"%252F[]%252Finfo"})
返回的数据中,总数及分页详情结构为:
"total":139, //总数
"info":{ //分页详情
   "total":139, //总数
   "count":5, //每页数量
   "page":0, //当前页码
   "max":27, //最大页码
   "more":true, //是否还有更多
   "first":true, //是否为首页
   "last":false //是否为尾页
}

④ Moment INNER JOIN User LEFT JOIN Comment:
["[]":{
   "join":"&/User/id@,\    "Moment":{
     "@group":"id" //主副表不是一对一,要去除重复数据
   },
   "User":{
     "name~":"t",
     "id@":"/Moment/userId"
   },
   "Comment":{
     "momentId@":"/Moment/id"
   }
}](http://apijson.cn:8080/get/{"[]":{"count":5,"join":"&%252FUser%252Fid@,\<%252FComment%252FmomentId@","Moment":{"@column":"id,userId,content","@group":"id"},"User":{"name~":"t","id@":"%252FMoment%252FuserId","@column":"id,name,head"},"Comment":{"momentId@":"%252FMoment%252Fid","@column":"id,momentId,content"}}})

⑤ 每一层都加当前用户名:
["User":{},
"[]":{
   "name@":"User/name", //自定义关键词
   "Moment":{}
}](http://apijson.cn:8080/get/{"User":{},"[]":{"name@":"User%252Fname","Moment":{}}}) - 对象关键词,可自定义 | "@key":Object,@key为 Table:{} 中{}内的关键词,Object的类型由@key指定

① "@combine":"key0 \| (key1 & (key2 \| !key3))...",条件组合方式,最终按
(其它key条件 AND 连接) AND (key0条件 OR (key1条件 AND (key2条件 OR (NOT key3条件))))
这种方式连接,其中 "其它key" 是指与 @combine 在同一对象,且未被它声明的条件 key,默认都是 & 连接

② "@column":"column;function(arg)...",返回字段

③ "@order":"column0+,column1-...",排序方式

④ "@group":"column0,column1...",分组方式。如果@column里声明了Table的id,则id也必须在@group中声明;其它情况下必须满足至少一个条件:
1.分组的key在@column里声明
2.Table主键在@group中声明

⑤ "@having":"function0(...)?value0;function1(...)?value1;function2(...)?value2...",SQL函数条件,一般和@group一起用,函数一般在@column里声明

⑥ "@schema":"sys",集合空间(数据库名/模式),非默认的值可通过它来指定,可以在最外层作为全局默认配置

⑦ "@database":"POSTGRESQL",数据库类型,非默认的值可通过它来指定,可以在最外层作为全局默认配置

⑧ "@datasource":"DRUID",跨数据源,非默认的值可通过它来指定,可以在最外层作为全局默认配置

⑨ "@json":"key0,key1...",转为 JSON 格式返回,符合 JSONObject 则转为 {...},符合 JSONArray 则转为 \[...]

⑩ "@role":"OWNER",来访角色,包括
UNKNOWN,LOGIN,CONTACT,CIRCLE,OWNER,ADMIN,
可以在最外层作为全局默认配置,
可自定义其它角色并重写 Verifier.verify 等相关方法来自定义校验

⑪ "@explain":true,性能分析,可以在最外层作为全局默认配置

⑫ "@raw":"key0,key1...",其中 key0, key1 都对应有键值对
"key0":"SQL片段或SQL片段的别名",
"key1":"SQL片段或SQL片段的别名"
自定义原始SQL片段,可扩展嵌套SQL函数等复杂语句,必须是后端已配置的,只有其它功能符都做不到才考虑,谨慎使用,注意防SQL注入

⑬ "@otherKey":Object,自定义关键词,名称和以上系统关键词不一样,且原样返回上传的值 | ① 搜索name或tag任何一个字段包含字符a的User列表:
["name~":"a",
"tag~":"a",
"@combine":"name~ \| tag~"](http://apijson.cn:8080/get/{"User[]":{"count":10,"User":{"@column":"id,name,tag","name~":"a","tag~":"a","@combine":"name~%20%7C%20tag~"}}})
对应SQL是`name REGEXP 'a' OR tag REGEXP 'a'`

② 只查询id,sex,name这几列并且请求结果也按照这个顺序:
["@column":"id,sex,name"](http://apijson.cn:8080/get/{"User":{"@column":"id,sex,name","id":38710}})
对应SQL是`SELECT id,sex,name`

③ 查询按 name降序、id默认顺序 排序的User数组:
["@order":"name-,id"](http://apijson.cn:8080/get/{"[]":{"count":10,"User":{"@column":"name,id","@order":"name-,id"}}})
对应SQL是`ORDER BY name DESC,id`

④ 查询按userId分组的Moment数组:
["@group":"userId,id"](http://apijson.cn:8080/get/{"[]":{"count":10,"Moment":%7B"@column":"userId,id","@group":"userId,id"}}})
对应SQL是`GROUP BY userId,id`

⑤ 查询 按userId分组、id最大值>=100 的Moment数组:
["@column":"userId;max(id)",
"@group":"userId",
"@having":"max(id)>=100"](http://apijson.cn:8080/get/{"[]":{"count":10,"Moment":{"@column":"userId%253Bmax(id)","@group":"userId","@having":"max(id)>=100"}}})
对应SQL是`SELECT userId,max(id) ... GROUP BY userId HAVING max(id)>=100`
还可以指定函数返回名:
["@column":"userId;max(id):maxId",
"@group":"userId",
"@having":"maxId>=100"](http://apijson.cn:8080/get/{"[]":{"count":10,"Moment":{"@column":"userId%253Bmax(id):maxId","@group":"userId","@having":"maxId>=100"}}})
对应SQL是`SELECT userId,max(id) AS maxId ... GROUP BY userId HAVING maxId>=100`

⑥ 查询 sys 内的 User 表:
["@schema":"sys"](http://apijson.cn:8080/get/{"User":{"@schema":"sys"}})
对应SQL是`FROM sys.User`

⑦ 查询 PostgreSQL 数据库的 User 表:
["@database":"POSTGRESQL"](http://apijson.cn:8080/get/{"User":{"@database":"POSTGRESQL","@explain":true}})

⑧ 使用 Druid 连接池查询 User 表:
["@datasource":"DRUID"](http://apijson.cn:8080/get/{"User":{"@datasource":"DRUID"}})

⑨ 将 VARCHAR 字符串字段 get 转为 JSONArray 返回:
["@json":"get"](http://apijson.cn:8080/get/{"Access":{"@json":"get"}})

⑩ 查询当前用户的动态:
["@role":"OWNER"](http://apijson.cn:8080/get/{"[]":{"Moment":{"@role":"OWNER"}}})

⑪ 开启性能分析:
["@explain":true](http://apijson.cn:8080/get/{"[]":{"Moment":{"@explain":true}}})
对应SQL是`EXPLAIN`

⑫ 统计最近一周偶数userId的数量
["@column":"date;left(date,10):day;sum(if(userId%2=0,1,0))",
"@group":"day",
"@having":"to_days(now())-to_days(\`date\`)<=7",
"@raw":"@column,@having"](http://apijson.cn:8080/get/{"[]":{"Moment":{"@column":"date%3bleft(date,10):day%3bsum(if(userId%252=0,1,0))","@group":"day","@having":"to_days(now())-to_days(\`date\`)<=7","@raw":"@column,@having"}}})
对应SQL是``SELECT date, left(date,10) AS day, sum(if(userId%2=0,1,0)) ... GROUP BY day HAVING to_days(now())-to_days(`date`)<=7``

⑬ 从pictureList获取第0张图片:
["@position":0, //自定义关键词
"firstPicture()":"getFromArray(pictureList,@position)"](http://apijson.cn:8080/get/{"User":{"id":38710,"@position":0,"firstPicture()":"getFromArray(pictureList,@position)"}}) + 对象关键词,可自定义 | "@key":Object,@key为 Table:{} 中{}内的关键词,Object的类型由@key指定

① "@combine":"key0 \| (key1 & (key2 \| !key3))...",条件组合方式,最终按
(其它key条件 AND 连接) AND (key0条件 OR (key1条件 AND (key2条件 OR (NOT key3条件))))
这种方式连接,其中 "其它key" 是指与 @combine 在同一对象,且未被它声明的条件 key,默认都是 & 连接

② "@column":"column;function(arg)...",返回字段

③ "@order":"column0+,column1-...",排序方式

④ "@group":"column0,column1...",分组方式。如果@column里声明了Table的id,则id也必须在@group中声明;其它情况下必须满足至少一个条件:
1.分组的key在@column里声明
2.Table主键在@group中声明

⑤ "@having":"function0(...)?value0;function1(...)?value1;function2(...)?value2...",SQL函数条件,一般和@group一起用,函数一般在@column里声明

⑥ "@schema":"sys",集合空间(数据库名/模式),非默认的值可通过它来指定,可以在最外层作为全局默认配置

⑦ "@database":"POSTGRESQL",数据库类型,非默认的值可通过它来指定,可以在最外层作为全局默认配置

⑧ "@datasource":"DRUID",跨数据源,非默认的值可通过它来指定,可以在最外层作为全局默认配置

⑨ "@json":"key0,key1...",转为 JSON 格式返回,符合 JSONObject 则转为 {...},符合 JSONArray 则转为 \[...]

⑩ "@role":"OWNER",来访角色,包括
UNKNOWN,LOGIN,CONTACT,CIRCLE,OWNER,ADMIN,
可以在最外层作为全局默认配置,
可自定义其它角色并重写 Verifier.verify 等相关方法来自定义校验

⑪ "@explain":true,性能分析,可以在最外层作为全局默认配置

⑫ "@raw":"key0,key1...",其中 key0, key1 都对应有键值对
"key0":"SQL片段或SQL片段的别名",
"key1":"SQL片段或SQL片段的别名"
自定义原始SQL片段,可扩展嵌套SQL函数等复杂语句,必须是后端已配置的,只有其它功能符都做不到才考虑,谨慎使用,注意防SQL注入

⑬ "@otherKey":Object,自定义关键词,名称和以上系统关键词不一样,且原样返回上传的值 | ① 搜索name或tag任何一个字段包含字符a的User列表:
["name~":"a",
"tag~":"a",
"@combine":"name~ \| tag~"](http://apijson.cn:8080/get/{"User[]":{"count":10,"User":{"@column":"id,name,tag","name~":"a","tag~":"a","@combine":"name~%20%7C%20tag~"}}})
对应SQL是`name REGEXP 'a' OR tag REGEXP 'a'`

② 只查询id,sex,name这几列并且请求结果也按照这个顺序:
["@column":"id,sex,name"](http://apijson.cn:8080/get/{"User":{"@column":"id,sex,name","id":38710}})
对应SQL是`SELECT id,sex,name`

③ 查询按 name降序、id默认顺序 排序的User数组:
["@order":"name-,id"](http://apijson.cn:8080/get/{"[]":{"count":10,"User":{"@column":"name,id","@order":"name-,id"}}})
对应SQL是`ORDER BY name DESC,id`

④ 查询按userId分组的Moment数组:
["@group":"userId,id"](http://apijson.cn:8080/get/{"[]":{"count":10,"Moment":%7B"@column":"userId,id","@group":"userId,id"}}})
对应SQL是`GROUP BY userId,id`

⑤ 查询 按userId分组、id最大值>=100 的Moment数组:
["@column":"userId;max(id)",
"@group":"userId",
"@having":"max(id)>=100"](http://apijson.cn:8080/get/{"[]":{"count":10,"Moment":{"@column":"userId%253Bmax(id)","@group":"userId","@having":"max(id)>=100"}}})
对应SQL是`SELECT userId,max(id) ... GROUP BY userId HAVING max(id)>=100`
还可以指定函数返回名:
["@column":"userId;max(id):maxId",
"@group":"userId",
"@having":"(maxId)>=100"](http://apijson.cn:8080/get/{"[]":{"count":10,"Moment":{"@column":"userId%253Bmax(id):maxId","@group":"userId","@having":"(maxId)>=100"}}})
对应SQL是`SELECT userId,max(id) AS maxId ... GROUP BY userId HAVING maxId>=100`

⑥ 查询 sys 内的 User 表:
["@schema":"sys"](http://apijson.cn:8080/get/{"User":{"@schema":"sys"}})
对应SQL是`FROM sys.User`

⑦ 查询 PostgreSQL 数据库的 User 表:
["@database":"POSTGRESQL"](http://apijson.cn:8080/get/{"User":{"@database":"POSTGRESQL","@explain":true}})

⑧ 使用 Druid 连接池查询 User 表:
["@datasource":"DRUID"](http://apijson.cn:8080/get/{"User":{"@datasource":"DRUID"}})

⑨ 将 VARCHAR 字符串字段 get 转为 JSONArray 返回:
["@json":"get"](http://apijson.cn:8080/get/{"Access":{"@json":"get"}})

⑩ 查询当前用户的动态:
["@role":"OWNER"](http://apijson.cn:8080/get/{"[]":{"Moment":{"@role":"OWNER"}}})

⑪ 开启性能分析:
["@explain":true](http://apijson.cn:8080/get/{"[]":{"Moment":{"@explain":true}}})
对应SQL是`EXPLAIN`

⑫ 统计最近一周偶数userId的数量
["@column":"date;left(date,10):day;sum(if(userId%2=0,1,0))",
"@group":"day",
"@having":"to_days(now())-to_days(\`date\`)<=7",
"@raw":"@column,@having"](http://apijson.cn:8080/get/{"[]":{"Moment":{"@column":"date%3bleft(date,10):day%3bsum(if(userId%252=0,1,0))","@group":"day","@having":"to_days(now())-to_days(\`date\`)<=7","@raw":"@column,@having"}}})
对应SQL是``SELECT date, left(date,10) AS day, sum(if(userId%2=0,1,0)) ... GROUP BY day HAVING to_days(now())-to_days(`date`)<=7``

⑬ 从pictureList获取第0张图片:
["@position":0, //自定义关键词
"firstPicture()":"getFromArray(pictureList,@position)"](http://apijson.cn:8080/get/{"User":{"id":38710,"@position":0,"firstPicture()":"getFromArray(pictureList,@position)"}}) 全局关键词 | 为最外层对象 {} 内的关键词。其中 @database,@schema, @datasource, @role, @explain 基本同对象关键词,见上方说明,区别是全局关键词会每个表对象中没有时自动放入,作为默认值。

① "tag":String,后面的 tag 是非 GET、HEAD 请求中匹配请求的 JSON 结构的标识,一般是要查询的 Table 的名称或该名称对应的数组 Table[] 或 Table:[],由后端 Request 表中指定。

② "version":Integer,接口版本,version 不传、为 null 或 <=0 都会使用最高版本,传了其它有效值则会使用最接近它的最低版本,由后端 Request 表中指定。

③ "format":Boolean,格式化返回 Response JSON 的 key,一般是将 TableName 转为 tableName, TableName[] 转为 tableNameList, Table:alias 转为 alias, TableName-key[] 转为 tableNameKeyList 等小驼峰格式。 | ① 查隐私信息:
[{"tag":"Privacy","Privacy":{"id":82001}}](http://apijson.cn/api?url=http%3A%2F%2Fapijson.cn%3A8080%2Fgets&type=JSON&json={%22tag%22:%22Privacy%22,%22Privacy%22:{%22id%22:82001}})

② 使用第 1 版接口查隐私信息:
[{"version":1,"tag":"Privacy","Privacy":{"id":82001}}](http://apijson.cn/api?url=http%3A%2F%2Fapijson.cn%3A8080%2Fgets&type=JSON&json={%22version%22:1,%22tag%22:%22Privacy%22,%22Privacy%22:{%22id%22:82001}})

③ 格式化朋友圈接口返回 JSON 中的 key:
[{
   "format":true,
   "[]":{
     "page":0,
     "count":3,
     "Moment":{},
     "User":{
       "id@":"/Moment/userId"
     },
     "Comment[]":{
       "count":3,
       "Comment":{
         "momentId@":"[]/Moment/id"
       }
     }
   }
}](http://apijson.cn:8080/get/{"format":true,"[]":{"page":0,"count":3,"Moment":{},"User":{"id@":"%252FMoment%252FuserId"},"Comment[]":{"count":3,"Comment":{"momentId@":"[]%252FMoment%252Fid"}}}})
From a11e876026c6db8b71ca9f76d83232f3c21112d2 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Tue, 5 Apr 2022 17:35:06 +0800 Subject: [PATCH 0608/1181] =?UTF-8?q?=E9=80=9A=E7=94=A8=E6=96=87=E6=A1=A3?= =?UTF-8?q?=EF=BC=9A=E6=9B=B4=E6=96=B0=205.0=20=E6=96=B0=E5=A2=9E=E7=9A=84?= =?UTF-8?q?=20@having&:"...",=20@having:{...}=20=E4=B8=A4=E7=A7=8D?= =?UTF-8?q?=E7=94=A8=E6=B3=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Document.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Document.md b/Document.md index 12bac1aa6..4bcb6a319 100644 --- a/Document.md +++ b/Document.md @@ -418,6 +418,6 @@ DELETE:
删除数据 | base_url/delete/ | {
   TableName:{< 比较运算 | >, <, >=, <= 比较运算符,用于
① 提供 "id{}":"<=90000" 这种条件范围的简化写法

② 实现子查询相关比较运算

不支持 "key=":Object 和 "key!=":Object 这两种写法,直接用更简单的 "key":Object 和 "key!":Object 替代。 | ① ["id<=":90000](http://apijson.cn:8080/get/{"[]":{"User":{"id<=":90000}}}),对应SQL是`id<=90000`,查询符合id<=90000的一个User数组

② ["id>@":{
   "from":"Comment",
   "Comment":{
      "@column":"min(userId)"
   }
}](http://apijson.cn:8080/get/{"User":{"id>@":{"from":"Comment","Comment":{"@column":"min(userId)"}}}})
WHERE id>(SELECT min(userId) FROM Comment) 逻辑运算 | &, \|, ! 逻辑运算符,对应数据库 SQL 中的 AND, OR, NOT。
横或纵与:同一键值对的值内条件默认 \| 或连接,可以在 key 后加逻辑运算符来具体指定;不同键值对的条件默认 & 与连接,可以用下面说明的对象关键词 @combine 来具体指定。

① & 可用于"key&{}":"条件"等

② \| 可用于"key\|{}":"条件", "key\|{}":[]等,一般可省略

③ ! 可单独使用,如"key!":Object,也可像&,\|一样配合其他功能符使用
"key!":null 无效,null 值会导致整个键值对被忽略解析,可以用 "key{}":"!=null" 替代,
"key":null 同理,用 "key{}":"=null" 替代。 | ① ["id&{}":">80000,<=90000"](http://apijson.cn:8080/head/{"User":{"id&{}":">80000,<=90000"}}),对应SQL是`id>80000 AND id<=90000`,即id满足id>80000 & id<=90000

② ["id\|{}":">90000,<=80000"](http://apijson.cn:8080/head/{"User":{"id\|{}":">90000,<=80000"}}),同"id{}":">90000,<=80000",对应SQL是`id>80000 OR id<=90000`,即id满足id>90000 \| id<=80000

③ ["id!{}":[82001,38710]](http://apijson.cn:8080/head/{"User":{"id!{}":[82001,38710]}}),对应SQL是`id NOT IN(82001,38710)`,即id满足 ! (id=82001 \| id=38710),可过滤黑名单的消息 数组关键词,可自定义 | "key":Object,key为 "[]":{} 中{}内的关键词,Object的类型由key指定

① "count":Integer,查询数量,0 表示最大值,默认最大值为100

② "page":Integer,查询页码,从0开始,默认最大值为100,一般和count一起用

③ "query":Integer,查询内容
0-对象,1-总数和分页详情,2-数据、总数和分页详情
总数关键词为 total,分页详情关键词为 info,
它们都和 query 同级,通过引用赋值得到,例如
"total@":"/[]/total", "info@":"/[]/info"
这里query及total仅为GET类型的请求提供方便,
一般可直接用HEAD类型的请求获取总数

④ "join":"&/Table0/key0@,\多表连接方式:
"\<" - LEFT JOIN
">" - RIGHT JOIN
"&" - INNER JOIN
"\|" - FULL JOIN
"!" - OUTER JOIN
"@" - APP JOIN
其中 @ APP JOIN 为应用层连表,会从已查出的主表里取得所有副表 key@ 关联的主表内的 refKey 作为一个数组 refKeys: [value0, value1...],然后把原来副表 count 次查询 key=$refKey 的 SQL 用 key IN($refKeys) 的方式合并为一条 SQL 来优化性能;
其它 JOIN 都是 SQL JOIN,具体功能和 MySQL,PostgreSQL 等数据库的 JOIN 一一对应
`"join":"`"MainTable":{},`
`"ViceTable":{"key@":"/MainTable/refKey"}`
会对应生成
`MainTable LEFT JOIN ViceTable`
`ON ViceTable.key=MainTable.refKey`

⑤ "otherKey":Object,自定义关键词,名称和以上系统关键词不一样,且原样返回上传的值 | ① 查询User数组,最多5个:
["count":5](http://apijson.cn:8080/get/{"[]":{"count":5,"User":{}}})
对应SQL是`LIMIT 5`

② 查询第3页的User数组,每页5个:
["count":5,
"page":3](http://apijson.cn:8080/get/{"[]":{"count":5,"page":3,"User":{}}})
对应SQL是`LIMIT 5 OFFSET 15`

③ 查询User数组和对应的User总数:
["[]":{
   "query":2,
   "User":{}
},
"total@":"/[]/total",
"info@":"/[]/info"](http://apijson.cn:8080/get/{"[]":{"query":2,"count":5,"User":{}},"total@":"%252F[]%252Ftotal","info@":"%252F[]%252Finfo"})
返回的数据中,总数及分页详情结构为:
"total":139, //总数
"info":{ //分页详情
   "total":139, //总数
   "count":5, //每页数量
   "page":0, //当前页码
   "max":27, //最大页码
   "more":true, //是否还有更多
   "first":true, //是否为首页
   "last":false //是否为尾页
}

④ Moment INNER JOIN User LEFT JOIN Comment:
["[]":{
   "join":"&/User/id@,\    "Moment":{
     "@group":"id" //主副表不是一对一,要去除重复数据
   },
   "User":{
     "name~":"t",
     "id@":"/Moment/userId"
   },
   "Comment":{
     "momentId@":"/Moment/id"
   }
}](http://apijson.cn:8080/get/{"[]":{"count":5,"join":"&%252FUser%252Fid@,\<%252FComment%252FmomentId@","Moment":{"@column":"id,userId,content","@group":"id"},"User":{"name~":"t","id@":"%252FMoment%252FuserId","@column":"id,name,head"},"Comment":{"momentId@":"%252FMoment%252Fid","@column":"id,momentId,content"}}})

⑤ 每一层都加当前用户名:
["User":{},
"[]":{
   "name@":"User/name", //自定义关键词
   "Moment":{}
}](http://apijson.cn:8080/get/{"User":{},"[]":{"name@":"User%252Fname","Moment":{}}}) - 对象关键词,可自定义 | "@key":Object,@key为 Table:{} 中{}内的关键词,Object的类型由@key指定

① "@combine":"key0 \| (key1 & (key2 \| !key3))...",条件组合方式,最终按
(其它key条件 AND 连接) AND (key0条件 OR (key1条件 AND (key2条件 OR (NOT key3条件))))
这种方式连接,其中 "其它key" 是指与 @combine 在同一对象,且未被它声明的条件 key,默认都是 & 连接

② "@column":"column;function(arg)...",返回字段

③ "@order":"column0+,column1-...",排序方式

④ "@group":"column0,column1...",分组方式。如果@column里声明了Table的id,则id也必须在@group中声明;其它情况下必须满足至少一个条件:
1.分组的key在@column里声明
2.Table主键在@group中声明

⑤ "@having":"function0(...)?value0;function1(...)?value1;function2(...)?value2...",SQL函数条件,一般和@group一起用,函数一般在@column里声明

⑥ "@schema":"sys",集合空间(数据库名/模式),非默认的值可通过它来指定,可以在最外层作为全局默认配置

⑦ "@database":"POSTGRESQL",数据库类型,非默认的值可通过它来指定,可以在最外层作为全局默认配置

⑧ "@datasource":"DRUID",跨数据源,非默认的值可通过它来指定,可以在最外层作为全局默认配置

⑨ "@json":"key0,key1...",转为 JSON 格式返回,符合 JSONObject 则转为 {...},符合 JSONArray 则转为 \[...]

⑩ "@role":"OWNER",来访角色,包括
UNKNOWN,LOGIN,CONTACT,CIRCLE,OWNER,ADMIN,
可以在最外层作为全局默认配置,
可自定义其它角色并重写 Verifier.verify 等相关方法来自定义校验

⑪ "@explain":true,性能分析,可以在最外层作为全局默认配置

⑫ "@raw":"key0,key1...",其中 key0, key1 都对应有键值对
"key0":"SQL片段或SQL片段的别名",
"key1":"SQL片段或SQL片段的别名"
自定义原始SQL片段,可扩展嵌套SQL函数等复杂语句,必须是后端已配置的,只有其它功能符都做不到才考虑,谨慎使用,注意防SQL注入

⑬ "@otherKey":Object,自定义关键词,名称和以上系统关键词不一样,且原样返回上传的值 | ① 搜索name或tag任何一个字段包含字符a的User列表:
["name~":"a",
"tag~":"a",
"@combine":"name~ \| tag~"](http://apijson.cn:8080/get/{"User[]":{"count":10,"User":{"@column":"id,name,tag","name~":"a","tag~":"a","@combine":"name~%20%7C%20tag~"}}})
对应SQL是`name REGEXP 'a' OR tag REGEXP 'a'`

② 只查询id,sex,name这几列并且请求结果也按照这个顺序:
["@column":"id,sex,name"](http://apijson.cn:8080/get/{"User":{"@column":"id,sex,name","id":38710}})
对应SQL是`SELECT id,sex,name`

③ 查询按 name降序、id默认顺序 排序的User数组:
["@order":"name-,id"](http://apijson.cn:8080/get/{"[]":{"count":10,"User":{"@column":"name,id","@order":"name-,id"}}})
对应SQL是`ORDER BY name DESC,id`

④ 查询按userId分组的Moment数组:
["@group":"userId,id"](http://apijson.cn:8080/get/{"[]":{"count":10,"Moment":%7B"@column":"userId,id","@group":"userId,id"}}})
对应SQL是`GROUP BY userId,id`

⑤ 查询 按userId分组、id最大值>=100 的Moment数组:
["@column":"userId;max(id)",
"@group":"userId",
"@having":"max(id)>=100"](http://apijson.cn:8080/get/{"[]":{"count":10,"Moment":{"@column":"userId%253Bmax(id)","@group":"userId","@having":"max(id)>=100"}}})
对应SQL是`SELECT userId,max(id) ... GROUP BY userId HAVING max(id)>=100`
还可以指定函数返回名:
["@column":"userId;max(id):maxId",
"@group":"userId",
"@having":"(maxId)>=100"](http://apijson.cn:8080/get/{"[]":{"count":10,"Moment":{"@column":"userId%253Bmax(id):maxId","@group":"userId","@having":"(maxId)>=100"}}})
对应SQL是`SELECT userId,max(id) AS maxId ... GROUP BY userId HAVING maxId>=100`

⑥ 查询 sys 内的 User 表:
["@schema":"sys"](http://apijson.cn:8080/get/{"User":{"@schema":"sys"}})
对应SQL是`FROM sys.User`

⑦ 查询 PostgreSQL 数据库的 User 表:
["@database":"POSTGRESQL"](http://apijson.cn:8080/get/{"User":{"@database":"POSTGRESQL","@explain":true}})

⑧ 使用 Druid 连接池查询 User 表:
["@datasource":"DRUID"](http://apijson.cn:8080/get/{"User":{"@datasource":"DRUID"}})

⑨ 将 VARCHAR 字符串字段 get 转为 JSONArray 返回:
["@json":"get"](http://apijson.cn:8080/get/{"Access":{"@json":"get"}})

⑩ 查询当前用户的动态:
["@role":"OWNER"](http://apijson.cn:8080/get/{"[]":{"Moment":{"@role":"OWNER"}}})

⑪ 开启性能分析:
["@explain":true](http://apijson.cn:8080/get/{"[]":{"Moment":{"@explain":true}}})
对应SQL是`EXPLAIN`

⑫ 统计最近一周偶数userId的数量
["@column":"date;left(date,10):day;sum(if(userId%2=0,1,0))",
"@group":"day",
"@having":"to_days(now())-to_days(\`date\`)<=7",
"@raw":"@column,@having"](http://apijson.cn:8080/get/{"[]":{"Moment":{"@column":"date%3bleft(date,10):day%3bsum(if(userId%252=0,1,0))","@group":"day","@having":"to_days(now())-to_days(\`date\`)<=7","@raw":"@column,@having"}}})
对应SQL是``SELECT date, left(date,10) AS day, sum(if(userId%2=0,1,0)) ... GROUP BY day HAVING to_days(now())-to_days(`date`)<=7``

⑬ 从pictureList获取第0张图片:
["@position":0, //自定义关键词
"firstPicture()":"getFromArray(pictureList,@position)"](http://apijson.cn:8080/get/{"User":{"id":38710,"@position":0,"firstPicture()":"getFromArray(pictureList,@position)"}}) + 对象关键词,可自定义 | "@key":Object,@key为 Table:{} 中{}内的关键词,Object的类型由@key指定

① "@combine":"key0 \| (key1 & (key2 \| !key3))...",条件组合方式,最终按
(其它key条件 AND 连接) AND (key0条件 OR (key1条件 AND (key2条件 OR (NOT key3条件))))
这种方式连接,其中 "其它key" 是指与 @combine 在同一对象,且未被它声明的条件 key,默认都是 & 连接

② "@column":"column;function(arg)...",返回字段

③ "@order":"column0+,column1-...",排序方式

④ "@group":"column0,column1...",分组方式。如果@column里声明了Table的id,则id也必须在@group中声明;其它情况下必须满足至少一个条件:
1.分组的key在@column里声明
2.Table主键在@group中声明

⑤ "@having":"function0(...)?value0;function1(...)?value1;function2(...)?value2..." // OR 连接,或
"@having&":"function0(...)?value0;function1(...)?value1;function2(...)?value2..." // AND 连接,或
"@having":{
   "h0":"function0(...)?value0",
   "h1":function1(...)?value1",
   "h2":function2(...)?value2...",
   "@combine":"h0 & (h1 \| !h2)" // 任意组合,非必传
}
SQL函数条件,一般和@group一起用,函数一般在@column里声明

⑥ "@schema":"sys",集合空间(数据库名/模式),非默认的值可通过它来指定,可以在最外层作为全局默认配置

⑦ "@database":"POSTGRESQL",数据库类型,非默认的值可通过它来指定,可以在最外层作为全局默认配置

⑧ "@datasource":"DRUID",跨数据源,非默认的值可通过它来指定,可以在最外层作为全局默认配置

⑨ "@json":"key0,key1...",转为 JSON 格式返回,符合 JSONObject 则转为 {...},符合 JSONArray 则转为 \[...]

⑩ "@role":"OWNER",来访角色,包括
UNKNOWN,LOGIN,CONTACT,CIRCLE,OWNER,ADMIN,
可以在最外层作为全局默认配置,
可自定义其它角色并重写 Verifier.verify 等相关方法来自定义校验

⑪ "@explain":true,性能分析,可以在最外层作为全局默认配置

⑫ "@raw":"key0,key1...",其中 key0, key1 都对应有键值对
"key0":"SQL片段或SQL片段的别名",
"key1":"SQL片段或SQL片段的别名"
自定义原始SQL片段,可扩展嵌套SQL函数等复杂语句,必须是后端已配置的,只有其它功能符都做不到才考虑,谨慎使用,注意防SQL注入

⑬ "@otherKey":Object,自定义关键词,名称和以上系统关键词不一样,且原样返回上传的值 | ① 搜索name或tag任何一个字段包含字符a的User列表:
["name~":"a",
"tag~":"a",
"@combine":"name~ \| tag~"](http://apijson.cn:8080/get/{"User[]":{"count":10,"User":{"@column":"id,name,tag","name~":"a","tag~":"a","@combine":"name~%20%7C%20tag~"}}})
对应SQL是`name REGEXP 'a' OR tag REGEXP 'a'`

② 只查询id,sex,name这几列并且请求结果也按照这个顺序:
["@column":"id,sex,name"](http://apijson.cn:8080/get/{"User":{"@column":"id,sex,name","id":38710}})
对应SQL是`SELECT id,sex,name`

③ 查询按 name降序、id默认顺序 排序的User数组:
["@order":"name-,id"](http://apijson.cn:8080/get/{"[]":{"count":10,"User":{"@column":"name,id","@order":"name-,id"}}})
对应SQL是`ORDER BY name DESC,id`

④ 查询按userId分组的Moment数组:
["@group":"userId,id"](http://apijson.cn:8080/get/{"[]":{"count":10,"Moment":%7B"@column":"userId,id","@group":"userId,id"}}})
对应SQL是`GROUP BY userId,id`

⑤ 查询 按userId分组、id最大值>=100 的Moment数组:
["@column":"userId;max(id)",
"@group":"userId",
"@having":"max(id)>=100"](http://apijson.cn:8080/get/{"[]":{"count":10,"Moment":{"@column":"userId%253Bmax(id)","@group":"userId","@having":"max(id)>=100"}}})
对应SQL是`SELECT userId,max(id) ... GROUP BY userId HAVING max(id)>=100`
还可以指定函数返回名:
["@column":"userId;max(id):maxId",
"@group":"userId",
"@having":"(maxId)>=100"](http://apijson.cn:8080/get/{"[]":{"count":10,"Moment":{"@column":"userId%253Bmax(id):maxId","@group":"userId","@having":"(maxId)>=100"}}})
对应SQL是`SELECT userId,max(id) AS maxId ... GROUP BY userId HAVING (maxId)>=100`

⑥ 查询 sys 内的 User 表:
["@schema":"sys"](http://apijson.cn:8080/get/{"User":{"@schema":"sys"}})
对应SQL是`FROM sys.User`

⑦ 查询 PostgreSQL 数据库的 User 表:
["@database":"POSTGRESQL"](http://apijson.cn:8080/get/{"User":{"@database":"POSTGRESQL","@explain":true}})

⑧ 使用 Druid 连接池查询 User 表:
["@datasource":"DRUID"](http://apijson.cn:8080/get/{"User":{"@datasource":"DRUID"}})

⑨ 将 VARCHAR 字符串字段 get 转为 JSONArray 返回:
["@json":"get"](http://apijson.cn:8080/get/{"Access":{"@json":"get"}})

⑩ 查询当前用户的动态:
["@role":"OWNER"](http://apijson.cn:8080/get/{"[]":{"Moment":{"@role":"OWNER"}}})

⑪ 开启性能分析:
["@explain":true](http://apijson.cn:8080/get/{"[]":{"Moment":{"@explain":true}}})
对应SQL是`EXPLAIN`

⑫ 统计最近一周偶数userId的数量
["@column":"date;left(date,10):day;sum(if(userId%2=0,1,0))",
"@group":"day",
"@having":"to_days(now())-to_days(\`date\`)<=7",
"@raw":"@column,@having"](http://apijson.cn:8080/get/{"[]":{"Moment":{"@column":"date%3bleft(date,10):day%3bsum(if(userId%252=0,1,0))","@group":"day","@having":"to_days(now())-to_days(\`date\`)<=7","@raw":"@column,@having"}}})
对应SQL是``SELECT date, left(date,10) AS day, sum(if(userId%2=0,1,0)) ... GROUP BY day HAVING to_days(now())-to_days(`date`)<=7``

⑬ 从pictureList获取第0张图片:
["@position":0, //自定义关键词
"firstPicture()":"getFromArray(pictureList,@position)"](http://apijson.cn:8080/get/{"User":{"id":38710,"@position":0,"firstPicture()":"getFromArray(pictureList,@position)"}}) 全局关键词 | 为最外层对象 {} 内的关键词。其中 @database,@schema, @datasource, @role, @explain 基本同对象关键词,见上方说明,区别是全局关键词会每个表对象中没有时自动放入,作为默认值。

① "tag":String,后面的 tag 是非 GET、HEAD 请求中匹配请求的 JSON 结构的标识,一般是要查询的 Table 的名称或该名称对应的数组 Table[] 或 Table:[],由后端 Request 表中指定。

② "version":Integer,接口版本,version 不传、为 null 或 <=0 都会使用最高版本,传了其它有效值则会使用最接近它的最低版本,由后端 Request 表中指定。

③ "format":Boolean,格式化返回 Response JSON 的 key,一般是将 TableName 转为 tableName, TableName[] 转为 tableNameList, Table:alias 转为 alias, TableName-key[] 转为 tableNameKeyList 等小驼峰格式。 | ① 查隐私信息:
[{"tag":"Privacy","Privacy":{"id":82001}}](http://apijson.cn/api?url=http%3A%2F%2Fapijson.cn%3A8080%2Fgets&type=JSON&json={%22tag%22:%22Privacy%22,%22Privacy%22:{%22id%22:82001}})

② 使用第 1 版接口查隐私信息:
[{"version":1,"tag":"Privacy","Privacy":{"id":82001}}](http://apijson.cn/api?url=http%3A%2F%2Fapijson.cn%3A8080%2Fgets&type=JSON&json={%22version%22:1,%22tag%22:%22Privacy%22,%22Privacy%22:{%22id%22:82001}})

③ 格式化朋友圈接口返回 JSON 中的 key:
[{
   "format":true,
   "[]":{
     "page":0,
     "count":3,
     "Moment":{},
     "User":{
       "id@":"/Moment/userId"
     },
     "Comment[]":{
       "count":3,
       "Comment":{
         "momentId@":"[]/Moment/id"
       }
     }
   }
}](http://apijson.cn:8080/get/{"format":true,"[]":{"page":0,"count":3,"Moment":{},"User":{"id@":"%252FMoment%252FuserId"},"Comment[]":{"count":3,"Comment":{"momentId@":"[]%252FMoment%252Fid"}}}})
From b22b6d96aa92b6c49ed539fdc64d5cd0774831f6 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Tue, 5 Apr 2022 17:49:37 +0800 Subject: [PATCH 0609/1181] =?UTF-8?q?=E9=80=9A=E7=94=A8=E6=96=87=E6=A1=A3?= =?UTF-8?q?=EF=BC=9A=E5=AE=8C=E5=96=84=20JOIN=20=E7=9A=84=E7=B1=BB?= =?UTF-8?q?=E5=9E=8B=E4=BB=A5=E5=8F=8A=20join:{...}=20=E8=BF=99=E7=A7=8D?= =?UTF-8?q?=E5=8F=AF=E5=B8=A6=20ON=20=E5=8F=8A=E5=8A=9F=E8=83=BD=E7=AC=A6?= =?UTF-8?q?=E7=9A=84=E5=86=99=E6=B3=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Document.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Document.md b/Document.md index 4bcb6a319..1c8094150 100644 --- a/Document.md +++ b/Document.md @@ -417,7 +417,7 @@ DELETE:
删除数据 | base_url/delete/ | {
   TableName:{< 减少 或 去除 | "key-":Object,与"key+"相反 | "balance-":100.00,对应SQL是`balance = balance - 100.00`,余额减少100.00,即花费了100元 比较运算 | >, <, >=, <= 比较运算符,用于
① 提供 "id{}":"<=90000" 这种条件范围的简化写法

② 实现子查询相关比较运算

不支持 "key=":Object 和 "key!=":Object 这两种写法,直接用更简单的 "key":Object 和 "key!":Object 替代。 | ① ["id<=":90000](http://apijson.cn:8080/get/{"[]":{"User":{"id<=":90000}}}),对应SQL是`id<=90000`,查询符合id<=90000的一个User数组

② ["id>@":{
   "from":"Comment",
   "Comment":{
      "@column":"min(userId)"
   }
}](http://apijson.cn:8080/get/{"User":{"id>@":{"from":"Comment","Comment":{"@column":"min(userId)"}}}})
WHERE id>(SELECT min(userId) FROM Comment) 逻辑运算 | &, \|, ! 逻辑运算符,对应数据库 SQL 中的 AND, OR, NOT。
横或纵与:同一键值对的值内条件默认 \| 或连接,可以在 key 后加逻辑运算符来具体指定;不同键值对的条件默认 & 与连接,可以用下面说明的对象关键词 @combine 来具体指定。

① & 可用于"key&{}":"条件"等

② \| 可用于"key\|{}":"条件", "key\|{}":[]等,一般可省略

③ ! 可单独使用,如"key!":Object,也可像&,\|一样配合其他功能符使用
"key!":null 无效,null 值会导致整个键值对被忽略解析,可以用 "key{}":"!=null" 替代,
"key":null 同理,用 "key{}":"=null" 替代。 | ① ["id&{}":">80000,<=90000"](http://apijson.cn:8080/head/{"User":{"id&{}":">80000,<=90000"}}),对应SQL是`id>80000 AND id<=90000`,即id满足id>80000 & id<=90000

② ["id\|{}":">90000,<=80000"](http://apijson.cn:8080/head/{"User":{"id\|{}":">90000,<=80000"}}),同"id{}":">90000,<=80000",对应SQL是`id>80000 OR id<=90000`,即id满足id>90000 \| id<=80000

③ ["id!{}":[82001,38710]](http://apijson.cn:8080/head/{"User":{"id!{}":[82001,38710]}}),对应SQL是`id NOT IN(82001,38710)`,即id满足 ! (id=82001 \| id=38710),可过滤黑名单的消息 - 数组关键词,可自定义 | "key":Object,key为 "[]":{} 中{}内的关键词,Object的类型由key指定

① "count":Integer,查询数量,0 表示最大值,默认最大值为100

② "page":Integer,查询页码,从0开始,默认最大值为100,一般和count一起用

③ "query":Integer,查询内容
0-对象,1-总数和分页详情,2-数据、总数和分页详情
总数关键词为 total,分页详情关键词为 info,
它们都和 query 同级,通过引用赋值得到,例如
"total@":"/[]/total", "info@":"/[]/info"
这里query及total仅为GET类型的请求提供方便,
一般可直接用HEAD类型的请求获取总数

④ "join":"&/Table0/key0@,\多表连接方式:
"\<" - LEFT JOIN
">" - RIGHT JOIN
"&" - INNER JOIN
"\|" - FULL JOIN
"!" - OUTER JOIN
"@" - APP JOIN
其中 @ APP JOIN 为应用层连表,会从已查出的主表里取得所有副表 key@ 关联的主表内的 refKey 作为一个数组 refKeys: [value0, value1...],然后把原来副表 count 次查询 key=$refKey 的 SQL 用 key IN($refKeys) 的方式合并为一条 SQL 来优化性能;
其它 JOIN 都是 SQL JOIN,具体功能和 MySQL,PostgreSQL 等数据库的 JOIN 一一对应
`"join":"`"MainTable":{},`
`"ViceTable":{"key@":"/MainTable/refKey"}`
会对应生成
`MainTable LEFT JOIN ViceTable`
`ON ViceTable.key=MainTable.refKey`

⑤ "otherKey":Object,自定义关键词,名称和以上系统关键词不一样,且原样返回上传的值 | ① 查询User数组,最多5个:
["count":5](http://apijson.cn:8080/get/{"[]":{"count":5,"User":{}}})
对应SQL是`LIMIT 5`

② 查询第3页的User数组,每页5个:
["count":5,
"page":3](http://apijson.cn:8080/get/{"[]":{"count":5,"page":3,"User":{}}})
对应SQL是`LIMIT 5 OFFSET 15`

③ 查询User数组和对应的User总数:
["[]":{
   "query":2,
   "User":{}
},
"total@":"/[]/total",
"info@":"/[]/info"](http://apijson.cn:8080/get/{"[]":{"query":2,"count":5,"User":{}},"total@":"%252F[]%252Ftotal","info@":"%252F[]%252Finfo"})
返回的数据中,总数及分页详情结构为:
"total":139, //总数
"info":{ //分页详情
   "total":139, //总数
   "count":5, //每页数量
   "page":0, //当前页码
   "max":27, //最大页码
   "more":true, //是否还有更多
   "first":true, //是否为首页
   "last":false //是否为尾页
}

④ Moment INNER JOIN User LEFT JOIN Comment:
["[]":{
   "join":"&/User/id@,\    "Moment":{
     "@group":"id" //主副表不是一对一,要去除重复数据
   },
   "User":{
     "name~":"t",
     "id@":"/Moment/userId"
   },
   "Comment":{
     "momentId@":"/Moment/id"
   }
}](http://apijson.cn:8080/get/{"[]":{"count":5,"join":"&%252FUser%252Fid@,\<%252FComment%252FmomentId@","Moment":{"@column":"id,userId,content","@group":"id"},"User":{"name~":"t","id@":"%252FMoment%252FuserId","@column":"id,name,head"},"Comment":{"momentId@":"%252FMoment%252Fid","@column":"id,momentId,content"}}})

⑤ 每一层都加当前用户名:
["User":{},
"[]":{
   "name@":"User/name", //自定义关键词
   "Moment":{}
}](http://apijson.cn:8080/get/{"User":{},"[]":{"name@":"User%252Fname","Moment":{}}}) - 对象关键词,可自定义 | "@key":Object,@key为 Table:{} 中{}内的关键词,Object的类型由@key指定

① "@combine":"key0 \| (key1 & (key2 \| !key3))...",条件组合方式,最终按
(其它key条件 AND 连接) AND (key0条件 OR (key1条件 AND (key2条件 OR (NOT key3条件))))
这种方式连接,其中 "其它key" 是指与 @combine 在同一对象,且未被它声明的条件 key,默认都是 & 连接

② "@column":"column;function(arg)...",返回字段

③ "@order":"column0+,column1-...",排序方式

④ "@group":"column0,column1...",分组方式。如果@column里声明了Table的id,则id也必须在@group中声明;其它情况下必须满足至少一个条件:
1.分组的key在@column里声明
2.Table主键在@group中声明

⑤ "@having":"function0(...)?value0;function1(...)?value1;function2(...)?value2..." // OR 连接,或
"@having&":"function0(...)?value0;function1(...)?value1;function2(...)?value2..." // AND 连接,或
"@having":{
   "h0":"function0(...)?value0",
   "h1":function1(...)?value1",
   "h2":function2(...)?value2...",
   "@combine":"h0 & (h1 \| !h2)" // 任意组合,非必传
}
SQL函数条件,一般和@group一起用,函数一般在@column里声明

⑥ "@schema":"sys",集合空间(数据库名/模式),非默认的值可通过它来指定,可以在最外层作为全局默认配置

⑦ "@database":"POSTGRESQL",数据库类型,非默认的值可通过它来指定,可以在最外层作为全局默认配置

⑧ "@datasource":"DRUID",跨数据源,非默认的值可通过它来指定,可以在最外层作为全局默认配置

⑨ "@json":"key0,key1...",转为 JSON 格式返回,符合 JSONObject 则转为 {...},符合 JSONArray 则转为 \[...]

⑩ "@role":"OWNER",来访角色,包括
UNKNOWN,LOGIN,CONTACT,CIRCLE,OWNER,ADMIN,
可以在最外层作为全局默认配置,
可自定义其它角色并重写 Verifier.verify 等相关方法来自定义校验

⑪ "@explain":true,性能分析,可以在最外层作为全局默认配置

⑫ "@raw":"key0,key1...",其中 key0, key1 都对应有键值对
"key0":"SQL片段或SQL片段的别名",
"key1":"SQL片段或SQL片段的别名"
自定义原始SQL片段,可扩展嵌套SQL函数等复杂语句,必须是后端已配置的,只有其它功能符都做不到才考虑,谨慎使用,注意防SQL注入

⑬ "@otherKey":Object,自定义关键词,名称和以上系统关键词不一样,且原样返回上传的值 | ① 搜索name或tag任何一个字段包含字符a的User列表:
["name~":"a",
"tag~":"a",
"@combine":"name~ \| tag~"](http://apijson.cn:8080/get/{"User[]":{"count":10,"User":{"@column":"id,name,tag","name~":"a","tag~":"a","@combine":"name~%20%7C%20tag~"}}})
对应SQL是`name REGEXP 'a' OR tag REGEXP 'a'`

② 只查询id,sex,name这几列并且请求结果也按照这个顺序:
["@column":"id,sex,name"](http://apijson.cn:8080/get/{"User":{"@column":"id,sex,name","id":38710}})
对应SQL是`SELECT id,sex,name`

③ 查询按 name降序、id默认顺序 排序的User数组:
["@order":"name-,id"](http://apijson.cn:8080/get/{"[]":{"count":10,"User":{"@column":"name,id","@order":"name-,id"}}})
对应SQL是`ORDER BY name DESC,id`

④ 查询按userId分组的Moment数组:
["@group":"userId,id"](http://apijson.cn:8080/get/{"[]":{"count":10,"Moment":%7B"@column":"userId,id","@group":"userId,id"}}})
对应SQL是`GROUP BY userId,id`

⑤ 查询 按userId分组、id最大值>=100 的Moment数组:
["@column":"userId;max(id)",
"@group":"userId",
"@having":"max(id)>=100"](http://apijson.cn:8080/get/{"[]":{"count":10,"Moment":{"@column":"userId%253Bmax(id)","@group":"userId","@having":"max(id)>=100"}}})
对应SQL是`SELECT userId,max(id) ... GROUP BY userId HAVING max(id)>=100`
还可以指定函数返回名:
["@column":"userId;max(id):maxId",
"@group":"userId",
"@having":"(maxId)>=100"](http://apijson.cn:8080/get/{"[]":{"count":10,"Moment":{"@column":"userId%253Bmax(id):maxId","@group":"userId","@having":"(maxId)>=100"}}})
对应SQL是`SELECT userId,max(id) AS maxId ... GROUP BY userId HAVING (maxId)>=100`

⑥ 查询 sys 内的 User 表:
["@schema":"sys"](http://apijson.cn:8080/get/{"User":{"@schema":"sys"}})
对应SQL是`FROM sys.User`

⑦ 查询 PostgreSQL 数据库的 User 表:
["@database":"POSTGRESQL"](http://apijson.cn:8080/get/{"User":{"@database":"POSTGRESQL","@explain":true}})

⑧ 使用 Druid 连接池查询 User 表:
["@datasource":"DRUID"](http://apijson.cn:8080/get/{"User":{"@datasource":"DRUID"}})

⑨ 将 VARCHAR 字符串字段 get 转为 JSONArray 返回:
["@json":"get"](http://apijson.cn:8080/get/{"Access":{"@json":"get"}})

⑩ 查询当前用户的动态:
["@role":"OWNER"](http://apijson.cn:8080/get/{"[]":{"Moment":{"@role":"OWNER"}}})

⑪ 开启性能分析:
["@explain":true](http://apijson.cn:8080/get/{"[]":{"Moment":{"@explain":true}}})
对应SQL是`EXPLAIN`

⑫ 统计最近一周偶数userId的数量
["@column":"date;left(date,10):day;sum(if(userId%2=0,1,0))",
"@group":"day",
"@having":"to_days(now())-to_days(\`date\`)<=7",
"@raw":"@column,@having"](http://apijson.cn:8080/get/{"[]":{"Moment":{"@column":"date%3bleft(date,10):day%3bsum(if(userId%252=0,1,0))","@group":"day","@having":"to_days(now())-to_days(\`date\`)<=7","@raw":"@column,@having"}}})
对应SQL是``SELECT date, left(date,10) AS day, sum(if(userId%2=0,1,0)) ... GROUP BY day HAVING to_days(now())-to_days(`date`)<=7``

⑬ 从pictureList获取第0张图片:
["@position":0, //自定义关键词
"firstPicture()":"getFromArray(pictureList,@position)"](http://apijson.cn:8080/get/{"User":{"id":38710,"@position":0,"firstPicture()":"getFromArray(pictureList,@position)"}}) + 数组关键词,可自定义 | "key":Object,key为 "[]":{} 中{}内的关键词,Object的类型由key指定

① "count":Integer,查询数量,0 表示最大值,默认最大值为100

② "page":Integer,查询页码,从0开始,默认最大值为100,一般和count一起用

③ "query":Integer,查询内容
0-对象,1-总数和分页详情,2-数据、总数和分页详情
总数关键词为 total,分页详情关键词为 info,
它们都和 query 同级,通过引用赋值得到,例如
"total@":"/[]/total", "info@":"/[]/info"
这里query及total仅为GET类型的请求提供方便,
一般可直接用HEAD类型的请求获取总数

④ "join":"&/Table0/key0@,\"join":{
   "&/Table0/key0@":{},
   "\      "key0":value0, // ON 条件
     "key1":value1,
     ...
     "@combine":"...", // ON 条件组合
     "@column":"...", // 外层 SELECT
     "@group":"...", // 外层 GROUP BY
     "@having":"..." // 外层 HAVING
   }
}
多表连接方式:
"@" - APP JOIN
"\<" - LEFT JOIN
">" - RIGHT JOIN
"&" - INNER JOIN
"\|" - FULL JOIN
"!" - OUTER JOIN
"*" - CROSS JOIN
"^" - SIDE JOIN
"(" - ANTI JOIN
")" - FOREIGN JOIN
其中 @ APP JOIN 为应用层连表,会从已查出的主表里取得所有副表 key@ 关联的主表内的 refKey 作为一个数组 refKeys: [value0, value1...],然后把原来副表 count 次查询 key=$refKey 的 SQL 用 key IN($refKeys) 的方式合并为一条 SQL 来优化性能;
其它 JOIN 都是 SQL JOIN,具体功能和 MySQL,PostgreSQL 等数据库的 JOIN 一一对应
`"join":"`"MainTable":{},`
`"ViceTable":{"key@":"/MainTable/refKey"}`
会对应生成
`MainTable LEFT JOIN ViceTable`
`ON ViceTable.key=MainTable.refKey`

⑤ "otherKey":Object,自定义关键词,名称和以上系统关键词不一样,且原样返回上传的值 | ① 查询User数组,最多5个:
["count":5](http://apijson.cn:8080/get/{"[]":{"count":5,"User":{}}})
对应SQL是`LIMIT 5`

② 查询第3页的User数组,每页5个:
["count":5,
"page":3](http://apijson.cn:8080/get/{"[]":{"count":5,"page":3,"User":{}}})
对应SQL是`LIMIT 5 OFFSET 15`

③ 查询User数组和对应的User总数:
["[]":{
   "query":2,
   "User":{}
},
"total@":"/[]/total",
"info@":"/[]/info"](http://apijson.cn:8080/get/{"[]":{"query":2,"count":5,"User":{}},"total@":"%252F[]%252Ftotal","info@":"%252F[]%252Finfo"})
返回的数据中,总数及分页详情结构为:
"total":139, //总数
"info":{ //分页详情
   "total":139, //总数
   "count":5, //每页数量
   "page":0, //当前页码
   "max":27, //最大页码
   "more":true, //是否还有更多
   "first":true, //是否为首页
   "last":false //是否为尾页
}

④ Moment INNER JOIN User LEFT JOIN Comment:
["[]":{
   "join":"&/User/id@,\    "Moment":{
     "@group":"id" //主副表不是一对一,要去除重复数据
   },
   "User":{
     "name~":"t",
     "id@":"/Moment/userId"
   },
   "Comment":{
     "momentId@":"/Moment/id"
   }
}](http://apijson.cn:8080/get/{"[]":{"count":5,"join":"&%252FUser%252Fid@,\<%252FComment%252FmomentId@","Moment":{"@column":"id,userId,content","@group":"id"},"User":{"name~":"t","id@":"%252FMoment%252FuserId","@column":"id,name,head"},"Comment":{"momentId@":"%252FMoment%252Fid","@column":"id,momentId,content"}}})

⑤ 每一层都加当前用户名:
["User":{},
"[]":{
   "name@":"User/name", //自定义关键词
   "Moment":{}
}](http://apijson.cn:8080/get/{"User":{},"[]":{"name@":"User%252Fname","Moment":{}}}) + 对象关键词,可自定义 | "@key":Object,@key为 Table:{} 中{}内的关键词,Object的类型由@key指定

① "@combine":"key0 \| (key1 & (key2 \| !key3))...",条件组合方式,最终按
(其它key条件 AND 连接) AND (key0条件 OR (key1条件 AND (key2条件 OR (NOT key3条件))))
这种方式连接,其中 "其它key" 是指与 @combine 在同一对象,且未被它声明的条件 key,默认都是 & 连接。注意不要缺少或多余任何一个空格。

② "@column":"column;function(arg)...",返回字段

③ "@order":"column0+,column1-...",排序方式

④ "@group":"column0,column1...",分组方式。如果@column里声明了Table的id,则id也必须在@group中声明;其它情况下必须满足至少一个条件:
1.分组的key在@column里声明
2.Table主键在@group中声明

⑤ "@having":"function0(...)?value0;function1(...)?value1;function2(...)?value2..." // OR 连接,或
"@having&":"function0(...)?value0;function1(...)?value1;function2(...)?value2..." // AND 连接,或
"@having":{
   "h0":"function0(...)?value0",
   "h1":function1(...)?value1",
   "h2":function2(...)?value2...",
   "@combine":"h0 & (h1 \| !h2)" // 任意组合,非必传
}
SQL函数条件,一般和@group一起用,函数一般在@column里声明

⑥ "@schema":"sys",集合空间(数据库名/模式),非默认的值可通过它来指定,可以在最外层作为全局默认配置

⑦ "@database":"POSTGRESQL",数据库类型,非默认的值可通过它来指定,可以在最外层作为全局默认配置

⑧ "@datasource":"DRUID",跨数据源,非默认的值可通过它来指定,可以在最外层作为全局默认配置

⑨ "@json":"key0,key1...",转为 JSON 格式返回,符合 JSONObject 则转为 {...},符合 JSONArray 则转为 \[...]

⑩ "@role":"OWNER",来访角色,包括
UNKNOWN,LOGIN,CONTACT,CIRCLE,OWNER,ADMIN,
可以在最外层作为全局默认配置,
可自定义其它角色并重写 Verifier.verify 等相关方法来自定义校验

⑪ "@explain":true,性能分析,可以在最外层作为全局默认配置

⑫ "@raw":"key0,key1...",其中 key0, key1 都对应有键值对
"key0":"SQL片段或SQL片段的别名",
"key1":"SQL片段或SQL片段的别名"
自定义原始SQL片段,可扩展嵌套SQL函数等复杂语句,必须是后端已配置的,只有其它功能符都做不到才考虑,谨慎使用,注意防SQL注入

⑬ "@otherKey":Object,自定义关键词,名称和以上系统关键词不一样,且原样返回上传的值 | ① 搜索name或tag任何一个字段包含字符a的User列表:
["name~":"a",
"tag~":"a",
"@combine":"name~ \| tag~"](http://apijson.cn:8080/get/{"User[]":{"count":10,"User":{"@column":"id,name,tag","name~":"a","tag~":"a","@combine":"name~%20%7C%20tag~"}}})
对应SQL是`name REGEXP 'a' OR tag REGEXP 'a'`

② 只查询id,sex,name这几列并且请求结果也按照这个顺序:
["@column":"id,sex,name"](http://apijson.cn:8080/get/{"User":{"@column":"id,sex,name","id":38710}})
对应SQL是`SELECT id,sex,name`

③ 查询按 name降序、id默认顺序 排序的User数组:
["@order":"name-,id"](http://apijson.cn:8080/get/{"[]":{"count":10,"User":{"@column":"name,id","@order":"name-,id"}}})
对应SQL是`ORDER BY name DESC,id`

④ 查询按userId分组的Moment数组:
["@group":"userId,id"](http://apijson.cn:8080/get/{"[]":{"count":10,"Moment":%7B"@column":"userId,id","@group":"userId,id"}}})
对应SQL是`GROUP BY userId,id`

⑤ 查询 按userId分组、id最大值>=100 的Moment数组:
["@column":"userId;max(id)",
"@group":"userId",
"@having":"max(id)>=100"](http://apijson.cn:8080/get/{"[]":{"count":10,"Moment":{"@column":"userId%253Bmax(id)","@group":"userId","@having":"max(id)>=100"}}})
对应SQL是`SELECT userId,max(id) ... GROUP BY userId HAVING max(id)>=100`
还可以指定函数返回名:
["@column":"userId;max(id):maxId",
"@group":"userId",
"@having":"(maxId)>=100"](http://apijson.cn:8080/get/{"[]":{"count":10,"Moment":{"@column":"userId%253Bmax(id):maxId","@group":"userId","@having":"(maxId)>=100"}}})
对应SQL是`SELECT userId,max(id) AS maxId ... GROUP BY userId HAVING (maxId)>=100`

⑥ 查询 sys 内的 User 表:
["@schema":"sys"](http://apijson.cn:8080/get/{"User":{"@schema":"sys"}})
对应SQL是`FROM sys.User`

⑦ 查询 PostgreSQL 数据库的 User 表:
["@database":"POSTGRESQL"](http://apijson.cn:8080/get/{"User":{"@database":"POSTGRESQL","@explain":true}})

⑧ 使用 Druid 连接池查询 User 表:
["@datasource":"DRUID"](http://apijson.cn:8080/get/{"User":{"@datasource":"DRUID"}})

⑨ 将 VARCHAR 字符串字段 get 转为 JSONArray 返回:
["@json":"get"](http://apijson.cn:8080/get/{"Access":{"@json":"get"}})

⑩ 查询当前用户的动态:
["@role":"OWNER"](http://apijson.cn:8080/get/{"[]":{"Moment":{"@role":"OWNER"}}})

⑪ 开启性能分析:
["@explain":true](http://apijson.cn:8080/get/{"[]":{"Moment":{"@explain":true}}})
对应SQL是`EXPLAIN`

⑫ 统计最近一周偶数userId的数量
["@column":"date;left(date,10):day;sum(if(userId%2=0,1,0))",
"@group":"day",
"@having":"to_days(now())-to_days(\`date\`)<=7",
"@raw":"@column,@having"](http://apijson.cn:8080/get/{"[]":{"Moment":{"@column":"date%3bleft(date,10):day%3bsum(if(userId%252=0,1,0))","@group":"day","@having":"to_days(now())-to_days(\`date\`)<=7","@raw":"@column,@having"}}})
对应SQL是``SELECT date, left(date,10) AS day, sum(if(userId%2=0,1,0)) ... GROUP BY day HAVING to_days(now())-to_days(`date`)<=7``

⑬ 从pictureList获取第0张图片:
["@position":0, //自定义关键词
"firstPicture()":"getFromArray(pictureList,@position)"](http://apijson.cn:8080/get/{"User":{"id":38710,"@position":0,"firstPicture()":"getFromArray(pictureList,@position)"}}) 全局关键词 | 为最外层对象 {} 内的关键词。其中 @database,@schema, @datasource, @role, @explain 基本同对象关键词,见上方说明,区别是全局关键词会每个表对象中没有时自动放入,作为默认值。

① "tag":String,后面的 tag 是非 GET、HEAD 请求中匹配请求的 JSON 结构的标识,一般是要查询的 Table 的名称或该名称对应的数组 Table[] 或 Table:[],由后端 Request 表中指定。

② "version":Integer,接口版本,version 不传、为 null 或 <=0 都会使用最高版本,传了其它有效值则会使用最接近它的最低版本,由后端 Request 表中指定。

③ "format":Boolean,格式化返回 Response JSON 的 key,一般是将 TableName 转为 tableName, TableName[] 转为 tableNameList, Table:alias 转为 alias, TableName-key[] 转为 tableNameKeyList 等小驼峰格式。 | ① 查隐私信息:
[{"tag":"Privacy","Privacy":{"id":82001}}](http://apijson.cn/api?url=http%3A%2F%2Fapijson.cn%3A8080%2Fgets&type=JSON&json={%22tag%22:%22Privacy%22,%22Privacy%22:{%22id%22:82001}})

② 使用第 1 版接口查隐私信息:
[{"version":1,"tag":"Privacy","Privacy":{"id":82001}}](http://apijson.cn/api?url=http%3A%2F%2Fapijson.cn%3A8080%2Fgets&type=JSON&json={%22version%22:1,%22tag%22:%22Privacy%22,%22Privacy%22:{%22id%22:82001}})

③ 格式化朋友圈接口返回 JSON 中的 key:
[{
   "format":true,
   "[]":{
     "page":0,
     "count":3,
     "Moment":{},
     "User":{
       "id@":"/Moment/userId"
     },
     "Comment[]":{
       "count":3,
       "Comment":{
         "momentId@":"[]/Moment/id"
       }
     }
   }
}](http://apijson.cn:8080/get/{"format":true,"[]":{"page":0,"count":3,"Moment":{},"User":{"id@":"%252FMoment%252FuserId"},"Comment[]":{"count":3,"Comment":{"momentId@":"[]%252FMoment%252Fid"}}}})
From 8401596d3ebe598707694004aff895f7634a2c99 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Tue, 5 Apr 2022 18:00:05 +0800 Subject: [PATCH 0610/1181] =?UTF-8?q?=E9=80=9A=E7=94=A8=E6=96=87=E6=A1=A3?= =?UTF-8?q?=EF=BC=9A=E5=AE=8C=E5=96=84=20JOIN=20ON=20=E7=9A=84=E5=90=84?= =?UTF-8?q?=E7=A7=8D=E5=85=B3=E8=81=94=E6=96=B9=E5=BC=8F=E3=80=81=E6=94=AF?= =?UTF-8?q?=E6=8C=81=E5=A4=9A=E5=AD=97=E6=AE=B5=E5=85=B3=E8=81=94=E3=80=81?= =?UTF-8?q?=E6=94=AF=E6=8C=81=E5=85=B6=E5=AE=83=E6=9D=A1=E4=BB=B6=E7=9A=84?= =?UTF-8?q?=E8=AF=B4=E6=98=8E?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Document.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Document.md b/Document.md index 1c8094150..4dcf02437 100644 --- a/Document.md +++ b/Document.md @@ -417,7 +417,7 @@ DELETE:
删除数据 | base_url/delete/ | {
   TableName:{< 减少 或 去除 | "key-":Object,与"key+"相反 | "balance-":100.00,对应SQL是`balance = balance - 100.00`,余额减少100.00,即花费了100元 比较运算 | >, <, >=, <= 比较运算符,用于
① 提供 "id{}":"<=90000" 这种条件范围的简化写法

② 实现子查询相关比较运算

不支持 "key=":Object 和 "key!=":Object 这两种写法,直接用更简单的 "key":Object 和 "key!":Object 替代。 | ① ["id<=":90000](http://apijson.cn:8080/get/{"[]":{"User":{"id<=":90000}}}),对应SQL是`id<=90000`,查询符合id<=90000的一个User数组

② ["id>@":{
   "from":"Comment",
   "Comment":{
      "@column":"min(userId)"
   }
}](http://apijson.cn:8080/get/{"User":{"id>@":{"from":"Comment","Comment":{"@column":"min(userId)"}}}})
WHERE id>(SELECT min(userId) FROM Comment) 逻辑运算 | &, \|, ! 逻辑运算符,对应数据库 SQL 中的 AND, OR, NOT。
横或纵与:同一键值对的值内条件默认 \| 或连接,可以在 key 后加逻辑运算符来具体指定;不同键值对的条件默认 & 与连接,可以用下面说明的对象关键词 @combine 来具体指定。

① & 可用于"key&{}":"条件"等

② \| 可用于"key\|{}":"条件", "key\|{}":[]等,一般可省略

③ ! 可单独使用,如"key!":Object,也可像&,\|一样配合其他功能符使用
"key!":null 无效,null 值会导致整个键值对被忽略解析,可以用 "key{}":"!=null" 替代,
"key":null 同理,用 "key{}":"=null" 替代。 | ① ["id&{}":">80000,<=90000"](http://apijson.cn:8080/head/{"User":{"id&{}":">80000,<=90000"}}),对应SQL是`id>80000 AND id<=90000`,即id满足id>80000 & id<=90000

② ["id\|{}":">90000,<=80000"](http://apijson.cn:8080/head/{"User":{"id\|{}":">90000,<=80000"}}),同"id{}":">90000,<=80000",对应SQL是`id>80000 OR id<=90000`,即id满足id>90000 \| id<=80000

③ ["id!{}":[82001,38710]](http://apijson.cn:8080/head/{"User":{"id!{}":[82001,38710]}}),对应SQL是`id NOT IN(82001,38710)`,即id满足 ! (id=82001 \| id=38710),可过滤黑名单的消息 - 数组关键词,可自定义 | "key":Object,key为 "[]":{} 中{}内的关键词,Object的类型由key指定

① "count":Integer,查询数量,0 表示最大值,默认最大值为100

② "page":Integer,查询页码,从0开始,默认最大值为100,一般和count一起用

③ "query":Integer,查询内容
0-对象,1-总数和分页详情,2-数据、总数和分页详情
总数关键词为 total,分页详情关键词为 info,
它们都和 query 同级,通过引用赋值得到,例如
"total@":"/[]/total", "info@":"/[]/info"
这里query及total仅为GET类型的请求提供方便,
一般可直接用HEAD类型的请求获取总数

④ "join":"&/Table0/key0@,\"join":{
   "&/Table0/key0@":{},
   "\      "key0":value0, // ON 条件
     "key1":value1,
     ...
     "@combine":"...", // ON 条件组合
     "@column":"...", // 外层 SELECT
     "@group":"...", // 外层 GROUP BY
     "@having":"..." // 外层 HAVING
   }
}
多表连接方式:
"@" - APP JOIN
"\<" - LEFT JOIN
">" - RIGHT JOIN
"&" - INNER JOIN
"\|" - FULL JOIN
"!" - OUTER JOIN
"*" - CROSS JOIN
"^" - SIDE JOIN
"(" - ANTI JOIN
")" - FOREIGN JOIN
其中 @ APP JOIN 为应用层连表,会从已查出的主表里取得所有副表 key@ 关联的主表内的 refKey 作为一个数组 refKeys: [value0, value1...],然后把原来副表 count 次查询 key=$refKey 的 SQL 用 key IN($refKeys) 的方式合并为一条 SQL 来优化性能;
其它 JOIN 都是 SQL JOIN,具体功能和 MySQL,PostgreSQL 等数据库的 JOIN 一一对应
`"join":"`"MainTable":{},`
`"ViceTable":{"key@":"/MainTable/refKey"}`
会对应生成
`MainTable LEFT JOIN ViceTable`
`ON ViceTable.key=MainTable.refKey`

⑤ "otherKey":Object,自定义关键词,名称和以上系统关键词不一样,且原样返回上传的值 | ① 查询User数组,最多5个:
["count":5](http://apijson.cn:8080/get/{"[]":{"count":5,"User":{}}})
对应SQL是`LIMIT 5`

② 查询第3页的User数组,每页5个:
["count":5,
"page":3](http://apijson.cn:8080/get/{"[]":{"count":5,"page":3,"User":{}}})
对应SQL是`LIMIT 5 OFFSET 15`

③ 查询User数组和对应的User总数:
["[]":{
   "query":2,
   "User":{}
},
"total@":"/[]/total",
"info@":"/[]/info"](http://apijson.cn:8080/get/{"[]":{"query":2,"count":5,"User":{}},"total@":"%252F[]%252Ftotal","info@":"%252F[]%252Finfo"})
返回的数据中,总数及分页详情结构为:
"total":139, //总数
"info":{ //分页详情
   "total":139, //总数
   "count":5, //每页数量
   "page":0, //当前页码
   "max":27, //最大页码
   "more":true, //是否还有更多
   "first":true, //是否为首页
   "last":false //是否为尾页
}

④ Moment INNER JOIN User LEFT JOIN Comment:
["[]":{
   "join":"&/User/id@,\    "Moment":{
     "@group":"id" //主副表不是一对一,要去除重复数据
   },
   "User":{
     "name~":"t",
     "id@":"/Moment/userId"
   },
   "Comment":{
     "momentId@":"/Moment/id"
   }
}](http://apijson.cn:8080/get/{"[]":{"count":5,"join":"&%252FUser%252Fid@,\<%252FComment%252FmomentId@","Moment":{"@column":"id,userId,content","@group":"id"},"User":{"name~":"t","id@":"%252FMoment%252FuserId","@column":"id,name,head"},"Comment":{"momentId@":"%252FMoment%252Fid","@column":"id,momentId,content"}}})

⑤ 每一层都加当前用户名:
["User":{},
"[]":{
   "name@":"User/name", //自定义关键词
   "Moment":{}
}](http://apijson.cn:8080/get/{"User":{},"[]":{"name@":"User%252Fname","Moment":{}}}) + 数组关键词,可自定义 | "key":Object,key为 "[]":{} 中{}内的关键词,Object的类型由key指定

① "count":Integer,查询数量,0 表示最大值,默认最大值为100

② "page":Integer,查询页码,从0开始,默认最大值为100,一般和count一起用

③ "query":Integer,查询内容
0-对象,1-总数和分页详情,2-数据、总数和分页详情
总数关键词为 total,分页详情关键词为 info,
它们都和 query 同级,通过引用赋值得到,例如
"total@":"/[]/total", "info@":"/[]/info"
这里query及total仅为GET类型的请求提供方便,
一般可直接用HEAD类型的请求获取总数

④ "join":"&/Table0@,\"join":{
   "&/Table0@":{} // 支持 ON 多个字段关联,
   "\      "key0":value0, // 其它ON条件
     "key1":value1,
     ...
     "@combine":"...", // 其它ON条件的组合方式
     "@column":"...", // 外层 SELECT
     "@group":"...", // 外层 GROUP BY
     "@having":"..." // 外层 HAVING
   }
}
多表连接方式:
"@" - APP JOIN
"\<" - LEFT JOIN
">" - RIGHT JOIN
"&" - INNER JOIN
"\|" - FULL JOIN
"!" - OUTER JOIN
"*" - CROSS JOIN
"^" - SIDE JOIN
"(" - ANTI JOIN
")" - FOREIGN JOIN
其中 @ APP JOIN 为应用层连表,会从已查出的主表里取得所有副表 key@ 关联的主表内的 refKey 作为一个数组 refKeys: [value0, value1...],然后把原来副表 count 次查询 key=$refKey 的 SQL 用 key IN($refKeys) 的方式合并为一条 SQL 来优化性能;
其它 JOIN 都是 SQL JOIN,具体功能和 MySQL,PostgreSQL 等数据库的 JOIN 一一对应
`"join":"`"MainTable":{},`
`"ViceTable":{"key@":"/MainTable/refKey"}`
会对应生成
`MainTable LEFT JOIN ViceTable`
`ON ViceTable.key=MainTable.refKey` AND 其它ON条件
除了 = 等价关联,也支持 ! 不等关联、\> \< \>= \<= 等比较关联和 $ ~ {} <> 等其它复杂关联方式

⑤ "otherKey":Object,自定义关键词,名称和以上系统关键词不一样,且原样返回上传的值 | ① 查询User数组,最多5个:
["count":5](http://apijson.cn:8080/get/{"[]":{"count":5,"User":{}}})
对应SQL是`LIMIT 5`

② 查询第3页的User数组,每页5个:
["count":5,
"page":3](http://apijson.cn:8080/get/{"[]":{"count":5,"page":3,"User":{}}})
对应SQL是`LIMIT 5 OFFSET 15`

③ 查询User数组和对应的User总数:
["[]":{
   "query":2,
   "User":{}
},
"total@":"/[]/total",
"info@":"/[]/info"](http://apijson.cn:8080/get/{"[]":{"query":2,"count":5,"User":{}},"total@":"%252F[]%252Ftotal","info@":"%252F[]%252Finfo"})
返回的数据中,总数及分页详情结构为:
"total":139, //总数
"info":{ //分页详情
   "total":139, //总数
   "count":5, //每页数量
   "page":0, //当前页码
   "max":27, //最大页码
   "more":true, //是否还有更多
   "first":true, //是否为首页
   "last":false //是否为尾页
}

④ Moment INNER JOIN User LEFT JOIN Comment:
["[]":{
   "join":"&/User/id@,\    "Moment":{
     "@group":"id" //主副表不是一对一,要去除重复数据
   },
   "User":{
     "name~":"t",
     "id@":"/Moment/userId"
   },
   "Comment":{
     "momentId@":"/Moment/id"
   }
}](http://apijson.cn:8080/get/{"[]":{"count":5,"join":"&%252FUser%252Fid@,\<%252FComment%252FmomentId@","Moment":{"@column":"id,userId,content","@group":"id"},"User":{"name~":"t","id@":"%252FMoment%252FuserId","@column":"id,name,head"},"Comment":{"momentId@":"%252FMoment%252Fid","@column":"id,momentId,content"}}})

⑤ 每一层都加当前用户名:
["User":{},
"[]":{
   "name@":"User/name", //自定义关键词
   "Moment":{}
}](http://apijson.cn:8080/get/{"User":{},"[]":{"name@":"User%252Fname","Moment":{}}}) 对象关键词,可自定义 | "@key":Object,@key为 Table:{} 中{}内的关键词,Object的类型由@key指定

① "@combine":"key0 \| (key1 & (key2 \| !key3))...",条件组合方式,最终按
(其它key条件 AND 连接) AND (key0条件 OR (key1条件 AND (key2条件 OR (NOT key3条件))))
这种方式连接,其中 "其它key" 是指与 @combine 在同一对象,且未被它声明的条件 key,默认都是 & 连接。注意不要缺少或多余任何一个空格。

② "@column":"column;function(arg)...",返回字段

③ "@order":"column0+,column1-...",排序方式

④ "@group":"column0,column1...",分组方式。如果@column里声明了Table的id,则id也必须在@group中声明;其它情况下必须满足至少一个条件:
1.分组的key在@column里声明
2.Table主键在@group中声明

⑤ "@having":"function0(...)?value0;function1(...)?value1;function2(...)?value2..." // OR 连接,或
"@having&":"function0(...)?value0;function1(...)?value1;function2(...)?value2..." // AND 连接,或
"@having":{
   "h0":"function0(...)?value0",
   "h1":function1(...)?value1",
   "h2":function2(...)?value2...",
   "@combine":"h0 & (h1 \| !h2)" // 任意组合,非必传
}
SQL函数条件,一般和@group一起用,函数一般在@column里声明

⑥ "@schema":"sys",集合空间(数据库名/模式),非默认的值可通过它来指定,可以在最外层作为全局默认配置

⑦ "@database":"POSTGRESQL",数据库类型,非默认的值可通过它来指定,可以在最外层作为全局默认配置

⑧ "@datasource":"DRUID",跨数据源,非默认的值可通过它来指定,可以在最外层作为全局默认配置

⑨ "@json":"key0,key1...",转为 JSON 格式返回,符合 JSONObject 则转为 {...},符合 JSONArray 则转为 \[...]

⑩ "@role":"OWNER",来访角色,包括
UNKNOWN,LOGIN,CONTACT,CIRCLE,OWNER,ADMIN,
可以在最外层作为全局默认配置,
可自定义其它角色并重写 Verifier.verify 等相关方法来自定义校验

⑪ "@explain":true,性能分析,可以在最外层作为全局默认配置

⑫ "@raw":"key0,key1...",其中 key0, key1 都对应有键值对
"key0":"SQL片段或SQL片段的别名",
"key1":"SQL片段或SQL片段的别名"
自定义原始SQL片段,可扩展嵌套SQL函数等复杂语句,必须是后端已配置的,只有其它功能符都做不到才考虑,谨慎使用,注意防SQL注入

⑬ "@otherKey":Object,自定义关键词,名称和以上系统关键词不一样,且原样返回上传的值 | ① 搜索name或tag任何一个字段包含字符a的User列表:
["name~":"a",
"tag~":"a",
"@combine":"name~ \| tag~"](http://apijson.cn:8080/get/{"User[]":{"count":10,"User":{"@column":"id,name,tag","name~":"a","tag~":"a","@combine":"name~%20%7C%20tag~"}}})
对应SQL是`name REGEXP 'a' OR tag REGEXP 'a'`

② 只查询id,sex,name这几列并且请求结果也按照这个顺序:
["@column":"id,sex,name"](http://apijson.cn:8080/get/{"User":{"@column":"id,sex,name","id":38710}})
对应SQL是`SELECT id,sex,name`

③ 查询按 name降序、id默认顺序 排序的User数组:
["@order":"name-,id"](http://apijson.cn:8080/get/{"[]":{"count":10,"User":{"@column":"name,id","@order":"name-,id"}}})
对应SQL是`ORDER BY name DESC,id`

④ 查询按userId分组的Moment数组:
["@group":"userId,id"](http://apijson.cn:8080/get/{"[]":{"count":10,"Moment":%7B"@column":"userId,id","@group":"userId,id"}}})
对应SQL是`GROUP BY userId,id`

⑤ 查询 按userId分组、id最大值>=100 的Moment数组:
["@column":"userId;max(id)",
"@group":"userId",
"@having":"max(id)>=100"](http://apijson.cn:8080/get/{"[]":{"count":10,"Moment":{"@column":"userId%253Bmax(id)","@group":"userId","@having":"max(id)>=100"}}})
对应SQL是`SELECT userId,max(id) ... GROUP BY userId HAVING max(id)>=100`
还可以指定函数返回名:
["@column":"userId;max(id):maxId",
"@group":"userId",
"@having":"(maxId)>=100"](http://apijson.cn:8080/get/{"[]":{"count":10,"Moment":{"@column":"userId%253Bmax(id):maxId","@group":"userId","@having":"(maxId)>=100"}}})
对应SQL是`SELECT userId,max(id) AS maxId ... GROUP BY userId HAVING (maxId)>=100`

⑥ 查询 sys 内的 User 表:
["@schema":"sys"](http://apijson.cn:8080/get/{"User":{"@schema":"sys"}})
对应SQL是`FROM sys.User`

⑦ 查询 PostgreSQL 数据库的 User 表:
["@database":"POSTGRESQL"](http://apijson.cn:8080/get/{"User":{"@database":"POSTGRESQL","@explain":true}})

⑧ 使用 Druid 连接池查询 User 表:
["@datasource":"DRUID"](http://apijson.cn:8080/get/{"User":{"@datasource":"DRUID"}})

⑨ 将 VARCHAR 字符串字段 get 转为 JSONArray 返回:
["@json":"get"](http://apijson.cn:8080/get/{"Access":{"@json":"get"}})

⑩ 查询当前用户的动态:
["@role":"OWNER"](http://apijson.cn:8080/get/{"[]":{"Moment":{"@role":"OWNER"}}})

⑪ 开启性能分析:
["@explain":true](http://apijson.cn:8080/get/{"[]":{"Moment":{"@explain":true}}})
对应SQL是`EXPLAIN`

⑫ 统计最近一周偶数userId的数量
["@column":"date;left(date,10):day;sum(if(userId%2=0,1,0))",
"@group":"day",
"@having":"to_days(now())-to_days(\`date\`)<=7",
"@raw":"@column,@having"](http://apijson.cn:8080/get/{"[]":{"Moment":{"@column":"date%3bleft(date,10):day%3bsum(if(userId%252=0,1,0))","@group":"day","@having":"to_days(now())-to_days(\`date\`)<=7","@raw":"@column,@having"}}})
对应SQL是``SELECT date, left(date,10) AS day, sum(if(userId%2=0,1,0)) ... GROUP BY day HAVING to_days(now())-to_days(`date`)<=7``

⑬ 从pictureList获取第0张图片:
["@position":0, //自定义关键词
"firstPicture()":"getFromArray(pictureList,@position)"](http://apijson.cn:8080/get/{"User":{"id":38710,"@position":0,"firstPicture()":"getFromArray(pictureList,@position)"}}) 全局关键词 | 为最外层对象 {} 内的关键词。其中 @database,@schema, @datasource, @role, @explain 基本同对象关键词,见上方说明,区别是全局关键词会每个表对象中没有时自动放入,作为默认值。

① "tag":String,后面的 tag 是非 GET、HEAD 请求中匹配请求的 JSON 结构的标识,一般是要查询的 Table 的名称或该名称对应的数组 Table[] 或 Table:[],由后端 Request 表中指定。

② "version":Integer,接口版本,version 不传、为 null 或 <=0 都会使用最高版本,传了其它有效值则会使用最接近它的最低版本,由后端 Request 表中指定。

③ "format":Boolean,格式化返回 Response JSON 的 key,一般是将 TableName 转为 tableName, TableName[] 转为 tableNameList, Table:alias 转为 alias, TableName-key[] 转为 tableNameKeyList 等小驼峰格式。 | ① 查隐私信息:
[{"tag":"Privacy","Privacy":{"id":82001}}](http://apijson.cn/api?url=http%3A%2F%2Fapijson.cn%3A8080%2Fgets&type=JSON&json={%22tag%22:%22Privacy%22,%22Privacy%22:{%22id%22:82001}})

② 使用第 1 版接口查隐私信息:
[{"version":1,"tag":"Privacy","Privacy":{"id":82001}}](http://apijson.cn/api?url=http%3A%2F%2Fapijson.cn%3A8080%2Fgets&type=JSON&json={%22version%22:1,%22tag%22:%22Privacy%22,%22Privacy%22:{%22id%22:82001}})

③ 格式化朋友圈接口返回 JSON 中的 key:
[{
   "format":true,
   "[]":{
     "page":0,
     "count":3,
     "Moment":{},
     "User":{
       "id@":"/Moment/userId"
     },
     "Comment[]":{
       "count":3,
       "Comment":{
         "momentId@":"[]/Moment/id"
       }
     }
   }
}](http://apijson.cn:8080/get/{"format":true,"[]":{"page":0,"count":3,"Moment":{},"User":{"id@":"%252FMoment%252FuserId"},"Comment[]":{"count":3,"Comment":{"momentId@":"[]%252FMoment%252Fid"}}}})
From 85ef5af251b1c1fbc3ec8a7d906315c138ba3da9 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Tue, 5 Apr 2022 18:14:28 +0800 Subject: [PATCH 0611/1181] Update Document.md --- Document.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Document.md b/Document.md index 4dcf02437..1c65d3039 100644 --- a/Document.md +++ b/Document.md @@ -417,7 +417,7 @@ DELETE:
删除数据 | base_url/delete/ | {
   TableName:{< 减少 或 去除 | "key-":Object,与"key+"相反 | "balance-":100.00,对应SQL是`balance = balance - 100.00`,余额减少100.00,即花费了100元 比较运算 | >, <, >=, <= 比较运算符,用于
① 提供 "id{}":"<=90000" 这种条件范围的简化写法

② 实现子查询相关比较运算

不支持 "key=":Object 和 "key!=":Object 这两种写法,直接用更简单的 "key":Object 和 "key!":Object 替代。 | ① ["id<=":90000](http://apijson.cn:8080/get/{"[]":{"User":{"id<=":90000}}}),对应SQL是`id<=90000`,查询符合id<=90000的一个User数组

② ["id>@":{
   "from":"Comment",
   "Comment":{
      "@column":"min(userId)"
   }
}](http://apijson.cn:8080/get/{"User":{"id>@":{"from":"Comment","Comment":{"@column":"min(userId)"}}}})
WHERE id>(SELECT min(userId) FROM Comment) 逻辑运算 | &, \|, ! 逻辑运算符,对应数据库 SQL 中的 AND, OR, NOT。
横或纵与:同一键值对的值内条件默认 \| 或连接,可以在 key 后加逻辑运算符来具体指定;不同键值对的条件默认 & 与连接,可以用下面说明的对象关键词 @combine 来具体指定。

① & 可用于"key&{}":"条件"等

② \| 可用于"key\|{}":"条件", "key\|{}":[]等,一般可省略

③ ! 可单独使用,如"key!":Object,也可像&,\|一样配合其他功能符使用
"key!":null 无效,null 值会导致整个键值对被忽略解析,可以用 "key{}":"!=null" 替代,
"key":null 同理,用 "key{}":"=null" 替代。 | ① ["id&{}":">80000,<=90000"](http://apijson.cn:8080/head/{"User":{"id&{}":">80000,<=90000"}}),对应SQL是`id>80000 AND id<=90000`,即id满足id>80000 & id<=90000

② ["id\|{}":">90000,<=80000"](http://apijson.cn:8080/head/{"User":{"id\|{}":">90000,<=80000"}}),同"id{}":">90000,<=80000",对应SQL是`id>80000 OR id<=90000`,即id满足id>90000 \| id<=80000

③ ["id!{}":[82001,38710]](http://apijson.cn:8080/head/{"User":{"id!{}":[82001,38710]}}),对应SQL是`id NOT IN(82001,38710)`,即id满足 ! (id=82001 \| id=38710),可过滤黑名单的消息 - 数组关键词,可自定义 | "key":Object,key为 "[]":{} 中{}内的关键词,Object的类型由key指定

① "count":Integer,查询数量,0 表示最大值,默认最大值为100

② "page":Integer,查询页码,从0开始,默认最大值为100,一般和count一起用

③ "query":Integer,查询内容
0-对象,1-总数和分页详情,2-数据、总数和分页详情
总数关键词为 total,分页详情关键词为 info,
它们都和 query 同级,通过引用赋值得到,例如
"total@":"/[]/total", "info@":"/[]/info"
这里query及total仅为GET类型的请求提供方便,
一般可直接用HEAD类型的请求获取总数

④ "join":"&/Table0@,\"join":{
   "&/Table0@":{} // 支持 ON 多个字段关联,
   "\      "key0":value0, // 其它ON条件
     "key1":value1,
     ...
     "@combine":"...", // 其它ON条件的组合方式
     "@column":"...", // 外层 SELECT
     "@group":"...", // 外层 GROUP BY
     "@having":"..." // 外层 HAVING
   }
}
多表连接方式:
"@" - APP JOIN
"\<" - LEFT JOIN
">" - RIGHT JOIN
"&" - INNER JOIN
"\|" - FULL JOIN
"!" - OUTER JOIN
"*" - CROSS JOIN
"^" - SIDE JOIN
"(" - ANTI JOIN
")" - FOREIGN JOIN
其中 @ APP JOIN 为应用层连表,会从已查出的主表里取得所有副表 key@ 关联的主表内的 refKey 作为一个数组 refKeys: [value0, value1...],然后把原来副表 count 次查询 key=$refKey 的 SQL 用 key IN($refKeys) 的方式合并为一条 SQL 来优化性能;
其它 JOIN 都是 SQL JOIN,具体功能和 MySQL,PostgreSQL 等数据库的 JOIN 一一对应
`"join":"`"MainTable":{},`
`"ViceTable":{"key@":"/MainTable/refKey"}`
会对应生成
`MainTable LEFT JOIN ViceTable`
`ON ViceTable.key=MainTable.refKey` AND 其它ON条件
除了 = 等价关联,也支持 ! 不等关联、\> \< \>= \<= 等比较关联和 $ ~ {} <> 等其它复杂关联方式

⑤ "otherKey":Object,自定义关键词,名称和以上系统关键词不一样,且原样返回上传的值 | ① 查询User数组,最多5个:
["count":5](http://apijson.cn:8080/get/{"[]":{"count":5,"User":{}}})
对应SQL是`LIMIT 5`

② 查询第3页的User数组,每页5个:
["count":5,
"page":3](http://apijson.cn:8080/get/{"[]":{"count":5,"page":3,"User":{}}})
对应SQL是`LIMIT 5 OFFSET 15`

③ 查询User数组和对应的User总数:
["[]":{
   "query":2,
   "User":{}
},
"total@":"/[]/total",
"info@":"/[]/info"](http://apijson.cn:8080/get/{"[]":{"query":2,"count":5,"User":{}},"total@":"%252F[]%252Ftotal","info@":"%252F[]%252Finfo"})
返回的数据中,总数及分页详情结构为:
"total":139, //总数
"info":{ //分页详情
   "total":139, //总数
   "count":5, //每页数量
   "page":0, //当前页码
   "max":27, //最大页码
   "more":true, //是否还有更多
   "first":true, //是否为首页
   "last":false //是否为尾页
}

④ Moment INNER JOIN User LEFT JOIN Comment:
["[]":{
   "join":"&/User/id@,\    "Moment":{
     "@group":"id" //主副表不是一对一,要去除重复数据
   },
   "User":{
     "name~":"t",
     "id@":"/Moment/userId"
   },
   "Comment":{
     "momentId@":"/Moment/id"
   }
}](http://apijson.cn:8080/get/{"[]":{"count":5,"join":"&%252FUser%252Fid@,\<%252FComment%252FmomentId@","Moment":{"@column":"id,userId,content","@group":"id"},"User":{"name~":"t","id@":"%252FMoment%252FuserId","@column":"id,name,head"},"Comment":{"momentId@":"%252FMoment%252Fid","@column":"id,momentId,content"}}})

⑤ 每一层都加当前用户名:
["User":{},
"[]":{
   "name@":"User/name", //自定义关键词
   "Moment":{}
}](http://apijson.cn:8080/get/{"User":{},"[]":{"name@":"User%252Fname","Moment":{}}}) + 数组关键词,可自定义 | "key":Object,key为 "[]":{} 中{}内的关键词,Object的类型由key指定

① "count":Integer,查询数量,0 表示最大值,默认最大值为100

② "page":Integer,查询页码,从0开始,默认最大值为100,一般和count一起用

③ "query":Integer,查询内容
0-对象,1-总数和分页详情,2-数据、总数和分页详情
总数关键词为 total,分页详情关键词为 info,
它们都和 query 同级,通过引用赋值得到,例如
"total@":"/[]/total", "info@":"/[]/info"
这里query及total仅为GET类型的请求提供方便,
一般可直接用HEAD类型的请求获取总数

④ "join":"&/Table0,\"join":{
   "&/Table0":{}, // 支持 ON 多个字段关联,
   "\      "key0":value0, // 其它ON条件
     "key2":value2,
     ...
     "@combine":"...", // 其它ON条件的组合方式
     "@column":"...", // 外层 SELECT
     "@group":"...", // 外层 GROUP BY
     "@having":"..." // 外层 HAVING
   }
}
多表连接方式:
"@" - APP JOIN
"\<" - LEFT JOIN
">" - RIGHT JOIN
"&" - INNER JOIN
"\|" - FULL JOIN
"!" - OUTER JOIN
"*" - CROSS JOIN
"^" - SIDE JOIN
"(" - ANTI JOIN
")" - FOREIGN JOIN
其中 @ APP JOIN 为应用层连表,会从已查出的主表里取得所有副表 key@ 关联的主表内的 refKey 作为一个数组 refKeys: [value0, value1...],然后把原来副表 count 次查询 key=$refKey 的 SQL 用 key IN($refKeys) 的方式合并为一条 SQL 来优化性能;
其它 JOIN 都是 SQL JOIN,具体功能和 MySQL,PostgreSQL 等数据库的 JOIN 一一对应
`"join":"`"MainTable":{},`
`"ViceTable":{"key@":"/MainTable/refKey"}`
会对应生成
`MainTable LEFT JOIN ViceTable`
`ON ViceTable.key=MainTable.refKey` AND 其它ON条件
除了 = 等价关联,也支持 ! 不等关联、\> \< \>= \<= 等比较关联和 $ ~ {} <> 等其它复杂关联方式

⑤ "otherKey":Object,自定义关键词,名称和以上系统关键词不一样,且原样返回上传的值 | ① 查询User数组,最多5个:
["count":5](http://apijson.cn:8080/get/{"[]":{"count":5,"User":{}}})
对应SQL是`LIMIT 5`

② 查询第3页的User数组,每页5个:
["count":5,
"page":3](http://apijson.cn:8080/get/{"[]":{"count":5,"page":3,"User":{}}})
对应SQL是`LIMIT 5 OFFSET 15`

③ 查询User数组和对应的User总数:
["[]":{
   "query":2,
   "User":{}
},
"total@":"/[]/total",
"info@":"/[]/info"](http://apijson.cn:8080/get/{"[]":{"query":2,"count":5,"User":{}},"total@":"%252F[]%252Ftotal","info@":"%252F[]%252Finfo"})
返回的数据中,总数及分页详情结构为:
"total":139, //总数
"info":{ //分页详情
   "total":139, //总数
   "count":5, //每页数量
   "page":0, //当前页码
   "max":27, //最大页码
   "more":true, //是否还有更多
   "first":true, //是否为首页
   "last":false //是否为尾页
}

④ Moment INNER JOIN User LEFT JOIN Comment:
["[]":{
   "join":"&/User/id@,\    "Moment":{
     "@group":"id" //主副表不是一对一,要去除重复数据
   },
   "User":{
     "name~":"t",
     "id@":"/Moment/userId"
   },
   "Comment":{
     "momentId@":"/Moment/id"
   }
}](http://apijson.cn/api/?type=JSON&url=http://apijson.cn:8080/get&json=%7B%22%5B%5D%22:%7B%22count%22:5,%22join%22:%22%26%2FUser%2Fid@,%3C%2FComment%22,%22Moment%22:%7B%22@column%22:%22id,userId,content%22,%22@group%22:%22id%22%7D,%22User%22:%7B%22name~%22:%22t%22,%22id@%22:%22%2FMoment%2FuserId%22,%22@column%22:%22id,name,head%22%7D,%22Comment%22:%7B%22momentId@%22:%22%2FMoment%2Fid%22,%22@column%22:%22id,momentId,content%22%7D%7D%7D)

⑤ 每一层都加当前用户名:
["User":{},
"[]":{
   "name@":"User/name", //自定义关键词
   "Moment":{}
}](http://apijson.cn:8080/get/{"User":{},"[]":{"name@":"User%252Fname","Moment":{}}}) 对象关键词,可自定义 | "@key":Object,@key为 Table:{} 中{}内的关键词,Object的类型由@key指定

① "@combine":"key0 \| (key1 & (key2 \| !key3))...",条件组合方式,最终按
(其它key条件 AND 连接) AND (key0条件 OR (key1条件 AND (key2条件 OR (NOT key3条件))))
这种方式连接,其中 "其它key" 是指与 @combine 在同一对象,且未被它声明的条件 key,默认都是 & 连接。注意不要缺少或多余任何一个空格。

② "@column":"column;function(arg)...",返回字段

③ "@order":"column0+,column1-...",排序方式

④ "@group":"column0,column1...",分组方式。如果@column里声明了Table的id,则id也必须在@group中声明;其它情况下必须满足至少一个条件:
1.分组的key在@column里声明
2.Table主键在@group中声明

⑤ "@having":"function0(...)?value0;function1(...)?value1;function2(...)?value2..." // OR 连接,或
"@having&":"function0(...)?value0;function1(...)?value1;function2(...)?value2..." // AND 连接,或
"@having":{
   "h0":"function0(...)?value0",
   "h1":function1(...)?value1",
   "h2":function2(...)?value2...",
   "@combine":"h0 & (h1 \| !h2)" // 任意组合,非必传
}
SQL函数条件,一般和@group一起用,函数一般在@column里声明

⑥ "@schema":"sys",集合空间(数据库名/模式),非默认的值可通过它来指定,可以在最外层作为全局默认配置

⑦ "@database":"POSTGRESQL",数据库类型,非默认的值可通过它来指定,可以在最外层作为全局默认配置

⑧ "@datasource":"DRUID",跨数据源,非默认的值可通过它来指定,可以在最外层作为全局默认配置

⑨ "@json":"key0,key1...",转为 JSON 格式返回,符合 JSONObject 则转为 {...},符合 JSONArray 则转为 \[...]

⑩ "@role":"OWNER",来访角色,包括
UNKNOWN,LOGIN,CONTACT,CIRCLE,OWNER,ADMIN,
可以在最外层作为全局默认配置,
可自定义其它角色并重写 Verifier.verify 等相关方法来自定义校验

⑪ "@explain":true,性能分析,可以在最外层作为全局默认配置

⑫ "@raw":"key0,key1...",其中 key0, key1 都对应有键值对
"key0":"SQL片段或SQL片段的别名",
"key1":"SQL片段或SQL片段的别名"
自定义原始SQL片段,可扩展嵌套SQL函数等复杂语句,必须是后端已配置的,只有其它功能符都做不到才考虑,谨慎使用,注意防SQL注入

⑬ "@otherKey":Object,自定义关键词,名称和以上系统关键词不一样,且原样返回上传的值 | ① 搜索name或tag任何一个字段包含字符a的User列表:
["name~":"a",
"tag~":"a",
"@combine":"name~ \| tag~"](http://apijson.cn:8080/get/{"User[]":{"count":10,"User":{"@column":"id,name,tag","name~":"a","tag~":"a","@combine":"name~%20%7C%20tag~"}}})
对应SQL是`name REGEXP 'a' OR tag REGEXP 'a'`

② 只查询id,sex,name这几列并且请求结果也按照这个顺序:
["@column":"id,sex,name"](http://apijson.cn:8080/get/{"User":{"@column":"id,sex,name","id":38710}})
对应SQL是`SELECT id,sex,name`

③ 查询按 name降序、id默认顺序 排序的User数组:
["@order":"name-,id"](http://apijson.cn:8080/get/{"[]":{"count":10,"User":{"@column":"name,id","@order":"name-,id"}}})
对应SQL是`ORDER BY name DESC,id`

④ 查询按userId分组的Moment数组:
["@group":"userId,id"](http://apijson.cn:8080/get/{"[]":{"count":10,"Moment":%7B"@column":"userId,id","@group":"userId,id"}}})
对应SQL是`GROUP BY userId,id`

⑤ 查询 按userId分组、id最大值>=100 的Moment数组:
["@column":"userId;max(id)",
"@group":"userId",
"@having":"max(id)>=100"](http://apijson.cn:8080/get/{"[]":{"count":10,"Moment":{"@column":"userId%253Bmax(id)","@group":"userId","@having":"max(id)>=100"}}})
对应SQL是`SELECT userId,max(id) ... GROUP BY userId HAVING max(id)>=100`
还可以指定函数返回名:
["@column":"userId;max(id):maxId",
"@group":"userId",
"@having":"(maxId)>=100"](http://apijson.cn:8080/get/{"[]":{"count":10,"Moment":{"@column":"userId%253Bmax(id):maxId","@group":"userId","@having":"(maxId)>=100"}}})
对应SQL是`SELECT userId,max(id) AS maxId ... GROUP BY userId HAVING (maxId)>=100`

⑥ 查询 sys 内的 User 表:
["@schema":"sys"](http://apijson.cn:8080/get/{"User":{"@schema":"sys"}})
对应SQL是`FROM sys.User`

⑦ 查询 PostgreSQL 数据库的 User 表:
["@database":"POSTGRESQL"](http://apijson.cn:8080/get/{"User":{"@database":"POSTGRESQL","@explain":true}})

⑧ 使用 Druid 连接池查询 User 表:
["@datasource":"DRUID"](http://apijson.cn:8080/get/{"User":{"@datasource":"DRUID"}})

⑨ 将 VARCHAR 字符串字段 get 转为 JSONArray 返回:
["@json":"get"](http://apijson.cn:8080/get/{"Access":{"@json":"get"}})

⑩ 查询当前用户的动态:
["@role":"OWNER"](http://apijson.cn:8080/get/{"[]":{"Moment":{"@role":"OWNER"}}})

⑪ 开启性能分析:
["@explain":true](http://apijson.cn:8080/get/{"[]":{"Moment":{"@explain":true}}})
对应SQL是`EXPLAIN`

⑫ 统计最近一周偶数userId的数量
["@column":"date;left(date,10):day;sum(if(userId%2=0,1,0))",
"@group":"day",
"@having":"to_days(now())-to_days(\`date\`)<=7",
"@raw":"@column,@having"](http://apijson.cn:8080/get/{"[]":{"Moment":{"@column":"date%3bleft(date,10):day%3bsum(if(userId%252=0,1,0))","@group":"day","@having":"to_days(now())-to_days(\`date\`)<=7","@raw":"@column,@having"}}})
对应SQL是``SELECT date, left(date,10) AS day, sum(if(userId%2=0,1,0)) ... GROUP BY day HAVING to_days(now())-to_days(`date`)<=7``

⑬ 从pictureList获取第0张图片:
["@position":0, //自定义关键词
"firstPicture()":"getFromArray(pictureList,@position)"](http://apijson.cn:8080/get/{"User":{"id":38710,"@position":0,"firstPicture()":"getFromArray(pictureList,@position)"}}) 全局关键词 | 为最外层对象 {} 内的关键词。其中 @database,@schema, @datasource, @role, @explain 基本同对象关键词,见上方说明,区别是全局关键词会每个表对象中没有时自动放入,作为默认值。

① "tag":String,后面的 tag 是非 GET、HEAD 请求中匹配请求的 JSON 结构的标识,一般是要查询的 Table 的名称或该名称对应的数组 Table[] 或 Table:[],由后端 Request 表中指定。

② "version":Integer,接口版本,version 不传、为 null 或 <=0 都会使用最高版本,传了其它有效值则会使用最接近它的最低版本,由后端 Request 表中指定。

③ "format":Boolean,格式化返回 Response JSON 的 key,一般是将 TableName 转为 tableName, TableName[] 转为 tableNameList, Table:alias 转为 alias, TableName-key[] 转为 tableNameKeyList 等小驼峰格式。 | ① 查隐私信息:
[{"tag":"Privacy","Privacy":{"id":82001}}](http://apijson.cn/api?url=http%3A%2F%2Fapijson.cn%3A8080%2Fgets&type=JSON&json={%22tag%22:%22Privacy%22,%22Privacy%22:{%22id%22:82001}})

② 使用第 1 版接口查隐私信息:
[{"version":1,"tag":"Privacy","Privacy":{"id":82001}}](http://apijson.cn/api?url=http%3A%2F%2Fapijson.cn%3A8080%2Fgets&type=JSON&json={%22version%22:1,%22tag%22:%22Privacy%22,%22Privacy%22:{%22id%22:82001}})

③ 格式化朋友圈接口返回 JSON 中的 key:
[{
   "format":true,
   "[]":{
     "page":0,
     "count":3,
     "Moment":{},
     "User":{
       "id@":"/Moment/userId"
     },
     "Comment[]":{
       "count":3,
       "Comment":{
         "momentId@":"[]/Moment/id"
       }
     }
   }
}](http://apijson.cn:8080/get/{"format":true,"[]":{"page":0,"count":3,"Moment":{},"User":{"id@":"%252FMoment%252FuserId"},"Comment[]":{"count":3,"Comment":{"momentId@":"[]%252FMoment%252Fid"}}}})
From a74084589abdf524089a5aa19eb74c3dec7089de Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Sat, 9 Apr 2022 00:39:54 +0800 Subject: [PATCH 0612/1181] Update README.md --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 1aca30303..1adbcfcda 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ This source code is licensed under the Apache License Version 2.0
APIJSON -

零代码、全自动、强安全 ORM 库
🚀 后端接口和文档零代码,前端(客户端) 定制返回 JSON 的数据和结构

+

零代码、全功能、强安全 ORM 库
🚀 后端接口和文档零代码,前端(客户端) 定制返回 JSON 的数据和结构

@@ -66,7 +66,7 @@ APIJSON 是一种专为 API 而生的 JSON 网络传输协议 以及 基于这 * 数据和结构完全定制,要啥有啥 * 看请求知结果,所求即所得 * 可一次获取任何数据、任何结构 -* 能去除重复数据,节省流量提高速度 +* 能去除多余数据,节省流量提高速度 #### 对于后端 * 提供通用接口,大部分 API 不用再写 From d868d8e6fd998591c0689fe80fb7dfb18d023a0b Mon Sep 17 00:00:00 2001 From: weiwei162 Date: Fri, 15 Apr 2022 18:12:32 +0800 Subject: [PATCH 0613/1181] fix #362 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 支持PUT请求修改json/jsonb类型字段 --- .../src/main/java/apijson/orm/AbstractObjectParser.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractObjectParser.java b/APIJSONORM/src/main/java/apijson/orm/AbstractObjectParser.java index 3f3a09a04..c64236960 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractObjectParser.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractObjectParser.java @@ -599,8 +599,8 @@ public void onPUTArrayParse(@NotNull String key, @NotNull JSONArray array) throw } else if (key.endsWith("-")) {//remove putType = 2; } else {//replace - // throw new IllegalAccessException("PUT " + path + ", PUT Array不允许 " + key + - // " 这种没有 + 或 - 结尾的key!不允许整个替换掉原来的Array!"); + sqlRequest.put(key, array); + return; } String realKey = AbstractSQLConfig.getRealKey(method, key, false, false); From e2e752caedc3a354566f7ae5bd9e61a9553701f9 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Sat, 16 Apr 2022 14:55:53 +0800 Subject: [PATCH 0614/1181] Update Document.md --- Document.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Document.md b/Document.md index 1c65d3039..fefcd5eae 100644 --- a/Document.md +++ b/Document.md @@ -405,7 +405,7 @@ DELETE:
删除数据 | base_url/delete/ | {
   TableName:{< 匹配条件范围 | "key{}":"条件0,条件1...",条件为SQL表达式字符串,可进行数字比较运算等 | ["id{}":"<=80000,\>90000"](http://apijson.cn:8080/get/{"User[]":{"count":3,"User":{"id{}":"<=80000,\>90000"}}}),对应SQL是`id<=80000 OR id>90000`,查询id符合id\<=80000 \| id>90000的一个User数组 包含选项范围 | "key<\>":Object => "key<\>":[Object],key对应值的类型必须为JSONArray,Object类型不能为JSON | ["contactIdList<\>":38710](http://apijson.cn:8080/get/{"User[]":{"count":3,"User":{"contactIdList<\>":38710}}}),对应SQL是`json_contains(contactIdList,38710)`,查询contactIdList包含38710的一个User数组 判断是否存在 | "key}{@":{
   "from":"Table",
   "Table":{ ... }
}
其中:
}{ 表示 EXISTS;
key 用来标识是哪个判断;
@ 后面是 子查询 对象,具体见下方 子查询 的说明。 | ["id}{@":{
   "from":"Comment",
   "Comment":{
      "momentId":15
   }
}](http://apijson.cn:8080/get/{"User":{"id}{@":{"from":"Comment","Comment":{"momentId":15}}}})
WHERE EXISTS(SELECT * FROM Comment WHERE momentId=15) - 远程调用函数 | "key()":"函数表达式",函数表达式为 function(key0,key1...),会调用后端对应的函数 function(JSONObject request, String key0, String key1...),实现 参数校验、数值计算、数据同步、消息推送、字段拼接、结构变换 等特定的业务逻辑处理,
可使用 - 和 + 表示优先级,解析 key-() > 解析当前对象 > 解析 key() > 解析子对象 > 解析 key+() | ["isPraised()":"isContain(praiseUserIdList,userId)"](http://apijson.cn:8080/get/{"Moment":{"id":301,"isPraised()":"isContain(praiseUserIdList,userId)"}}),会调用远程函数 boolean isContain(JSONObject request, String array, String value) ,然后变为 "isPraised":true 这种(假设点赞用户id列表包含了userId,即这个User点了赞) + 远程调用函数 | "key()":"函数表达式",函数表达式为 function(key0,key1...),会调用后端对应的函数 function(JSONObject request, String key0, String key1...),实现 参数校验、数值计算、数据同步、消息推送、字段拼接、结构变换 等特定的业务逻辑处理,
可使用 - 和 + 表示优先级,解析 key-() > 解析当前对象 > 解析 key() > 解析子对象 > 解析 key+() | ["isPraised()":"isContain(praiseUserIdList,userId)"](http://apijson.cn:8080/get/{"Moment":{"id":301,"isPraised()":"isContain(praiseUserIdList,userId)"}}),会调用远程函数 [boolean isContain(JSONObject request, String array, String value)](https://github.com/APIJSON/apijson-framework/blob/master/src/main/java/apijson/framework/APIJSONFunctionParser.java#L361-L374) ,然后变为 "isPraised":true 这种(假设点赞用户id列表包含了userId,即这个User点了赞) 存储过程 | "@key()":"SQL函数表达式",函数表达式为
function(key0,key1...)
会调用后端数据库对应的存储过程 SQL函数
function(String key0, String key1...)
除了参数会提前赋值,其它和 远程函数 一致 | ["@limit":10,
"@offset":0,
"@procedure()":"getCommentByUserId(id,@limit,@offset)"](http://apijson.cn:8080/get/{"User":{"@limit":10,"@offset":0,"@procedure()":"getCommentByUserId(id,@limit,@offset)"}})
会转为
`getCommentByUserId(38710,10,0)`
来调用存储过程 SQL 函数
`getCommentByUserId(IN id bigint, IN limit int, IN offset int)`
然后变为
"procedure":{
   "count":-1,
   "update":false,
   "list":[]
}
其中 count 是指写操作影响记录行数,-1 表示不是写操作;update 是指是否为写操作(增删改);list 为返回结果集 引用赋值 | "key@":"key0/key1/.../refKey",引用路径为用/分隔的字符串。以/开头的是缺省引用路径,从声明key所处容器的父容器路径开始;其它是完整引用路径,从最外层开始。
被引用的refKey必须在声明key的上面。如果对refKey的容器指定了返回字段,则被引用的refKey必须写在@column对应的值内,例如 "@column":"refKey,key1,..." | ["Moment":{
   "userId":38710
},
"User":{
   "id@":"/Moment/userId"
}](http://apijson.cn:8080/get/{"Moment":{"userId":38710},"User":{"id@":"%252FMoment%252FuserId"}})
User内的id引用了与User同级的Moment内的userId,
即User.id = Moment.userId,请求完成后
"id@":"/Moment/userId" 会变成 "id":38710 子查询 | "key@":{
   "range":"ALL",
   "from":"Table",
   "Table":{ ... }
}
其中:
range 可为 ALL,ANY;
from 为目标表 Table 的名称;
@ 后面的对象类似数组对象,可使用 count 和 join 等功能。 | ["id@":{
   "from":"Comment",
   "Comment":{
      "@column":"min(userId)"
   }
}](http://apijson.cn:8080/get/{"User":{"id@":{"from":"Comment","Comment":{"@column":"min(userId)"}}}})
WHERE id=(SELECT min(userId) FROM Comment) From 5073ee5ccb43f8f1cdd851b5961e4eafd190fe73 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Sat, 16 Apr 2022 15:04:07 +0800 Subject: [PATCH 0615/1181] Update README.md --- README.md | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 1adbcfcda..28bf5c25c 100644 --- a/README.md +++ b/README.md @@ -9,6 +9,12 @@ This source code is licensed under the Apache License Version 2.0

零代码、全功能、强安全 ORM 库
🚀 后端接口和文档零代码,前端(客户端) 定制返回 JSON 的数据和结构

+

+ English +  通用文档 + 视频教程 + 在线体验 +

  @@ -28,17 +34,15 @@ This source code is licensed under the Apache License Version 2.0
 

+

+ +   +   +

    -

-

- English -  通用文档 - 视频教程 - 在线体验 -

From 06658dfd843513ad0a0ed3597808caf4a2fbe56d Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Sat, 16 Apr 2022 15:06:01 +0800 Subject: [PATCH 0616/1181] Update README.md --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 28bf5c25c..3937a0806 100644 --- a/README.md +++ b/README.md @@ -35,9 +35,9 @@ This source code is licensed under the Apache License Version 2.0

- -   -   + +   +  

From 3f5a1ae4737ff6d898bff0cb97862c940cef4044 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Sat, 16 Apr 2022 15:27:08 +0800 Subject: [PATCH 0617/1181] Update README-English.md --- README-English.md | 65 +++++++++++++++++++++++++++-------------------- 1 file changed, 38 insertions(+), 27 deletions(-) diff --git a/README-English.md b/README-English.md index eeceb8783..c0d40e09c 100644 --- a/README-English.md +++ b/README-English.md @@ -6,35 +6,43 @@ This source code is licensed under the Apache License Version 2.0
APIJSON -

🚀 A JSON Transmission Protocol and an ORM Library for providing APIs and Documents automatically.

+

🏆 Tencent top 10 open source project, Achieved 5 awards inside & outside Tencent
🚀 A JSON Transmission Protocol and an ORM Library for providing APIs and Documents without writing any code.

+

+  中文版  +  Document  +  Video  +  Test  +

    - + + + + +

- + +   -   +     -

+

+ +   +   +

    -

-

-  中文版  -  Document  -  Video  -  Test  -

@@ -309,23 +317,26 @@ If you have any questions or suggestions, you can [create an issue](https://gith https://github.com/Tencent/APIJSON/issues/187

- - - + + +
- - - - - - - - - - - - + + + + + + + + + + + + + + +
[More APIJSON Users](https://github.com/Tencent/APIJSON/issues/73) From 9a70737eb832c614fa7f8fe0fc88792066969543 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Sat, 16 Apr 2022 15:31:02 +0800 Subject: [PATCH 0618/1181] Update Document-English.md --- Document-English.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Document-English.md b/Document-English.md index eb737bb4b..fa1785dd5 100644 --- a/Document-English.md +++ b/Document-English.md @@ -42,8 +42,8 @@ Add / expand an item | `"key+":Object`
The type of Object is decided by *key*. Types can be Number, String, JSONArray. Froms are 82001,"apijson",["url0","url1"] respectively. It’s only applicable to PUT request.| "praiseUserIdList+":[82001]. In SQL, it's
`json_insert(praiseUserIdList,82001)`.
Add an *id* that praised the Moment. Delete / decrease an item | `"Key-":Object`
It’s the contrary of "key+" | "balance-":100.00. In SQL, it's
`balance = balance - 100.00`,
meaning there's 100 less in balance. Operations | &, \|, !
They're used in logic operations. It’s the same as AND, OR, NOT in SQL respectively.
By default, for the same key, it’s ‘\|’ (OR)operation among conditions; for different keys, the default operation among conditions is ‘&’(AND).
| ① ["id&{}":">80000,<=90000"](http://apijson.cn:8080/head/{"User":{"id&{}":">80000,<=90000"}})
In SQL, it's
`id>80000 AND id<=90000`,
meaning *id* needs to be id>80000 & id<=90000

② ["id\|{}":">90000,<=80000"](http://apijson.cn:8080/head/{"User":{"id\|{}":">90000,<=80000"}})
It's the same as "id{}":">90000,<=80000".
In SQL, it's
`id>80000 OR id<=90000`,
meaning that *id* needs to be id>90000 \| id<=80000

③ ["id!{}":[82001,38710]](http://apijson.cn:8080/head/{"User":{"id!{}":[82001,38710]}})
In SQL, it's
`id NOT IN(82001,38710)`,
meaning id needs to be ! (id=82001 \| id=38710). - Keywords in an Array: It can be self-defined. | As for `"key":Object`, *key* is the keyword of *{}* in *"[]":{}*. The type of *Object* is up to *key*.

① `"count":Integer` It's used to count the number. The default largest number is 100.

② `"page":Integer` It’s used for getting data from which page, starting from 0. The default largest number is 100. It’s usually used with COUNT.

③ `"query":Integer` Get the number of items that match conditions
When to get the object, the integer should be 0; when to get the total number, it’s 1; when both above, it’s 2.
You can get the total number with keyword total. It can be referred to other values.
Eg.
`"total@":"/[]/total"`
Put it as the same level of query.
*Query* and *total* are used in GET requests just for convenience. Generally, HEAD request is for getting numbers like the total number.

④ `"join":"&/Table0/key0@,Join tables:
"\<" - LEFT JOIN
">" - RIGHT JOIN
"&" - INNER JOIN
"\|" - FULL JOIN
"!" - OUTER JOIN
"@" - APP JOIN
Where @ APP JOIN is in application layer.It’ll get all the keys in tables that refKeys in result tables are referred to, like refKeys:[value0, value1….]. Then, as the results get data according to `key=$refKey` a number of times (COUNT), it uses key `IN($refKeys)` to put these counts together in just one SQL query, in order to improve the performance.
Other JOIN functions are the same as those in SQL.
`"join":"`"MainTable":{},`
`"ViceTable":{"key@":"/MainTable/refKey"}`
will return
`MainTable LEFT JOIN ViceTable`
`ON ViceTable.key=MainTable.refKey`

⑤ `"otherKey":Object` Self-defined keyword other than those that already in the system. It also returns with self-defined keywords.| ① Get User arrays with maximum of 5:
["count":5](http://apijson.cn:8080/get/{"[]":{"count":5,"User":{}}})

② Look into User arrays on page 3. Show 5 of them each page.
["count":5,
"page":3](http://apijson.cn:8080/get/{"[]":{"count":5,"page":3,"User":{}}})

③ Get User Arrays and count the total number of Users:
["[]":{
   "query":2,
   "User":{}
},
"total@":"/[]/total"](http://apijson.cn:8080/get/{"[]":{"query":2,"count":5,"User":{}},"total@":"%252F[]%252Ftotal"})
Questions like total page numbers or if there's next page can be solved by total,count,page functions,
Total page number:
`int totalPage = Math.ceil(total / count)`
If this is the last page:
`boolean hasNextPage = total > count*page`
If this is the first page:
`boolean isFirstPage = page <= 0`
If it's the last page:
`boolean isLastPage = total <= count*page`
...

④ Moment INNER JOIN User LEFT JOIN Comment:
["[]":{
   "join": "&/User/id@,\    "Moment":{},
   "User":{
     "name~":"t",
     "id@": "/Moment/userId"
   },
   "Comment":{
     "momentId@": "/Moment/id"
   }
}](http://apijson.cn:8080/get/{"[]":{"count":5,"join":"&%252FUser%252Fid@,\<%252FComment%252FmomentId@","Moment":{"@column":"id,userId,content"},"User":{"name~":"t","id@":"%252FMoment%252FuserId","@column":"id,name,head"},"Comment":{"momentId@":"%252FMoment%252Fid","@column":"id,momentId,content"}}})

⑤ Add the current user to every level:
["User":{},
"[]":{
   "name@":"User/name", //self-defined keyword
   "Moment":{}
}](http://apijson.cn:8080/get/{"User":{},"[]":{"name@":"User%252Fname","Moment":{}}}) - Keywords in Objects: It can be self-defined. | `"@key":Object` @key is the keyword of {} in Table:{}. The type of Object is decided by @key

① `"@combine":"&key0,&key1,\|key2,key3,`
`!key4,!key5,&key6,key7..."`
First, it’ll group data with same operators. Within one group, it operates from left to right. Then it’ll follow the order of & \| ! to do the operation. Different groups are connected with &. So the expression above will be :
(key0 & key1 & key6 & other key) & (key2 \| key3 \| key7) & !(key4 \| key5)
\| is optional.

② `"@column":"column;function(arg)..."` Return with specific columns.

③ `"@order":"column0+,column1-..."` Decide the order of returning results:

④ `"@group":"column0,column1..."` How to group data. If @column has declared Table id, this id need to be included in @group. In other situations, at least one of the following needs to be done:
1.Group id is declared in @column
2.Primary Key of the table is declared in @group.

⑤ `@having":"function0(...)?value0;function1(...)?value1;function2(...)?value2..."` Add conditions on return results with @having. Usually working with@group, it’s declared in @column.

⑥ `"@schema":"sys"` Can be set as default setting.

⑦ `"@database":"POSTGRESQL"` Get data from a different database.Can be set as default setting.

⑧ `"@role":"OWNER"` Get information of the user, including
UNKNOWN,LOGIN,CONTACT,CIRCLE,OWNER,ADMIN,
Can be set as default setting.
You can self-define a new role or rewrite a role. Use`Verifier.verify` etc. to self-define validation methods.

⑨ `"@explain":true` Profiling. Can be set as default setting.

⑩ `"@otherKey":Object` Self-define keyword | ① Search *Users* that *name* or *tag* contains the letter "a":
["name~":"a",
"tag~":"a",
"@combine":"name~,tag~"](http://apijson.cn:8080/get/{"User[]":{"count":10,"User":{"@column":"id,name,tag","name~":"a","tag~":"a","@combine":"name~,tag~"}}})

② Only search column id,sex,name and return with the same order:
["@column":"id,sex,name"](http://apijson.cn:8080/get/{"User":{"@column":"id,sex,name","id":38710}})

③ Search Users that have descending order of name and default order of id:
["@order":"name-,id"](http://apijson.cn:8080/get/{"[]":{"count":10,"User":{"@column":"name,id","@order":"name-,id"}}})

④ Search Moment grouped with userId:
["@group":"userId,id"](http://apijson.cn:8080/get/{"[]":{"count":10,"Moment":%7B"@column":"userId,id","@group":"userId,id"}}})

⑤ Search Moments that id equals or less than 100 and group with userId:
["@column":"userId;max(id)",
"@group":"userId",
"@having":"max(id)>=100"](http://apijson.cn:8080/get/{"[]":{"count":10,"Moment":{"@column":"userId%253Bmax(id)","@group":"userId","@having":"max(id)>=100"}}})
You can also define the name of the returned function:
["@column":"userId;max(id):maxId",
"@group":"userId",
"@having":"maxId>=100"](http://apijson.cn:8080/get/{"[]":{"count":10,"Moment":{"@column":"userId%253Bmax(id):maxId","@group":"userId","@having":"maxId>=100"}}})

⑥ Check Users table in sys:
["@schema":"sys"](http://apijson.cn:8080/get/{"User":{"@schema":"sys"}})

⑦ Check Users table in PostgreSQL:
["@database":"POSTGRESQL"](http://apijson.cn:8080/get/{"User":{"@database":"POSTGRESQL"}})

⑧ Check the current user's activity:
["@role":"OWNER"](http://apijson.cn:8080/get/{"[]":{"Moment":{"@role":"OWNER"}}})

⑨ Turn on profiling:
["@explain":true](http://apijson.cn:8080/get/{"[]":{"Moment":{"@explain":true}}})

⑩ Get the No.0 picture from pictureList:
["@position":0, //self-defined keyword
"firstPicture()":"getFromArray(pictureList,@position)"](http://apijson.cn:8080/get/{"User":{"id":38710,"@position":0,"firstPicture()":"getFromArray(pictureList,@position)"}}) + Keywords in an Array: It can be self-defined. | As for `"key":Object`, *key* is the keyword of *{}* in *"[]":{}*. The type of *Object* is up to *key*.

① `"count":Integer` It's used to count the number. The default largest number is 100.

② `"page":Integer` It’s used for getting data from which page, starting from 0. The default largest number is 100. It’s usually used with COUNT.

③ `"query":Integer` Get the number of items that match conditions
When to get the object, the integer should be 0; when to get the total number, it’s 1; when both above, it’s 2.
You can get the total number with keyword total. It can be referred to other values.
Eg.
`"total@":"/[]/total"`
Put it as the same level of query.
*Query* and *total* are used in GET requests just for convenience. Generally, HEAD request is for getting numbers like the total number.

④ `"join":"&/Table0,Join tables:
"\<" - LEFT JOIN
">" - RIGHT JOIN
"&" - INNER JOIN
"\|" - FULL JOIN
"!" - OUTER JOIN
"@" - APP JOIN
Where @ APP JOIN is in application layer.It’ll get all the keys in tables that refKeys in result tables are referred to, like refKeys:[value0, value1….]. Then, as the results get data according to `key=$refKey` a number of times (COUNT), it uses key `IN($refKeys)` to put these counts together in just one SQL query, in order to improve the performance.
Other JOIN functions are the same as those in SQL.
`"join":"`"MainTable":{},`
`"ViceTable":{"key@":"/MainTable/refKey"}`
will return
`MainTable LEFT JOIN ViceTable`
`ON ViceTable.key=MainTable.refKey`

⑤ `"otherKey":Object` Self-defined keyword other than those that already in the system. It also returns with self-defined keywords.| ① Get User arrays with maximum of 5:
["count":5](http://apijson.cn:8080/get/{"[]":{"count":5,"User":{}}})

② Look into User arrays on page 3. Show 5 of them each page.
["count":5,
"page":3](http://apijson.cn:8080/get/{"[]":{"count":5,"page":3,"User":{}}})

③ Get User Arrays and count the total number of Users:
["[]":{
   "query":2,
   "User":{}
},
"total@":"/[]/total"](http://apijson.cn:8080/get/{"[]":{"query":2,"count":5,"User":{}},"total@":"%252F[]%252Ftotal"})
Questions like total page numbers or if there's next page can be solved by total,count,page functions,
Total page number:
`int totalPage = Math.ceil(total / count)`
If this is the last page:
`boolean hasNextPage = total > count*page`
If this is the first page:
`boolean isFirstPage = page <= 0`
If it's the last page:
`boolean isLastPage = total <= count*page`
...

④ Moment INNER JOIN User LEFT JOIN Comment:
["[]":{
   "join": "&/User,\    "Moment":{},
   "User":{
     "name~":"t",
     "id@": "/Moment/userId"
   },
   "Comment":{
     "momentId@": "/Moment/id"
   }
}](http://apijson.cn:8080/get/{"[]":{"count":5,"join":"&%252FUser,\<%252FComment","Moment":{"@column":"id,userId,content"},"User":{"name~":"t","id@":"%252FMoment%252FuserId","@column":"id,name,head"},"Comment":{"momentId@":"%252FMoment%252Fid","@column":"id,momentId,content"}}})

⑤ Add the current user to every level:
["User":{},
"[]":{
   "name@":"User/name", //self-defined keyword
   "Moment":{}
}](http://apijson.cn:8080/get/{"User":{},"[]":{"name@":"User%252Fname","Moment":{}}}) + Keywords in Objects: It can be self-defined. | `"@key":Object` @key is the keyword of {} in Table:{}. The type of Object is decided by @key

① `"@combine":"&key0,&key1,\|key2,key3,`
`!key4,!key5,&key6,key7..."`
First, it’ll group data with same operators. Within one group, it operates from left to right. Then it’ll follow the order of & \| ! to do the operation. Different groups are connected with &. So the expression above will be :
(key0 & key1 & key6 & other key) & (key2 \| key3 \| key7) & !(key4 \| key5)
\| is optional.

② `"@column":"column;function(arg)..."` Return with specific columns.

③ `"@order":"column0+,column1-..."` Decide the order of returning results:

④ `"@group":"column0,column1..."` How to group data. If @column has declared Table id, this id need to be included in @group. In other situations, at least one of the following needs to be done:
1.Group id is declared in @column
2.Primary Key of the table is declared in @group.

⑤ `@having":"function0(...)?value0;function1(...)?value1;function2(...)?value2..."` Add conditions on return results with @having. Usually working with@group, it’s declared in @column.

⑥ `"@schema":"sys"` Can be set as default setting.

⑦ `"@database":"POSTGRESQL"` Get data from a different database.Can be set as default setting.

⑧ `"@role":"OWNER"` Get information of the user, including
UNKNOWN,LOGIN,CONTACT,CIRCLE,OWNER,ADMIN,
Can be set as default setting.
You can self-define a new role or rewrite a role. Use`Verifier.verify` etc. to self-define validation methods.

⑨ `"@explain":true` Profiling. Can be set as default setting.

⑩ `"@otherKey":Object` Self-define keyword | ① Search *Users* that *name* or *tag* contains the letter "a":
["name~":"a",
"tag~":"a",
"@combine":"name~,tag~"](http://apijson.cn:8080/get/{"User[]":{"count":10,"User":{"@column":"id,name,tag","name~":"a","tag~":"a","@combine":"name~,tag~"}}})

② Only search column id,sex,name and return with the same order:
["@column":"id,sex,name"](http://apijson.cn:8080/get/{"User":{"@column":"id,sex,name","id":38710}})

③ Search Users that have descending order of name and default order of id:
["@order":"name-,id"](http://apijson.cn:8080/get/{"[]":{"count":10,"User":{"@column":"name,id","@order":"name-,id"}}})

④ Search Moment grouped with userId:
["@group":"userId,id"](http://apijson.cn:8080/get/{"[]":{"count":10,"Moment":%7B"@column":"userId,id","@group":"userId,id"}}})

⑤ Search Moments that id equals or less than 100 and group with userId:
["@column":"userId;max(id)",
"@group":"userId",
"@having":"max(id)>=100"](http://apijson.cn:8080/get/{"[]":{"count":10,"Moment":{"@column":"userId%253Bmax(id)","@group":"userId","@having":"max(id)>=100"}}})
You can also define the name of the returned function:
["@column":"userId;max(id):maxId",
"@group":"userId",
"@having":"(maxId)>=100"](http://apijson.cn:8080/get/{"[]":{"count":10,"Moment":{"@column":"userId%253Bmax(id):maxId","@group":"userId","@having":"(maxId)>=100"}}})

⑥ Check Users table in sys:
["@schema":"sys"](http://apijson.cn:8080/get/{"User":{"@schema":"sys"}})

⑦ Check Users table in PostgreSQL:
["@database":"POSTGRESQL"](http://apijson.cn:8080/get/{"User":{"@database":"POSTGRESQL"}})

⑧ Check the current user's activity:
["@role":"OWNER"](http://apijson.cn:8080/get/{"[]":{"Moment":{"@role":"OWNER"}}})

⑨ Turn on profiling:
["@explain":true](http://apijson.cn:8080/get/{"[]":{"Moment":{"@explain":true}}})

⑩ Get the No.0 picture from pictureList:
["@position":0, //self-defined keyword
"firstPicture()":"getFromArray(pictureList,@position)"](http://apijson.cn:8080/get/{"User":{"id":38710,"@position":0,"firstPicture()":"getFromArray(pictureList,@position)"}})
From df74e3a904f20d7717f53702e04835f5010c99a1 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Sat, 16 Apr 2022 15:35:13 +0800 Subject: [PATCH 0619/1181] Update README-English.md --- README-English.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README-English.md b/README-English.md index c0d40e09c..cc599efea 100644 --- a/README-English.md +++ b/README-English.md @@ -12,8 +12,8 @@ This source code is licensed under the Apache License Version 2.0

 中文版   Document  -  Video  -  Test  +  Video  +  Test 

From 286dd3d41f9114378f5c7e943cc1bd0f03725289 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Sat, 16 Apr 2022 15:35:27 +0800 Subject: [PATCH 0620/1181] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 3937a0806..ca8cbc6ab 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ This source code is licensed under the Apache License Version 2.0

English  通用文档 - 视频教程 + 视频教程 在线体验

From ec8c2601ff736f50aa0fd2d0ba4d365dcd324464 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Mon, 18 Apr 2022 01:00:17 +0800 Subject: [PATCH 0621/1181] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E8=B7=AF=E7=94=B1?= =?UTF-8?q?=E6=8F=92=E4=BB=B6=EF=BC=8C=E5=AF=B9=E5=A4=96=E6=9A=B4=E9=9C=B2?= =?UTF-8?q?=E7=B1=BB=20RESTful=20=E6=8E=A5=E5=8F=A3=EF=BC=8C=E5=86=85?= =?UTF-8?q?=E9=83=A8=E8=BD=AC=E6=88=90=20APIJSON=20=E6=8E=A5=E5=8F=A3?= =?UTF-8?q?=E6=89=A7=E8=A1=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit https://github.com/APIJSON/apijson-router --- README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index ca8cbc6ab..f2893e86c 100644 --- a/README.md +++ b/README.md @@ -451,8 +451,10 @@ https://github.com/Tencent/APIJSON/blob/master/CONTRIBUTING.md [apijson-orm](https://github.com/APIJSON/apijson-orm) APIJSON ORM 库,可通过 Maven, Gradle 等远程依赖 -[apijson-framework](https://github.com/APIJSON/apijson-framework) APIJSON 服务端框架,可通过 Maven, Gradle 等远程依赖 +[apijson-framework](https://github.com/APIJSON/apijson-framework) APIJSON 服务端框架,通过数据库表配置角色权限、参数校验等,简化使用 +[apijson-router](https://github.com/APIJSON/apijson-router) APIJSON 的路由插件,对外暴露类 RESTful 接口,内部转成 APIJSON 接口执行 + [apijson-column](https://github.com/APIJSON/apijson-column) APIJSON 的字段插件,支持 字段名映射 和 !key 反选字段 [APIAuto](https://github.com/TommyLemon/APIAuto) 敏捷开发最强大易用的 HTTP 接口工具,机器学习零代码测试、生成代码与静态检查、生成文档与光标悬浮注释 From 30bbea94acef614fb2c1004266d75a5f08a40ead Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Mon, 18 Apr 2022 01:10:36 +0800 Subject: [PATCH 0622/1181] =?UTF-8?q?=E4=BC=98=E5=8C=96=E8=A7=92=E8=89=B2?= =?UTF-8?q?=E6=9D=83=E9=99=90=E3=80=81=E5=8F=82=E6=95=B0=E6=A0=A1=E9=AA=8C?= =?UTF-8?q?=E3=80=81=E8=BF=9C=E7=A8=8B=E5=87=BD=E6=95=B0=E7=9A=84=E5=88=9D?= =?UTF-8?q?=E5=A7=8B=E5=8C=96=EF=BC=9B=E8=A7=A3=E5=86=B3=20format:=20true?= =?UTF-8?q?=20=E5=9C=A8=20Log.DEBUG=20=E6=97=B6=E4=B9=9F=E4=B8=8D=E8=BF=94?= =?UTF-8?q?=E5=9B=9E=20SQL=E3=80=81=E6=97=B6=E9=97=B4=E7=AD=89=E8=B0=83?= =?UTF-8?q?=E8=AF=95=E4=BF=A1=E6=81=AF=EF=BC=9B=E5=8D=87=E7=BA=A7=E8=87=AA?= =?UTF-8?q?=E8=BA=AB=E7=89=88=E6=9C=AC=E4=B8=BA=205.0.5=EF=BC=9B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- APIJSONORM/pom.xml | 2 +- APIJSONORM/src/main/java/apijson/StringUtil.java | 16 ++++++++++++++-- .../java/apijson/orm/AbstractFunctionParser.java | 2 +- .../main/java/apijson/orm/AbstractParser.java | 14 +++++++------- .../main/java/apijson/orm/AbstractVerifier.java | 10 +++++----- 5 files changed, 28 insertions(+), 16 deletions(-) diff --git a/APIJSONORM/pom.xml b/APIJSONORM/pom.xml index edd10dfb9..187aa087f 100755 --- a/APIJSONORM/pom.xml +++ b/APIJSONORM/pom.xml @@ -5,7 +5,7 @@ apijson.orm apijson-orm - 5.0.0 + 5.0.5 jar APIJSONORM diff --git a/APIJSONORM/src/main/java/apijson/StringUtil.java b/APIJSONORM/src/main/java/apijson/StringUtil.java index ecc64a083..795392e49 100755 --- a/APIJSONORM/src/main/java/apijson/StringUtil.java +++ b/APIJSONORM/src/main/java/apijson/StringUtil.java @@ -348,6 +348,7 @@ public static boolean isNotEmpty(String s, boolean trim) { public static final Pattern PATTERN_NAME; public static final Pattern PATTERN_ALPHA_BIG; public static final Pattern PATTERN_ALPHA_SMALL; + public static final Pattern PATTERN_BRANCH_URL; static { PATTERN_NUMBER = Pattern.compile("^[0-9]+$"); PATTERN_ALPHA = Pattern.compile("^[a-zA-Z]+$"); @@ -359,6 +360,7 @@ public static boolean isNotEmpty(String s, boolean trim) { PATTERN_EMAIL = Pattern.compile("^([a-zA-Z0-9_\\-\\.]+)@((\\[[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}\\.)|(([a-zA-Z0-9\\-]+\\.)+))([a-zA-Z]{2,4}|[0-9]{1,3})(\\]?)$"); PATTERN_ID_CARD = Pattern.compile("(^[1-9]\\d{5}(18|19|([23]\\d))\\d{2}((0[1-9])|(10|11|12))(([0-2][1-9])|10|20|30|31)\\d{3}[0-9Xx]$)|(^[1-9]\\d{5}\\d{2}((0[1-9])|(10|11|12))(([0-2][1-9])|10|20|30|31)\\d{2}$)"); PATTERN_PASSWORD = Pattern.compile("^[0-9a-zA-Z]+$"); + PATTERN_BRANCH_URL = Pattern.compile("^[0-9a-zA-Z-_/]+$"); } /**判断手机格式是否正确 @@ -392,7 +394,7 @@ public static boolean isNumberPassword(String s) { * @return */ public static boolean isEmail(String email) { - if (isNotEmpty(email, true) == false) { + if (isEmpty(email, true)) { return false; } @@ -512,7 +514,8 @@ public static boolean isIDCard(String number) { public static boolean isUrl(String url) { if (isNotEmpty(url, true) == false) { return false; - } else if (! url.startsWith(URL_PREFIX) && ! url.startsWith(URL_PREFIXs)) { + } + if (! url.startsWith(URL_PREFIX) && ! url.startsWith(URL_PREFIXs)) { return false; } @@ -520,6 +523,15 @@ public static boolean isUrl(String url) { return true; } + public static boolean isBranchUrl(String branchUrl) { + if (isEmpty(branchUrl, false)) { + return false; + } + + return PATTERN_BRANCH_URL.matcher(branchUrl).matches(); + } + + public static final String FILE_PATH_PREFIX = "file://"; /**判断文件路径是否存在 * @param path diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractFunctionParser.java b/APIJSONORM/src/main/java/apijson/orm/AbstractFunctionParser.java index 3debe096a..880b917f0 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractFunctionParser.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractFunctionParser.java @@ -28,7 +28,7 @@ public class AbstractFunctionParser implements FunctionParser { // // > - public static final Map FUNCTION_MAP; + public static Map FUNCTION_MAP; static { FUNCTION_MAP = new HashMap<>(); } diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java b/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java index f56af8cc8..f3fa6e18c 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java @@ -441,16 +441,16 @@ public JSONObject parseResponse(JSONObject request) { long duration = endTime - startTime; if (Log.DEBUG) { - requestObject.put("sql:generate|cache|execute|maxExecute", getSQLExecutor().getGeneratedSQLCount() + "|" + getSQLExecutor().getCachedSQLCount() + "|" + getSQLExecutor().getExecutedSQLCount() + "|" + getMaxSQLCount()); - requestObject.put("depth:count|max", queryDepth + "|" + getMaxQueryDepth()); + res.put("sql:generate|cache|execute|maxExecute", getSQLExecutor().getGeneratedSQLCount() + "|" + getSQLExecutor().getCachedSQLCount() + "|" + getSQLExecutor().getExecutedSQLCount() + "|" + getMaxSQLCount()); + res.put("depth:count|max", queryDepth + "|" + getMaxQueryDepth()); executedSQLDuration += sqlExecutor.getExecutedSQLDuration() + sqlExecutor.getSqlResultDuration(); long parseDuration = duration - executedSQLDuration; - requestObject.put("time:start|duration|end|parse|sql", startTime + "|" + duration + "|" + endTime + "|" + parseDuration + "|" + executedSQLDuration); + res.put("time:start|duration|end|parse|sql", startTime + "|" + duration + "|" + endTime + "|" + parseDuration + "|" + executedSQLDuration); if (error != null) { - requestObject.put("trace:throw", error.getClass().getName()); - requestObject.put("trace:stack", error.getStackTrace()); + res.put("trace:throw", error.getClass().getName()); + res.put("trace:stack", error.getStackTrace()); } } @@ -947,7 +947,7 @@ public JSONObject getStructure(@NotNull String table, String method, String tag, } if (result == null) { - if (AbstractVerifier.REQUEST_MAP.isEmpty() == false) { + if (Log.DEBUG == false && AbstractVerifier.REQUEST_MAP.isEmpty() == false) { return null; // 已使用 REQUEST_MAP 缓存全部,但没查到 } @@ -961,7 +961,7 @@ public JSONObject getStructure(@NotNull String table, String method, String tag, where.put(JSONRequest.KEY_TAG, tag); if (version > 0) { - where.put(JSONRequest.KEY_VERSION + "{}", ">=" + version); + where.put(JSONRequest.KEY_VERSION + ">=", version); } config.setWhere(where); config.setOrder(JSONRequest.KEY_VERSION + (version > 0 ? "+" : "-")); diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractVerifier.java b/APIJSONORM/src/main/java/apijson/orm/AbstractVerifier.java index feda82ac1..0431d1773 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractVerifier.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractVerifier.java @@ -109,14 +109,14 @@ public abstract class AbstractVerifier implements Verifier, IdCallback { // > // > @NotNull - public static final Map> SYSTEM_ACCESS_MAP; + public static Map> SYSTEM_ACCESS_MAP; @NotNull - public static final Map> ACCESS_MAP; + public static Map> ACCESS_MAP; // > // > @NotNull - public static final Map> REQUEST_MAP; + public static Map> REQUEST_MAP; // 正则匹配的别名快捷方式,例如用 "PHONE" 代替 "^((13[0-9])|(15[^4,\\D])|(18[0-2,5-9])|(17[0-9]))\\d{8}$" @NotNull @@ -164,7 +164,7 @@ public abstract class AbstractVerifier implements Verifier, IdCallback { ACCESS_MAP = new HashMap<>(SYSTEM_ACCESS_MAP); - REQUEST_MAP = new HashMap<>(ACCESS_MAP.size()*6); // 单个与批量增删改 + REQUEST_MAP = new HashMap<>(ACCESS_MAP.size()*7); // 单个与批量增删改 COMPILE_MAP = new HashMap(); } @@ -1495,7 +1495,7 @@ public static void verifyRepeat(String table, String key, Object value, long exc } public static String getCacheKeyForRequest(String method, String tag) { - return method + " " + tag; + return method + "/" + tag; } From e5641b721cd8c2523533903a7235e3d9e2fccdef Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Tue, 19 Apr 2022 22:43:10 +0800 Subject: [PATCH 0623/1181] Update CONTRIBUTING.md --- CONTRIBUTING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 703b9224b..0cb35f97d 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -52,7 +52,7 @@ Zerounary 提交的 6 个 Commits, 对 APIJSON 做出了 1,104 增加和 1 处
APIJSON 持续招募贡献者,即使是在 Issue 中回答问题,或者做一些简单的 Bug Fix ,也会给 APIJSON 带来很大的帮助。
APIJSON 已开发近 4 年,在此感谢所有开发者对于 APIJSON 的喜欢和支持,希望你能够成为 APIJSON 的核心贡献者,
-加入 APIJSON ,共同打造一个更棒的自动化 ORM 库!🍾🎉 +加入 APIJSON ,共同打造一个更棒的零代码、全自动、强安全 ORM 库!🍾🎉 ### 为什么一定要贡献代码? APIJSON 作为腾讯开源的知名热门项目,贡献代码除了可以给简历加亮点、为面试加分,还可以避免你碰到以下麻烦:
From b83a891b7e6d662e4e766b9d6a4c093ee73bd2e0 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Fri, 22 Apr 2022 21:56:27 +0800 Subject: [PATCH 0624/1181] Update README.md --- README.md | 24 ++++++++++++++---------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index f2893e86c..b02b4dfbe 100644 --- a/README.md +++ b/README.md @@ -65,13 +65,6 @@ APIJSON 是一种专为 API 而生的 JSON 网络传输协议 以及 基于这 ### 特点功能 -#### 对于前端 -* 不用再向后端催接口、求文档 -* 数据和结构完全定制,要啥有啥 -* 看请求知结果,所求即所得 -* 可一次获取任何数据、任何结构 -* 能去除多余数据,节省流量提高速度 - #### 对于后端 * 提供通用接口,大部分 API 不用再写 * 自动生成文档,不用再编写和维护 @@ -79,6 +72,13 @@ APIJSON 是一种专为 API 而生的 JSON 网络传输协议 以及 基于这 * 开放 API 无需划分版本,始终保持兼容 * 支持增删改查、复杂查询、跨库连表、远程函数等 +#### 对于前端 +* 不用再向后端催接口、求文档 +* 数据和结构完全定制,要啥有啥 +* 看请求知结果,所求即所得 +* 可一次获取任何数据、任何结构 +* 能去除多余数据,节省流量提高速度 +
### APIJSON 接口展示 @@ -385,11 +385,15 @@ https://github.com/Tencent/APIJSON/blob/master/Roadmap.md 如果你解决了某些bug,或者新增了一些功能,欢迎 [贡献代码](https://github.com/Tencent/APIJSON/pulls),感激不尽~
https://github.com/Tencent/APIJSON/blob/master/CONTRIBUTING.md -QQ 技术群: 734652054(新)、607020115(旧) +QQ 技术群: 734652054(新)、607020115(旧)
+ +**原则上优先解决 登记用户 和 贡献者 的问题,不解决 态度无礼 或 问题描述简陋 的问题!** + +如果你 [登记了自己使用 APIJSON 的公司](https://github.com/Tencent/APIJSON/issues/187),可以加专门的 企业用户支持群,作者亲自答疑。 如果你为 APIJSON 做出了以下任何一个贡献:
-[提交了 PR 且被合并](https://github.com/Tencent/APIJSON/pull/92)、[提交了优质 Issue](https://github.com/Tencent/APIJSON/issues/189)、[发表了优质文章](https://blog.csdn.net/qq_41829492/article/details/88670940)、[开发了可用的生态项目](https://github.com/zhangchunlin/uliweb-apijson) 或 [登记了你的公司](https://github.com/Tencent/APIJSON/issues/187),可以加
-贡献者微信群,注意联系 LonelyExplorer,加好友描述中附上贡献链接,谢谢 +[提交了 PR 且被合并](https://github.com/Tencent/APIJSON/pull/92)、[提交了优质 Issue](https://github.com/Tencent/APIJSON/issues/189)、[发表了优质文章](https://blog.csdn.net/qq_41829492/article/details/88670940)、[开发了可用的生态项目](https://github.com/zhangchunlin/uliweb-apijson),
+可以加 贡献者交流群,注意联系 LonelyExplorer,加好友描述中附上贡献链接,谢谢 ### 相关推荐 From e3ecce3a2c6643bedf4fd9e75e69e0263c6afdad Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Fri, 22 Apr 2022 22:11:31 +0800 Subject: [PATCH 0625/1181] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index b02b4dfbe..29de492ac 100644 --- a/README.md +++ b/README.md @@ -387,7 +387,7 @@ https://github.com/Tencent/APIJSON/blob/master/CONTRIBUTING.md QQ 技术群: 734652054(新)、607020115(旧)
-**原则上优先解决 登记用户 和 贡献者 的问题,不解决 态度无礼 或 问题描述简陋 的问题!** +**时间精力有限,原则上优先解决 登记用户 和 贡献者 的问题,不解决 文档/视频/常见问题 已明确说明、描述简陋 或 态度无礼 的问题!** 如果你 [登记了自己使用 APIJSON 的公司](https://github.com/Tencent/APIJSON/issues/187),可以加专门的 企业用户支持群,作者亲自答疑。 From a591ff1784e7c78bd1619860ea7a09dc89bbcf4d Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Fri, 22 Apr 2022 22:14:11 +0800 Subject: [PATCH 0626/1181] Update README.md --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 29de492ac..1d647188e 100644 --- a/README.md +++ b/README.md @@ -387,7 +387,8 @@ https://github.com/Tencent/APIJSON/blob/master/CONTRIBUTING.md QQ 技术群: 734652054(新)、607020115(旧)
-**时间精力有限,原则上优先解决 登记用户 和 贡献者 的问题,不解决 文档/视频/常见问题 已明确说明、描述简陋 或 态度无礼 的问题!** +时间精力有限,原则上优先解决 登记用户 和 贡献者 的问题,
+不解决 文档/视频/常见问题 已明确说明、描述简陋 或 态度无礼 的问题! 如果你 [登记了自己使用 APIJSON 的公司](https://github.com/Tencent/APIJSON/issues/187),可以加专门的 企业用户支持群,作者亲自答疑。 From ce636ceab65a2a228043c8a8e95a4abeb6e19d29 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Fri, 22 Apr 2022 22:16:13 +0800 Subject: [PATCH 0627/1181] Update README.md --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 1d647188e..5eb054cfd 100644 --- a/README.md +++ b/README.md @@ -387,8 +387,8 @@ https://github.com/Tencent/APIJSON/blob/master/CONTRIBUTING.md QQ 技术群: 734652054(新)、607020115(旧)
-时间精力有限,原则上优先解决 登记用户 和 贡献者 的问题,
-不解决 文档/视频/常见问题 已明确说明、描述简陋 或 态度无礼 的问题! +**时间精力有限,原则上优先解决 登记用户 和 贡献者 的问题,**
+**不解决 文档/视频/常见问题 已明确说明、描述简陋 或 态度无礼 的问题!** 如果你 [登记了自己使用 APIJSON 的公司](https://github.com/Tencent/APIJSON/issues/187),可以加专门的 企业用户支持群,作者亲自答疑。 From 0580b3038c3184f0dca2f3cca6b0a05c6b5cfa0d Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Fri, 22 Apr 2022 22:17:21 +0800 Subject: [PATCH 0628/1181] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 5eb054cfd..79b085933 100644 --- a/README.md +++ b/README.md @@ -387,7 +387,7 @@ https://github.com/Tencent/APIJSON/blob/master/CONTRIBUTING.md QQ 技术群: 734652054(新)、607020115(旧)
-**时间精力有限,原则上优先解决 登记用户 和 贡献者 的问题,**
+**开发者时间精力有限,原则上优先解决 登记用户 和 贡献者 的问题,**
**不解决 文档/视频/常见问题 已明确说明、描述简陋 或 态度无礼 的问题!** 如果你 [登记了自己使用 APIJSON 的公司](https://github.com/Tencent/APIJSON/issues/187),可以加专门的 企业用户支持群,作者亲自答疑。 From 3d42b895b581aa9b6d319e4cbd6a91e973458dff Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Sat, 23 Apr 2022 00:18:27 +0800 Subject: [PATCH 0629/1181] Update README.md --- README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 79b085933..0e326d860 100644 --- a/README.md +++ b/README.md @@ -66,11 +66,11 @@ APIJSON 是一种专为 API 而生的 JSON 网络传输协议 以及 基于这 ### 特点功能 #### 对于后端 -* 提供通用接口,大部分 API 不用再写 -* 自动生成文档,不用再编写和维护 +* 提供万能通用接口,大部分 HTTP API 不用再写 +* 零代码增删改查、各种跨库连表、多层嵌套子查询等 +* 自动生成文档,不用再编写和维护,且自动静态检查 * 自动校验权限、自动管理版本、自动防 SQL 注入 -* 开放 API 无需划分版本,始终保持兼容 -* 支持增删改查、复杂查询、跨库连表、远程函数等 +* 开放 HTTP API 无需划分版本,始终保持兼容 #### 对于前端 * 不用再向后端催接口、求文档 From 61c21535509dacc35aa3cc4a4cccae9745473a91 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Sat, 23 Apr 2022 01:08:56 +0800 Subject: [PATCH 0630/1181] Update README.md --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 0e326d860..be1828fa7 100644 --- a/README.md +++ b/README.md @@ -54,11 +54,11 @@ This source code is licensed under the Apache License Version 2.0
APIJSON 是一种专为 API 而生的 JSON 网络传输协议 以及 基于这套协议实现的 ORM 库。
-为各种增删改查提供了完全自动化的万能 API,零代码实时满足千变万化的各种新增和变更需求。
+为各种增删改查提供了完全自动化的万能通用接口,零代码实时满足千变万化的各种新增和变更需求。
能大幅降低开发和沟通成本,简化开发流程,缩短开发周期。
适合中小型前后端分离的项目,尤其是 初创项目、内部项目、低代码/零代码、小程序、BaaS、Serverless 等。
-通过万能的 API,前端可以定制任何数据、任何结构。
+通过万能通用接口,前端可以定制任何数据、任何结构。
大部分 HTTP 请求后端再也不用写接口了,更不用写文档了。
前端再也不用和后端沟通接口或文档问题了。再也不会被文档各种错误坑了。
后端再也不用为了兼容旧接口写新版接口和文档了。再也不会被前端随时随地没完没了地烦了。 From c3c6a08c12f2ca4b7f4dfbae015f29f656a4cef8 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Sat, 23 Apr 2022 02:49:20 +0800 Subject: [PATCH 0631/1181] =?UTF-8?q?=E7=9B=B8=E5=85=B3=E6=8E=A8=E8=8D=90?= =?UTF-8?q?=E6=96=B0=E5=A2=9E=E3=80=8Aapijson=E5=9C=A8=E5=90=8C=E4=B8=80?= =?UTF-8?q?=E4=B8=AA=E6=8E=A5=E5=8F=A3=E8=B0=83=E7=94=A8=E4=B8=AD=20?= =?UTF-8?q?=E4=BD=BF=E7=94=A8=E8=BF=9C=E7=A8=8B=E5=87=BD=E6=95=B0=E5=86=99?= =?UTF-8?q?=E5=85=A5=E6=9B=B4=E6=96=B0=E6=97=B6=E9=97=B4=E5=92=8C=E5=88=9B?= =?UTF-8?q?=E5=BB=BA=E6=97=B6=E9=97=B4=E3=80=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 点赞、收藏支持下博主吧~ https://blog.csdn.net/qietingfengsong/article/details/124097229 --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index be1828fa7..c8574d809 100644 --- a/README.md +++ b/README.md @@ -435,6 +435,8 @@ https://github.com/Tencent/APIJSON/blob/master/CONTRIBUTING.md [新手搭建 APIJSON 项目指北](https://github.com/jerrylususu/apijson_todo_demo/blob/master/FULLTEXT.md) [使用APIJSON写低代码Crud接口](https://blog.csdn.net/weixin_42375862/article/details/121654264) + +[apijson在同一个接口调用中 使用远程函数写入更新时间和创建时间](https://blog.csdn.net/qietingfengsong/article/details/124097229) [APIJSON(一:综述)](https://blog.csdn.net/qq_50861917/article/details/120556168) From 23032713dc90b4391694141134f98ac59e09d413 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Sat, 23 Apr 2022 21:53:47 +0800 Subject: [PATCH 0632/1181] Update --bug.md --- .github/ISSUE_TEMPLATE/--bug.md | 25 +++++++++++++++++++++++-- 1 file changed, 23 insertions(+), 2 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/--bug.md b/.github/ISSUE_TEMPLATE/--bug.md index 6e545c22d..a9662be96 100755 --- a/.github/ISSUE_TEMPLATE/--bug.md +++ b/.github/ISSUE_TEMPLATE/--bug.md @@ -4,6 +4,26 @@ about: Create a report to help us improve --- +**提 bug 请发请求和响应的【完整截屏】,没图的自行解决! +开发者有限的时间和精力主要放在【维护项目源码和文档】上! +【描述不详细】 或 【文档/常见问题 已有答案】 的问题可能会被忽略!! +【态度 不文明/不友善】的可能会被拉黑,问题也可能不予解答!!!** + +请求参数 JSON 中表名、字段名、关键词及对应的值都是大小写敏感、逗号敏感、分号敏感、空格敏感、换行敏感, +大部分情况都不允许空格和换行,表名以大写字母开头,不要想当然,请严格按照 设计规范 来调用 API #181 + +1.尝试在 [常见问题](https://github.com/Tencent/APIJSON/issues/36) 和 [历史问题](https://github.com/TommyLemon/APIJSON/issues?q=is%3Aissue+is%3Aclosed) 搜索答案。 +2.尝试阅读 [通用文档](https://github.com/TommyLemon/APIJSON/blob/master/Document.md) 或看 [视频教程](https://search.bilibili.com/all?keyword=APIJSON) 找到答案。 +3.尝试阅读 [Demo 示例代码](https://github.com/APIJSON/APIJSON-Demo/blob/master/APIJSON-Java-Server/APIJSONBoot-MultiDataSource/src/main/java/apijson/demo/DemoSQLConfig.java) 以找到答案。 +4.尝试自己 [检查或试验](http://apijson.cn/api) 以找到答案。 +5.尝试阅读 [源码和注释](https://github.com/Tencent/APIJSON/blob/master/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java) 以找到答案。 + +如果以上都尝试过了请填写以下模板提一个新的issue。 +强烈推荐阅读 [《如何向开源社区提问题》](https://github.com/seajs/seajs/issues/545)、[《如何有效地报告 Bug》](http://www.chiark.greenend.org.uk/~sgtatham/bugs-cn.html)、[《如何向开源项目提交无法解答的问题》](https://zhuanlan.zhihu.com/p/25795393) +和 [《提问的智慧》](https://github.com/ryanhanwu/How-To-Ask-Questions-The-Smart-Way),更好的问题更容易获得帮助。 + + + **环境信息** - 系统: - JDK: @@ -12,8 +32,9 @@ about: Create a report to help us improve **问题描述** - +可以输入具体的步骤,代码信息,或者截图,能帮助我们更快的解决您的问题 **错误信息** - \ No newline at end of file +运行日志面板错误信息,错误截图,或者【帮助>切换开发者工具】日志,以及【帮助>查看运行日志】log.log 文件 + From 614cc0a1c27971ecda0a06348337391b0e92ecb3 Mon Sep 17 00:00:00 2001 From: SingleDogL <100330117+SingleDogL@users.noreply.github.com> Date: Wed, 27 Apr 2022 10:38:09 +0800 Subject: [PATCH 0633/1181] =?UTF-8?q?=E4=BF=AE=E5=A4=8Doracle=E5=88=86?= =?UTF-8?q?=E9=A1=B5=E8=8E=B7=E5=8F=96=E6=97=B6=E6=97=A0=E6=B3=95=E8=8E=B7?= =?UTF-8?q?=E5=8F=96=E9=99=A4=E7=AC=AC=E4=B8=80=E9=A1=B5=E4=BB=A5=E5=A4=96?= =?UTF-8?q?=E7=9A=84=E6=95=B0=E6=8D=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 原来的getLimitString的方法所生成的语句oracle并无法正确获取,因为oracle的between并不支持获取除1开始以外的数据 也就是 无法获取到除了第一页以外的数据 --- .../main/java/apijson/orm/AbstractSQLConfig.java | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java index 6698e4f4b..4c2c81aee 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java @@ -3946,13 +3946,20 @@ public static String getSQL(AbstractSQLConfig config) throws Exception { && StringUtil.isNotEmpty(config.getGroup(),true)){ return explain + "SELECT count(*) FROM (SELECT " + (config.getCache() == JSONRequest.CACHE_RAM ? "SQL_NO_CACHE " : "") + column + " FROM " + getConditionString(column, tablePath, config) + ") " + config.getLimitString(); } - return explain + "SELECT * FROM (SELECT " + (config.getCache() == JSONRequest.CACHE_RAM ? "SQL_NO_CACHE " : "") + column + " FROM " + getConditionString(column, tablePath, config) + ") " + config.getLimitString(); + String sql = "SELECT " + (config.getCache() == JSONRequest.CACHE_RAM ? "SQL_NO_CACHE " : "") + column + " FROM " + getConditionString(column, tablePath, config); + return explain + config.getOraclePageSql(config, sql); } - return explain + "SELECT " + (config.getCache() == JSONRequest.CACHE_RAM ? "SQL_NO_CACHE " : "") + column + " FROM " + getConditionString(column, tablePath, config) + config.getLimitString(); } - } + } + private String getOraclePageSql(AbstractSQLConfig config, String sql) { + int offset = getOffset(config.getPage(), config.getCount()); + String pageSql; + pageSql = "SELECT * FROM (SELECT t.*,ROWNUM RN FROM (" + sql + ") t WHERE ROWNUM <= " + (offset + count) + ") WHERE RN > " + offset; + return pageSql; + } + /**获取条件SQL字符串 * @param column * @param table From 4f40cbd56192dce95e2f51beb2723e994e313e7b Mon Sep 17 00:00:00 2001 From: SingleDogL <100330117+SingleDogL@users.noreply.github.com> Date: Fri, 29 Apr 2022 09:23:39 +0800 Subject: [PATCH 0634/1181] Update AbstractSQLConfig.java --- .../src/main/java/apijson/orm/AbstractSQLConfig.java | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java index 4c2c81aee..87d44f7d9 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java @@ -3952,7 +3952,12 @@ public static String getSQL(AbstractSQLConfig config) throws Exception { return explain + "SELECT " + (config.getCache() == JSONRequest.CACHE_RAM ? "SQL_NO_CACHE " : "") + column + " FROM " + getConditionString(column, tablePath, config) + config.getLimitString(); } } - + + /**Oracle的分页获取 + * @param config + * @param sql + * @return + */ private String getOraclePageSql(AbstractSQLConfig config, String sql) { int offset = getOffset(config.getPage(), config.getCount()); String pageSql; From 5b7d54a492eb93ccfd57918bfa84cb792469b5b4 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Sun, 1 May 2022 16:24:44 +0800 Subject: [PATCH 0635/1181] =?UTF-8?q?=E4=BC=98=E5=8C=96=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/main/java/apijson/RequestMethod.java | 6 ++-- .../java/apijson/orm/AbstractSQLConfig.java | 34 +++++++++++-------- 2 files changed, 21 insertions(+), 19 deletions(-) diff --git a/APIJSONORM/src/main/java/apijson/RequestMethod.java b/APIJSONORM/src/main/java/apijson/RequestMethod.java index e196e4dc0..035147d3a 100755 --- a/APIJSONORM/src/main/java/apijson/RequestMethod.java +++ b/APIJSONORM/src/main/java/apijson/RequestMethod.java @@ -52,8 +52,7 @@ public enum RequestMethod { * @return */ public static boolean isGetMethod(RequestMethod method, boolean containPrivate) { - boolean is = method == null || method == GET; - return containPrivate == false ? is : is || method == GETS; + return method == null || method == GET || (containPrivate && method == GETS); } /**是否为HEAD请求方法 @@ -62,8 +61,7 @@ public static boolean isGetMethod(RequestMethod method, boolean containPrivate) * @return */ public static boolean isHeadMethod(RequestMethod method, boolean containPrivate) { - boolean is = method == HEAD; - return containPrivate == false ? is : is || method == HEADS; + return method == HEAD || (containPrivate && method == HEADS); } /**是否为查询的请求方法 diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java index 87d44f7d9..2f6b2fba7 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java @@ -27,7 +27,6 @@ import static apijson.RequestMethod.DELETE; import static apijson.RequestMethod.GET; import static apijson.RequestMethod.GETS; -import static apijson.RequestMethod.HEAD; import static apijson.RequestMethod.HEADS; import static apijson.RequestMethod.POST; import static apijson.RequestMethod.PUT; @@ -3942,28 +3941,30 @@ public static String getSQL(AbstractSQLConfig config) throws Exception { if (config.isOracle()) { //When config's database is oracle,Using subquery since Oracle12 below does not support OFFSET FETCH paging syntax. //针对oracle分组后条数的统计 - if ((config.getMethod() == HEAD || config.getMethod() == HEADS) - && StringUtil.isNotEmpty(config.getGroup(),true)){ + if (StringUtil.isNotEmpty(config.getGroup(),true) && RequestMethod.isHeadMethod(config.getMethod(), true)){ return explain + "SELECT count(*) FROM (SELECT " + (config.getCache() == JSONRequest.CACHE_RAM ? "SQL_NO_CACHE " : "") + column + " FROM " + getConditionString(column, tablePath, config) + ") " + config.getLimitString(); } + String sql = "SELECT " + (config.getCache() == JSONRequest.CACHE_RAM ? "SQL_NO_CACHE " : "") + column + " FROM " + getConditionString(column, tablePath, config); - return explain + config.getOraclePageSql(config, sql); + return explain + config.getOraclePageSql(sql); } + return explain + "SELECT " + (config.getCache() == JSONRequest.CACHE_RAM ? "SQL_NO_CACHE " : "") + column + " FROM " + getConditionString(column, tablePath, config) + config.getLimitString(); } - } - + } + /**Oracle的分页获取 * @param config * @param sql * @return */ - private String getOraclePageSql(AbstractSQLConfig config, String sql) { - int offset = getOffset(config.getPage(), config.getCount()); - String pageSql; - pageSql = "SELECT * FROM (SELECT t.*,ROWNUM RN FROM (" + sql + ") t WHERE ROWNUM <= " + (offset + count) + ") WHERE RN > " + offset; - return pageSql; - } + protected String getOraclePageSql(String sql) { + int count = getCount(); + int offset = getOffset(getPage(), count); + String alias = getAliasWithQuote(); + + return "SELECT * FROM (SELECT " + alias + ".*, ROWNUM RN FROM (" + sql + ") " + alias + " WHERE ROWNUM <= " + (offset + count) + ") WHERE RN > " + offset; + } /**获取条件SQL字符串 * @param column @@ -3981,16 +3982,19 @@ private static String getConditionString(String column, String table, AbstractSQ } //根据方法不同,聚合语句不同。GROUP BY 和 HAVING 可以加在 HEAD 上, HAVING 可以加在 PUT, DELETE 上,GET 全加,POST 全都不加 - String aggregation = ""; + String aggregation; if (RequestMethod.isGetMethod(config.getMethod(), true)) { aggregation = config.getGroupString(true) + config.getHavingString(true) + config.getOrderString(true); } - if (RequestMethod.isHeadMethod(config.getMethod(), true)) { // TODO 加参数 isPagenation 判断是 GET 内分页 query:2 查总数,不用加这些条件 + else if (RequestMethod.isHeadMethod(config.getMethod(), true)) { // TODO 加参数 isPagenation 判断是 GET 内分页 query:2 查总数,不用加这些条件 aggregation = config.getGroupString(true) + config.getHavingString(true) ; } - if (config.getMethod() == PUT || config.getMethod() == DELETE) { + else if (config.getMethod() == PUT || config.getMethod() == DELETE) { aggregation = config.getHavingString(true) ; } + else { + aggregation = ""; + } String condition = table + config.getJoinString() + where + aggregation; ; //+ config.getLimitString(); From 0fb42569cfbce38d6ace21611f6ec3cf512747d6 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Sun, 1 May 2022 16:34:06 +0800 Subject: [PATCH 0636/1181] =?UTF-8?q?=E4=BC=98=E5=8C=96=20AbstractSQLExecu?= =?UTF-8?q?tor=20=E5=85=B3=E4=BA=8E=E8=80=97=E6=97=B6=E7=BB=9F=E8=AE=A1?= =?UTF-8?q?=E7=9A=84=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/apijson/orm/AbstractSQLExecutor.java | 68 +++++-------------- 1 file changed, 17 insertions(+), 51 deletions(-) diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java index 3606c5043..208fc165f 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java @@ -45,15 +45,9 @@ public abstract class AbstractSQLExecutor implements SQLExecutor { private static final String TAG = "AbstractSQLExecutor"; - - private int generatedSQLCount; - private int cachedSQLCount; - private int executedSQLCount; - public AbstractSQLExecutor() { - generatedSQLCount = 0; - cachedSQLCount = 0; - executedSQLCount = 0; - } + private int generatedSQLCount = 0; + private int cachedSQLCount = 0; + private int executedSQLCount = 0; @Override public int getGeneratedSQLCount() { @@ -68,26 +62,11 @@ public int getExecutedSQLCount() { return executedSQLCount; } - // 只要不是并发执行且执行完立刻获取,就不会是错的,否则需要一并返回,可以 JSONObject.put("@EXECUTED_SQL_TIME:START|DURATION|END", ) - private long executedSQLStartTime; - private long executedSQLEndTime; - private long executedSQLDuration; - private long sqlResultDuration; - - public long getExecutedSQLStartTime() { - return executedSQLStartTime; - } - public long getExecutedSQLEndTime() { - return executedSQLEndTime; - } + private long executedSQLDuration = 0; + private long sqlResultDuration = 0; @Override public long getExecutedSQLDuration() { - if (executedSQLDuration <= 0) { - long startTime = getExecutedSQLStartTime(); - long endTime = getExecutedSQLEndTime(); - executedSQLDuration = startTime <= 0 || endTime <= 0 ? 0 : endTime - startTime; // FIXME 有时莫名其妙地算出来是负数 - } - return executedSQLDuration < 0 ? 0 : executedSQLDuration; + return executedSQLDuration; } @Override @@ -96,7 +75,7 @@ public long getSqlResultDuration() { } /** - * 缓存map + * 缓存 Map */ protected Map> cacheMap = new HashMap<>(); @@ -104,7 +83,7 @@ public long getSqlResultDuration() { /**保存缓存 * @param sql * @param list - * @param isStatic + * @param type */ @Override public void putCache(String sql, List list, int type) { @@ -116,7 +95,7 @@ public void putCache(String sql, List list, int type) { } /**移除缓存 * @param sql - * @param isStatic + * @param type */ @Override public void removeCache(String sql, int type) { @@ -126,7 +105,10 @@ public void removeCache(String sql, int type) { } cacheMap.remove(sql); } - + /**获取缓存 + * @param sql + * @param type + */ @Override public List getCache(String sql, int type) { return cacheMap.get(sql); @@ -154,24 +136,18 @@ public JSONObject getCacheItem(String sql, int position, int type) { @Override public ResultSet executeQuery(@NotNull Statement statement, String sql) throws Exception { -// executedSQLStartTime = System.currentTimeMillis(); ResultSet rs = statement.executeQuery(sql); -// executedSQLEndTime = System.currentTimeMillis(); return rs; } @Override public int executeUpdate(@NotNull Statement statement, String sql) throws Exception { -// executedSQLStartTime = System.currentTimeMillis(); int c = statement.executeUpdate(sql); -// executedSQLEndTime = System.currentTimeMillis(); return c; } @Override public ResultSet execute(@NotNull Statement statement, String sql) throws Exception { -// executedSQLStartTime = System.currentTimeMillis(); statement.execute(sql); ResultSet rs = statement.getResultSet(); -// executedSQLEndTime = System.currentTimeMillis(); return rs; } @@ -182,10 +158,7 @@ public ResultSet execute(@NotNull Statement statement, String sql) throws Except */ @Override public JSONObject execute(@NotNull SQLConfig config, boolean unknowType) throws Exception { -// executedSQLDuration = 0; - executedSQLStartTime = System.currentTimeMillis(); - executedSQLEndTime = executedSQLStartTime; -// sqlResultDuration = 0; + long executedSQLStartTime = System.currentTimeMillis(); boolean isPrepared = config.isPrepared(); @@ -231,8 +204,7 @@ public JSONObject execute(@NotNull SQLConfig config, boolean unknowType) throws rs = execute(statement, sql); int updateCount = statement.getUpdateCount(); if (isExplain == false) { - executedSQLEndTime = System.currentTimeMillis(); - executedSQLDuration += executedSQLEndTime - executedSQLStartTime; + executedSQLDuration += System.currentTimeMillis() - executedSQLStartTime; } result = new JSONObject(true); @@ -251,8 +223,7 @@ public JSONObject execute(@NotNull SQLConfig config, boolean unknowType) throws } int updateCount = executeUpdate(config); if (isExplain == false) { - executedSQLEndTime = System.currentTimeMillis(); - executedSQLDuration += executedSQLEndTime - executedSQLStartTime; + executedSQLDuration += System.currentTimeMillis() - executedSQLStartTime; } if (updateCount <= 0) { @@ -294,8 +265,7 @@ public JSONObject execute(@NotNull SQLConfig config, boolean unknowType) throws } rs = executeQuery(config); //FIXME SQL Server 是一次返回两个结果集,包括查询结果和执行计划,需要 moreResults if (isExplain == false) { - executedSQLEndTime = System.currentTimeMillis(); - executedSQLDuration += executedSQLEndTime - executedSQLStartTime; + executedSQLDuration += System.currentTimeMillis() - executedSQLStartTime; } break; @@ -1131,9 +1101,7 @@ public void close() { @Override public ResultSet executeQuery(@NotNull SQLConfig config) throws Exception { PreparedStatement stt = getStatement(config); - // 不准,getStatement 有时比 execute sql 更耗时 executedSQLStartTime = System.currentTimeMillis(); ResultSet rs = stt.executeQuery(); //PreparedStatement 不用传 SQL - // executedSQLEndTime = System.currentTimeMillis(); // if (config.isExplain() && (config.isSQLServer() || config.isOracle())) { // FIXME 返回的是 boolean 值 rs = stt.getMoreResults(Statement.CLOSE_CURRENT_RESULT); // } @@ -1144,9 +1112,7 @@ public ResultSet executeQuery(@NotNull SQLConfig config) throws Exception { @Override public int executeUpdate(@NotNull SQLConfig config) throws Exception { PreparedStatement stt = getStatement(config); -// 不准,getStatement 有时比 execute sql 更耗时 executedSQLStartTime = System.currentTimeMillis(); int count = stt.executeUpdate(); // PreparedStatement 不用传 SQL -// executedSQLEndTime = System.currentTimeMillis(); if (count <= 0 && config.isHive()) { count = 1; From b92fab305df2620b62904756614f5f0712703c08 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Sun, 1 May 2022 16:55:47 +0800 Subject: [PATCH 0637/1181] Update --bug.md --- .github/ISSUE_TEMPLATE/--bug.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/ISSUE_TEMPLATE/--bug.md b/.github/ISSUE_TEMPLATE/--bug.md index a9662be96..3683b0f3e 100755 --- a/.github/ISSUE_TEMPLATE/--bug.md +++ b/.github/ISSUE_TEMPLATE/--bug.md @@ -12,7 +12,7 @@ about: Create a report to help us improve 请求参数 JSON 中表名、字段名、关键词及对应的值都是大小写敏感、逗号敏感、分号敏感、空格敏感、换行敏感, 大部分情况都不允许空格和换行,表名以大写字母开头,不要想当然,请严格按照 设计规范 来调用 API #181 -1.尝试在 [常见问题](https://github.com/Tencent/APIJSON/issues/36) 和 [历史问题](https://github.com/TommyLemon/APIJSON/issues?q=is%3Aissue+is%3Aclosed) 搜索答案。 +1.尝试在 [常见问题](https://github.com/Tencent/APIJSON/issues/36) 和 [历史问题](https://github.com/TommyLemon/APIJSON/issues?q=is%3Aissue) 搜索答案。 2.尝试阅读 [通用文档](https://github.com/TommyLemon/APIJSON/blob/master/Document.md) 或看 [视频教程](https://search.bilibili.com/all?keyword=APIJSON) 找到答案。 3.尝试阅读 [Demo 示例代码](https://github.com/APIJSON/APIJSON-Demo/blob/master/APIJSON-Java-Server/APIJSONBoot-MultiDataSource/src/main/java/apijson/demo/DemoSQLConfig.java) 以找到答案。 4.尝试自己 [检查或试验](http://apijson.cn/api) 以找到答案。 From 5d402171b0d6f1d5395a037b51eb6bac7d450f00 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Sun, 1 May 2022 17:10:25 +0800 Subject: [PATCH 0638/1181] =?UTF-8?q?=E8=A7=A3=E5=86=B3=20JOIN=20=E5=89=AF?= =?UTF-8?q?=E8=A1=A8=E8=BF=94=E5=9B=9E=E7=A9=BA=E5=AF=B9=E8=B1=A1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/main/java/apijson/orm/AbstractSQLExecutor.java | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java index 208fc165f..01cdb308d 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java @@ -783,9 +783,10 @@ protected JSONObject onPutColumn(@NotNull SQLConfig config, @NotNull ResultSet r String lable = getKey(config, rs, rsmd, tablePosition, table, columnIndex, childMap); Object value = getValue(config, rs, rsmd, tablePosition, table, columnIndex, lable, childMap); - // 必须 put 进去,否则某个字段为 null 可能导致中断后续正常返回值 if (value != null) { - table.put(lable, value); - // } + // 主表必须 put 至少一个 null 进去,否则全部字段为 null 都不 put 会导致中断后续正常返回值 + if (value != null || (join == null && table.isEmpty())) { + table.put(lable, value); + } return table; } From 8df36e26d7ad74f2df4df4e9ce5b29530d814141 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Mon, 2 May 2022 00:32:51 +0800 Subject: [PATCH 0639/1181] =?UTF-8?q?=E4=BC=98=E5=8C=96=20SQL=20=E6=89=A7?= =?UTF-8?q?=E8=A1=8C=E7=BC=93=E5=AD=98=EF=BC=9B=E4=BC=98=E5=8C=96=E4=B8=BB?= =?UTF-8?q?=E9=94=AE=E6=B3=9B=E5=9E=8B=EF=BC=9B=E9=83=A8=E5=88=86=E5=B8=B8?= =?UTF-8?q?=E9=87=8F=E6=94=B9=E4=B8=BA=E5=8F=AF=E8=87=AA=E5=AE=9A=E4=B9=89?= =?UTF-8?q?=E9=9D=99=E6=80=81=E5=8F=98=E9=87=8F=EF=BC=9B=E8=A7=A3=E5=86=B3?= =?UTF-8?q?=E9=AB=98=E5=B9=B6=E5=8F=91=E4=B8=8B=20id=20=E5=86=B2=E7=AA=81?= =?UTF-8?q?=E5=AF=BC=E8=87=B4=E6=96=B0=E5=A2=9E=E8=AE=B0=E5=BD=95=E5=A4=B1?= =?UTF-8?q?=E8=B4=A5=E7=AD=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/main/java/apijson/RequestMethod.java | 1 + .../apijson/orm/AbstractFunctionParser.java | 4 +- .../apijson/orm/AbstractObjectParser.java | 4 +- .../main/java/apijson/orm/AbstractParser.java | 84 ++++++++++--------- .../java/apijson/orm/AbstractSQLConfig.java | 50 ++++++----- .../java/apijson/orm/AbstractSQLExecutor.java | 66 ++++++++------- .../java/apijson/orm/AbstractVerifier.java | 39 ++++----- .../src/main/java/apijson/orm/Parser.java | 14 +--- .../main/java/apijson/orm/ParserCreator.java | 2 +- .../main/java/apijson/orm/SQLExecutor.java | 29 ++++--- .../src/main/java/apijson/orm/Subquery.java | 2 +- .../java/apijson/orm/VerifierCreator.java | 2 +- 12 files changed, 156 insertions(+), 141 deletions(-) diff --git a/APIJSONORM/src/main/java/apijson/RequestMethod.java b/APIJSONORM/src/main/java/apijson/RequestMethod.java index 035147d3a..caca99225 100755 --- a/APIJSONORM/src/main/java/apijson/RequestMethod.java +++ b/APIJSONORM/src/main/java/apijson/RequestMethod.java @@ -45,6 +45,7 @@ public enum RequestMethod { */ DELETE; + public static final RequestMethod[] ALL = new RequestMethod[]{ GET, HEAD, GETS, HEADS, POST, PUT, DELETE}; /**是否为GET请求方法 * @param method diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractFunctionParser.java b/APIJSONORM/src/main/java/apijson/orm/AbstractFunctionParser.java index 880b917f0..cde0a6228 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractFunctionParser.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractFunctionParser.java @@ -178,7 +178,7 @@ public static Object invoke(@NotNull AbstractFunctionParser parser, @NotNull Str + "\n请检查函数名和参数数量是否与已定义的函数一致!" + "\n且必须为 function(key0,key1,...) 这种单函数格式!" + "\nfunction必须符合Java函数命名,key是用于在request内取值的键!" - + "\n调用时不要有空格!"); + + "\n调用时不要有空格!" + e.getMessage()); } if (e instanceof InvocationTargetException) { Throwable te = ((InvocationTargetException) e).getTargetException(); @@ -186,7 +186,7 @@ public static Object invoke(@NotNull AbstractFunctionParser parser, @NotNull Str throw te instanceof Exception ? (Exception) te : new Exception(te.getMessage()); } throw new IllegalArgumentException("字符 " + function + " 对应的远程函数传参类型错误!" - + "\n请检查 key:value 中value的类型是否满足已定义的函数 " + getFunction(fb.getMethod(), fb.getKeys()) + " 的要求!"); + + "\n请检查 key:value 中value的类型是否满足已定义的函数 " + getFunction(fb.getMethod(), fb.getKeys()) + " 的要求!" + e.getMessage()); } throw e; } diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractObjectParser.java b/APIJSONORM/src/main/java/apijson/orm/AbstractObjectParser.java index c64236960..bdbb366e7 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractObjectParser.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractObjectParser.java @@ -908,7 +908,7 @@ public JSONObject onSQLExecute() throws Exception { && (table.equals(arrayTable)); // 提取并缓存数组主表的列表数据 - rawList = (List) result.remove(SQLExecutor.KEY_RAW_LIST); + rawList = (List) result.remove(AbstractSQLExecutor.KEY_RAW_LIST); if (rawList != null) { String arrayPath = parentPath.substring(0, parentPath.lastIndexOf("[]") + 2); @@ -936,7 +936,7 @@ public JSONObject onSQLExecute() throws Exception { parser.putQueryResult(path, result); // 解决获取关联数据时requestObject里不存在需要的关联数据 if (isSimpleArray && rawList != null) { - result.put(SQLExecutor.KEY_RAW_LIST, rawList); + result.put(AbstractSQLExecutor.KEY_RAW_LIST, rawList); } } } diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java b/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java index f3fa6e18c..5135a7279 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java @@ -50,7 +50,7 @@ /**parser for parsing request to JSONObject * @author Lemon */ -public abstract class AbstractParser implements Parser, ParserCreator, VerifierCreator, SQLCreator { +public abstract class AbstractParser implements Parser, ParserCreator, VerifierCreator, SQLCreator { protected static final String TAG = "AbstractParser"; /** @@ -72,6 +72,49 @@ public abstract class AbstractParser implements Parser, ParserCreator, public static boolean IS_PRINT_REQUEST_ENDTIME_LOG = false; + public static int DEFAULT_QUERY_COUNT = 10; + public static int MAX_QUERY_PAGE = 100; + public static int MAX_QUERY_COUNT = 100; + public static int MAX_UPDATE_COUNT = 10; + public static int MAX_SQL_COUNT = 200; + public static int MAX_OBJECT_COUNT = 5; + public static int MAX_ARRAY_COUNT = 5; + public static int MAX_QUERY_DEPTH = 5; + + @Override + public int getDefaultQueryCount() { + return DEFAULT_QUERY_COUNT; + } + @Override + public int getMaxQueryPage() { + return MAX_QUERY_PAGE; + } + @Override + public int getMaxQueryCount() { + return MAX_QUERY_COUNT; + } + @Override + public int getMaxUpdateCount() { + return MAX_UPDATE_COUNT; + } + @Override + public int getMaxSQLCount() { + return MAX_SQL_COUNT; + } + @Override + public int getMaxObjectCount() { + return MAX_OBJECT_COUNT; + } + @Override + public int getMaxArrayCount() { + return MAX_ARRAY_COUNT; + } + @Override + public int getMaxQueryDepth() { + return MAX_QUERY_DEPTH; + } + + /** * method = null */ @@ -1276,7 +1319,7 @@ else if (childKeys.length == 1 && JSONRequest.isTableKey(childKeys[0])) { // */ JSONObject fo = i != 0 || arrTableKey == null ? null : parent.getJSONObject(arrTableKey); @SuppressWarnings("unchecked") - List list = fo == null ? null : (List) fo.remove(SQLExecutor.KEY_RAW_LIST); + List list = fo == null ? null : (List) fo.remove(AbstractSQLExecutor.KEY_RAW_LIST); if (list != null && list.isEmpty() == false) { isExtract = false; @@ -1629,43 +1672,6 @@ else if (join != null){ return joinList; } - - - - @Override - public int getDefaultQueryCount() { - return DEFAULT_QUERY_COUNT; - } - @Override - public int getMaxQueryPage() { - return MAX_QUERY_PAGE; - } - @Override - public int getMaxQueryCount() { - return MAX_QUERY_COUNT; - } - @Override - public int getMaxUpdateCount() { - return MAX_UPDATE_COUNT; - } - @Override - public int getMaxSQLCount() { - return MAX_SQL_COUNT; - } - @Override - public int getMaxObjectCount() { - return MAX_OBJECT_COUNT; - } - @Override - public int getMaxArrayCount() { - return MAX_ARRAY_COUNT; - } - @Override - public int getMaxQueryDepth() { - return MAX_QUERY_DEPTH; - } - - /**根据路径取值 * @param parent * @param pathKeys diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java index 2f6b2fba7..c9c9c8623 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java @@ -103,22 +103,21 @@ public abstract class AbstractSQLConfig implements SQLConfig { public static String PREFFIX_DISTINCT = "DISTINCT "; // * 和 / 不能同时出现,防止 /* */ 段注释! # 和 -- 不能出现,防止行注释! ; 不能出现,防止隔断SQL语句!空格不能出现,防止 CRUD,DROP,SHOW TABLES等语句! - private static final Pattern PATTERN_RANGE; - private static final Pattern PATTERN_FUNCTION; + private static Pattern PATTERN_RANGE; + private static Pattern PATTERN_FUNCTION; /** * 表名映射,隐藏真实表名,对安全要求很高的表可以这么做 */ - public static final Map TABLE_KEY_MAP; - public static final List CONFIG_TABLE_LIST; - public static final List DATABASE_LIST; + public static Map TABLE_KEY_MAP; + public static List CONFIG_TABLE_LIST; + public static List DATABASE_LIST; // 自定义原始 SQL 片段 Map:当 substring 为 null 时忽略;当 substring 为 "" 时整个 value 是 raw SQL;其它情况则只是 substring 这段为 raw SQL - public static final Map RAW_MAP; + public static Map RAW_MAP; // 允许调用的 SQL 函数:当 substring 为 null 时忽略;当 substring 为 "" 时整个 value 是 raw SQL;其它情况则只是 substring 这段为 raw SQL - public static final Map SQL_AGGREGATE_FUNCTION_MAP; - public static final Map SQL_FUNCTION_MAP; - + public static Map SQL_AGGREGATE_FUNCTION_MAP; + public static Map SQL_FUNCTION_MAP; static { // 凡是 SQL 边界符、分隔符、注释符 都不允许,例如 ' " ` ( ) ; # -- /**/ ,以免拼接 SQL 时被注入意外可执行指令 PATTERN_RANGE = Pattern.compile("^[0-9%,!=\\<\\>/\\.\\+\\-\\*\\^]+$"); // ^[a-zA-Z0-9_*%!=<>(),"]+$ 导致 exists(select*from(Comment)) 通过! @@ -4339,7 +4338,7 @@ protected void onGetCrossJoinString(Join j) throws UnsupportedOperationException * @return * @throws Exception */ - public static SQLConfig newSQLConfig(RequestMethod method, String table, String alias, JSONObject request, List joinList, boolean isProcedure, Callback callback) throws Exception { + public static SQLConfig newSQLConfig(RequestMethod method, String table, String alias, JSONObject request, List joinList, boolean isProcedure, Callback callback) throws Exception { if (request == null) { // User:{} 这种空内容在查询时也有效 throw new NullPointerException(TAG + ": newSQLConfig request == null!"); } @@ -4357,7 +4356,7 @@ public static SQLConfig newSQLConfig(RequestMethod method, String table, String String schema = request.getString(KEY_SCHEMA); String datasource = request.getString(KEY_DATASOURCE); - SQLConfig config = callback.getSQLConfig(method, database, schema, table); + SQLConfig config = callback.getSQLConfig(method, database, schema, datasource, table); config.setAlias(alias); config.setDatabase(database); //不删,后面表对象还要用的,必须放在 parseJoin 前 @@ -4404,7 +4403,7 @@ public static SQLConfig newSQLConfig(RequestMethod method, String table, String Object id = request.get(idKey); if (id == null && method == POST) { - id = callback.newId(method, database, schema, table); // null 表示数据库自增 id + id = callback.newId(method, database, schema, datasource, table); // null 表示数据库自增 id } if (id != null) { //null无效 @@ -4992,7 +4991,7 @@ else if (newHaving != null) { * @return * @throws Exception */ - public static SQLConfig parseJoin(RequestMethod method, SQLConfig config, List joinList, Callback callback) throws Exception { + public static SQLConfig parseJoin(RequestMethod method, SQLConfig config, List joinList, Callback callback) throws Exception { boolean isQuery = RequestMethod.isQueryMethod(method); config.setKeyPrefix(isQuery && config.isMain() == false); @@ -5203,7 +5202,7 @@ else if (key.endsWith("-")) {//缩减,PUT查询时处理 } - public static interface IdCallback { + public static interface IdCallback { /**为 post 请求新建 id, 只能是 Long 或 String * @param method * @param database @@ -5211,7 +5210,7 @@ public static interface IdCallback { * @param table * @return */ - Object newId(RequestMethod method, String database, String schema, String table); + T newId(RequestMethod method, String database, String schema, String datasource, String table); /**获取主键名 @@ -5231,7 +5230,7 @@ public static interface IdCallback { String getUserIdKey(String database, String schema, String datasource, String table); } - public static interface Callback extends IdCallback { + public static interface Callback extends IdCallback { /**获取 SQLConfig 的实例 * @param method * @param database @@ -5239,7 +5238,7 @@ public static interface Callback extends IdCallback { * @param table * @return */ - SQLConfig getSQLConfig(RequestMethod method, String database, String schema, String table); + SQLConfig getSQLConfig(RequestMethod method, String database, String schema, String datasource, String table); /**combine 里的 key 在 request 中 value 为 null 或不存在,即 request 中缺少用来作为 combine 条件的 key: value * @param combine @@ -5249,12 +5248,23 @@ public static interface Callback extends IdCallback { public void onMissingKey4Combine(String name, JSONObject request, String combine, String item, String key) throws Exception; } - public static abstract class SimpleCallback implements Callback { + public static Long LAST_ID; + static { + LAST_ID = System.currentTimeMillis(); + } + public static abstract class SimpleCallback implements Callback { + @SuppressWarnings("unchecked") @Override - public Object newId(RequestMethod method, String database, String schema, String table) { - return System.currentTimeMillis(); + public T newId(RequestMethod method, String database, String schema, String datasource, String table) { + Long id = System.currentTimeMillis(); + if (id <= LAST_ID) { + id = LAST_ID + 1; // 解决高并发下 id 冲突导致新增记录失败 + } + LAST_ID = id; + + return (T) id; } @Override diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java index 01cdb308d..1095658bc 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java @@ -45,6 +45,8 @@ public abstract class AbstractSQLExecutor implements SQLExecutor { private static final String TAG = "AbstractSQLExecutor"; + public static String KEY_RAW_LIST = "@RAW@LIST"; // 避免和字段命名冲突,不用 $RAW@LIST$ 是因为 $ 会在 fastjson 内部转义,浪费性能 + private int generatedSQLCount = 0; private int cachedSQLCount = 0; private int executedSQLCount = 0; @@ -74,64 +76,66 @@ public long getSqlResultDuration() { return sqlResultDuration; } + /** * 缓存 Map */ protected Map> cacheMap = new HashMap<>(); - /**保存缓存 - * @param sql - * @param list - * @param type + * @param sql key + * @param list value + * @param config 一般主表 SQLConfig 不为 null,JOIN 副表的为 null */ @Override - public void putCache(String sql, List list, int type) { - if (sql == null || list == null) { //空map有效,说明查询过sql了 || list.isEmpty()) { + public void putCache(String sql, List list, SQLConfig config) { + if (sql == null || list == null) { // 空 list 有效,说明查询过 sql 了 || list.isEmpty()) { Log.i(TAG, "saveList sql == null || list == null >> return;"); return; } + cacheMap.put(sql, list); } - /**移除缓存 - * @param sql - * @param type - */ - @Override - public void removeCache(String sql, int type) { - if (sql == null) { - Log.i(TAG, "removeList sql == null >> return;"); - return; - } - cacheMap.remove(sql); - } + /**获取缓存 - * @param sql - * @param type + * @param sql key + * @param config 一般主表 SQLConfig 不为 null,JOIN 副表的为 null */ @Override - public List getCache(String sql, int type) { + public List getCache(String sql, SQLConfig config) { return cacheMap.get(sql); } /**获取缓存 - * @param sql + * @param sql key * @param position - * @param type + * @param config 一般主表 SQLConfig 不为 null,JOIN 副表的为 null * @return */ @Override - public JSONObject getCacheItem(String sql, int position, int type) { - List list = getCache(sql, type); - //只要map不为null,则如果 list.get(position) == null,则返回 {} ,避免再次SQL查询 + public JSONObject getCacheItem(String sql, int position, SQLConfig config) { + List list = getCache(sql, config); + // 只要 list 不为 null,则如果 list.get(position) == null,则返回 {} ,避免再次 SQL 查询 if (list == null) { return null; } - + JSONObject result = position >= list.size() ? null : list.get(position); return result != null ? result : new JSONObject(); } + /**移除缓存 + * @param sql key + * @param config + */ + @Override + public void removeCache(String sql, SQLConfig config) { + if (sql == null) { + Log.i(TAG, "removeList sql == null >> return;"); + return; + } + cacheMap.remove(sql); + } @Override @@ -250,7 +254,7 @@ public JSONObject execute(@NotNull SQLConfig config, boolean unknowType) throws case GETS: case HEAD: case HEADS: - result = isHead || isExplain ? null : getCacheItem(sql, position, config.getCache()); + result = isHead || isExplain ? null : getCacheItem(sql, position, config); Log.i(TAG, ">>> execute result = getCache('" + sql + "', " + position + ") = " + result); if (result != null) { cachedSQLCount ++; @@ -306,7 +310,7 @@ public JSONObject execute(@NotNull SQLConfig config, boolean unknowType) throws capacity = ((Collection) idIn).size(); } else { // 预估容量 - capacity = config.getCount() <= 0 ? Parser.MAX_QUERY_COUNT : config.getCount(); + capacity = config.getCount() <= 0 ? AbstractParser.MAX_QUERY_COUNT : config.getCount(); if (capacity > 100) { // 有 WHERE 条件,条件越多过滤数据越多,暂时不考虑 @combine:"a | (b & !c)" 里面 | OR 和 ! NOT 条件,太复杂也不是很必要 Map> combine = config.getCombineMap(); @@ -594,10 +598,10 @@ else if (curJoin.isOuterJoin() || curJoin.isAntiJoin()) { for (Entry entry : set) { List l = new ArrayList<>(); l.add(entry.getValue()); - putCache(entry.getKey(), l, JSONRequest.CACHE_ROM); + putCache(entry.getKey(), l, null); } - putCache(sql, resultList, config.getCache()); + putCache(sql, resultList, config); Log.i(TAG, ">>> execute putCache('" + sql + "', resultList); resultList.size() = " + resultList.size()); // 数组主表对象额外一次返回全部,方便 Parser 缓存来提高性能 diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractVerifier.java b/APIJSONORM/src/main/java/apijson/orm/AbstractVerifier.java index 0431d1773..f62dfa94d 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractVerifier.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractVerifier.java @@ -73,7 +73,7 @@ * @author Lemon * @param id 与 userId 的类型,一般为 Long */ -public abstract class AbstractVerifier implements Verifier, IdCallback { +public abstract class AbstractVerifier implements Verifier, IdCallback { private static final String TAG = "AbstractVerifier"; /**未登录,不明身份的用户 @@ -102,9 +102,9 @@ public abstract class AbstractVerifier implements Verifier, IdCallback { // 共享 STRUCTURE_MAP 则不能 remove 等做任何变更,否则在并发情况下可能会出错,加锁效率又低,所以这里改为忽略对应的 key - public static final Map> ROLE_MAP; + public static Map> ROLE_MAP; - public static final List OPERATION_KEY_LIST; + public static List OPERATION_KEY_LIST; // > // > @@ -205,9 +205,10 @@ public String getUserIdKey(String database, String schema, String datasource, St return apijson.JSONObject.KEY_USER_ID; } + @SuppressWarnings("unchecked") @Override - public Object newId(RequestMethod method, String database, String schema, String table) { - return System.currentTimeMillis(); + public T newId(RequestMethod method, String database, String schema, String datasource, String table) { + return (T) Long.valueOf(System.currentTimeMillis()); } @@ -437,17 +438,17 @@ public void verifyUseRole(SQLConfig config, String table, RequestMethod method, public void verifyLogin() throws Exception { //未登录没有权限操作 if (visitorId == null) { - throw new NotLoggedInException("未登录,请登录后再操作!"); + throw new NotLoggedInException("未登录或登录超时,请登录后再操作!"); } if (visitorId instanceof Number) { if (((Number) visitorId).longValue() <= 0) { - throw new NotLoggedInException("未登录,请登录后再操作!"); + throw new NotLoggedInException("未登录或登录超时,请登录后再操作!"); } } else if (visitorId instanceof String) { if (StringUtil.isEmpty(visitorId, true)) { - throw new NotLoggedInException("未登录,请登录后再操作!"); + throw new NotLoggedInException("未登录或登录超时,请登录后再操作!"); } } else { @@ -539,7 +540,7 @@ public JSONObject verifyRequest(@NotNull final RequestMethod method, final Strin */ public static JSONObject verifyRequest(@NotNull final RequestMethod method, final String name , final JSONObject target, final JSONObject request, final SQLCreator creator) throws Exception { - return verifyRequest(method, name, target, request, Parser.MAX_UPDATE_COUNT, creator); + return verifyRequest(method, name, target, request, AbstractParser.MAX_UPDATE_COUNT, creator); } /**从request提取target指定的内容 * @param method @@ -568,9 +569,9 @@ public static JSONObject verifyRequest(@NotNull final RequestMethod method, fina * @return * @throws Exception */ - public static JSONObject verifyRequest(@NotNull final RequestMethod method, final String name + public static JSONObject verifyRequest(@NotNull final RequestMethod method, final String name , final JSONObject target, final JSONObject request, final int maxUpdateCount - , final String database, final String schema, final IdCallback idCallback, final SQLCreator creator) throws Exception { + , final String database, final String schema, final IdCallback idCallback, final SQLCreator creator) throws Exception { return verifyRequest(method, name, target, request, maxUpdateCount, database, schema, null, idCallback, creator); } /**从request提取target指定的内容 @@ -585,9 +586,9 @@ public static JSONObject verifyRequest(@NotNull final RequestMethod method, fina * @return * @throws Exception */ - public static JSONObject verifyRequest(@NotNull final RequestMethod method, final String name + public static JSONObject verifyRequest(@NotNull final RequestMethod method, final String name , final JSONObject target, final JSONObject request, final int maxUpdateCount - , final String database, final String schema, final String datasource, final IdCallback idCallback, final SQLCreator creator) throws Exception { + , final String database, final String schema, final String datasource, final IdCallback idCallback, final SQLCreator creator) throws Exception { Log.i(TAG, "verifyRequest method = " + method + "; name = " + name + "; target = \n" + JSON.toJSONString(target) @@ -782,9 +783,9 @@ public static JSONObject verifyResponse(@NotNull final RequestMethod method, fin * @return * @throws Exception */ - public static JSONObject verifyResponse(@NotNull final RequestMethod method, final String name + public static JSONObject verifyResponse(@NotNull final RequestMethod method, final String name , final JSONObject target, final JSONObject response, final String database, final String schema - , final IdCallback idKeyCallback, SQLCreator creator, OnParseCallback callback) throws Exception { + , final IdCallback idKeyCallback, SQLCreator creator, OnParseCallback callback) throws Exception { Log.i(TAG, "verifyResponse method = " + method + "; name = " + name + "; target = \n" + JSON.toJSONString(target) @@ -832,8 +833,8 @@ public static JSONObject parse(@NotNull final RequestMethod method, String name, * @return * @throws Exception */ - public static JSONObject parse(@NotNull final RequestMethod method, String name, JSONObject target, JSONObject real - , final String database, final String schema, final IdCallback idCallback, SQLCreator creator, @NotNull OnParseCallback callback) throws Exception { + public static JSONObject parse(@NotNull final RequestMethod method, String name, JSONObject target, JSONObject real + , final String database, final String schema, final IdCallback idCallback, SQLCreator creator, @NotNull OnParseCallback callback) throws Exception { return parse(method, name, target, real, database, schema, null, idCallback, creator, callback); } /**对request和response不同的解析用callback返回 @@ -850,8 +851,8 @@ public static JSONObject parse(@NotNull final RequestMethod method, String name, * @return * @throws Exception */ - public static JSONObject parse(@NotNull final RequestMethod method, String name, JSONObject target, JSONObject real - , final String database, final String schema, final String datasource, final IdCallback idCallback, SQLCreator creator, @NotNull OnParseCallback callback) throws Exception { + public static JSONObject parse(@NotNull final RequestMethod method, String name, JSONObject target, JSONObject real + , final String database, final String schema, final String datasource, final IdCallback idCallback, SQLCreator creator, @NotNull OnParseCallback callback) throws Exception { if (target == null) { return null; } diff --git a/APIJSONORM/src/main/java/apijson/orm/Parser.java b/APIJSONORM/src/main/java/apijson/orm/Parser.java index db98ea1f5..50b86b5f5 100755 --- a/APIJSONORM/src/main/java/apijson/orm/Parser.java +++ b/APIJSONORM/src/main/java/apijson/orm/Parser.java @@ -17,18 +17,8 @@ /**解析器 * @author Lemon */ -public interface Parser { - - int DEFAULT_QUERY_COUNT = 10; - int MAX_QUERY_PAGE = 100; - int MAX_QUERY_COUNT = 100; - int MAX_UPDATE_COUNT = 10; - int MAX_SQL_COUNT = 200; - int MAX_OBJECT_COUNT = 5; - int MAX_ARRAY_COUNT = 5; - int MAX_QUERY_DEPTH = 5; - - +public interface Parser { + @NotNull Visitor getVisitor(); Parser setVisitor(@NotNull Visitor visitor); diff --git a/APIJSONORM/src/main/java/apijson/orm/ParserCreator.java b/APIJSONORM/src/main/java/apijson/orm/ParserCreator.java index 2af2767c3..4da410b46 100755 --- a/APIJSONORM/src/main/java/apijson/orm/ParserCreator.java +++ b/APIJSONORM/src/main/java/apijson/orm/ParserCreator.java @@ -10,7 +10,7 @@ /**SQL相关创建器 * @author Lemon */ -public interface ParserCreator { +public interface ParserCreator { @NotNull Parser createParser(); diff --git a/APIJSONORM/src/main/java/apijson/orm/SQLExecutor.java b/APIJSONORM/src/main/java/apijson/orm/SQLExecutor.java index 3cb2bf60a..7e700c269 100755 --- a/APIJSONORM/src/main/java/apijson/orm/SQLExecutor.java +++ b/APIJSONORM/src/main/java/apijson/orm/SQLExecutor.java @@ -22,31 +22,34 @@ */ public interface SQLExecutor { - String KEY_RAW_LIST = "@RAW@LIST"; // 避免和字段命名冲突,不用 $RAW@LIST$ 是因为 $ 会在 fastjson 内部转义,浪费性能 - /**保存缓存 * @param sql - * @param map - * @param type + * @param list + * @param config */ - void putCache(String sql, List list, int type);; - - List getCache(String sql, int type); + void putCache(String sql, List list, SQLConfig config); - /**移除缓存 + /**获取缓存 * @param sql - * @param type + * @param config + * @return */ - void removeCache(String sql, int type); + List getCache(String sql, SQLConfig config); + /**获取缓存 * @param sql * @param position - * @param type + * @param config * @return */ - JSONObject getCacheItem(String sql, int position, int type); - + JSONObject getCacheItem(String sql, int position, SQLConfig config); + /**移除缓存 + * @param sql + * @param config + */ + void removeCache(String sql, SQLConfig config); + /**执行SQL * @param config * @return diff --git a/APIJSONORM/src/main/java/apijson/orm/Subquery.java b/APIJSONORM/src/main/java/apijson/orm/Subquery.java index 66fdf14df..de8603b6d 100644 --- a/APIJSONORM/src/main/java/apijson/orm/Subquery.java +++ b/APIJSONORM/src/main/java/apijson/orm/Subquery.java @@ -18,7 +18,7 @@ public class Subquery { private JSONObject originValue; // { "from": "Comment", "Comment": {...} } private String from; // Comment - private String range; // any, all + private String range; // ANY, ALL private String key; //id{} private SQLConfig config; diff --git a/APIJSONORM/src/main/java/apijson/orm/VerifierCreator.java b/APIJSONORM/src/main/java/apijson/orm/VerifierCreator.java index 735b1104d..0caa6f8b1 100644 --- a/APIJSONORM/src/main/java/apijson/orm/VerifierCreator.java +++ b/APIJSONORM/src/main/java/apijson/orm/VerifierCreator.java @@ -10,7 +10,7 @@ /**验证器相关创建器 * @author Lemon */ -public interface VerifierCreator { +public interface VerifierCreator { @NotNull Verifier createVerifier(); From 0e2b645465a3189e467aa7be4575d18cd1a16d10 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Mon, 2 May 2022 00:39:16 +0800 Subject: [PATCH 0640/1181] =?UTF-8?q?=E4=BC=98=E5=8C=96=E7=99=BB=E5=BD=95?= =?UTF-8?q?=E6=8A=A5=E9=94=99=E5=92=8C=E8=BF=9C=E7=A8=8B=E5=87=BD=E6=95=B0?= =?UTF-8?q?=E6=89=A7=E8=A1=8C=E6=8A=A5=E9=94=99=E7=9A=84=E6=8F=90=E7=A4=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/main/java/apijson/orm/AbstractFunctionParser.java | 6 ++++-- APIJSONORM/src/main/java/apijson/orm/AbstractVerifier.java | 6 +++--- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractFunctionParser.java b/APIJSONORM/src/main/java/apijson/orm/AbstractFunctionParser.java index cde0a6228..795062d7a 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractFunctionParser.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractFunctionParser.java @@ -16,6 +16,7 @@ import com.alibaba.fastjson.JSONArray; import com.alibaba.fastjson.JSONObject; +import apijson.Log; import apijson.NotNull; import apijson.RequestMethod; import apijson.StringUtil; @@ -178,7 +179,7 @@ public static Object invoke(@NotNull AbstractFunctionParser parser, @NotNull Str + "\n请检查函数名和参数数量是否与已定义的函数一致!" + "\n且必须为 function(key0,key1,...) 这种单函数格式!" + "\nfunction必须符合Java函数命名,key是用于在request内取值的键!" - + "\n调用时不要有空格!" + e.getMessage()); + + "\n调用时不要有空格!" + (Log.DEBUG ? e.getMessage() : "")); } if (e instanceof InvocationTargetException) { Throwable te = ((InvocationTargetException) e).getTargetException(); @@ -186,7 +187,8 @@ public static Object invoke(@NotNull AbstractFunctionParser parser, @NotNull Str throw te instanceof Exception ? (Exception) te : new Exception(te.getMessage()); } throw new IllegalArgumentException("字符 " + function + " 对应的远程函数传参类型错误!" - + "\n请检查 key:value 中value的类型是否满足已定义的函数 " + getFunction(fb.getMethod(), fb.getKeys()) + " 的要求!" + e.getMessage()); + + "\n请检查 key:value 中value的类型是否满足已定义的函数 " + getFunction(fb.getMethod(), fb.getKeys()) + " 的要求!" + + (Log.DEBUG ? e.getMessage() : "")); } throw e; } diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractVerifier.java b/APIJSONORM/src/main/java/apijson/orm/AbstractVerifier.java index f62dfa94d..c46b055bd 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractVerifier.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractVerifier.java @@ -438,17 +438,17 @@ public void verifyUseRole(SQLConfig config, String table, RequestMethod method, public void verifyLogin() throws Exception { //未登录没有权限操作 if (visitorId == null) { - throw new NotLoggedInException("未登录或登录超时,请登录后再操作!"); + throw new NotLoggedInException("未登录或登录过期,请登录后再操作!"); } if (visitorId instanceof Number) { if (((Number) visitorId).longValue() <= 0) { - throw new NotLoggedInException("未登录或登录超时,请登录后再操作!"); + throw new NotLoggedInException("未登录或登录过期,请登录后再操作!"); } } else if (visitorId instanceof String) { if (StringUtil.isEmpty(visitorId, true)) { - throw new NotLoggedInException("未登录或登录超时,请登录后再操作!"); + throw new NotLoggedInException("未登录或登录过期,请登录后再操作!"); } } else { From 92c396ced2d13756487444cdeedf4f7b5967d053 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Mon, 2 May 2022 05:04:33 +0800 Subject: [PATCH 0641/1181] =?UTF-8?q?=E8=AF=B7=E6=B1=82=E5=8F=82=E6=95=B0?= =?UTF-8?q?=E6=A0=A1=E9=AA=8C=EF=BC=9AREFUSE=20=E6=96=B0=E5=A2=9E=E6=94=AF?= =?UTF-8?q?=E6=8C=81=20!key=20=E6=8E=92=E9=99=A4=E7=A6=81=E6=AD=A2?= =?UTF-8?q?=E5=AD=97=E6=AE=B5=EF=BC=8C=E4=BC=98=E5=8C=96=20MUST=20?= =?UTF-8?q?=E5=92=8C=20REFUSE=20=E5=A4=84=E7=90=86=E6=80=A7=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/apijson/orm/AbstractVerifier.java | 93 +++++++++++++------ 1 file changed, 65 insertions(+), 28 deletions(-) diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractVerifier.java b/APIJSONORM/src/main/java/apijson/orm/AbstractVerifier.java index c46b055bd..4b4c08b2c 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractVerifier.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractVerifier.java @@ -103,7 +103,7 @@ public abstract class AbstractVerifier implements Verifier, // 共享 STRUCTURE_MAP 则不能 remove 等做任何变更,否则在并发情况下可能会出错,加锁效率又低,所以这里改为忽略对应的 key public static Map> ROLE_MAP; - + public static List OPERATION_KEY_LIST; // > @@ -129,7 +129,7 @@ public abstract class AbstractVerifier implements Verifier, ROLE_MAP.put(CIRCLE, new Entry("userId-()", "verifyCircle()")); // "userId{}", "circleIdList")); // 还是 {"userId":"currentUserId", "userId{}": "contactIdList", "@combine": "userId,userId{}" } ? ROLE_MAP.put(OWNER, new Entry("userId", "userId")); ROLE_MAP.put(ADMIN, new Entry("userId-()", "verifyAdmin()")); - + OPERATION_KEY_LIST = new ArrayList<>(); OPERATION_KEY_LIST.add(TYPE.name()); OPERATION_KEY_LIST.add(VERIFY.name()); @@ -204,7 +204,7 @@ public String getIdKey(String database, String schema, String datasource, String public String getUserIdKey(String database, String schema, String datasource, String table) { return apijson.JSONObject.KEY_USER_ID; } - + @SuppressWarnings("unchecked") @Override public T newId(RequestMethod method, String database, String schema, String datasource, String table) { @@ -247,7 +247,7 @@ public boolean verifyAccess(SQLConfig config) throws Exception { if (table == null) { return true; } - + String role = config.getRole(); if (role == null) { role = UNKNOWN; @@ -265,10 +265,10 @@ public boolean verifyAccess(SQLConfig config) throws Exception { RequestMethod method = config.getMethod(); verifyRole(config, table, method, role); - + return true; } - + @Override public void verifyRole(SQLConfig config, String table, RequestMethod method, String role) throws Exception { verifyAllowRole(config, table, method, role); //验证允许的角色 @@ -289,7 +289,7 @@ public void verifyAllowRole(SQLConfig config, String table, RequestMethod method if (table == null) { table = config == null ? null : config.getTable(); } - + if (table != null) { if (method == null) { method = config == null ? GET : config.getMethod(); @@ -297,7 +297,7 @@ public void verifyAllowRole(SQLConfig config, String table, RequestMethod method if (role == null) { role = config == null ? UNKNOWN : config.getRole(); } - + Map map = ACCESS_MAP.get(table); if (map == null || Arrays.asList(map.get(method)).contains(role) == false) { @@ -329,7 +329,7 @@ public void verifyUseRole(SQLConfig config, String table, RequestMethod method, if (role == null) { role = config == null ? UNKNOWN : config.getRole(); } - + Object requestId; switch (role) { case LOGIN://verifyRole通过就行 @@ -882,11 +882,15 @@ public static JSONObject parse(@NotNull final RequestMethod m // 判断必要字段是否都有<<<<<<<<<<<<<<<<<<< String[] musts = StringUtil.split(must); - List mustList = musts == null ? new ArrayList() : Arrays.asList(musts); - for (String s : mustList) { - if (real.get(s) == null) { // 可能传null进来,这里还会通过 real.containsKey(s) == false) { - throw new IllegalArgumentException(method + "请求," + name - + " 里面不能缺少 " + s + " 等[" + must + "]内的任何字段!"); + Set mustSet = new HashSet(); + + if (musts != null && musts.length > 0) { + for (String s : musts) { + if (real.get(s) == null) { // 可能传null进来,这里还会通过 real.containsKey(s) == false) { + throw new IllegalArgumentException(method + "请求," + name + " 里面不能缺少 " + s + " 等[" + must + "]内的任何字段!"); + } + + mustSet.add(s); } } //判断必要字段是否都有>>>>>>>>>>>>>>>>>>> @@ -947,28 +951,61 @@ public static JSONObject parse(@NotNull final RequestMethod m Set rkset = real.keySet(); //解析内容并没有改变rkset //解析不允许的字段<<<<<<<<<<<<<<<<<<< - List refuseList = new ArrayList(); - if ("!".equals(refuse)) {//所有非 must,改成 !must 更好 - for (String key : rkset) {//对@key放行,@role,@column,自定义@position等 - if (key != null && key.startsWith("@") == false - && mustList.contains(key) == false && objKeySet.contains(key) == false) { - refuseList.add(key); + String[] refuses = StringUtil.split(refuse); + Set refuseSet = new HashSet(); + + if (refuses != null && refuses.length > 0) { + Set notRefuseSet = new HashSet(); + + for (String rfs : refuses) { + if (rfs == null) { // StringUtil.isEmpty(rfs, true) { + continue; + } + + if (rfs.startsWith("!")) { + rfs = rfs.substring(1); + + if (notRefuseSet.contains(rfs)) { + throw new ConflictException(REFUSE.name() + ":value 中出现了重复的 !" + rfs + " !不允许重复,也不允许一个 key 和取反 !key 同时使用!"); + } + if (refuseSet.contains(rfs)) { + throw new ConflictException(REFUSE.name() + ":value 中同时出现了 " + rfs + " 和 !" + rfs + " !不允许重复,也不允许一个 key 和取反 !key 同时使用!"); + } + + if (rfs.equals("")) { // 所有非 MUST + for (String key : rkset) { // 对@key放行,@role,@column,自定义@position等, @key:{ "Table":{} } 不会解析内部 + if (key == null || key.startsWith("@") || notRefuseSet.contains(key) || mustSet.contains(key) || objKeySet.contains(key)) { + continue; + } + + refuseSet.add(key); + } + } + else { // 排除 !key 后再禁传其它的 + notRefuseSet.add(rfs); + } + } + else { + if (refuseSet.contains(rfs)) { + throw new ConflictException(REFUSE.name() + ":value 中出现了重复的 " + rfs + " !不允许重复,也不允许一个 key 和取反 !key 同时使用!"); + } + if (notRefuseSet.contains(rfs)) { + throw new ConflictException(REFUSE.name() + ":value 中同时出现了 " + rfs + " 和 !" + rfs + " !不允许重复,也不允许一个 key 和取反 !key 同时使用!"); + } + + refuseSet.add(rfs); } - } - } else { - String[] refuses = StringUtil.split(refuse); - if (refuses != null && refuses.length > 0) { - refuseList.addAll(Arrays.asList(refuses)); } } + //解析不允许的字段>>>>>>>>>>>>>>>>>>> //判断不允许传的key<<<<<<<<<<<<<<<<<<<<<<<<< for (String rk : rkset) { - if (refuseList.contains(rk)) { //不允许的字段 + if (refuseSet.contains(rk)) { //不允许的字段 throw new IllegalArgumentException(method + "请求," + name - + " 里面不允许传 " + rk + " 等" + StringUtil.getString(refuseList) + "内的任何字段!"); + + " 里面不允许传 " + rk + " 等" + StringUtil.getString(refuseSet) + "内的任何字段!"); } if (rk == null) { //无效的key @@ -1391,7 +1428,7 @@ private static void verifyCondition(@NotNull String funChar, @NotNull JSONObject } finally { executor.close(); } - + if (result != null && JSONResponse.isExist(result.getIntValue(JSONResponse.KEY_COUNT)) == false) { throw new IllegalArgumentException(rk + ":value 中value不合法!必须匹配 '" + tk + "': '" + tv + "' !"); } From f442543f7a1f21eae2444da187dbf72a9909db38 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Wed, 4 May 2022 01:08:08 +0800 Subject: [PATCH 0642/1181] =?UTF-8?q?=E8=A7=A3=E5=86=B3=E9=A2=84=E4=BC=B0?= =?UTF-8?q?=E5=AE=B9=E9=87=8F=E5=88=A4=E6=96=AD=20NOT=20=E6=9D=A1=E4=BB=B6?= =?UTF-8?q?=E7=94=A8=E9=94=99=E9=80=BB=E8=BE=91=20key?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java index 1095658bc..cd705a88a 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java @@ -321,7 +321,7 @@ public JSONObject execute(@NotNull SQLConfig config, boolean unknowType) throws List orList = combine == null ? null : combine.get("|"); int orCondCount = orList == null ? 0 : orList.size(); - List notList = combine == null ? null : combine.get("|"); + List notList = combine == null ? null : combine.get("!"); int notCondCount = notList == null ? 0 : notList.size(); // 有 GROUP BY 分组,字段越少过滤数据越多 From 95432dde2c7a1f5147f9fe2f77f5500d33c651d0 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Wed, 4 May 2022 01:52:49 +0800 Subject: [PATCH 0643/1181] =?UTF-8?q?=E5=8D=87=E7=BA=A7=E7=89=88=E6=9C=AC?= =?UTF-8?q?=E5=8F=B7=E4=B8=BA=205.1.0=EF=BC=9B=E5=88=A0=E9=99=A4=E4=B8=8D?= =?UTF-8?q?=E5=86=8D=E9=9C=80=E8=A6=81=E7=9A=84=E4=BE=9D=E8=B5=96=20javax.?= =?UTF-8?q?activation?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- APIJSONORM/pom.xml | 7 +------ APIJSONORM/src/main/java/apijson/Log.java | 2 +- 2 files changed, 2 insertions(+), 7 deletions(-) diff --git a/APIJSONORM/pom.xml b/APIJSONORM/pom.xml index 187aa087f..63d7086c7 100755 --- a/APIJSONORM/pom.xml +++ b/APIJSONORM/pom.xml @@ -5,7 +5,7 @@ apijson.orm apijson-orm - 5.0.5 + 5.1.0 jar APIJSONORM @@ -23,11 +23,6 @@ fastjson 1.2.79 - - javax.activation - activation - 1.1.1 - diff --git a/APIJSONORM/src/main/java/apijson/Log.java b/APIJSONORM/src/main/java/apijson/Log.java index 9c2c60a98..f3328c768 100755 --- a/APIJSONORM/src/main/java/apijson/Log.java +++ b/APIJSONORM/src/main/java/apijson/Log.java @@ -14,7 +14,7 @@ public class Log { public static boolean DEBUG = true; - public static final String VERSION = "5.0.0"; + public static final String VERSION = "5.1.0"; public static final String KEY_SYSTEM_INFO_DIVIDER = "---|-----APIJSON SYSTEM INFO-----|---"; //默认的时间格式 From 9f724c171978c89f52bff957d208b23abf9b2f65 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Wed, 4 May 2022 06:14:40 +0800 Subject: [PATCH 0644/1181] =?UTF-8?q?=E8=BF=98=E5=8E=9F=E4=BE=9D=E8=B5=96?= =?UTF-8?q?=20javax.activation=EF=BC=8C=E5=AE=9E=E6=B5=8B=20JDK=2011,=2013?= =?UTF-8?q?=20=E9=83=BD=E9=9C=80=E8=A6=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 否则 APIJSONBoot DemoApplication 第 95 行报错 NoClassDefFoundError static { Map COMPILE_MAP = AbstractVerifier.COMPILE_MAP; ... } Exception in thread "main" java.lang.NoClassDefFoundError: javax/activation/UnsupportedDataTypeException https://github.com/APIJSON/APIJSON-Demo/blob/master/APIJSON-Java-Server/APIJSONBoot/src/main/java/apijson/boot/DemoApplication.java#L95 --- APIJSONORM/pom.xml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/APIJSONORM/pom.xml b/APIJSONORM/pom.xml index 63d7086c7..c1bdf23dc 100755 --- a/APIJSONORM/pom.xml +++ b/APIJSONORM/pom.xml @@ -23,6 +23,11 @@ fastjson 1.2.79 + + javax.activation + activation + 1.1.1 + From ffdc33229dd044a7a22681e88e37a4942415c3c8 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Sat, 7 May 2022 22:47:53 +0800 Subject: [PATCH 0645/1181] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index c8574d809..236292a55 100644 --- a/README.md +++ b/README.md @@ -460,7 +460,7 @@ https://github.com/Tencent/APIJSON/blob/master/CONTRIBUTING.md [apijson-framework](https://github.com/APIJSON/apijson-framework) APIJSON 服务端框架,通过数据库表配置角色权限、参数校验等,简化使用 -[apijson-router](https://github.com/APIJSON/apijson-router) APIJSON 的路由插件,对外暴露类 RESTful 接口,内部转成 APIJSON 接口执行 +[apijson-router](https://github.com/APIJSON/apijson-router) APIJSON 的路由插件,可控地对公网暴露类 RESTful 简单接口,内部转成 APIJSON 格式请求来执行。 [apijson-column](https://github.com/APIJSON/apijson-column) APIJSON 的字段插件,支持 字段名映射 和 !key 反选字段 From be268dc5a028a9f8fe0148e7b3bd52dcbc8590e6 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Sun, 8 May 2022 01:22:38 +0800 Subject: [PATCH 0646/1181] Update README.md --- README.md | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/README.md b/README.md index 236292a55..5b36395a1 100644 --- a/README.md +++ b/README.md @@ -175,6 +175,31 @@ https://github.com/Tencent/APIJSON/wiki * **工程轻量小巧** (仅依赖 fastjson,Jar 仅 280KB,Java 文件仅 59 个共 13719 行代码,例如 APIJSONORM 4.3.1) * **多年持续迭代** (自 2016 年开源至今已连续维护 5 年多,累计 2000+ Commits、80+ Releases,不断更新迭代中...) +![image](https://user-images.githubusercontent.com/5738175/167264836-9c5d8f8a-99e1-4e1e-9864-e8f906b8e704.png) + +### 用户反馈 +**腾讯 IEG 数据产品开发组负责人 xinlin:** +“腾讯的 APIJSON 开源方案,它可以做到零代码生成接口和文档,并且整个生成过程是自动化。当企业有元数据的时候,马上就可以获得接口” + +**腾讯科技 后台开发高级工程师 雷大锤:** +“可以抽出时间来看apijson了,这个可以为T10做准备,也是业界很火的东西,可以提升个人影响力!” + +**腾讯 bodian520:** +“在调试GET、POST、PUT接口时遇到了一些问题,把个人的摸索经验分享一下,希望作者能梳理下文档,方便我们更好的接入” + +**华为 minshiwu:** +“demo工程,默认使用apijson-framework,可以做到无任何配置即可体验apijson的各种能力。” + +**百度智慧城市研发 lpeng:** +“很兴奋的发现APIJSON很适合我们的一个开发场景,作为我们协议定义的一部分” + +**中兴工程师 duyijiang:** +“感谢腾讯大大提供的框架,很好用” + +https://github.com/Tencent/APIJSON/issues/132#issuecomment-1106669540 + +
+ ### 常见问题 #### 1.如何定制业务逻辑? 在后端编写 远程函数,可以拿到 session、version、当前 JSON 对象、参数名称 等,然后对查到的数据自定义处理
From 19706c6fa5eef31bfd3854c0ea475945638c2954 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Sun, 8 May 2022 01:25:55 +0800 Subject: [PATCH 0647/1181] =?UTF-8?q?=E7=9B=B8=E5=85=B3=E6=8E=A8=E8=8D=90?= =?UTF-8?q?=E6=96=B0=E5=A2=9E=20=E8=85=BE=E8=AE=AF=E4=B8=9A=E5=8A=A1?= =?UTF-8?q?=E7=99=BE=E4=B8=87=E6=95=B0=E6=8D=AE=206s=20=E5=93=8D=E5=BA=94?= =?UTF-8?q?=EF=BC=8CAPIJSON=20=E6=80=A7=E8=83=BD=E4=BC=98=E5=8C=96?= =?UTF-8?q?=E8=83=8C=E5=90=8E=E7=9A=84=E6=95=85=E4=BA=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit https://my.oschina.net/tommylemon/blog/5375645 --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 5b36395a1..f37bea636 100644 --- a/README.md +++ b/README.md @@ -435,6 +435,8 @@ https://github.com/Tencent/APIJSON/blob/master/CONTRIBUTING.md [APIJSON对接分布式HTAP数据库TiDB](https://asktug.com/t/htap-tidb/395) +[腾讯业务百万数据 6s 响应,APIJSON 性能优化背后的故事](https://my.oschina.net/tommylemon/blog/5375645) + [APIJSON教程(一):上手apijson项目,学习apijson语法,并实现持久层配置](https://zhuanlan.zhihu.com/p/375681893) [apijson简单demo](https://blog.csdn.net/dmw412724/article/details/113558115) From beac8231a65899741f3a973524253ffe75da8509 Mon Sep 17 00:00:00 2001 From: Montos <1367654518@qq.com> Date: Tue, 10 May 2022 00:20:06 +0800 Subject: [PATCH 0648/1181] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E5=AF=B9LocalDateT?= =?UTF-8?q?ime=E7=B1=BB=E5=9E=8B=E6=94=AF=E6=8C=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 增加对LocalDateTime类型支持 --- APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java index cd705a88a..a02bd79b0 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java @@ -19,6 +19,7 @@ import java.sql.Statement; import java.sql.Time; import java.sql.Timestamp; +import java.time.LocalDateTime; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; @@ -879,6 +880,9 @@ else if (value instanceof Date) { else if (value instanceof Time) { value = ((Time) value).toString(); } + else if (value instanceof LocalDateTime) { + value = ((LocalDateTime) value).toString(); + } else if (value instanceof String && isJSONType(config, rsmd, columnIndex, lable)) { //json String castToJson = true; } From 89accdacdc24fabaa6c9527931fe475cccedb71b Mon Sep 17 00:00:00 2001 From: ysy Date: Sun, 29 May 2022 11:20:48 +0800 Subject: [PATCH 0649/1181] fastjson up2 2.0.4 --- APIJSONORM/pom.xml | 4 ++-- APIJSONORM/src/main/java/apijson/JSON.java | 19 +++++++++++-------- 2 files changed, 13 insertions(+), 10 deletions(-) diff --git a/APIJSONORM/pom.xml b/APIJSONORM/pom.xml index c1bdf23dc..3f776fb6a 100755 --- a/APIJSONORM/pom.xml +++ b/APIJSONORM/pom.xml @@ -5,7 +5,7 @@ apijson.orm apijson-orm - 5.1.0 + 5.1.0-F2 jar APIJSONORM @@ -21,7 +21,7 @@ com.alibaba fastjson - 1.2.79 + 2.0.4 javax.activation diff --git a/APIJSONORM/src/main/java/apijson/JSON.java b/APIJSONORM/src/main/java/apijson/JSON.java index 28c124cab..a5b501ead 100755 --- a/APIJSONORM/src/main/java/apijson/JSON.java +++ b/APIJSONORM/src/main/java/apijson/JSON.java @@ -64,18 +64,20 @@ public static String getCorrectJson(String s, boolean isArray) { * @param json * @return */ + private static final Feature[] DEFAULT_FASTJSON_FEATURES = {Feature.OrderedField, Feature.AllowSingleQuotes, Feature.DisableCircularReferenceDetect, Feature.UseBigDecimal, Feature.UseObjectArray}; public static Object parse(Object obj) { int features = com.alibaba.fastjson.JSON.DEFAULT_PARSER_FEATURE; features |= Feature.OrderedField.getMask(); try { - return com.alibaba.fastjson.JSON.parse(obj instanceof String ? (String) obj : toJSONString(obj), features); + return com.alibaba.fastjson.JSON.parse(obj instanceof String ? (String) obj : toJSONString(obj), DEFAULT_FASTJSON_FEATURES); } catch (Exception e) { Log.i(TAG, "parse catch \n" + e.getMessage()); } return null; } + /**obj转JSONObject - * @param json + * @param obj * @return */ public static JSONObject parseObject(Object obj) { @@ -89,16 +91,17 @@ public static JSONObject parseObject(Object obj) { * @return */ public static JSONObject parseObject(String json) { - int features = com.alibaba.fastjson.JSON.DEFAULT_PARSER_FEATURE; - features |= Feature.OrderedField.getMask(); - return parseObject(json, features); + return parseObject(json, DEFAULT_FASTJSON_FEATURES); } - /**json转JSONObject + + /** + * json转JSONObject + * * @param json * @param features * @return */ - public static JSONObject parseObject(String json, int features) { + public static JSONObject parseObject(String json, Feature... features) { try { return com.alibaba.fastjson.JSON.parseObject(getCorrectJson(json), JSONObject.class, features); } catch (Exception e) { @@ -127,7 +130,7 @@ public static T parseObject(String json, Class clazz) { try { int features = com.alibaba.fastjson.JSON.DEFAULT_PARSER_FEATURE; features |= Feature.OrderedField.getMask(); - return com.alibaba.fastjson.JSON.parseObject(getCorrectJson(json), clazz, features); + return com.alibaba.fastjson.JSON.parseObject(getCorrectJson(json), clazz, DEFAULT_FASTJSON_FEATURES); } catch (Exception e) { Log.i(TAG, "parseObject catch \n" + e.getMessage()); } From 641d9409413b9b8c0cb4bba03bdefce18c25374d Mon Sep 17 00:00:00 2001 From: ysy Date: Sun, 29 May 2022 12:33:57 +0800 Subject: [PATCH 0650/1181] fix dead loop --- APIJSONORM/src/main/java/apijson/JSON.java | 2 +- .../apijson/orm/AbstractObjectParser.java | 40 +++++++++---------- 2 files changed, 20 insertions(+), 22 deletions(-) diff --git a/APIJSONORM/src/main/java/apijson/JSON.java b/APIJSONORM/src/main/java/apijson/JSON.java index a5b501ead..09dcd058d 100755 --- a/APIJSONORM/src/main/java/apijson/JSON.java +++ b/APIJSONORM/src/main/java/apijson/JSON.java @@ -64,7 +64,7 @@ public static String getCorrectJson(String s, boolean isArray) { * @param json * @return */ - private static final Feature[] DEFAULT_FASTJSON_FEATURES = {Feature.OrderedField, Feature.AllowSingleQuotes, Feature.DisableCircularReferenceDetect, Feature.UseBigDecimal, Feature.UseObjectArray}; + private static final Feature[] DEFAULT_FASTJSON_FEATURES = {Feature.OrderedField, Feature.AllowSingleQuotes, Feature.UseBigDecimal, Feature.UseObjectArray}; public static Object parse(Object obj) { int features = com.alibaba.fastjson.JSON.DEFAULT_PARSER_FEATURE; features |= Feature.OrderedField.getMask(); diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractObjectParser.java b/APIJSONORM/src/main/java/apijson/orm/AbstractObjectParser.java index bdbb366e7..a034d0e81 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractObjectParser.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractObjectParser.java @@ -5,13 +5,19 @@ package apijson.orm; -import static apijson.JSONObject.KEY_COMBINE; -import static apijson.JSONObject.KEY_DROP; -import static apijson.JSONObject.KEY_TRY; -import static apijson.RequestMethod.POST; -import static apijson.RequestMethod.PUT; -import static apijson.orm.SQLConfig.TYPE_ITEM; +import apijson.JSONResponse; +import apijson.Log; +import apijson.NotNull; +import apijson.RequestMethod; +import apijson.StringUtil; +import apijson.orm.AbstractFunctionParser.FunctionBean; +import apijson.orm.exception.ConflictException; +import apijson.orm.exception.NotExistException; +import com.alibaba.fastjson.JSON; +import com.alibaba.fastjson.JSONArray; +import com.alibaba.fastjson.JSONObject; +import javax.activation.UnsupportedDataTypeException; import java.rmi.ServerException; import java.util.ArrayList; import java.util.Arrays; @@ -22,20 +28,12 @@ import java.util.Map.Entry; import java.util.Set; -import javax.activation.UnsupportedDataTypeException; - -import com.alibaba.fastjson.JSON; -import com.alibaba.fastjson.JSONArray; -import com.alibaba.fastjson.JSONObject; - -import apijson.JSONResponse; -import apijson.Log; -import apijson.NotNull; -import apijson.RequestMethod; -import apijson.StringUtil; -import apijson.orm.AbstractFunctionParser.FunctionBean; -import apijson.orm.exception.ConflictException; -import apijson.orm.exception.NotExistException; +import static apijson.JSONObject.KEY_COMBINE; +import static apijson.JSONObject.KEY_DROP; +import static apijson.JSONObject.KEY_TRY; +import static apijson.RequestMethod.POST; +import static apijson.RequestMethod.PUT; +import static apijson.orm.SQLConfig.TYPE_ITEM; /**简化Parser,getObject和getArray(getArrayConfig)都能用 @@ -572,7 +570,7 @@ public JSON onChildParse(int index, String key, JSONObject value) throws Excepti invalidate(); } } - Log.i(TAG, "onChildParse ObjectParser.onParse key = " + key + "; child = " + child); +// Log.i(TAG, "onChildParse ObjectParser.onParse key = " + key + "; child = " + child); return isEmpty ? null : child;//只添加! isChildEmpty的值,可能数据库返回数据不够count } From be00ec44a1e4a56dfa67db68d3deeb06ff895511 Mon Sep 17 00:00:00 2001 From: ysy Date: Sun, 29 May 2022 15:47:55 +0800 Subject: [PATCH 0651/1181] fix field order --- APIJSONORM/src/main/java/apijson/JSON.java | 39 +++---------------- .../apijson/orm/AbstractObjectParser.java | 2 +- 2 files changed, 6 insertions(+), 35 deletions(-) diff --git a/APIJSONORM/src/main/java/apijson/JSON.java b/APIJSONORM/src/main/java/apijson/JSON.java index 09dcd058d..b610cfc5b 100755 --- a/APIJSONORM/src/main/java/apijson/JSON.java +++ b/APIJSONORM/src/main/java/apijson/JSON.java @@ -6,8 +6,8 @@ import com.alibaba.fastjson.JSONArray; import com.alibaba.fastjson.JSONObject; -import com.alibaba.fastjson.parser.Feature; import com.alibaba.fastjson.serializer.SerializerFeature; +import com.alibaba.fastjson2.JSONReader; import java.util.List; @@ -64,12 +64,10 @@ public static String getCorrectJson(String s, boolean isArray) { * @param json * @return */ - private static final Feature[] DEFAULT_FASTJSON_FEATURES = {Feature.OrderedField, Feature.AllowSingleQuotes, Feature.UseBigDecimal, Feature.UseObjectArray}; + private static final JSONReader.Feature[] DEFAULT_FASTJSON_FEATURES = {JSONReader.Feature.FieldBased, JSONReader.Feature.UseBigDecimalForDoubles}; public static Object parse(Object obj) { - int features = com.alibaba.fastjson.JSON.DEFAULT_PARSER_FEATURE; - features |= Feature.OrderedField.getMask(); try { - return com.alibaba.fastjson.JSON.parse(obj instanceof String ? (String) obj : toJSONString(obj), DEFAULT_FASTJSON_FEATURES); + return com.alibaba.fastjson2.JSON.parse(obj instanceof String ? (String) obj : toJSONString(obj), DEFAULT_FASTJSON_FEATURES); } catch (Exception e) { Log.i(TAG, "parse catch \n" + e.getMessage()); } @@ -91,32 +89,7 @@ public static JSONObject parseObject(Object obj) { * @return */ public static JSONObject parseObject(String json) { - return parseObject(json, DEFAULT_FASTJSON_FEATURES); - } - - /** - * json转JSONObject - * - * @param json - * @param features - * @return - */ - public static JSONObject parseObject(String json, Feature... features) { - try { - return com.alibaba.fastjson.JSON.parseObject(getCorrectJson(json), JSONObject.class, features); - } catch (Exception e) { - Log.i(TAG, "parseObject catch \n" + e.getMessage()); - } - return null; - } - - /**JSONObject转实体类 - * @param object - * @param clazz - * @return - */ - public static T parseObject(JSONObject object, Class clazz) { - return parseObject(toJSONString(object), clazz); + return parseObject(json, JSONObject.class); } /**json转实体类 * @param json @@ -128,9 +101,7 @@ public static T parseObject(String json, Class clazz) { Log.e(TAG, "parseObject clazz == null >> return null;"); } else { try { - int features = com.alibaba.fastjson.JSON.DEFAULT_PARSER_FEATURE; - features |= Feature.OrderedField.getMask(); - return com.alibaba.fastjson.JSON.parseObject(getCorrectJson(json), clazz, DEFAULT_FASTJSON_FEATURES); + return com.alibaba.fastjson2.JSON.parseObject(getCorrectJson(json), clazz, DEFAULT_FASTJSON_FEATURES); } catch (Exception e) { Log.i(TAG, "parseObject catch \n" + e.getMessage()); } diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractObjectParser.java b/APIJSONORM/src/main/java/apijson/orm/AbstractObjectParser.java index a034d0e81..e571cd8b4 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractObjectParser.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractObjectParser.java @@ -425,7 +425,7 @@ else if (value instanceof String) { // //key{}@ getRealKey, 引用赋值路径 String replaceKey = key.substring(0, key.length() - 1); // System.out.println("getObject key.endsWith(@) >> parseRelation = " + parseRelation); - String targetPath = AbstractParser.getValuePath(type == TYPE_ITEM ? path : parentPath, new String((String) value)); + String targetPath = AbstractParser.getValuePath(type == TYPE_ITEM ? path : parentPath, (String) value); //先尝试获取,尽量保留缺省依赖路径,这样就不需要担心路径改变 Object target = onReferenceParse(targetPath); From 3ed76c34a3fb700c45463e47664a7614a200b2f2 Mon Sep 17 00:00:00 2001 From: huangcanjia Date: Thu, 2 Jun 2022 13:07:17 +0800 Subject: [PATCH 0652/1181] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=E5=A4=9A?= =?UTF-8?q?=E5=AD=97=E6=AE=B5=E5=8F=82=E4=B8=8Ejoin=E6=97=B6=EF=BC=8C?= =?UTF-8?q?=E6=B2=A1=E6=9C=89=E5=91=BD=E4=B8=AD=E7=BC=93=E5=AD=98=E8=80=8C?= =?UTF-8?q?=E5=87=BA=E7=8E=B0=E7=9A=841+N=E6=9F=A5=E8=AF=A2=E6=80=A7?= =?UTF-8?q?=E8=83=BD=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 在缓存副表数据到ChildMap时,反向遍历onList集合,避免除了idKey,userKey之外的字段在putWhere时,跟前端传参指定的顺序相反,导致没有命中缓存。 - 将onList反转 issue #402 --- APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java index a02bd79b0..1f1d4bd96 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java @@ -23,6 +23,7 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; +import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -519,6 +520,7 @@ else if (config.isClickHouse() && (sqlTable.startsWith("`") || sqlTable.startsWi if (viceConfig != null) { //FIXME 只有和主表关联才能用 item,否则应该从 childMap 查其它副表数据 List onList = curJoin.getOnList(); if (onList != null) { + Collections.reverse(onList); for (On on : onList) { if (on != null) { String ok = on.getOriginKey(); From 6ab88d13d17265bf637c81eda5f1b95239f68dcb Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Wed, 8 Jun 2022 21:16:21 +0800 Subject: [PATCH 0653/1181] Update README.md --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index f37bea636..5b7be0593 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,7 @@ Tencent is pleased to support the open source community by making APIJSON availa Copyright (C) 2020 THL A29 Limited, a Tencent company. All rights reserved.
This source code is licensed under the Apache License Version 2.0
+[APIJSON 已加入 腾源会开源摘星计划(WeOpen Star),该计划提供奖励以鼓励你加入我们的社区](https://github.com/weopenprojects/WeOpen-Star/issues/79)

APIJSON From 51f94299189a58e1971913274ffb7769cca216b7 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Wed, 8 Jun 2022 21:24:40 +0800 Subject: [PATCH 0654/1181] Update CONTRIBUTING.md --- CONTRIBUTING.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 0cb35f97d..e424b7050 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -50,9 +50,9 @@ ruoranw 提交的 18 个 Commits, 对 APIJSON 做出了 328 增加和 520 处删 Zerounary 提交的 6 个 Commits, 对 APIJSON 做出了 1,104 增加和 1 处删减(截止 2020/11/04 日)。

-APIJSON 持续招募贡献者,即使是在 Issue 中回答问题,或者做一些简单的 Bug Fix ,也会给 APIJSON 带来很大的帮助。
-APIJSON 已开发近 4 年,在此感谢所有开发者对于 APIJSON 的喜欢和支持,希望你能够成为 APIJSON 的核心贡献者,
-加入 APIJSON ,共同打造一个更棒的零代码、全自动、强安全 ORM 库!🍾🎉 +APIJSON 持续招募贡献者,新增功能、修复 Bug、完善文档、修正错误、宣传推广、回答问题等,都能帮助项目及广大用户。
+APIJSON 已开发近 6 年,在此感谢所有开发者对于 APIJSON 的喜欢和支持,希望你能够成为 APIJSON 的核心贡献者,
+加入 APIJSON ,共同打造一个更棒的零代码、全功能、强安全 ORM 库,造福更多前后端开发者!🍾🎉 ### 为什么一定要贡献代码? APIJSON 作为腾讯开源的知名热门项目,贡献代码除了可以给简历加亮点、为面试加分,还可以避免你碰到以下麻烦:
From f8a3c6706b9dec303e8802ccb8caaf740a3ebc5d Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Wed, 8 Jun 2022 21:25:42 +0800 Subject: [PATCH 0655/1181] Update CONTRIBUTING.md --- CONTRIBUTING.md | 31 ++++++++++++++++--------------- 1 file changed, 16 insertions(+), 15 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index e424b7050..939328aa7 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -61,21 +61,6 @@ APIJSON 作为腾讯开源的知名热门项目,贡献代码除了可以给简 3.你需要自己维护你的代码,每次升级 APIJSON 版本时,你都需要下载 APIJSON 新代码再合并你自己的更改
#### 所以为了让你自己的更改始终能跟上项目版本,得到他人给予的可靠且持续的维护,强烈建议 [提交 Pull Request](/CONTRIBUTING.md#pull-request) 来贡献代码。 -​ - -## Issue 提交 - -#### 对于贡献者 - -在提 Issue 前请确保满足一下条件: - -- 必须是一个 Bug 或者功能新增。 -- 必须是 APIJSON 相关问题。 -- 已经在 Issue 中搜索过,并且没有找到相似的 Issue 或者解决方案。 -- 完善下面模板中的信息 - -如果已经满足以上条件,我们提供了 Issue 的标准模版,请按照模板填写。 - ​ ## Pull Request @@ -145,3 +130,19 @@ https://www.jianshu.com/p/00cf29d2d66c

如何在 Github 上给别人的项目贡献代码
https://git-scm.com/book/zh/v2/GitHub-%E5%AF%B9%E9%A1%B9%E7%9B%AE%E5%81%9A%E5%87%BA%E8%B4%A1%E7%8C%AE + + +​ + +## Issue 提交 + +#### 对于贡献者 + +在提 Issue 前请确保满足一下条件: + +- 必须是一个 Bug 或者功能新增。 +- 必须是 APIJSON 相关问题。 +- 已经在 Issue 中搜索过,并且没有找到相似的 Issue 或者解决方案。 +- 完善下面模板中的信息 + +如果已经满足以上条件,我们提供了 Issue 的标准模版,请按照模板填写。 From 9fb6c885f0f5d493bf160e8f60762266823e838c Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Fri, 10 Jun 2022 17:24:11 +0800 Subject: [PATCH 0656/1181] Update README.md --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 5b7be0593..a9f52add0 100644 --- a/README.md +++ b/README.md @@ -219,8 +219,8 @@ https://github.com/Tencent/APIJSON/issues/36
### 注意事项 -请求参数 JSON 中表名、字段名、关键词及对应的值都是大小写敏感、逗号敏感、分号敏感、空格敏感、换行敏感,
-大部分情况都不允许空格和换行,表名以大写字母开头,不要想当然,请严格按照 [设计规范](https://github.com/Tencent/APIJSON/blob/master/Document.md#3) 来调用 API +**请求参数 JSON 中表名、字段名、关键词及对应的值都是大小写敏感、逗号敏感、分号敏感、空格敏感、换行敏感,
+大部分情况都不允许空格和换行,表名以大写字母开头,不要想当然,请严格按照 [设计规范](https://github.com/Tencent/APIJSON/blob/master/Document.md#3) 来调用 API !** [#181](https://github.com/Tencent/APIJSON/issues/181)

From ed935c1c4ad32157cf35ab91a0752985c14bfc73 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Fri, 10 Jun 2022 17:27:56 +0800 Subject: [PATCH 0657/1181] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index a9f52add0..17777f3a1 100644 --- a/README.md +++ b/README.md @@ -88,7 +88,7 @@ APIJSON 是一种专为 API 而生的 JSON 网络传输协议 以及 基于这
#### APIAuto 展示 APIJSON -使用 APIAuto-机器学习接口工具 来管理和测试 HTTP API 可大幅提升接口联调效率
+**使用 APIAuto-机器学习接口工具 来管理和测试 HTTP API 可大幅 减少传参错误、提升联调效率**
(注意网页工具界面是 APIAuto,里面的 URL+JSON 才是 APIJSON 的 HTTP API):

From 0600a8e93436b0ac60ca8f18c9c02176287b6434 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Fri, 10 Jun 2022 18:50:31 +0800 Subject: [PATCH 0658/1181] Update --bug.md --- .github/ISSUE_TEMPLATE/--bug.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.github/ISSUE_TEMPLATE/--bug.md b/.github/ISSUE_TEMPLATE/--bug.md index 3683b0f3e..35df8dccd 100755 --- a/.github/ISSUE_TEMPLATE/--bug.md +++ b/.github/ISSUE_TEMPLATE/--bug.md @@ -4,6 +4,12 @@ about: Create a report to help us improve --- +如果你已经知道问题所在、怎么解决,请直接提交 Pull Request 为社区做贡献谢谢,开源要大家参与贡献才会更美好。
+开发者也是人,也需要工作和休息,养活自己和家人,也会有心情不好和身体病痛,往往没有额外的时间精力顾及一些小问题,请理解和支持~ +https://github.com/Tencent/APIJSON/issues/406 + +_________________________________ + **提 bug 请发请求和响应的【完整截屏】,没图的自行解决! 开发者有限的时间和精力主要放在【维护项目源码和文档】上! 【描述不详细】 或 【文档/常见问题 已有答案】 的问题可能会被忽略!! From e2826242b89291ecd0505efe7aa61eb8f1eef1d5 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Fri, 10 Jun 2022 18:57:52 +0800 Subject: [PATCH 0659/1181] Update --bug.md --- .github/ISSUE_TEMPLATE/--bug.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/--bug.md b/.github/ISSUE_TEMPLATE/--bug.md index 35df8dccd..193baa055 100755 --- a/.github/ISSUE_TEMPLATE/--bug.md +++ b/.github/ISSUE_TEMPLATE/--bug.md @@ -4,8 +4,9 @@ about: Create a report to help us improve --- -如果你已经知道问题所在、怎么解决,请直接提交 Pull Request 为社区做贡献谢谢,开源要大家参与贡献才会更美好。
-开发者也是人,也需要工作和休息,养活自己和家人,也会有心情不好和身体病痛,往往没有额外的时间精力顾及一些小问题,请理解和支持~ +如果你已经知道问题所在、怎么解决,请直接提交 Pull Request 为社区做贡献,非常感谢。 +开发者也是人,也需要工作、休息、恋爱、养活自己、陪伴家人等,也有心情不好和身体病痛, +往往没有额外的时间精力顾及一些小问题,请理解和支持,开源要大家参与贡献才会更美好~ https://github.com/Tencent/APIJSON/issues/406 _________________________________ From bd3dc264f234f4834c3296c3f3bfd327d5e2165e Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Fri, 10 Jun 2022 18:58:19 +0800 Subject: [PATCH 0660/1181] Update --bug.md --- .github/ISSUE_TEMPLATE/--bug.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/--bug.md b/.github/ISSUE_TEMPLATE/--bug.md index 193baa055..c752ac3d0 100755 --- a/.github/ISSUE_TEMPLATE/--bug.md +++ b/.github/ISSUE_TEMPLATE/--bug.md @@ -4,9 +4,9 @@ about: Create a report to help us improve --- -如果你已经知道问题所在、怎么解决,请直接提交 Pull Request 为社区做贡献,非常感谢。 -开发者也是人,也需要工作、休息、恋爱、养活自己、陪伴家人等,也有心情不好和身体病痛, -往往没有额外的时间精力顾及一些小问题,请理解和支持,开源要大家参与贡献才会更美好~ +如果你已经知道问题所在、怎么解决,请直接提交 Pull Request 为社区做贡献,非常感谢。
+开发者也是人,也需要工作、休息、恋爱、养活自己、陪伴家人等,也有心情不好和身体病痛,
+往往没有额外的时间精力顾及一些小问题,请理解和支持,开源要大家参与贡献才会更美好~
https://github.com/Tencent/APIJSON/issues/406 _________________________________ From d95a1b90c8ead2e6a5d2b0f219b5795b706532e2 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Fri, 10 Jun 2022 18:58:59 +0800 Subject: [PATCH 0661/1181] Update --bug.md --- .github/ISSUE_TEMPLATE/--bug.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/--bug.md b/.github/ISSUE_TEMPLATE/--bug.md index c752ac3d0..1bb8e0264 100755 --- a/.github/ISSUE_TEMPLATE/--bug.md +++ b/.github/ISSUE_TEMPLATE/--bug.md @@ -4,9 +4,9 @@ about: Create a report to help us improve --- -如果你已经知道问题所在、怎么解决,请直接提交 Pull Request 为社区做贡献,非常感谢。
-开发者也是人,也需要工作、休息、恋爱、养活自己、陪伴家人等,也有心情不好和身体病痛,
-往往没有额外的时间精力顾及一些小问题,请理解和支持,开源要大家参与贡献才会更美好~
+如果你已经知道问题所在、怎么解决,请直接提交 Pull Request 为社区做贡献,非常感谢。 +开发者也是人,也需要工作、休息、恋爱、养活自己、陪伴家人等,也有心情不好和身体病痛, +往往没有额外的时间精力顾及一些小问题,请理解和支持,开源要大家参与贡献才会更美好~ https://github.com/Tencent/APIJSON/issues/406 _________________________________ From 88d895d0f06911693e1b6289a00a8a1ca51f3341 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Fri, 10 Jun 2022 19:00:54 +0800 Subject: [PATCH 0662/1181] Update --bug.md --- .github/ISSUE_TEMPLATE/--bug.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/--bug.md b/.github/ISSUE_TEMPLATE/--bug.md index 1bb8e0264..545c06b9c 100755 --- a/.github/ISSUE_TEMPLATE/--bug.md +++ b/.github/ISSUE_TEMPLATE/--bug.md @@ -4,9 +4,9 @@ about: Create a report to help us improve --- -如果你已经知道问题所在、怎么解决,请直接提交 Pull Request 为社区做贡献,非常感谢。 -开发者也是人,也需要工作、休息、恋爱、养活自己、陪伴家人等,也有心情不好和身体病痛, -往往没有额外的时间精力顾及一些小问题,请理解和支持,开源要大家参与贡献才会更美好~ +如果你已经知道问题所在、怎么解决,请直接 提交 Pull Request 为社区做贡献,非常感谢。 +开发者也是人,也需要工作、休息、恋爱、陪伴家人、走亲访友等,也有心情不好和身体病痛, +往往没有额外的时间精力顾及一些小问题,请理解和支持,开源要大家参与贡献才会更美好~ https://github.com/Tencent/APIJSON/issues/406 _________________________________ From f4d63d1799f160c32390b462ea4e68ee2c7ac422 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Fri, 10 Jun 2022 19:08:23 +0800 Subject: [PATCH 0663/1181] Update --bug.md --- .github/ISSUE_TEMPLATE/--bug.md | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/ISSUE_TEMPLATE/--bug.md b/.github/ISSUE_TEMPLATE/--bug.md index 545c06b9c..ec35b18dc 100755 --- a/.github/ISSUE_TEMPLATE/--bug.md +++ b/.github/ISSUE_TEMPLATE/--bug.md @@ -7,6 +7,7 @@ about: Create a report to help us improve 如果你已经知道问题所在、怎么解决,请直接 提交 Pull Request 为社区做贡献,非常感谢。 开发者也是人,也需要工作、休息、恋爱、陪伴家人、走亲访友等,也有心情不好和身体病痛, 往往没有额外的时间精力顾及一些小问题,请理解和支持,开源要大家参与贡献才会更美好~ +少数个人的热情终有被耗尽的一天,只有大家共同建设和繁荣社区,才能让开源可持续发展! https://github.com/Tencent/APIJSON/issues/406 _________________________________ From 76c91a9557c34a9b187fee1befedc5a8c36b0c6a Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Fri, 10 Jun 2022 19:09:12 +0800 Subject: [PATCH 0664/1181] Update --bug.md --- .github/ISSUE_TEMPLATE/--bug.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/ISSUE_TEMPLATE/--bug.md b/.github/ISSUE_TEMPLATE/--bug.md index ec35b18dc..072aba60f 100755 --- a/.github/ISSUE_TEMPLATE/--bug.md +++ b/.github/ISSUE_TEMPLATE/--bug.md @@ -5,7 +5,7 @@ about: Create a report to help us improve --- 如果你已经知道问题所在、怎么解决,请直接 提交 Pull Request 为社区做贡献,非常感谢。 -开发者也是人,也需要工作、休息、恋爱、陪伴家人、走亲访友等,也有心情不好和身体病痛, +开发者也是人,也需要工作、休息、恋爱、陪伴家人、走亲会友等,也有心情不好和身体病痛, 往往没有额外的时间精力顾及一些小问题,请理解和支持,开源要大家参与贡献才会更美好~ 少数个人的热情终有被耗尽的一天,只有大家共同建设和繁荣社区,才能让开源可持续发展! https://github.com/Tencent/APIJSON/issues/406 From 0a764540fdaf5cd80ec79728604080087d29bb78 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Thu, 23 Jun 2022 23:45:26 +0800 Subject: [PATCH 0665/1181] Update Document.md --- Document.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Document.md b/Document.md index fefcd5eae..afc131f5a 100644 --- a/Document.md +++ b/Document.md @@ -409,7 +409,7 @@ DELETE:
删除数据 | base_url/delete/ | {
   TableName:{< 存储过程 | "@key()":"SQL函数表达式",函数表达式为
function(key0,key1...)
会调用后端数据库对应的存储过程 SQL函数
function(String key0, String key1...)
除了参数会提前赋值,其它和 远程函数 一致 | ["@limit":10,
"@offset":0,
"@procedure()":"getCommentByUserId(id,@limit,@offset)"](http://apijson.cn:8080/get/{"User":{"@limit":10,"@offset":0,"@procedure()":"getCommentByUserId(id,@limit,@offset)"}})
会转为
`getCommentByUserId(38710,10,0)`
来调用存储过程 SQL 函数
`getCommentByUserId(IN id bigint, IN limit int, IN offset int)`
然后变为
"procedure":{
   "count":-1,
   "update":false,
   "list":[]
}
其中 count 是指写操作影响记录行数,-1 表示不是写操作;update 是指是否为写操作(增删改);list 为返回结果集 引用赋值 | "key@":"key0/key1/.../refKey",引用路径为用/分隔的字符串。以/开头的是缺省引用路径,从声明key所处容器的父容器路径开始;其它是完整引用路径,从最外层开始。
被引用的refKey必须在声明key的上面。如果对refKey的容器指定了返回字段,则被引用的refKey必须写在@column对应的值内,例如 "@column":"refKey,key1,..." | ["Moment":{
   "userId":38710
},
"User":{
   "id@":"/Moment/userId"
}](http://apijson.cn:8080/get/{"Moment":{"userId":38710},"User":{"id@":"%252FMoment%252FuserId"}})
User内的id引用了与User同级的Moment内的userId,
即User.id = Moment.userId,请求完成后
"id@":"/Moment/userId" 会变成 "id":38710 子查询 | "key@":{
   "range":"ALL",
   "from":"Table",
   "Table":{ ... }
}
其中:
range 可为 ALL,ANY;
from 为目标表 Table 的名称;
@ 后面的对象类似数组对象,可使用 count 和 join 等功能。 | ["id@":{
   "from":"Comment",
   "Comment":{
      "@column":"min(userId)"
   }
}](http://apijson.cn:8080/get/{"User":{"id@":{"from":"Comment","Comment":{"@column":"min(userId)"}}}})
WHERE id=(SELECT min(userId) FROM Comment) - 模糊搜索 | "key$":"SQL搜索表达式" => "key$":["SQL搜索表达式"],任意SQL搜索表达式字符串,如 %key%(包含key), key%(以key开始), %k%e%y%(包含字母k,e,y) 等,%表示任意字符 | ["name$":"%m%"](http://apijson.cn:8080/get/{"User[]":{"count":3,"User":{"name$":"%2525m%2525"}}}),对应SQL是`name LIKE '%m%'`,查询name包含"m"的一个User数组 + 模糊搜索 | `"key$":"SQL搜索表达式"` => `"key$":["SQL搜索表达式"]`,任意SQL搜索表达式字符串,如 %key%(包含key), key%(以key开始), %k%e%y%(包含字母k,e,y) 等,%表示任意字符 | ["name$":"%m%"](http://apijson.cn:8080/get/{"User[]":{"count":3,"User":{"name$":"%2525m%2525"}}}),对应SQL是`name LIKE '%m%'`,查询name包含"m"的一个User数组 正则匹配 | "key~":"正则表达式" => "key~":["正则表达式"],任意正则表达式字符串,如 ^[0-9]+$ ,*~ 忽略大小写,可用于高级搜索 | ["name~":"^[0-9]+$"](http://apijson.cn:8080/get/{"User[]":{"count":3,"User":{"name~":"^[0-9]%252B$"}}}),对应SQL是`name REGEXP '^[0-9]+$'`,查询name中字符全为数字的一个User数组 连续范围 | "key%":"start,end" => "key%":["start,end"],其中 start 和 end 都只能为 Boolean, Number, String 中的一种,如 "2017-01-01,2019-01-01" ,["1,90000", "82001,100000"] ,可用于连续范围内的筛选 | ["date%":"2017-10-01,2018-10-01"](http://apijson.cn:8080/get/{"User[]":{"count":3,"User":{"date%2525":"2017-10-01,2018-10-01"}}}),对应SQL是`date BETWEEN '2017-10-01' AND '2018-10-01'`,查询在2017-10-01和2018-10-01期间注册的用户的一个User数组 新建别名 | "name:alias",name映射为alias,用alias替代name。可用于 column,Table,SQL函数 等。只用于GET类型、HEAD类型的请求 | ["@column":"toId:parentId"](http://apijson.cn:8080/get/{"Comment":{"@column":"id,toId:parentId","id":51}}),对应SQL是`toId AS parentId`,将查询的字段toId变为parentId返回 From 4dfd9d4d4fa1b5a524634580cc8beed60027bdf9 Mon Sep 17 00:00:00 2001 From: huangcanjia Date: Wed, 6 Jul 2022 18:51:20 +0800 Subject: [PATCH 0666/1181] =?UTF-8?q?feat:=20=E6=94=AF=E6=8C=81=E8=B7=A8?= =?UTF-8?q?=E5=B1=82=E7=BA=A7app=20join?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 优化app join模式下,一对多join表查询时的1+N性能问题 - 支持客户端join字段,path的多层路径指定 - Join类增加count字段,以支持在生成副表sql时,按照指定count数量生成 - 处理App Join的查询结果时,将'一条条缓存'调整为'攒一起再缓存',防止错误替换 --- .../main/java/apijson/orm/AbstractParser.java | 18 +++++++++---- .../java/apijson/orm/AbstractSQLConfig.java | 2 +- .../java/apijson/orm/AbstractSQLExecutor.java | 26 +++++++++++-------- .../src/main/java/apijson/orm/Join.java | 8 ++++++ 4 files changed, 37 insertions(+), 17 deletions(-) diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java b/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java index 5135a7279..c472ceb80 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java @@ -1457,10 +1457,11 @@ else if (join != null){ // } path = path.substring(index + 1); - index = path.indexOf("/"); + index = path.lastIndexOf("/"); String tableKey = index < 0 ? path : path.substring(0, index); // User:owner apijson.orm.Entry entry = Pair.parseEntry(tableKey, true); - String table = entry.getKey(); // User + String[] tablePath = entry.getKey().split("/"); // User + String table = tableKey = tablePath[tablePath.length - 1]; // path最后一级为真实table;如:@/A/b/id@,b为目录最后一级 if (StringUtil.isName(table) == false) { throw new IllegalArgumentException(JSONRequest.KEY_JOIN + ":value 中 value 的 Table 值 " + table + " 不合法!" + "必须为 &/Table0, onList = new ArrayList<>(); for (Entry refEntry : refSet) { @@ -1656,7 +1664,7 @@ else if (join != null){ if (refObj.size() != tableObj.size()) { // 把 key 强制放最前,AbstractSQLExcecutor 中 config.putWhere 也是放尽可能最前 refObj.putAll(tableObj); - request.put(tableKey, refObj); + parentPathObj.put(tableKey, refObj); // tableObj.clear(); // tableObj.putAll(refObj); diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java index c9c9c8623..cfe4df81d 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java @@ -5008,7 +5008,7 @@ public static SQLConfig parseJoin(RequestMethod method, SQLCo alias = j.getAlias(); //JOIN子查询不能设置LIMIT,因为ON关系是在子查询后处理的,会导致结果会错误 SQLConfig joinConfig = newSQLConfig(method, table, alias, j.getRequest(), null, false, callback); - SQLConfig cacheConfig = j.canCacheViceTable() == false ? null : newSQLConfig(method, table, alias, j.getRequest(), null, false, callback).setCount(1); + SQLConfig cacheConfig = j.canCacheViceTable() == false ? null : newSQLConfig(method, table, alias, j.getRequest(), null, false, callback).setCount(j.getCount()); if (j.isAppJoin() == false) { //除了 @ APP JOIN,其它都是 SQL JOIN,则副表要这样配置 if (joinConfig.getDatabase() == null) { diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java index 1f1d4bd96..fb5713be3 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java @@ -260,7 +260,9 @@ public JSONObject execute(@NotNull SQLConfig config, boolean unknowType) throws Log.i(TAG, ">>> execute result = getCache('" + sql + "', " + position + ") = " + result); if (result != null) { cachedSQLCount ++; - + if (getCache(sql,config).size() > 1) { + result.put(KEY_RAW_LIST, getCache(sql,config)); + } Log.d(TAG, "\n\n execute result != null >> return result;" + "\n>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>\n\n"); return result; } @@ -589,19 +591,17 @@ else if (curJoin.isOuterJoin() || curJoin.isAntiJoin()) { if (isHead == false) { // @ APP JOIN 查询副表并缓存到 childMap <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< - - executeAppJoin(config, resultList, childMap); + Map> appJoinChildMap = new HashMap<>(); + executeAppJoin(config, resultList, appJoinChildMap); // @ APP JOIN 查询副表并缓存到 childMap >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> //子查询 SELECT Moment.*, Comment.id 中的 Comment 内字段 - Set> set = childMap.entrySet(); + Set>> set = appJoinChildMap.entrySet(); // - for (Entry entry : set) { - List l = new ArrayList<>(); - l.add(entry.getValue()); - putCache(entry.getKey(), l, null); + for (Entry> entry : set) { + putCache(entry.getKey(), entry.getValue(), null); } putCache(sql, resultList, config); @@ -633,7 +633,7 @@ else if (curJoin.isOuterJoin() || curJoin.isAntiJoin()) { * @param childMap * @throws Exception */ - protected void executeAppJoin(SQLConfig config, List resultList, Map childMap) throws Exception { + protected void executeAppJoin(SQLConfig config, List resultList, Map> childMap) throws Exception { List joinList = config.getJoinList(); if (joinList != null) { @@ -737,8 +737,12 @@ protected void executeAppJoin(SQLConfig config, List resultList, Map } } cacheSql = cc.getSQL(false); - childMap.put(cacheSql, result); - + List results = childMap.get(cacheSql); + if (results == null) { + results = new ArrayList<>(); + childMap.put(cacheSql,results); + } + results.add(result); Log.d(TAG, ">>> executeAppJoin childMap.put('" + cacheSql + "', result); childMap.size() = " + childMap.size()); } } diff --git a/APIJSONORM/src/main/java/apijson/orm/Join.java b/APIJSONORM/src/main/java/apijson/orm/Join.java index 4fb34b0a9..af3c2979e 100644 --- a/APIJSONORM/src/main/java/apijson/orm/Join.java +++ b/APIJSONORM/src/main/java/apijson/orm/Join.java @@ -22,6 +22,7 @@ public class Join { private String joinType; // "@" - APP, "<" - LEFT, ">" - RIGHT, "*" - CROSS, "&" - INNER, "|" - FULL, "!" - OUTER, "^" - SIDE, "(" - ANTI, ")" - FOREIGN private String table; // User private String alias; // owner + private int count = 1; // 当app join子表,需要返回子表的行数,默认1行; private List onList; // ON User.id = Moment.userId AND ... private JSONObject request; // { "id@":"/Moment/userId" } @@ -39,6 +40,13 @@ public void setPath(String path) { this.path = path; } + public int getCount() { + return count; + } + public void setCount(int count) { + this.count = count; + } + public String getJoinType() { return joinType; } From 5525eab38d5c3886a93d082102835b08ad338979 Mon Sep 17 00:00:00 2001 From: TommyLemon <1184482681@qq.com> Date: Thu, 7 Jul 2022 05:52:44 +0800 Subject: [PATCH 0667/1181] =?UTF-8?q?=E5=AE=8C=E5=96=84=E5=AF=B9=20APP=20J?= =?UTF-8?q?OIN=20=E5=90=8C=E5=B1=82=E5=92=8C=E8=B7=A8=E5=B1=82=E7=9A=84?= =?UTF-8?q?=E6=A0=A1=E9=AA=8C=EF=BC=9B=E5=AE=8C=E5=96=84=E5=AF=B9=20APP=20?= =?UTF-8?q?JOIN=20=E7=9A=84=20SQL=20=E6=89=A7=E8=A1=8C=E4=B8=8E=E7=BC=93?= =?UTF-8?q?=E5=AD=98=E6=AC=A1=E6=95=B0=E7=BB=9F=E8=AE=A1=EF=BC=9B=E8=A7=A3?= =?UTF-8?q?=E5=86=B3=E5=90=8C=E5=B1=82=20JOIN=20=E6=96=B0=E5=A2=9E?= =?UTF-8?q?=E7=9A=84=E6=8A=A5=E9=94=99=20bug=EF=BC=9B=E8=A7=A3=E5=86=B3=20?= =?UTF-8?q?APP=20JOIN=20=E5=89=AF=E8=A1=A8=E8=BF=94=E5=9B=9E=E5=86=85?= =?UTF-8?q?=E9=83=A8=E5=AD=97=E6=AE=B5=20@RAW@LIST=EF=BC=9Bfastjson2=20?= =?UTF-8?q?=E6=94=B9=E4=B8=BA=201.2.79?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- APIJSONORM/pom.xml | 2 +- APIJSONORM/src/main/java/apijson/JSON.java | 9 +- .../apijson/orm/AbstractObjectParser.java | 20 +-- .../main/java/apijson/orm/AbstractParser.java | 160 +++++++++++------- .../java/apijson/orm/AbstractSQLExecutor.java | 159 +++++++++-------- 5 files changed, 202 insertions(+), 148 deletions(-) diff --git a/APIJSONORM/pom.xml b/APIJSONORM/pom.xml index 3f776fb6a..fcc4ffa63 100755 --- a/APIJSONORM/pom.xml +++ b/APIJSONORM/pom.xml @@ -21,7 +21,7 @@ com.alibaba fastjson - 2.0.4 + 1.2.79 javax.activation diff --git a/APIJSONORM/src/main/java/apijson/JSON.java b/APIJSONORM/src/main/java/apijson/JSON.java index b610cfc5b..40ef46f9c 100755 --- a/APIJSONORM/src/main/java/apijson/JSON.java +++ b/APIJSONORM/src/main/java/apijson/JSON.java @@ -6,8 +6,9 @@ import com.alibaba.fastjson.JSONArray; import com.alibaba.fastjson.JSONObject; +import com.alibaba.fastjson.parser.Feature; import com.alibaba.fastjson.serializer.SerializerFeature; -import com.alibaba.fastjson2.JSONReader; +import com.alibaba.fastjson.JSONReader; import java.util.List; @@ -64,10 +65,10 @@ public static String getCorrectJson(String s, boolean isArray) { * @param json * @return */ - private static final JSONReader.Feature[] DEFAULT_FASTJSON_FEATURES = {JSONReader.Feature.FieldBased, JSONReader.Feature.UseBigDecimalForDoubles}; + private static final Feature[] DEFAULT_FASTJSON_FEATURES = {Feature.OrderedField, Feature.UseBigDecimal}; public static Object parse(Object obj) { try { - return com.alibaba.fastjson2.JSON.parse(obj instanceof String ? (String) obj : toJSONString(obj), DEFAULT_FASTJSON_FEATURES); + return com.alibaba.fastjson.JSON.parse(obj instanceof String ? (String) obj : toJSONString(obj), DEFAULT_FASTJSON_FEATURES); } catch (Exception e) { Log.i(TAG, "parse catch \n" + e.getMessage()); } @@ -101,7 +102,7 @@ public static T parseObject(String json, Class clazz) { Log.e(TAG, "parseObject clazz == null >> return null;"); } else { try { - return com.alibaba.fastjson2.JSON.parseObject(getCorrectJson(json), clazz, DEFAULT_FASTJSON_FEATURES); + return com.alibaba.fastjson.JSON.parseObject(getCorrectJson(json), clazz, DEFAULT_FASTJSON_FEATURES); } catch (Exception e) { Log.i(TAG, "parseObject catch \n" + e.getMessage()); } diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractObjectParser.java b/APIJSONORM/src/main/java/apijson/orm/AbstractObjectParser.java index e571cd8b4..68b31a87e 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractObjectParser.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractObjectParser.java @@ -71,7 +71,7 @@ public AbstractObjectParser setParser(AbstractParser parser) { * @param parentPath * @param request * @param name - * @throws Exception + * @throws Exception */ public AbstractObjectParser(@NotNull JSONObject request, String parentPath, SQLConfig arrayConfig , boolean isSubquery, boolean isTable, boolean isArrayMainTable) throws Exception { @@ -400,7 +400,7 @@ public boolean onParse(@NotNull String key, @NotNull Object value) throws Except if (arrObj == null) { throw new IllegalArgumentException("子查询 " + path + "/" + key + ":{ from:value } 中 value 对应的主表对象 " + from + ":{} 不存在!"); } - // + // SQLConfig cfg = (SQLConfig) arrObj.get(AbstractParser.KEY_CONFIG); if (cfg == null) { throw new NotExistException(TAG + ".onParse cfg == null"); @@ -453,7 +453,7 @@ else if (value instanceof String) { // //key{}@ getRealKey, 引用赋值路径 Log.d(TAG, "onParse isTable(table) == false >> return true;"); return true;//舍去,对Table无影响 } - } + } //直接替换原来的key@:path为key:target Log.i(TAG, "onParse >> key = replaceKey; value = target;"); @@ -517,7 +517,7 @@ else if (isTable && key.startsWith("@") && JSONRequest.TABLE_KEY_LIST.contains(k /** * @param key * @param value - * @param isFirst + * @param isFirst * @return * @throws Exception */ @@ -553,7 +553,7 @@ public JSON onChildParse(int index, String key, JSONObject value) throws Excepti + "数组 []:{} 中每个 key:{} 都必须是表 TableKey:{} 或 数组 arrayKey[]:{} !"); } - if ( //避免使用 "test":{"Test":{}} 绕过限制,实现查询爆炸 isTableKey && + if ( //避免使用 "test":{"Test":{}} 绕过限制,实现查询爆炸 isTableKey && (arrayConfig == null || arrayConfig.getPosition() == 0)) { objectCount ++; int maxObjectCount = parser.getMaxObjectCount(); @@ -577,7 +577,7 @@ public JSON onChildParse(int index, String key, JSONObject value) throws Excepti - //TODO 改用 MySQL json_add,json_remove,json_contains 等函数! + //TODO 改用 MySQL json_add,json_remove,json_contains 等函数! /**PUT key:[] * @param key * @param array @@ -757,7 +757,7 @@ public AbstractObjectParser executeSQL() throws Exception { //执行SQL操作数据库 if (isTable == false) {//提高性能 sqlReponse = new JSONObject(sqlRequest); - } + } else { try { sqlReponse = onSQLExecute(); @@ -896,7 +896,8 @@ public JSONObject onSQLExecute() throws Exception { result = parser.executeSQL(sqlConfig, isSubquery); boolean isSimpleArray = false; - List rawList = null; + // 提取并缓存数组主表的列表数据 + List rawList = (List) result.remove(AbstractSQLExecutor.KEY_RAW_LIST); if (isArrayMainTable && position == 0 && result != null) { @@ -905,8 +906,7 @@ public JSONObject onSQLExecute() throws Exception { && (childMap == null || childMap.isEmpty()) && (table.equals(arrayTable)); - // 提取并缓存数组主表的列表数据 - rawList = (List) result.remove(AbstractSQLExecutor.KEY_RAW_LIST); + // APP JOIN 副表时副表返回了这个字段 rawList = (List) result.remove(AbstractSQLExecutor.KEY_RAW_LIST); if (rawList != null) { String arrayPath = parentPath.substring(0, parentPath.lastIndexOf("[]") + 2); diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java b/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java index c472ceb80..3f598df1d 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java @@ -6,6 +6,7 @@ package apijson.orm; import static apijson.JSONObject.KEY_EXPLAIN; +import static apijson.JSONObject.KEY_JSON; import static apijson.RequestMethod.GET; import java.io.UnsupportedEncodingException; @@ -80,7 +81,7 @@ public abstract class AbstractParser implements Parser, Par public static int MAX_OBJECT_COUNT = 5; public static int MAX_ARRAY_COUNT = 5; public static int MAX_QUERY_DEPTH = 5; - + @Override public int getDefaultQueryCount() { return DEFAULT_QUERY_COUNT; @@ -122,13 +123,13 @@ public AbstractParser() { this(null); } /**needVerify = true - * @param requestMethod null ? requestMethod = GET + * @param method null ? requestMethod = GET */ public AbstractParser(RequestMethod method) { this(method, true); } /** - * @param requestMethod null ? requestMethod = GET + * @param method null ? requestMethod = GET * @param needVerify 仅限于为服务端提供方法免验证特权,普通请求不要设置为 false ! 如果对应Table有权限也建议用默认值 true,保持和客户端权限一致 */ public AbstractParser(RequestMethod method, boolean needVerify) { @@ -136,7 +137,7 @@ public AbstractParser(RequestMethod method, boolean needVerify) { setMethod(method); setNeedVerify(needVerify); } - + protected boolean isRoot = true; public boolean isRoot() { return isRoot; @@ -145,7 +146,7 @@ public AbstractParser setRoot(boolean isRoot) { this.isRoot = isRoot; return this; } - + @NotNull protected Visitor visitor; @@ -464,7 +465,7 @@ public JSONObject parseResponse(JSONObject request) { try { queryDepth = 0; executedSQLDuration = 0; - + requestObject = onObjectParse(request, null, null, null, false); onCommit(); @@ -486,7 +487,7 @@ public JSONObject parseResponse(JSONObject request) { if (Log.DEBUG) { res.put("sql:generate|cache|execute|maxExecute", getSQLExecutor().getGeneratedSQLCount() + "|" + getSQLExecutor().getCachedSQLCount() + "|" + getSQLExecutor().getExecutedSQLCount() + "|" + getMaxSQLCount()); res.put("depth:count|max", queryDepth + "|" + getMaxQueryDepth()); - + executedSQLDuration += sqlExecutor.getExecutedSQLDuration() + sqlExecutor.getSqlResultDuration(); long parseDuration = duration - executedSQLDuration; res.put("time:start|duration|end|parse|sql", startTime + "|" + duration + "|" + endTime + "|" + parseDuration + "|" + executedSQLDuration); @@ -551,7 +552,7 @@ public void onVerifyRole(@NotNull SQLConfig config) throws Exception { /**解析请求JSONObject * @param request => URLDecoder.decode(request, UTF_8); * @return - * @throws Exception + * @throws Exception */ @NotNull public static JSONObject parseRequest(String request) throws Exception { @@ -624,7 +625,7 @@ public static JSONObject wrapRequest(RequestMethod method, String tag, JSONObjec String arrKey = key + "[]"; if (target.containsKey(arrKey) == false) { - target.put(arrKey, new JSONArray()); + target.put(arrKey, new JSONArray()); } try { @@ -680,7 +681,7 @@ public static JSONObject newResult(int code, String msg) { public static JSONObject newResult(int code, String msg, boolean isRoot) { return extendResult(null, code, msg, isRoot); } - + /**添加JSONObject的状态内容,一般用于错误提示结果 * @param object * @param code @@ -702,13 +703,13 @@ public static JSONObject extendResult(JSONObject object, int code, String msg, b + " \n | \n 常见问题:https://github.com/Tencent/APIJSON/issues/36" + " \n 通用文档:https://github.com/Tencent/APIJSON/blob/master/Document.md" + " \n 视频教程:https://search.bilibili.com/all?keyword=APIJSON"); - + msg = index >= 0 ? msg.substring(0, index) : msg; - + if (object == null) { object = new JSONObject(true); } - + if (object.containsKey(JSONResponse.KEY_OK) == false) { object.put(JSONResponse.KEY_OK, JSONResponse.isSuccess(code)); } @@ -720,12 +721,12 @@ public static JSONObject extendResult(JSONObject object, int code, String msg, b if (m.isEmpty() == false) { msg = m + " ;\n " + StringUtil.getString(msg); } - + object.put(JSONResponse.KEY_MSG, msg); if (debug != null) { object.put("debug:info|help", debug); } - + return object; } @@ -758,7 +759,7 @@ public static JSONObject newSuccessResult() { public static JSONObject newSuccessResult(boolean isRoot) { return newResult(JSONResponse.CODE_SUCCESS, JSONResponse.MSG_SUCCEED, isRoot); } - + /**添加请求成功的状态内容 * @param object * @param e @@ -872,7 +873,7 @@ public static JSONObject newErrorResult(Exception e, boolean isRoot) { int code; if (e instanceof UnsupportedEncodingException) { code = JSONResponse.CODE_UNSUPPORTED_ENCODING; - } + } else if (e instanceof IllegalAccessException) { code = JSONResponse.CODE_ILLEGAL_ACCESS; } @@ -890,7 +891,7 @@ else if (e instanceof NotLoggedInException) { } else if (e instanceof TimeoutException) { code = JSONResponse.CODE_TIME_OUT; - } + } else if (e instanceof ConflictException) { code = JSONResponse.CODE_CONFLICT; } @@ -921,10 +922,8 @@ else if (e instanceof NullPointerException) { //TODO 启动时一次性加载Request所有内容,作为初始化。 /**获取正确的请求,非GET请求必须是服务器指定的 - * @param method - * @param request * @return - * @throws Exception + * @throws Exception */ @Override public JSONObject parseCorrectRequest() throws Exception { @@ -1027,13 +1026,14 @@ public JSONObject getStructure(@NotNull String table, String method, String tag, // protected SQLConfig itemConfig; /**获取单个对象,该对象处于parentObject内 - * @param parentPath parentObject的路径 - * @param name parentObject的key - * @param request parentObject的value - * @param config for array item - * @return - * @throws Exception - */ + * @param request parentObject 的 value + * @param parentPath parentObject 的路径 + * @param name parentObject 的 key + * @param arrayConfig config for array item + * @param isSubquery 是否为子查询 + * @return + * @throws Exception + */ @Override public JSONObject onObjectParse(final JSONObject request , String parentPath, String name, final SQLConfig arrayConfig, boolean isSubquery) throws Exception { @@ -1093,7 +1093,7 @@ public JSONObject onObjectParse(final JSONObject request if (type == SQLConfig.TYPE_ITEM_CHILD_0 && query != JSONRequest.QUERY_TABLE && position == 0) { //TODO 应在这里判断 @column 中是否有聚合函数,而不是 AbstractSQLConfig.getColumnString - + JSONObject rp; Boolean compat = arrayConfig.getCompat(); if (compat != null && compat) { @@ -1192,7 +1192,7 @@ public JSONObject onObjectParse(final JSONObject request * @param parentPath parentObject的路径 * @param name parentObject的key * @param request parentObject的value - * @return + * @return * @throws Exception */ @Override @@ -1283,7 +1283,7 @@ public JSONArray onArrayParse(JSONObject request, String parentPath, String name String[] childKeys = StringUtil.split(childPath, "-", false); if (childKeys == null || childKeys.length <= 0 || request.containsKey(childKeys[0]) == false) { childKeys = null; - } + } else if (childKeys.length == 1 && JSONRequest.isTableKey(childKeys[0])) { // 可能无需提取,直接返回 rawList 即可 arrTableKey = childKeys[0]; } @@ -1402,12 +1402,12 @@ else if (childKeys.length == 1 && JSONRequest.isTableKey(childKeys[0])) { // JOIN_COPY_KEY_LIST.add(JSONRequest.KEY_ORDER); JOIN_COPY_KEY_LIST.add(JSONRequest.KEY_RAW); } - + /**JOIN 多表同时筛选 * @param join "&/User,0"} * @param request - * @return - * @throws Exception + * @return + * @throws Exception */ private List onJoinParse(Object join, JSONObject request) throws Exception { JSONObject joinMap = null; @@ -1440,7 +1440,7 @@ else if (join != null){ // 分割 /Table/key String path = e == null ? null : e.getKey(); Object outer = path == null ? null : e.getValue(); - + if (outer instanceof JSONObject == false) { throw new IllegalArgumentException(JSONRequest.KEY_JOIN + ":value 中value不合法!" + "必须为 &/Table0/key0, entry = Pair.parseEntry(tableKey, true); - String[] tablePath = entry.getKey().split("/"); // User - String table = tableKey = tablePath[tablePath.length - 1]; // path最后一级为真实table;如:@/A/b/id@,b为目录最后一级 + int index2 = tableKey.lastIndexOf("/"); + String arrKey = index2 < 0 ? null : tableKey.substring(0, index2); + if (arrKey != null && JSONRequest.isArrayKey(arrKey) == false) { + throw new IllegalArgumentException(JSONRequest.KEY_JOIN + ":'" + e.getKey() + "' 对应的 " + arrKey + " 不是合法的数组 key[] !" + + "@ APP JOIN 最多允许跨 1 层,只能是子数组,且数组对象中不能有 join: value 键值对!"); + } + + tableKey = index2 < 0 ? tableKey : tableKey.substring(index2+1); + + apijson.orm.Entry entry = Pair.parseEntry(tableKey, true); + String table = entry.getKey(); // User if (StringUtil.isName(table) == false) { throw new IllegalArgumentException(JSONRequest.KEY_JOIN + ":value 中 value 的 Table 值 " + table + " 不合法!" + "必须为 &/Table0,> tableSet = tableObj.entrySet(); // 取出所有 join 条件 JSONObject requestObj = new JSONObject(true); // (JSONObject) obj.clone(); @@ -1538,7 +1565,19 @@ else if (join != null){ apijson.orm.Entry te = tk == null || p.substring(ind2 + 1).indexOf("/") >= 0 ? null : Pair.parseEntry(tk, true); if (te != null && JSONRequest.isTableKey(te.getKey()) && request.get(tk) instanceof JSONObject) { - refObj.put(k, v); + if (isAppJoin) { + if (refObj.size() >= 1) { + throw new IllegalArgumentException(JSONRequest.KEY_JOIN + ":" + e.getKey() + " 中 " + k + " 不合法!" + + "@ APP JOIN 必须有且只有一个引用赋值键值对!"); + } + + if (StringUtil.isName(k.substring(0, k.length() - 1)) == false) { + throw new IllegalArgumentException(JSONRequest.KEY_JOIN + ":'" + e.getKey() + "' 中 " + k + " 不合法 !" + + "@ APP JOIN 只允许 key@:/Table/refKey 这种 = 等价连接!"); + } + } + + refObj.put(k, v); continue; } } @@ -1585,8 +1624,9 @@ else if (join != null){ j.setAlias(alias); j.setOuter((JSONObject) outer); j.setRequest(requestObj); - if (parentPathObj != null) { - j.setCount(parentPathObj.getInteger("count") != null ? parentPathObj.getInteger("count") : 1); + if (arrKey != null) { + Integer count = parentPathObj.getInteger(JSONRequest.KEY_COUNT); + j.setCount(count == null ? getDefaultQueryCount() : count); } List onList = new ArrayList<>(); @@ -1598,7 +1638,7 @@ else if (join != null){ throw new IllegalArgumentException(e.getKey() + ":value 中 value 值 " + targetPath + " 不合法!必须为引用赋值的路径 '/targetTable/targetKey' !"); } - // 取出引用赋值路径 targetPath 对应的 Table 和 key + // 取出引用赋值路径 targetPath 对应的 Table 和 key index = targetPath.lastIndexOf("/"); String targetKey = index < 0 ? null : targetPath.substring(index + 1); if (StringUtil.isName(targetKey) == false) { @@ -1638,24 +1678,24 @@ else if (join != null){ if (targetObj == null) { throw new IllegalArgumentException(e.getKey() + ":'/targetTable/targetKey' 中路径对应的对象 '" + targetTableKey + "':{} 不存在或值为 null !必须是 {} 这种 JSONObject 格式!"); } - + Join.On on = new Join.On(); on.setKeyAndType(j.getJoinType(), j.getTable(), originKey); if (StringUtil.isName(on.getKey()) == false) { throw new IllegalArgumentException(JSONRequest.KEY_JOIN + ":value 中 value 的 key@ 中 key 值 " + on.getKey() + " 不合法!必须满足英文单词变量名格式!"); } - + on.setOriginKey(originKey); on.setOriginValue((String) refEntry.getValue()); on.setTargetTable(targetTable); on.setTargetAlias(targetAlias); on.setTargetKey(targetKey); - + onList.add(on); } - + j.setOnList(onList); - + joinList.add(j); // onList.add(table + "." + key + " = " + targetTable + "." + targetKey); // ON User.id = Moment.userId @@ -1762,7 +1802,7 @@ public static String replaceArrayChildPath(String parentPath, String valuePath) pos = ps[i+1].contains("/") == false ? ps[i+1] : ps[i+1].substring(0, ps[i+1].indexOf("/")); if ( - //StringUtil.isNumer(pos) && + //StringUtil.isNumer(pos) && vs[i+1].startsWith(pos + "/") == false) { vs[i+1] = pos + "/" + vs[i+1]; } diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java index fb5713be3..1dcbb7544 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java @@ -65,14 +65,14 @@ public int getCachedSQLCount() { public int getExecutedSQLCount() { return executedSQLCount; } - + private long executedSQLDuration = 0; private long sqlResultDuration = 0; @Override public long getExecutedSQLDuration() { return executedSQLDuration; } - + @Override public long getSqlResultDuration() { return sqlResultDuration; @@ -95,7 +95,7 @@ public void putCache(String sql, List list, SQLConfig config) { Log.i(TAG, "saveList sql == null || list == null >> return;"); return; } - + cacheMap.put(sql, list); } @@ -165,7 +165,7 @@ public ResultSet execute(@NotNull Statement statement, String sql) throws Except @Override public JSONObject execute(@NotNull SQLConfig config, boolean unknowType) throws Exception { long executedSQLStartTime = System.currentTimeMillis(); - + boolean isPrepared = config.isPrepared(); final String sql = config.getSQL(false); @@ -212,7 +212,7 @@ public JSONObject execute(@NotNull SQLConfig config, boolean unknowType) throws if (isExplain == false) { executedSQLDuration += System.currentTimeMillis() - executedSQLStartTime; } - + result = new JSONObject(true); result.put(JSONResponse.KEY_COUNT, updateCount); result.put("update", updateCount >= 0); @@ -241,7 +241,7 @@ public JSONObject execute(@NotNull SQLConfig config, boolean unknowType) throws //id,id{}至少一个会有,一定会返回,不用抛异常来阻止关联写操作时前面错误导致后面无条件执行! result.put(JSONResponse.KEY_COUNT, updateCount);//返回修改的记录数 - + String idKey = config.getIdKey(); if (config.getId() != null) { result.put(idKey, config.getId()); @@ -249,7 +249,7 @@ public JSONObject execute(@NotNull SQLConfig config, boolean unknowType) throws if (config.getIdIn() != null) { result.put(idKey + "[]", config.getIdIn()); } - + return result; case GET: @@ -271,7 +271,7 @@ public JSONObject execute(@NotNull SQLConfig config, boolean unknowType) throws executedSQLCount ++; executedSQLStartTime = System.currentTimeMillis(); } - rs = executeQuery(config); //FIXME SQL Server 是一次返回两个结果集,包括查询结果和执行计划,需要 moreResults + rs = executeQuery(config); //FIXME SQL Server 是一次返回两个结果集,包括查询结果和执行计划,需要 moreResults if (isExplain == false) { executedSQLDuration += System.currentTimeMillis() - executedSQLStartTime; } @@ -318,23 +318,23 @@ public JSONObject execute(@NotNull SQLConfig config, boolean unknowType) throws if (capacity > 100) { // 有 WHERE 条件,条件越多过滤数据越多,暂时不考虑 @combine:"a | (b & !c)" 里面 | OR 和 ! NOT 条件,太复杂也不是很必要 Map> combine = config.getCombineMap(); - + List andList = combine == null ? null : combine.get("&"); int andCondCount = andList == null ? (config.getWhere() == null ? 0 : config.getWhere().size()) : andList.size(); - + List orList = combine == null ? null : combine.get("|"); int orCondCount = orList == null ? 0 : orList.size(); - + List notList = combine == null ? null : combine.get("!"); int notCondCount = notList == null ? 0 : notList.size(); - + // 有 GROUP BY 分组,字段越少过滤数据越多 String[] group = StringUtil.split(config.getGroup()); int groupCount = group == null ? 0 : group.length; if (groupCount > 0 && Arrays.asList(group).contains(config.getIdKey())) { groupCount = 0; } - + // 有 HAVING 聚合函数,字段越多过滤数据越多,暂时不考虑 @combine:"a | (b & !c)" 里面 | OR 和 ! NOT 条件,太复杂也不是很必要 Map having = config.getHaving(); int havingCount = having == null ? 0 : having.size(); @@ -350,7 +350,7 @@ public JSONObject execute(@NotNull SQLConfig config, boolean unknowType) throws } } } - + resultList = new ArrayList<>(capacity); } @@ -363,7 +363,7 @@ public JSONObject execute(@NotNull SQLConfig config, boolean unknowType) throws //