Tencent / APIJSON

🏆 实时 零代码、全功能、强安全 ORM 库 🚀 后端接口和文档零代码,前端(客户端) 定制返回 JSON 的数据和结构 🏆 Real-Time coding-free, powerful and secure ORM 🚀 providing APIs and Docs without coding by Backend, and the returned JSON of API can be customized by Frontend(Client) users
http://apijson.cn
Other
17.1k stars 2.14k forks source link

apijson json服务编排 #482

Open cloudAndMonkey opened 1 year ago

cloudAndMonkey commented 1 year ago

Description

@TommyLemon 功能描述: 一个服务编排的伪码流程设计 将伪码转换为 apijson json、前置、后置函数、javascript、redis、elasticSearch等 并且能够mork每一步,执行中间流程,还能查询每个阶段执行的sql语句,及结果.

json格式:

{
    "@transaction": true, 
    "id@-()": "getCurrentUserId()", // 前置函数
    "@post": [
        "xxx"
    ], 
    "xxx": {
     "@method": "xxx",
      "@version": "", // 版本,控制校验
      "@datasource": "" // 数据源相关配置
     // 分表规则等,后面再说
   }
"@explain": true
}

1、独立定义一个url method, 通过解析不同method执行不同流程 和已有method区分开,避免歧义 2、最外层新增传参 "transaction": true 来指定开启事务 3、控制每条语句的数据源 4、完善 “@Explain" 如果没有执行计划,则返回sql语句. 能够在 reponse返回值中, 看到json中执行的每条sql,方便排错 5、@version支持 定义不同场景的 新增、修改、删除等执行规则. 请通过version版本区分 6、前置函数 1) 要能拿到其他数据源 2) 能拿到json执行过程中的数据 3) 拿到当前数据源(和外部json一个事物执行) 4) 从数据库/或其他数据源 查询获取对照关系 组装数据, 调用对应数据源的api方法执行即可 5) 前置函数,能调用其他函数 把一些计算, 执行,计算 等 放到一个函数进行整合 还没细化 7、支持mork 通过伪码,分解为不同 阶段 的json语句执行

测试点: 1、测试 一个json多条语句,后置函数啥时候执行 2、操作其他数据源, 事物是json执行完才会提交, 需要保证一致性 3、redis、elasticSearch、javascript、lua等功能测试

cloudAndMonkey commented 1 year ago

还好 apijson支持prepare Statement, elastic-sql 不支持statement ,哈哈

cloudAndMonkey commented 1 year ago

@TommyLemon elasticSearch插入成功啦, 单条, 其他情况 我还要多测一下 image 查询插入的数据: image

昨天我想了好久, 然后把源码流程再梳理了一下. 新增/修改/删除 实现: SQLExecutor public int executeUpdate(@NotNull SQLConfig config, String sql) throws Exception

查询实现: SQLExecutor ResultSet executeQuery(@NotNull SQLConfig config, String sql) throws Exception;

image

不执行生成sql语句, 和 jdbc sql执行, 😄

cloudAndMonkey commented 1 year ago

elasticSearch不支持事物 目前我们是 一条一条执行, 然后 统一提交 需要 将相同数据源,非jdbc 的语句 进行批量操作吗?

TommyLemon commented 1 year ago

APIJSON 目前是因为要处理 权限、远程函数、子对象,才不得不逐条执行,而不是 INSERT INTO table VALUES(..),(..),(..)... 尽管 AbstractSQLConfig 是支持这种批量提交方式,但在 AbstractObjectParser.onTableArrayParse 还是拆分成了多条。

AbstractObjectParser.onTableArrayParse https://github.com/Tencent/APIJSON/blob/master/APIJSONORM/src/main/java/apijson/orm/AbstractObjectParser.java#L637-L661

image

不过只要有 1 条失败,就会整体失败(后续可能在后端加个配置,允许部分失败),所以不用担心不支持事务这个问题。 如果要优化性能,则可以考虑改下源码。

只是实现这个功能不难,关键是要兼容 权限、远程函数、子对象,保证每条都能执行到。 目前也可以类似 AbstractParser.onJoinParse 提前扫描所有子对象,判断是否存在 角色权限、远程函数、子对象,如果都不存在,则不用拆分。 或者先试试直接在 AbstractObjectParser 中判断,这个实现更简单,类似之前对单表批量查询性能的优化。 https://my.oschina.net/tommylemon/blog/5375645

https://github.com/Tencent/APIJSON/blob/master/APIJSONORM/src/main/java/apijson/orm/AbstractObjectParser.java#L906-L932

image

主要改动点在: AbstractObjectParser.onTableArrayParse https://github.com/Tencent/APIJSON/blob/master/APIJSONORM/src/main/java/apijson/orm/AbstractObjectParser.java#L637-L661

image
cloudAndMonkey commented 1 year ago

等我忙完,再研究一下

TommyLemon commented 1 year ago

赞,我更新了以上回复,目前可以不用管的,只要有 1 条 INSERT/UPDATE 失败就整体失败

TommyLemon commented 1 year ago

@TommyLemon 请问调用js脚本, 要咋弄呀, 我也测一下

用最新代码,然后 Function 表配置(type=1) 属性、Script 表配置具体脚本就行了,前端不需要改动参数。 image

INSERT INTO `Function` VALUES (3,0,0,0,'countArray','int','array','{\"array\": [1, 2, 3]}','获取数组长度。没写调用键值对,会自动补全 \"result()\": \"countArray(array)\"',0,NULL,NULL,'2018-10-13 08:23:23',NULL),(4,0,0,0,'countObject','int','object','{\"object\": {\"key0\": 1, \"key1\": 2}}','获取对象长度。',0,NULL,NULL,'2018-10-13 08:23:23',NULL),(5,0,0,1,'isContain','Boolean','array,value','{\"array\": [1, 2, 3], \"value\": 2}','判断是否数组包含值。',0,NULL,NULL,'2018-10-13 08:23:23',NULL),(6,0,0,0,'isContainKey','boolean','object,key','{\"key\": \"id\", \"object\": {\"id\": 1}}','判断是否对象包含键。',0,NULL,NULL,'2018-10-13 08:30:31',NULL),(7,0,0,0,'isContainValue','boolean','object,value','{\"value\": 1, \"object\": {\"id\": 1}}','判断是否对象包含值。',0,NULL,NULL,'2018-10-13 08:30:31',NULL),(8,0,0,0,'getFromArray','Object','array,position','{\"array\": [1, 2, 3], \"result()\": \"getFromArray(array,1)\"}','根据下标获取数组里的值。position 传数字时直接作为值,而不是从所在对象 request 中取值',0,NULL,NULL,'2018-10-13 08:30:31',NULL),(9,0,0,0,'getFromObject','Object','object,key','{\"key\": \"id\", \"object\": {\"id\": 1}}','根据键获取对象里的值。',0,NULL,NULL,'2018-10-13 08:30:31',NULL),(10,0,0,0,'deleteCommentOfMoment','int','momentId','{\"momentId\": 1}','根据动态 id 删除它的所有评论',0,'Moment','DELETE','2019-08-17 18:46:56',NULL),(11,0,0,0,'verifyIdList',NULL,'array','{\"array\": [1, 2, 3], \"result()\": \"verifyIdList(array)\"}','校验类型为 id 列表',0,NULL,NULL,'2019-08-17 19:58:33',NULL),(12,0,0,0,'verifyURLList',NULL,'array','{\"array\": [\"http://123.com/1.jpg\", \"http://123.com/a.png\", \"http://www.abc.com/test.gif\"], \"result()\": \"verifyURLList(array)\"}','校验类型为 URL 列表',0,NULL,NULL,'2019-08-17 19:58:33',NULL),(13,0,0,0,'getWithDefault','Object','value,defaultValue','{\"value\": null, \"defaultValue\": 1}','如果 value 为 null,则返回 defaultValue',0,NULL,NULL,'2019-08-20 15:26:36',NULL),(14,0,0,0,'removeKey','Object','key','{\"key\": \"s\", \"key2\": 2}','从对象里移除 key',0,NULL,NULL,'2019-08-20 15:26:36',NULL),(15,0,0,0,'getFunctionDemo','JSONObject',NULL,'{}','获取远程函数的 Demo',0,NULL,NULL,'2019-08-20 15:26:36',NULL),(16,0,0,0,'getFunctionDetail','String',NULL,'{}','获取远程函数的详情',0,NULL,NULL,'2019-08-20 15:26:36',NULL),(17,0,0,0,'getMethodArguments','String','methodArgs','{\"methodArgs\": \"methodArgs\"}','获取远程函数的参数',0,NULL,NULL,'2021-07-29 09:32:22',NULL),(18,0,0,0,'getMethodDefination','String','method,arguments,type,exceptions,language','{\"method\": \"method\"}','获取远程函数的签名定义',0,NULL,NULL,'2021-07-29 09:34:37',NULL),(19,0,0,0,'getMethodRequest','String',NULL,'{}','获取远程函数的请求',0,NULL,NULL,'2021-07-29 09:35:37',NULL),(20,0,0,0,'deleteChildComment','int','commentId','{}','删除评论的子评论',0,NULL,NULL,'2021-09-10 06:53:24',NULL),(21,0,0,0,'getCurrentUserId','Long',NULL,'{}','获取当前登录用户 id',0,NULL,NULL,'2022-02-19 17:27:41',NULL),(22,0,0,0,'getCurrentUserIdAsList','List',NULL,'{}','获取当前登录用户 id 列表,只包含一个 id,只是为了前端方便构造某些请求',0,NULL,NULL,'2022-02-19 17:27:41',NULL),(24,0,0,0,'getCurrentUser','Visitor',NULL,'{}','获取当前登录用户公开信息',0,NULL,NULL,'2022-02-19 17:34:58',NULL),(26,0,0,0,'getCurrentContactIdList','List',NULL,'{}','获取当前登录用户的联系人 id 列表',0,NULL,NULL,'2022-02-19 17:37:35',NULL),(27,0,0,1,'getType','String','val','{\"val\": 1}','获取类型',0,NULL,NULL,'2022-11-16 16:05:41',NULL),(28,0,0,1,'length','Integer','val','{\"val\": [1, 2, 3]}','获取长度',0,NULL,NULL,'2022-11-16 17:21:53',NULL),(29,0,0,0,'getMethodDefinition','String','method,arguments,type,exceptions,language','{\"method\": \"method\"}','获取远程函数的签名定义',0,NULL,NULL,'2021-07-29 09:34:37',NULL);
image
INSERT INTO `Script` VALUES (1,0,0,0,'getType','function getType(curObj, key) {\n    var val = curObj == null ? null : curObj[key];\n    return val instanceof Array ? \"array\" : typeof val;\n}','2022-11-16 16:01:23',0),(2,0,0,0,'isContain','function isContain(curObj, arrKey, valKey) {\n    var arr = curObj == null ? null : curObj[arrKey];\n    var val = curObj == null ? null : curObj[valKey];\n    return arr != null && arr.indexOf(val) >=0;\n}','2022-11-16 16:02:48',0),(3,0,0,1,'init','var i = 1;\n\"init done \"  + i;','2022-11-16 16:41:35',0),(4,0,0,0,'length','function length(curObj, key) {\n    var val = curObj == null ? null : curObj[key];\n    return val == null ? 0 : val.length;\n}','2022-11-16 17:18:43',0);

