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 001/915] 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 002/915] 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 003/915] 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 004/915] =?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 005/915] 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 006/915] 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 007/915] 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 008/915] =?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 009/915] =?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 010/915] =?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 011/915] =?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 012/915] =?UTF-8?q?=E4=B8=BA=E7=94=A8=E6=88=B7=20=E6=8A=95?=
=?UTF-8?q?=E6=8A=95=E7=A7=91=E6=8A=80-=E8=A1=8C=E4=B8=9A=E9=A2=86?=
=?UTF-8?q?=E5=85=88=E7=9A=84=E5=B9=B3=E5=8F=B0=E5=9E=8B=E9=87=91=E8=9E=8D?=
=?UTF-8?q?=E7=A7=91=E6=8A=80=E5=85=AC=E5=8F=B8=20=E6=9B=B4=E9=80=82?=
=?UTF-8?q?=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 013/915] =?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 014/915] 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 015/915] 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 016/915] 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 017/915] 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 018/915] 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 019/915] 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 020/915] 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 021/915] 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 022/915] 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 023/915] 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 024/915] 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 025/915] =?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 026/915] =?UTF-8?q?=E8=B4=A1=E7=8C=AE=E8=80=85=20=E6=96=B0?=
=?UTF-8?q?=E5=A2=9E=E5=A4=9A=E4=B8=AA=E7=94=9F=E6=80=81=E9=A1=B9=E7=9B=AE?=
=?UTF-8?q?=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 027/915] =?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 028/915] 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 029/915] =?UTF-8?q?=E8=A7=A3=E5=86=B3=E5=AF=B9=20Response?=
=?UTF-8?q?=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 030/915] =?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 031/915] =?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 032/915] =?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 033/915] =?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 034/915] =?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 035/915] =?UTF-8?q?=E6=96=B0=E5=A2=9E=20=E6=95=B4=E5=90=88?=
=?UTF-8?q?=20APIJSON=20=E5=92=8C=E5=BE=AE=E6=9C=8D=E5=8A=A1=E6=A1=86?=
=?UTF-8?q?=E6=9E=B6=20light4j=20=E7=9A=84=20Demo(=E5=90=8C=E6=97=B6?=
=?UTF-8?q?=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 036/915] =?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 037/915] =?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 038/915] =?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 039/915] =?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 040/915] =?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 041/915] =?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 042/915] =?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 043/915] =?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 044/915] =?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 045/915] =?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 046/915] =?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 047/915] =?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 048/915] =?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 049/915] =?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 050/915] =?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 051/915] =?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 052/915] =?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 053/915] =?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 054/915] =?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 055/915] =?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 056/915] 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 057/915] =?UTF-8?q?feat:=20=E4=BD=BF=E7=94=A8StringBuilder?=
=?UTF-8?q?=E4=BC=98=E5=8C=96=E4=BA=86=E5=AD=97=E7=AC=A6=E4=B8=B2=E6=8B=BC?=
=?UTF-8?q?=E6=8E=A5=20=E5=B0=86StringUtil.java=E7=B1=BB=E4=B8=AD=E4=B8=89?=
=?UTF-8?q?=E5=A4=84=E5=AD=97=E7=AC=A6=E4=B8=B2=E7=9B=B4=E6=8E=A5=E6=8B=BC?=
=?UTF-8?q?=E6=8E=A5=E4=BC=98=E5=8C=96=E4=B8=BA=E4=BD=BF=E7=94=A8StringBui?=
=?UTF-8?q?lder=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 058/915] 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 059/915] 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 060/915] 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 061/915] 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 062/915] =?UTF-8?q?=E5=B0=9D=E8=AF=95=E4=BF=AE=E5=A4=8D=20?=
=?UTF-8?q?oracle=20select=20=E5=88=86=E9=A1=B5=E8=AF=AD=E6=B3=95=E9=97=AE?=
=?UTF-8?q?=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 063/915] =?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 064/915] =?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 065/915] 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 066/915] 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 067/915] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E4=B8=80=E4=B8=AA=20?=
=?UTF-8?q?PHP=20=E7=89=88=E6=9C=AC=E7=9A=84=20APIJSON=EF=BC=9AAPIJSON-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 068/915] 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 069/915] =?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 070/915] =?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 071/915] =?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 072/915] =?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 073/915] =?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 074/915] =?UTF-8?q?=E6=96=B0=E5=A2=9E=20=E3=80=90=E8=85=BE?=
=?UTF-8?q?=E8=AE=AF=E7=8A=80=E7=89=9B=E9=B8=9F=E5=BC=80=E6=BA=90=E4=BA=BA?=
=?UTF-8?q?=E6=89=8D=E5=9F=B9=E5=85=BB=E8=AE=A1=E5=88=92=E3=80=91=E5=85=AC?=
=?UTF-8?q?=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 075/915] =?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 076/915] =?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 077/915] 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 078/915] =?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 079/915] =?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 080/915] 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
+
+
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 081/915] 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

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 082/915] 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 083/915] 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 084/915] 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 085/915] 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 086/915] 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 087/915] 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 088/915] 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 089/915] 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 090/915] 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
-
+
@@ -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 091/915] 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 092/915] 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 093/915] 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 094/915] 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 095/915] 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 096/915] 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 097/915] =?UTF-8?q?=E4=BC=98=E5=8C=96=20system.err.printli?=
=?UTF-8?q?n=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 098/915] =?UTF-8?q?=E4=BC=98=E5=8C=96=20system.err.printli?=
=?UTF-8?q?n=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 099/915] =?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 100/915] =?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 101/915] =?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 102/915] =?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 103/915] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E6=96=87=E7=AB=A0=20?=
=?UTF-8?q?APIJSON=E6=95=99=E7=A8=8B=EF=BC=88=E4=B8=80=EF=BC=89=EF=BC=9A?=
=?UTF-8?q?=E4=B8=8A=E6=89=8Bapijson=E9=A1=B9=E7=9B=AE=EF=BC=8C=E5=AD=A6?=
=?UTF-8?q?=E4=B9=A0apijson=E8=AF=AD=E6=B3=95=EF=BC=8C=E5=B9=B6=E5=AE=9E?=
=?UTF-8?q?=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 104/915] 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 105/915] 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)

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 106/915] 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 107/915] 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

+APIJSON和APIAuto-零代码开发和测试(QECon 全球软件质量&效能大会)
+https://www.bilibili.com/video/BV1yv411p7Y4
+
+
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 108/915] =?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
+

-APIJSON和APIAuto-零代码开发和测试(QECon 全球软件质量&效能大会)
+
+#### APIJSON 和 APIAuto-零代码开发和测试(QECon 全球软件质量&效能大会)
+
https://www.bilibili.com/video/BV1yv411p7Y4
+
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 109/915] 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 110/915] =?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 111/915] =?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 112/915] =?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 113/915] =?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 114/915] =?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吧
+ //
+ 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 115/915] =?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