远程函数:支持 JavaScript 外的更多脚本语言,例如 Python, Ruby, Lua, PHP 等 https://github.com/Tencent/APIJSON/commit/dd372797e30bff6db705eee550b63e28b92a0f1a

image image
DROP TABLE IF EXISTS `Function`;
/*!40101 SET @saved_cs_client     = @@character_set_client */;
/*!50503 SET character_set_client = utf8mb4 */;
CREATE TABLE `Function` (
  `id` bigint NOT NULL AUTO_INCREMENT,
  `debug` tinyint NOT NULL DEFAULT '0' COMMENT '是否为 DEBUG 调试数据,只允许在开发环境使用,测试和线上环境禁用:0-否,1-是。',
  `userId` bigint NOT NULL COMMENT '管理员用户Id',
  `language` varchar(45) DEFAULT NULL COMMENT '语言:Java(java), JavaScript(js), Lua(lua), Python(py), Ruby(ruby), PHP(php) 等,NULL 默认为 Java,JDK 1.6-11 默认支持 JavaScript,JDK 12+ 需要额外依赖 Nashron/Rhiro 等 js 引擎库,其它的语言需要依赖对应的引擎库,并在 ScriptEngineManager 中注册',
  `name` varchar(50) NOT NULL COMMENT '方法名',
  `returnType` varchar(50) DEFAULT 'Object' COMMENT '返回值类型。TODO RemoteFunction 校验 type 和 back',
  `arguments` varchar(100) DEFAULT NULL COMMENT '参数列表,每个参数的类型都是 String。\n用 , 分割的字符串 比 [JSONArray] 更好,例如 array,item ,更直观,还方便拼接函数。',
  `demo` json NOT NULL COMMENT '可用的示例。\nTODO 改成 call,和返回值示例 back 对应。',
  `detail` varchar(1000) NOT NULL COMMENT '详细描述',
  `version` tinyint NOT NULL DEFAULT '0' COMMENT '允许的最低版本号,只限于GET,HEAD外的操作方法。\nTODO 使用 requestIdList 替代 version,tag,methods',
  `tag` varchar(20) DEFAULT NULL COMMENT '允许的标签.\nnull - 允许全部\nTODO 使用 requestIdList 替代 version,tag,methods',
  `methods` varchar(50) DEFAULT NULL COMMENT '允许的操作方法。\nnull - 允许全部\nTODO 使用 requestIdList 替代 version,tag,methods',
  `date` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  `return` varchar(45) DEFAULT NULL COMMENT '返回值示例',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=30 DEFAULT CHARSET=utf8mb3 COMMENT='远程函数。强制在启动时校验所有demo是否能正常运行通过';
/*!40101 SET character_set_client = @saved_cs_client */;
INSERT INTO `Function` VALUES (3,0,0,0,'countArray','int','array','{\"array\": [1, 2, 3]}','获取数组长度。没写调用键值对,会自动补全 \"result()\": \"countArray(array)\"',0,NULL,NULL,'2018-10-13 08:23:23',NULL),(4,0,0,0,'countObject','int','object','{\"object\": {\"key0\": 1, \"key1\": 2}}','获取对象长度。',0,NULL,NULL,'2018-10-13 08:23:23',NULL),(5,0,0,1,'isContain','Boolean','array,value','{\"array\": [1, 2, 3], \"value\": 2}','判断是否数组包含值。',0,NULL,NULL,'2018-10-13 08:23:23',NULL),(6,0,0,0,'isContainKey','boolean','object,key','{\"key\": \"id\", \"object\": {\"id\": 1}}','判断是否对象包含键。',0,NULL,NULL,'2018-10-13 08:30:31',NULL),(7,0,0,0,'isContainValue','boolean','object,value','{\"value\": 1, \"object\": {\"id\": 1}}','判断是否对象包含值。',0,NULL,NULL,'2018-10-13 08:30:31',NULL),(8,0,0,0,'getFromArray','Object','array,position','{\"array\": [1, 2, 3], \"result()\": \"getFromArray(array,1)\"}','根据下标获取数组里的值。position 传数字时直接作为值,而不是从所在对象 request 中取值',0,NULL,NULL,'2018-10-13 08:30:31',NULL),(9,0,0,0,'getFromObject','Object','object,key','{\"key\": \"id\", \"object\": {\"id\": 1}}','根据键获取对象里的值。',0,NULL,NULL,'2018-10-13 08:30:31',NULL),(10,0,0,0,'deleteCommentOfMoment','int','momentId','{\"momentId\": 1}','根据动态 id 删除它的所有评论',0,'Moment','DELETE','2019-08-17 18:46:56',NULL),(11,0,0,0,'verifyIdList',NULL,'array','{\"array\": [1, 2, 3], \"result()\": \"verifyIdList(array)\"}','校验类型为 id 列表',0,NULL,NULL,'2019-08-17 19:58:33',NULL),(12,0,0,0,'verifyURLList',NULL,'array','{\"array\": [\"http://123.com/1.jpg\", \"http://123.com/a.png\", \"http://www.abc.com/test.gif\"], \"result()\": \"verifyURLList(array)\"}','校验类型为 URL 列表',0,NULL,NULL,'2019-08-17 19:58:33',NULL),(13,0,0,0,'getWithDefault','Object','value,defaultValue','{\"value\": null, \"defaultValue\": 1}','如果 value 为 null,则返回 defaultValue',0,NULL,NULL,'2019-08-20 15:26:36',NULL),(14,0,0,0,'removeKey','Object','key','{\"key\": \"s\", \"key2\": 2}','从对象里移除 key',0,NULL,NULL,'2019-08-20 15:26:36',NULL),(15,0,0,0,'getFunctionDemo','JSONObject',NULL,'{}','获取远程函数的 Demo',0,NULL,NULL,'2019-08-20 15:26:36',NULL),(16,0,0,0,'getFunctionDetail','String',NULL,'{}','获取远程函数的详情',0,NULL,NULL,'2019-08-20 15:26:36',NULL),(17,0,0,0,'getMethodArguments','String','methodArgs','{\"methodArgs\": \"methodArgs\"}','获取远程函数的参数',0,NULL,NULL,'2021-07-29 09:32:22',NULL),(18,0,0,0,'getMethodDefination','String','method,arguments,type,exceptions,language','{\"method\": \"method\"}','获取远程函数的签名定义',0,NULL,NULL,'2021-07-29 09:34:37',NULL),(19,0,0,0,'getMethodRequest','String',NULL,'{}','获取远程函数的请求',0,NULL,NULL,'2021-07-29 09:35:37',NULL),(20,0,0,0,'deleteChildComment','int','commentId','{}','删除评论的子评论',0,NULL,NULL,'2021-09-10 06:53:24',NULL),(21,0,0,0,'getCurrentUserId','Long',NULL,'{}','获取当前登录用户 id',0,NULL,NULL,'2022-02-19 17:27:41',NULL),(22,0,0,0,'getCurrentUserIdAsList','List',NULL,'{}','获取当前登录用户 id 列表,只包含一个 id,只是为了前端方便构造某些请求',0,NULL,NULL,'2022-02-19 17:27:41',NULL),(24,0,0,0,'getCurrentUser','Visitor',NULL,'{}','获取当前登录用户公开信息',0,NULL,NULL,'2022-02-19 17:34:58',NULL),(26,0,0,0,'getCurrentContactIdList','List',NULL,'{}','获取当前登录用户的联系人 id 列表',0,NULL,NULL,'2022-02-19 17:37:35',NULL),(27,0,0,1,'getType','String','val','{\"val\": 1}','获取类型',0,NULL,NULL,'2022-11-16 16:05:41',NULL),(28,0,0,1,'length','Integer','val','{\"val\": [1, 2, 3]}','获取长度',0,NULL,NULL,'2022-11-16 17:21:53',NULL),(29,0,0,0,'getMethodDefinition','String','method,arguments,type,exceptions,language','{\"method\": \"method\"}','获取远程函数的签名定义',0,NULL,NULL,'2021-07-29 09:34:37',NULL); 

js 的 Array 是用 Object 实现的,key 为下标 index,用 typeof arr 得到的不是 array 而是 object 😂

image
cloudAndMonkey commented 1 year ago

我还没开始测远程函数呢,哈哈

cloudAndMonkey commented 1 year ago

elasticSearch非sql方式, update、delete实现了sql方式操作elasticSearch 通过elasticSearch explain将sql语句转换为 dsl 字符串. elasticSearch代码执行 dsl字符串 如果不转换, 需要解析where条件(支持id/非id, 模糊匹配等等),生成elasticSearch 功能实现代码

TommyLemon commented 1 year ago

@TommyLemon { "ES_blog:a": { "@Datasource": "ELASTICSEARCH", "title:test": "Xshell6安装" } } 我偶然测试发现: 字段名 : 分隔,会被截取. 要不要处理一下? <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< 已生成 1 条 SQL execute startTime = 1670311557672 database = ELASTICSEARCH; schema = ; sql = SELECT * FROM es_blog WHERE ( (test = 'Xshell6安装') ) LIMIT 1

应该是用 Pair.parseEntry 解析了 name:alias 用冒号分割的 key,然后只用到了 name 导致。 用 title.keyword 格式更直观,更贴近用户习惯,只是和 apijson-router 用点分割路径中所有字段这个冲突。 https://github.com/APIJSON/apijson-router image

得想想怎么取舍,或者改用 - 中横线等替代方式。

后续实现多字段关联同一个子查询也考虑用中横线 -,其次是分号 ;

{
  "id-name@": {  // 还有 IN "id-name{}@" 等
    "from": "User",
    "User": {
      "@column":"id,name"
    }
  }
}
WHERE (id,name) = (SELECT id,name FROM User) // WHERE (id,name) IN (SELECT id,name FROM User)

关于新增功能、优化性能等的一些想法 #37 (comment) image

现在看应该是 "title-keyword" 这种用中横线的格式更适合作为递进层级的 key,替代 title.keyword,类似 "User-id[]":{} 提取字段也是 id 从属 User 这种递进关系; https://github.com/Tencent/APIJSON/blob/master/Document.md#3.2

image

另外多个字段子查询则使用分号 ; 隔开,例如

{
   "id;name@": {  // 还有 IN "id;name{}@" 等
     "from": "User",
     "User": {
       "@column":"id,name"
     }
   }
}
WHERE (id,name) = (SELECT id,name FROM User) // WHERE (id,name) IN (SELECT id,name FROM User)

不能用逗号 , ,因为会和现有的 join 冲突 "join":"/User/id,name@"
会被分拆成 /User/id 和 name@ ,当成 JOIN 两张副表

TommyLemon commented 1 year ago

elasticSearch非sql方式, update、delete实现了sql方式操作elasticSearch 通过elasticSearch explain将sql语句转换为 dsl 字符串. elasticSearch代码执行 dsl字符串 如果不转换, 需要解析where条件(支持id/非id, 模糊匹配等等),生成elasticSearch 功能实现代码

既然 Elasticsearch 最核心的查询已经实现了,我看可以先给 apijson-framework 提交 PR ,或者开源一个叫 apijson-elasticsearch 的插件,可能很多用户对于未实现的功能不是刚需(不一定通过 APIJSON JSON 协议实现,可以有 远程函数 或 新增接口单独调用 Elasticsearch RESTful API 等多种替代方式),后面再逐步完善

TommyLemon commented 1 year ago

@TommyLemon 请问调用js脚本, 要咋弄呀, 我也测一下

用最新代码,然后 Function 表配置(type=1) 属性、Script 表配置具体脚本就行了,前端不需要改动参数。 image

INSERT INTO `Function` VALUES (3,0,0,0,'countArray','int','array','{\"array\": [1, 2, 3]}','获取数组长度。没写调用键值对,会自动补全 \"result()\": \"countArray(array)\"',0,NULL,NULL,'2018-10-13 08:23:23',NULL),(4,0,0,0,'countObject','int','object','{\"object\": {\"key0\": 1, \"key1\": 2}}','获取对象长度。',0,NULL,NULL,'2018-10-13 08:23:23',NULL),(5,0,0,1,'isContain','Boolean','array,value','{\"array\": [1, 2, 3], \"value\": 2}','判断是否数组包含值。',0,NULL,NULL,'2018-10-13 08:23:23',NULL),(6,0,0,0,'isContainKey','boolean','object,key','{\"key\": \"id\", \"object\": {\"id\": 1}}','判断是否对象包含键。',0,NULL,NULL,'2018-10-13 08:30:31',NULL),(7,0,0,0,'isContainValue','boolean','object,value','{\"value\": 1, \"object\": {\"id\": 1}}','判断是否对象包含值。',0,NULL,NULL,'2018-10-13 08:30:31',NULL),(8,0,0,0,'getFromArray','Object','array,position','{\"array\": [1, 2, 3], \"result()\": \"getFromArray(array,1)\"}','根据下标获取数组里的值。position 传数字时直接作为值,而不是从所在对象 request 中取值',0,NULL,NULL,'2018-10-13 08:30:31',NULL),(9,0,0,0,'getFromObject','Object','object,key','{\"key\": \"id\", \"object\": {\"id\": 1}}','根据键获取对象里的值。',0,NULL,NULL,'2018-10-13 08:30:31',NULL),(10,0,0,0,'deleteCommentOfMoment','int','momentId','{\"momentId\": 1}','根据动态 id 删除它的所有评论',0,'Moment','DELETE','2019-08-17 18:46:56',NULL),(11,0,0,0,'verifyIdList',NULL,'array','{\"array\": [1, 2, 3], \"result()\": \"verifyIdList(array)\"}','校验类型为 id 列表',0,NULL,NULL,'2019-08-17 19:58:33',NULL),(12,0,0,0,'verifyURLList',NULL,'array','{\"array\": [\"http://123.com/1.jpg\", \"http://123.com/a.png\", \"http://www.abc.com/test.gif\"], \"result()\": \"verifyURLList(array)\"}','校验类型为 URL 列表',0,NULL,NULL,'2019-08-17 19:58:33',NULL),(13,0,0,0,'getWithDefault','Object','value,defaultValue','{\"value\": null, \"defaultValue\": 1}','如果 value 为 null,则返回 defaultValue',0,NULL,NULL,'2019-08-20 15:26:36',NULL),(14,0,0,0,'removeKey','Object','key','{\"key\": \"s\", \"key2\": 2}','从对象里移除 key',0,NULL,NULL,'2019-08-20 15:26:36',NULL),(15,0,0,0,'getFunctionDemo','JSONObject',NULL,'{}','获取远程函数的 Demo',0,NULL,NULL,'2019-08-20 15:26:36',NULL),(16,0,0,0,'getFunctionDetail','String',NULL,'{}','获取远程函数的详情',0,NULL,NULL,'2019-08-20 15:26:36',NULL),(17,0,0,0,'getMethodArguments','String','methodArgs','{\"methodArgs\": \"methodArgs\"}','获取远程函数的参数',0,NULL,NULL,'2021-07-29 09:32:22',NULL),(18,0,0,0,'getMethodDefination','String','method,arguments,type,exceptions,language','{\"method\": \"method\"}','获取远程函数的签名定义',0,NULL,NULL,'2021-07-29 09:34:37',NULL),(19,0,0,0,'getMethodRequest','String',NULL,'{}','获取远程函数的请求',0,NULL,NULL,'2021-07-29 09:35:37',NULL),(20,0,0,0,'deleteChildComment','int','commentId','{}','删除评论的子评论',0,NULL,NULL,'2021-09-10 06:53:24',NULL),(21,0,0,0,'getCurrentUserId','Long',NULL,'{}','获取当前登录用户 id',0,NULL,NULL,'2022-02-19 17:27:41',NULL),(22,0,0,0,'getCurrentUserIdAsList','List',NULL,'{}','获取当前登录用户 id 列表,只包含一个 id,只是为了前端方便构造某些请求',0,NULL,NULL,'2022-02-19 17:27:41',NULL),(24,0,0,0,'getCurrentUser','Visitor',NULL,'{}','获取当前登录用户公开信息',0,NULL,NULL,'2022-02-19 17:34:58',NULL),(26,0,0,0,'getCurrentContactIdList','List',NULL,'{}','获取当前登录用户的联系人 id 列表',0,NULL,NULL,'2022-02-19 17:37:35',NULL),(27,0,0,1,'getType','String','val','{\"val\": 1}','获取类型',0,NULL,NULL,'2022-11-16 16:05:41',NULL),(28,0,0,1,'length','Integer','val','{\"val\": [1, 2, 3]}','获取长度',0,NULL,NULL,'2022-11-16 17:21:53',NULL),(29,0,0,0,'getMethodDefinition','String','method,arguments,type,exceptions,language','{\"method\": \"method\"}','获取远程函数的签名定义',0,NULL,NULL,'2021-07-29 09:34:37',NULL);
image
INSERT INTO `Script` VALUES (1,0,0,0,'getType','function getType(curObj, key) {\n    var val = curObj == null ? null : curObj[key];\n    return val instanceof Array ? \"array\" : typeof val;\n}','2022-11-16 16:01:23',0),(2,0,0,0,'isContain','function isContain(curObj, arrKey, valKey) {\n    var arr = curObj == null ? null : curObj[arrKey];\n    var val = curObj == null ? null : curObj[valKey];\n    return arr != null && arr.indexOf(val) >=0;\n}','2022-11-16 16:02:48',0),(3,0,0,1,'init','var i = 1;\n\"init done \"  + i;','2022-11-16 16:41:35',0),(4,0,0,0,'length','function length(curObj, key) {\n    var val = curObj == null ? null : curObj[key];\n    return val == null ? 0 : val.length;\n}','2022-11-16 17:18:43',0);

远程函数:支持 JavaScript 外的更多脚本语言,例如 Python, Ruby, Lua, PHP 等 dd37279 image image

DROP TABLE IF EXISTS `Function`;
/*!40101 SET @saved_cs_client     = @@character_set_client */;
/*!50503 SET character_set_client = utf8mb4 */;
CREATE TABLE `Function` (
  `id` bigint NOT NULL AUTO_INCREMENT,
  `debug` tinyint NOT NULL DEFAULT '0' COMMENT '是否为 DEBUG 调试数据,只允许在开发环境使用,测试和线上环境禁用:0-否,1-是。',
  `userId` bigint NOT NULL COMMENT '管理员用户Id',
  `language` varchar(45) DEFAULT NULL COMMENT '语言:Java(java), JavaScript(js), Lua(lua), Python(py), Ruby(ruby), PHP(php) 等,NULL 默认为 Java,JDK 1.6-11 默认支持 JavaScript,JDK 12+ 需要额外依赖 Nashron/Rhiro 等 js 引擎库,其它的语言需要依赖对应的引擎库,并在 ScriptEngineManager 中注册',
  `name` varchar(50) NOT NULL COMMENT '方法名',
  `returnType` varchar(50) DEFAULT 'Object' COMMENT '返回值类型。TODO RemoteFunction 校验 type 和 back',
  `arguments` varchar(100) DEFAULT NULL COMMENT '参数列表,每个参数的类型都是 String。\n用 , 分割的字符串 比 [JSONArray] 更好,例如 array,item ,更直观,还方便拼接函数。',
  `demo` json NOT NULL COMMENT '可用的示例。\nTODO 改成 call,和返回值示例 back 对应。',
  `detail` varchar(1000) NOT NULL COMMENT '详细描述',
  `version` tinyint NOT NULL DEFAULT '0' COMMENT '允许的最低版本号,只限于GET,HEAD外的操作方法。\nTODO 使用 requestIdList 替代 version,tag,methods',
  `tag` varchar(20) DEFAULT NULL COMMENT '允许的标签.\nnull - 允许全部\nTODO 使用 requestIdList 替代 version,tag,methods',
  `methods` varchar(50) DEFAULT NULL COMMENT '允许的操作方法。\nnull - 允许全部\nTODO 使用 requestIdList 替代 version,tag,methods',
  `date` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  `return` varchar(45) DEFAULT NULL COMMENT '返回值示例',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=30 DEFAULT CHARSET=utf8mb3 COMMENT='远程函数。强制在启动时校验所有demo是否能正常运行通过';
/*!40101 SET character_set_client = @saved_cs_client */;
INSERT INTO `Function` VALUES (3,0,0,0,'countArray','int','array','{\"array\": [1, 2, 3]}','获取数组长度。没写调用键值对,会自动补全 \"result()\": \"countArray(array)\"',0,NULL,NULL,'2018-10-13 08:23:23',NULL),(4,0,0,0,'countObject','int','object','{\"object\": {\"key0\": 1, \"key1\": 2}}','获取对象长度。',0,NULL,NULL,'2018-10-13 08:23:23',NULL),(5,0,0,1,'isContain','Boolean','array,value','{\"array\": [1, 2, 3], \"value\": 2}','判断是否数组包含值。',0,NULL,NULL,'2018-10-13 08:23:23',NULL),(6,0,0,0,'isContainKey','boolean','object,key','{\"key\": \"id\", \"object\": {\"id\": 1}}','判断是否对象包含键。',0,NULL,NULL,'2018-10-13 08:30:31',NULL),(7,0,0,0,'isContainValue','boolean','object,value','{\"value\": 1, \"object\": {\"id\": 1}}','判断是否对象包含值。',0,NULL,NULL,'2018-10-13 08:30:31',NULL),(8,0,0,0,'getFromArray','Object','array,position','{\"array\": [1, 2, 3], \"result()\": \"getFromArray(array,1)\"}','根据下标获取数组里的值。position 传数字时直接作为值,而不是从所在对象 request 中取值',0,NULL,NULL,'2018-10-13 08:30:31',NULL),(9,0,0,0,'getFromObject','Object','object,key','{\"key\": \"id\", \"object\": {\"id\": 1}}','根据键获取对象里的值。',0,NULL,NULL,'2018-10-13 08:30:31',NULL),(10,0,0,0,'deleteCommentOfMoment','int','momentId','{\"momentId\": 1}','根据动态 id 删除它的所有评论',0,'Moment','DELETE','2019-08-17 18:46:56',NULL),(11,0,0,0,'verifyIdList',NULL,'array','{\"array\": [1, 2, 3], \"result()\": \"verifyIdList(array)\"}','校验类型为 id 列表',0,NULL,NULL,'2019-08-17 19:58:33',NULL),(12,0,0,0,'verifyURLList',NULL,'array','{\"array\": [\"http://123.com/1.jpg\", \"http://123.com/a.png\", \"http://www.abc.com/test.gif\"], \"result()\": \"verifyURLList(array)\"}','校验类型为 URL 列表',0,NULL,NULL,'2019-08-17 19:58:33',NULL),(13,0,0,0,'getWithDefault','Object','value,defaultValue','{\"value\": null, \"defaultValue\": 1}','如果 value 为 null,则返回 defaultValue',0,NULL,NULL,'2019-08-20 15:26:36',NULL),(14,0,0,0,'removeKey','Object','key','{\"key\": \"s\", \"key2\": 2}','从对象里移除 key',0,NULL,NULL,'2019-08-20 15:26:36',NULL),(15,0,0,0,'getFunctionDemo','JSONObject',NULL,'{}','获取远程函数的 Demo',0,NULL,NULL,'2019-08-20 15:26:36',NULL),(16,0,0,0,'getFunctionDetail','String',NULL,'{}','获取远程函数的详情',0,NULL,NULL,'2019-08-20 15:26:36',NULL),(17,0,0,0,'getMethodArguments','String','methodArgs','{\"methodArgs\": \"methodArgs\"}','获取远程函数的参数',0,NULL,NULL,'2021-07-29 09:32:22',NULL),(18,0,0,0,'getMethodDefination','String','method,arguments,type,exceptions,language','{\"method\": \"method\"}','获取远程函数的签名定义',0,NULL,NULL,'2021-07-29 09:34:37',NULL),(19,0,0,0,'getMethodRequest','String',NULL,'{}','获取远程函数的请求',0,NULL,NULL,'2021-07-29 09:35:37',NULL),(20,0,0,0,'deleteChildComment','int','commentId','{}','删除评论的子评论',0,NULL,NULL,'2021-09-10 06:53:24',NULL),(21,0,0,0,'getCurrentUserId','Long',NULL,'{}','获取当前登录用户 id',0,NULL,NULL,'2022-02-19 17:27:41',NULL),(22,0,0,0,'getCurrentUserIdAsList','List',NULL,'{}','获取当前登录用户 id 列表,只包含一个 id,只是为了前端方便构造某些请求',0,NULL,NULL,'2022-02-19 17:27:41',NULL),(24,0,0,0,'getCurrentUser','Visitor',NULL,'{}','获取当前登录用户公开信息',0,NULL,NULL,'2022-02-19 17:34:58',NULL),(26,0,0,0,'getCurrentContactIdList','List',NULL,'{}','获取当前登录用户的联系人 id 列表',0,NULL,NULL,'2022-02-19 17:37:35',NULL),(27,0,0,1,'getType','String','val','{\"val\": 1}','获取类型',0,NULL,NULL,'2022-11-16 16:05:41',NULL),(28,0,0,1,'length','Integer','val','{\"val\": [1, 2, 3]}','获取长度',0,NULL,NULL,'2022-11-16 17:21:53',NULL),(29,0,0,0,'getMethodDefinition','String','method,arguments,type,exceptions,language','{\"method\": \"method\"}','获取远程函数的签名定义',0,NULL,NULL,'2021-07-29 09:34:37',NULL); 

js 的 Array 是用 Object 实现的,key 为下标 index,用 typeof arr 得到的不是 array 而是 object 😂

image

目前对于脚本格式的远程函数,实现有个 NPE 问题: Invocable invocable = engine instanceof Invocable ? (Invocable) engine : null; 为 null 时执行 invocable.invokeFunction 会抛 NullPointerException,得想想不是什么情况下 engine 不属于 Invocable,如果不属于,以什么方式来替代 Invocable.invokeFunction 方法,能够执行脚本里的 方法/函数 https://github.com/Tencent/APIJSON/blob/master/APIJSONORM/src/main/java/apijson/orm/AbstractFunctionParser.java#L360-L364

https://github.com/Tencent/APIJSON/commit/dd372797e30bff6db705eee550b63e28b92a0f1a#commitcomment-92319801

image
cloudAndMonkey commented 1 year ago

elasticSearch非sql方式, update、delete实现了sql方式操作elasticSearch 通过elasticSearch explain将sql语句转换为 dsl 字符串. elasticSearch代码执行 dsl字符串 如果不转换, 需要解析where条件(支持id/非id, 模糊匹配等等),生成elasticSearch 功能实现代码

既然 Elasticsearch 最核心的查询已经实现了,我看可以先给 apijson-framework 提交 PR ,或者开源一个叫 apijson-elasticsearch 的插件,可能很多用户对于未实现的功能不是刚需(不一定通过 APIJSON JSON 协议实现,可以有 远程函数 或 新增接口单独调用 Elasticsearch RESTful API 等多种替代方式),后面再逐步完善 @TommyLemon update、delete最终解决方案(个人理解): 1、id /id{} update、delete 这个简单 2、条件 update、delete 1) 重用 apijson sql解析 生成elasticSearch语句,导入 elasticSearch api 执行(重写executeUpdate即可) 需要解决语法兼容等, 感觉还是通过sql语句,让 elasticSearch去生成dsl简单. 只是多了一次转换和远程调用 2) 调用 elasticSearch sql explain 生成 query dsl string, 将 修改字段和 query 导入 elasticSearch api 执行

我想实现apijson配置就能自动执行,不需要关心 elasticSearch语法. 哈哈😄 我稍微再完善一下

cloudAndMonkey commented 1 year ago

搞定啦, 哈哈 update 和 delete 按照elasticsearch-sql 一样的流程执行啦 将sql解析为 elasticSearch api 执行

  1. apijson使用 elasticSearch-sql执行查询流程:
    • 将 json转换为sql
    • sql解析为 elasticsearch api
    • TransportClient调用elasticSearch执行 elasticsearch api
  2. elasticSearch-sql delete delete 和 search调用API方式存在不一致 image 3.update 为什么 elasticSearch-sql 实现了delete条件删除 , 没有实现 update 条件更新 TransportClient (9300 通讯端口 )不支持条件更新, RestHighLevelClient (9200 http服务端口) 支持 update 和 search调用方式存在不一致 image

感觉可以稍微调整一下elasticSearch-sql 源码, apijson就能无缝集成,同时支持查询、新增、修改、删除 1、apijson支持操作类型method 2、apijson能通过method 控制 statement

使用x-pack 收费版, 更简单, 哈哈

cloudAndMonkey commented 1 year ago

赞,我更新了以上回复,目前可以不用管的,只要有 1 条 INSERT/UPDATE 失败就整体失败

我详细看了你的意思, 我也觉得先不用管

cloudAndMonkey commented 1 year ago

@TommyLemon 我把多数据源、elasticSearch 回归测试,搞完善再提交pr哈

  1. 多数据源 子查询和主语句使用一个数据源 ok join, UNION 还没加 SQL 子查询, SQL 主表 JOIN 副表 最终都是生成和执行一条 SQL,必须在同一个 connection 中(@datasource, @database, method, @role 当然也必须相同)。

  2. elasticSearch回归拉通测试

  3. 非jdbc, 不支持 事物 apijson程序setAutoCommit 不处理哦 我们设置conncetion setAutoCommit 没有意义 image 4、elasticSearch title.keywrod 这次需要调整吗? 5、 update、delete 对象不存在, 没有更新、删除成功的情况 image json全局配置参数来控制比较合理,比如: image

TommyLemon commented 1 year ago

1、JOIN, UNION 可以等提交 PR 后再搞 看起来 Elasticseach 不支持 SQL JOIN 功能,搜到的 JOIN 都支持合并数据而不是 SQL JOIN 这种联表查询,实现应该比较麻烦。 https://www.elastic.co/cn/search?q=JOIN&size=n_20_n&filters%5B0%5D%5Bfield%5D=product_name&filters%5B0%5D%5Bvalues%5D%5B0%5D=Elasticsearch&filters%5B0%5D%5Btype%5D=all https://www.elastic.co/guide/en/elasticsearch/reference/current/joining-queries.html

https://rockset.com/blog/how-to-join-data-in-elasticsearch-vs-rockset/ http://leapfire.com/elasticsearchjoin.html

3、既然它不支持事务的话只能暂时不处理事务 4、可以先提交代码后再调整 5、这个确实可以加个参数,但应该由后端控制,不能让前端决定事务,避免造成数据不一致等问题

cloudAndMonkey commented 1 year ago

@TommyLemon 我有个疑问 web系统, json从客户端网页 传递到服务端, 网页改变了 json,不就改变了流程? 请问你们一般怎么处理的? 如果是单条sql 操作, 还影响不大

cloudAndMonkey commented 1 year ago

@TommyLemon elasticsearch-sql不更新了, 并且elasticsearch8 不支持 TransportClient 可以参考: https://github.com/opendistro-for-elasticsearch/sql/blob/main/sql-jdbc/src/test/java/com/amazon/opendistroforelasticsearch/jdbc/ResultSetTests.java 这是亚马逊 用开源 elasticsearch 搞的免费版

TommyLemon commented 1 year ago

@TommyLemon 我有个疑问 web系统, json从客户端网页 传递到服务端, 网页改变了 json,不就改变了流程? 请问你们一般怎么处理的? 如果是单条sql 操作, 还影响不大

这就是为啥增删改要通过 Request 表配置校验规则(包括必须执行的远程函数等),让前端(客户端)必须按规则来传参 https://github.com/APIJSON/apijson_todo_demo/blob/master/FULLTEXT.md#%E8%AE%BF%E9%97%AE%E6%96%B9%E6%B3%95%E8%AF%B7%E6%B1%82%E4%BD%93%E4%B8%8E-request

image
cloudAndMonkey commented 1 year ago

@TommyLemon 我有个疑问 web系统, json从客户端网页 传递到服务端, 网页改变了 json,不就改变了流程? 请问你们一般怎么处理的? 如果是单条sql 操作, 还影响不大

这就是为啥增删改要通过 Request 表配置校验规则(包括必须执行的远程函数等),让前端(客户端)必须按规则来传参 https://github.com/APIJSON/apijson_todo_demo/blob/master/FULLTEXT.md#%E8%AE%BF%E9%97%AE%E6%96%B9%E6%B3%95%E8%AF%B7%E6%B1%82%E4%BD%93%E4%B8%8E-request image

request只能控制单条语句的校验, crud 包含多条语句

TommyLemon commented 1 year ago

不只是单条语句,而是整个请求,你看下 POST register, DELETE Document 等记录,都是两张不同表,至少 2 条 SQL。

image image
cloudAndMonkey commented 1 year ago

@TommyLemon 多数据源 SQL 子查询、join、子查询+join、union(不影响, apijosn是解析成单条语句执行, 然后对查询结果数据拼接) SQL 主表 JOIN 副表 最终都是生成和执行一条 SQL,必须在同一个 connection 中(@Datasource, @database, method, @ROLE 当然也必须相同)。

  1. 子查询

    {
    "@get": {
        "Sys_user_role": {
    
        },
        "Sys_role_permission": {
        }
    },
    "sql@": {
        "with": true,
        "from": "Sys_role",
        "Sys_role": {
            "@datasource": "db2",
            "@column": "id",
            "role_name": "超级管理员"
        }
    },
    "sql_user@": {
        "with": true,
        "from": "Sys_user",
        "Sys_user": {
            "@datasource": "db2",
            "@column": "id",
            "id": "4732209c-5785-4827-b532-5092f154fd94"
        }
    },
    "Sys_user_role:sur[]": {
        "Sys_user_role": {
            "@datasource": "db1",
            "role_id{}@": "sql",
            "user_id{}@": "sql_user"
        }
    },
    "Sys_role_permission:srp[]": {
        "Sys_role_permission": {
            "@datasource": "db2",
            "role_id{}@": "sql"
        }
    },
    "@explain": true
    }

    image

image

  1. join

    {
    "Sys_permission[]": {
        "Sys_permission": {
            "@from@": {
                "from": "Sys_user_role",
                "join": "&/Sys_role/id@,&/Sys_role_permission/role_id@,&/Sys_permission/id@",
                "Sys_user_role": {
    
                    "user_id": "4732209c-5785-4827-b532-5092f154fd94", 
                    "@column": ""
                },
                "Sys_role": {
                   "@datasource": "155db",
                   "@column": "id:role_id,role_name",
                   "id@": "/Sys_user_role/role_id"
                },
                "Sys_role_permission": {
                    "@datasource": "155db",
                   "@column": "",
                   "role_id@": "/Sys_role/id"
                },
                "Sys_permission": {
                   "@datasource": "155db",
                   "@column": "id:permission_id,name:permission_name",
                   "id@": "/Sys_role_permission/permission_id"
                }
            }
        }
    },
    "@explain": true
    }

    image

3、join + 子查询

{
    //"@datasource": "155db",
    "@get": {
        "Sys_user_role": {

        },
        "Sys_role_permission": {
        }
    },"sql@": {
        "with": true,
        "from": "Sys_role",
        "Sys_role": {
            "@datasource": "db2",
            "@column": "id",
            "role_name": "超级管理员"
        }
    },"Sys_permission[]": {
        "Sys_permission": {
            "@from@": {
                "from": "Sys_user_role",
                "join": "&/Sys_role/id@,&/Sys_role_permission/role_id@,&/Sys_permission/id@",
                "Sys_user_role": {
                    "@datasource": "155db",
                    "user_id": "4732209c-5785-4827-b532-5092f154fd94", 
                    "role_id{}@": "sql",
                    "@column": ""
                },
                "Sys_role": {
                   "@datasource": "155db",
                   "@column": "id:role_id,role_name",
                   "id@": "/Sys_user_role/role_id"
                },
                "Sys_role_permission": {
                    "@datasource": "db1",
                   "@column": "",
                   "role_id@": "/Sys_role/id"
                },
                "Sys_permission": {
                   "@datasource": "db2",
                   "@column": "id:permission_id,name:permission_name",
                   "id@": "/Sys_role_permission/permission_id"
                }
            }
        }
    },
    "@explain": true
}

image 4、union 程序是把多条语句拆成单条语句执行,不用做数据源合并

{

    "Sys_user:su": {
        "@datasource": "master",
        "@column": "count(id):total",
        "username": "normalTest"
    },
    "Sys_user:su2": {
        "@datasource": "155db",
        "@column": "count(id):total",
        "username": "323232"
    }
}

image

image

没改几行代码, 主要是要理解业务执行逻辑,理了我两个小时, join、子查询解析有代码重叠: image

cloudAndMonkey commented 1 year ago

我开始合并代码,提交pr,哈哈 elasticSearch-sql支持, 很完善了, 只要elasticSearch-sql支持的功能, apijson都能用 修改、删除也沿用了 elasticsearch-sql的解析. 学会apijson语法, 写好json即可,不用关心elasticsearch语法啦

cloudAndMonkey commented 1 year ago

不只是单条语句,而是整个请求,你看下 POST register, DELETE Document 等记录,都是两张不同表,至少 2 条 SQL。 image

image

这个等我提完代码再跟进,详细梳理一下

cloudAndMonkey commented 1 year ago

redis sql集成: rediSQL 高版本也可以使用, 通过sql语句操作redis数据库 也支持虚拟表, 映射 redis hashmap,比如: https://redisql.redbeardlab.com/references/#redisql_tables_brute_hash redis cluster能不能安装,我要再测一下, 它也有点贱, 它搞了一个遥测函数,定时把redis statistics 定时发送给他的服务器, 如果去掉遥测, 要给钱,买pro版本,😄 https://github.com/RedBeardLab/rediSQL/blob/master/telemetrics/src/lib.rs

TommyLemon commented 1 year ago

@TommyLemon 多数据源 SQL 子查询、join、子查询+join、union(不影响, apijosn是解析成单条语句执行, 然后对查询结果数据拼接) SQL 主表 JOIN 副表 最终都是生成和执行一条 SQL,必须在同一个 connection 中(@Datasource, @database, method, @ROLE 当然也必须相同)。

  1. 子查询
{
  "@get": {
      "Sys_user_role": {

      },
      "Sys_role_permission": {
      }
  },
  "sql@": {
        "with": true,
        "from": "Sys_role",
        "Sys_role": {
          "@datasource": "db2",
          "@column": "id",
          "role_name": "超级管理员"
        }
    },
    "sql_user@": {
        "with": true,
        "from": "Sys_user",
        "Sys_user": {
          "@datasource": "db2",
          "@column": "id",
          "id": "4732209c-5785-4827-b532-5092f154fd94"
        }
    },
    "Sys_user_role:sur[]": {
        "Sys_user_role": {
          "@datasource": "db1",
            "role_id{}@": "sql",
            "user_id{}@": "sql_user"
        }
    },
    "Sys_role_permission:srp[]": {
        "Sys_role_permission": {
          "@datasource": "db2",
            "role_id{}@": "sql"
        }
    },
    "@explain": true
}

image

image

  1. join
{
    "Sys_permission[]": {
        "Sys_permission": {
            "@from@": {
                "from": "Sys_user_role",
                "join": "&/Sys_role/id@,&/Sys_role_permission/role_id@,&/Sys_permission/id@",
                "Sys_user_role": {

                    "user_id": "4732209c-5785-4827-b532-5092f154fd94", 
                    "@column": ""
                },
                "Sys_role": {
                   "@datasource": "155db",
                   "@column": "id:role_id,role_name",
                   "id@": "/Sys_user_role/role_id"
                },
                "Sys_role_permission": {
                  "@datasource": "155db",
                   "@column": "",
                   "role_id@": "/Sys_role/id"
                },
                "Sys_permission": {
                   "@datasource": "155db",
                   "@column": "id:permission_id,name:permission_name",
                   "id@": "/Sys_role_permission/permission_id"
                }
            }
        }
    },
    "@explain": true
}

image

3、join + 子查询

{
  //"@datasource": "155db",
  "@get": {
      "Sys_user_role": {

      },
      "Sys_role_permission": {
      }
  },"sql@": {
        "with": true,
        "from": "Sys_role",
        "Sys_role": {
          "@datasource": "db2",
          "@column": "id",
          "role_name": "超级管理员"
        }
    },"Sys_permission[]": {
        "Sys_permission": {
            "@from@": {
                "from": "Sys_user_role",
                "join": "&/Sys_role/id@,&/Sys_role_permission/role_id@,&/Sys_permission/id@",
                "Sys_user_role": {
                  "@datasource": "155db",
                    "user_id": "4732209c-5785-4827-b532-5092f154fd94", 
                    "role_id{}@": "sql",
                    "@column": ""
                },
                "Sys_role": {
                   "@datasource": "155db",
                   "@column": "id:role_id,role_name",
                   "id@": "/Sys_user_role/role_id"
                },
                "Sys_role_permission": {
                  "@datasource": "db1",
                   "@column": "",
                   "role_id@": "/Sys_role/id"
                },
                "Sys_permission": {
                   "@datasource": "db2",
                   "@column": "id:permission_id,name:permission_name",
                   "id@": "/Sys_role_permission/permission_id"
                }
            }
        }
    },
    "@explain": true
}

image 4、union 程序是把多条语句拆成单条语句执行,不用做数据源合并

{

   "Sys_user:su": {
      "@datasource": "master",
      "@column": "count(id):total",
       "username": "normalTest"
   },
   "Sys_user:su2": {
      "@datasource": "155db",
       "@column": "count(id):total",
       "username": "323232"
   }
}

image

image

没改几行代码, 主要是要理解业务执行逻辑,理了我两个小时, join、子查询解析有代码重叠: image

4、union 两个 count 返回的都是单条数据,用不上 UNION。 但如果是 SELECT */字段名 返回的数据有多条,APIJSON 目前只能分别查询,合并只能靠重写方法或远程函数

"User[]":{},
"User:2[]":{},
"list()":"merge(User[],User:2[],true)"  // public JSONArray merge(JSONObject curObj, JSONArray arr1, JSONArray arr2, boolean unique)

如果需要在一条 SQL 中处理,这样就不行了,必须支持 SQL 中的 UNION 和 UNION ALL

https://github.com/Tencent/APIJSON/blob/master/Roadmap.md#%E6%96%B0%E5%A2%9E%E6%94%AF%E6%8C%81-union

这个功能可以先提交 PR 后再考虑。

cloudAndMonkey commented 1 year ago

image 这个功能确实需要

cloudAndMonkey commented 1 year ago
{
"[]": {
"User": {
"name~": "a",
"tag~": "a",
"@combine": "name~,tag~",    
"@union": 1  //将 @combine 中的 N 个 OR 连接字段用 UNION 替换,原本一条 SQL 需要拆分成 N 条 SQL 来 UNION 
}
},
"@explain": true
}

@TommyLemon

  1. 按照上面的写法, 不方便理解,是否沿用 join查询(支持相同表和不同表union)

    {
    "Sys_permission[]": {
        "Sys_permission": {
            "@from@": {
                "from": "Sys_user_role",
                "union": "xxx1,xxx2,xxx3",
            }
        }
    },
    "@explain": true
    }
  2. 如下写法,数据库支持, 底层直接转换为 union即可,减少用户学习成本

    {
    "[]": {
        "User": {
            "name~": "a",
            "tag~": "a",
            "@combine": "name~,tag~",   //将 @combine 中的 N 个 OR 连接字段用 UNION 替换,原本一条 SQL 需要拆分成 N 条 SQL 来 UNION 
        }
    },
    "@explain": true
    }
cloudAndMonkey commented 1 year ago

调用一个函数(javascript、lua 、java函数) 通过返回结果来控制crud执行流程 我想了一下,还是外层来控制吧

TommyLemon commented 1 year ago

3. 如下写法,数据库支持, 底层直接转换为 union即可,减少用户学习成本

1.不同表 UNION 会导致很多 NULL 值(部分是主表所有字段为 NULL,部分是副表所有字段为 NULL),目前除了水平拆分表,还没看到其它对应的需求场景

2.部分数据库的较新版本支持,所以其实也不急于实现

TommyLemon commented 1 year ago

调用一个函数(javascript、lua 、java函数) 通过返回结果来控制crud执行流程 我想了一下,还是外层来控制吧

APIJSON 请求参数 JSON 是有序的,除了关键词,执行顺序都就是按传参的键值对顺序来的。 目前你发现在哪些场景满足不了流程控制的需求呢?

cloudAndMonkey commented 1 year ago

我头疼得要死,好像新冠了,等我好了, elasticsearch-sql demo合并提交

TommyLemon commented 1 year ago

这样啊,先不急着贡献,好好休息,严重就及时就医,祝早日康复哈~

新冠肺炎检测结果呈现阳性及安全地居家管理新冠肺炎 https://www.health.nsw.gov.au/Infectious/covid-19/Documents/translated/confirmed-cases-cs.pdf

新型冠状病毒肺炎(COVID-19)的居家管理:照护者指南 https://www.mskcc.org/zh-hans/cancer-care/patient-education/managing-covid-19-home-information-caregivers

感染新冠病毒“吃药顺序图”靠谱么?专家重要提醒 https://www.suzhou.gov.cn/szsrmzf/mszx/202212/5858911e74084131b3e646c10ab08611.shtml

cloudAndMonkey commented 1 year ago

@TommyLemon 非id条件修改,这里判断有问题. 修改成功多条记录,这是正常情况. return 0 // 一条都未修改成功, 配置 ALLOW_PARTIAL_UPDATE_FAILED 不抛出异常,继续执行 https://github.com/Tencent/APIJSON/blob/master/APIJSONORM/src/main/java/apijson/orm/AbstractObjectParser.java#L696-L710 image

比如: { "User:aa[]": [ { "username~": "test-3", "password": "1111" } ], "tag": "User[]", "explain": true } 修改成功3条 image

cfg.setTable(childKey); // Request 表 structure 中配置 "ALLOW_PARTIAL_UPDATE_FAILED": "Table[],key[],key:alias[]" 自动配置 boolean allowPartialFailed = cfg.allowPartialUpdateFailed();

我本地测试,没有 自动配置呀, 哈哈

cloudAndMonkey commented 1 year ago

调用一个函数(javascript、lua 、java函数) 通过返回结果来控制crud执行流程 我想了一下,还是外层来控制吧

APIJSON 请求参数 JSON 是有序的,除了关键词,执行顺序都就是按传参的键值对顺序来的。 目前你发现在哪些场景满足不了流程控制的需求呢?

我想做服务编排,就像工作流引擎: 条件判断A: A=1 : 执行 a、b A=2: 执行b、c 我在外层做就好了,具体业务场景 等后面具体补充

cloudAndMonkey commented 1 year ago

不只是单条语句,而是整个请求,你看下 POST register, DELETE Document 等记录,都是两张不同表,至少 2 条 SQL。 image

image

@TommyLemon 我看了post register 1、json包含多个对象 2、代码解析每个对象进行校验 3、保存注册用户等信息 image

按照post register的实现思路, 新增一个 crud json(包含多条语句), 需要 新增后端接口进行json校验

我的场景: 后端页面配置(增删改查表、elasticsearch、rpc、redis等), 用户页面填写数据, 生成crud json, 调用crud方法,执行流程 我的实现思路: 1) 后端页面配置生成crud模版 2) 用户页面填写数据 3) 后端接口 模版+ 数据 => 生成json 减少开发后端接口, 校验交给apijson 函数去搞定 4) 调用apijson crud方法

TommyLemon commented 1 year ago

@TommyLemon 非id条件修改,这里判断有问题. 修改成功多条记录,这是正常情况. return 0 // 一条都未修改成功, 配置 ALLOW_PARTIAL_UPDATE_FAILED 不抛出异常,继续执行 https://github.com/Tencent/APIJSON/blob/master/APIJSONORM/src/main/java/apijson/orm/AbstractObjectParser.java#L696-L710 image

比如: { "User:aa[]": [ { "username~": "test-3", "password": "1111" } ], "tag": "User[]", "explain": true } 修改成功3条 image

cfg.setTable(childKey); // Request 表 structure 中配置 "ALLOW_PARTIAL_UPDATE_FAILED": "Table[],key[],key:alias[]" 自动配置 boolean allowPartialFailed = cfg.allowPartialUpdateFailed();

我本地测试,没有 自动配置呀, 哈哈

确实哦,只考虑了传 id,没考虑到最近你新增的非 id 情况,这里 count 得根据之前加的 AbstractVerifier.IS_UPDATE_MUST_HAVE_ID_CONDITION 来判断,如果是 false,则根据实际返回的 count 来,true 则按 1 来。

ALLOW_PARTIAL_UPDATE_FAILED 是在数组子项数量在 2 以上,有 1 个以上失败,但至少有 1 个成功,才允许通过。

TommyLemon commented 1 year ago

调用一个函数(javascript、lua 、java函数) 通过返回结果来控制crud执行流程 我想了一下,还是外层来控制吧

APIJSON 请求参数 JSON 是有序的,除了关键词,执行顺序都就是按传参的键值对顺序来的。 目前你发现在哪些场景满足不了流程控制的需求呢?

我想做服务编排,就像工作流引擎: 条件判断A: A=1 : 执行 a、b A=2: 执行b、c 我在外层做就好了,具体业务场景 等后面具体补充

这样啊,这种逻辑编排目前放在外层做确实更好,也有各种开源的(Airflow, LiteFlow)、商业的流程引擎,包括腾讯问卷这种拖拽式的流程图方式来处理 if-else 分支等。 APIJSON ORM 没必要加上这个 ORM 不是强相关的功能,如果要扩展也是通过插件等方式。

cloudAndMonkey commented 1 year ago

调用一个函数(javascript、lua 、java函数) 通过返回结果来控制crud执行流程 我想了一下,还是外层来控制吧

APIJSON 请求参数 JSON 是有序的,除了关键词,执行顺序都就是按传参的键值对顺序来的。 目前你发现在哪些场景满足不了流程控制的需求呢?

我想做服务编排,就像工作流引擎: 条件判断A: A=1 : 执行 a、b A=2: 执行b、c 我在外层做就好了,具体业务场景 等后面具体补充

这样啊,这种逻辑编排目前放在外层做确实更好,也有各种开源的(Airflow, LiteFlow)、商业的流程引擎,包括腾讯问卷这种拖拽式的流程图方式来处理 if-else 分支等。 APIJSON ORM 没必要加上这个 ORM 不是强相关的功能,如果要扩展也是通过插件等方式。

嗯嗯

cloudAndMonkey commented 1 year ago

不只是单条语句,而是整个请求,你看下 POST register, DELETE Document 等记录,都是两张不同表,至少 2 条 SQL。 image

image

@TommyLemon 我看了post register 1、json包含多个对象 2、代码解析每个对象进行校验 3、保存注册用户等信息 image

按照post register的实现思路, 新增一个 crud json(包含多条语句), 需要 新增后端接口进行json校验

我的场景: 后端页面配置(增删改查表、elasticsearch、rpc、redis等), 用户页面填写数据, 生成crud json, 调用crud方法,执行流程 我的实现思路:

  1. 后端页面配置生成crud模版
  2. 用户页面填写数据
  3. 后端接口 模版+ 数据 => 生成json 减少开发后端接口, 校验交给apijson 函数去搞定
  4. 调用apijson crud方法

@TommyLemon 请问上面我的理解有没有问题呀

TommyLemon commented 1 year ago

调用一个函数(javascript、lua 、java函数) 通过返回结果来控制crud执行流程 我想了一下,还是外层来控制吧

APIJSON 请求参数 JSON 是有序的,除了关键词,执行顺序都就是按传参的键值对顺序来的。 目前你发现在哪些场景满足不了流程控制的需求呢?

我想做服务编排,就像工作流引擎: 条件判断A: A=1 : 执行 a、b A=2: 执行b、c 我在外层做就好了,具体业务场景 等后面具体补充

这样啊,这种逻辑编排目前放在外层做确实更好,也有各种开源的(Airflow, LiteFlow)、商业的流程引擎,包括腾讯问卷这种拖拽式的流程图方式来处理 if-else 分支等。 APIJSON ORM 没必要加上这个 ORM 不是强相关的功能,如果要扩展也是通过插件等方式。

或许提供接入流程引擎的 Demo 就行了,例如对接连接池 Druid, HikariCP 就是这样,APIJSON ORM, apijson-framework 都没做任何相关改动。 https://github.com/APIJSON/APIJSON-Demo/tree/master/APIJSON-Java-Server

cloudAndMonkey commented 1 year ago

这个不难, apijson支持多数据源、crud(增删改查), 添加一个demo, 等后面我弄到这块再加.主要是这块需要页面配合,设计流程模版

TommyLemon commented 1 year ago

这个不难, apijson支持多数据源、crud(增删改查), 添加一个demo, 等后面我弄到这块再加.主要是这块需要页面配合,设计流程模版

是的,赞

TommyLemon commented 1 year ago

不只是单条语句,而是整个请求,你看下 POST register, DELETE Document 等记录,都是两张不同表,至少 2 条 SQL。 image

image

@TommyLemon 我看了post register 1、json包含多个对象 2、代码解析每个对象进行校验 3、保存注册用户等信息 image

按照post register的实现思路, 新增一个 crud json(包含多条语句), 需要 新增后端接口进行json校验

我的场景: 后端页面配置(增删改查表、elasticsearch、rpc、redis等), 用户页面填写数据, 生成crud json, 调用crud方法,执行流程 我的实现思路:

  1. 后端页面配置生成crud模版
  2. 用户页面填写数据
  3. 后端接口 模版+ 数据 => 生成json 减少开发后端接口, 校验交给apijson 函数去搞定
  4. 调用apijson crud方法

这个可行,有些用 APIJSON 的低代码平台基本就是这个原理。 不过我回答你上面那个问题用 POST register 举例不太合理,因为是单独实现的 /register 接口,不是用万能通用接口。 POST Document, DELETE Moment 这样的就是走万能通用接口,并且涉及多表关联 新增/删除。

cloudAndMonkey commented 1 year ago

不只是单条语句,而是整个请求,你看下 POST register, DELETE Document 等记录,都是两张不同表,至少 2 条 SQL。 image

image

@TommyLemon 我看了post register 1、json包含多个对象 2、代码解析每个对象进行校验 3、保存注册用户等信息 image 按照post register的实现思路, 新增一个 crud json(包含多条语句), 需要 新增后端接口进行json校验 我的场景: 后端页面配置(增删改查表、elasticsearch、rpc、redis等), 用户页面填写数据, 生成crud json, 调用crud方法,执行流程 我的实现思路:

  1. 后端页面配置生成crud模版
  2. 用户页面填写数据
  3. 后端接口 模版+ 数据 => 生成json 减少开发后端接口, 校验交给apijson 函数去搞定
  4. 调用apijson crud方法

这个可行,有些用 APIJSON 的低代码平台基本就是这个原理。 不过我回答你上面那个问题用 POST register 举例不太合理,因为是单独实现的 /register 接口,不是用万能通用接口。 POST Document, DELETE Moment 这样的就是走万能通用接口,并且涉及多表关联 新增/删除。

好的,谢谢

TommyLemon commented 1 year ago

简单的校验可以直接用现有的 Operation 中校验规则 https://github.com/Tencent/APIJSON/blob/master/APIJSONORM/src/main/java/apijson/orm/Operation.java

复杂的就写 js/lua 等脚本代码实现远程函数

cloudAndMonkey commented 1 year ago

简单的校验可以直接用现有的 Operation 中校验规则 https://github.com/Tencent/APIJSON/blob/master/APIJSONORM/src/main/java/apijson/orm/Operation.java

复杂的就写 js/lua 等脚本代码实现远程函数

嗯嗯,是的.

cloudAndMonkey commented 1 year ago

@TommyLemon 有一个点: apijson支持 javascript、lua 前端开发人员不熟悉后端业务、复杂业务逻辑有难度 后端排斥javascript写业务,习惯了java apijson 支持rpc, Java开发人员用java来写业务逻辑, apijson整合

TommyLemon commented 1 year ago

如果有专职的后端人员,那就用他们熟悉的语言,想写 Java 就写 Java,不过不能像脚本语言这么灵活。 如果是全栈开发,很多都是出自前端用 JavaScript 的人,就会倾向于也用 JavaScript 实现后端。 远程函数 也是一种 RPC,你说的 RPC 是指 gRPC, Dubbo 这类提前定义 struct/message 结构体,生成前后端关于结构体和请求相关代码的项目吗? https://github.com/protocolbuffers/protobuf/blob/main/examples/addressbook.proto 这种一般必须提前定义好,没法实现动态逻辑(除非是用脚本语言的版本,动态生成结构体)