Skip to content

apijson function支持脚本引擎,比如JavaScript、lua等 #495

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
cloudAndMonkey opened this issue Jan 10, 2023 · 23 comments
Open

apijson function支持脚本引擎,比如JavaScript、lua等 #495

cloudAndMonkey opened this issue Jan 10, 2023 · 23 comments
Labels
Enhancement 增强 增强功能、提高性能等

Comments

@cloudAndMonkey
Copy link
Contributor

Description

丰富apijson周边

@cloudAndMonkey
Copy link
Contributor Author

cloudAndMonkey commented Jan 10, 2023

@TommyLemon
在这里独立讨论吧,这两天有点忙,javascript、lua的差不多开发完了,等我忙完再测试
开发思路:
1、抽象脚本引擎执行器 ScriptExecutor
image
2、 JSR223 script engine的统一实现抽象类
image
3、可以自定义实现脚本引擎执行器 ScriptExecutor
比如实现 javascript graal.js 脚本引擎
等我后面补充
4、javascript执行器
image
5、lua执行器
image

apijson执行集成方式:
1、APIJSONFunctionParser.init 初始化默认脚本引擎
image
2、APIJSONFunctionParser.init 加载脚本
image
3、加载扩展脚本执行器(暂时放这里,等我忙完再调整,说明思路)
APIJSONApplication
image
4、apijson执行脚本
AbstractFunctionParser.invokeScript
image
5、传递 相关全局元数据
比如 request、tag、version等

等我忙完再测试

@TommyLemon
Copy link
Collaborator

赞,新开 issue 确实方便很多。
ScriptExecutor 封装很合理,可以更好地兼容不同的脚本引擎实现。

@cloudAndMonkey
Copy link
Contributor Author

@TommyLemon
javascript已经测试完成
注意事项:
1、并发问题
ScriptEngine是线程安全的,自身没有线程问题,但是变量作用域(Bindings)存在线程问题
2、性能优化
NashornScriptEngineFactory factory = new NashornScriptEngineFactory();
String[] stringArray = new String[]{"-doe", "--global-per-engine"};
ScriptEngine engine = factory.getScriptEngine(stringArray);
Compilable compilingEngine = (Compilable) engine;
CompiledScript cscript = compilingEngine.compile(scriptVar);
比:
ScriptEngineManager engineManager = new ScriptEngineManager();
ScriptEngine engine = engineManager.getEngineByName("javascript");
Compilable compilingEngine = (Compilable) engine;
CompiledScript cscript = compilingEngine.compile(scriptVar);
压力测试,性能要高很多

@cloudAndMonkey
Copy link
Contributor Author

cloudAndMonkey commented Jan 12, 2023

1、开始实现多种javascript 引擎
2、扩展 方法, 业务侧可以配置java类,供脚步调用执行
3、传递 相关全局元数据
比如 request、tag、version等

@cloudAndMonkey
Copy link
Contributor Author

javascript、lua已经调通
经测试:
javascript SimpleBindings 方式能保证线程安全
lua: SimpleBindings无法保证线程安全
需要通过外部锁,比如 lock、synchronized、redis 分布式锁,防止并发问题

@cloudAndMonkey
Copy link
Contributor Author

我把graalvm js加上去,其他脚步他们自己扩展

@cloudAndMonkey
Copy link
Contributor Author

搞完了,晚一点我提交

@cloudAndMonkey
Copy link
Contributor Author

@TommyLemon
校验模块好像这两个字端,没有传递到后端
function tag、version
我还没测

@TommyLemon
Copy link
Collaborator

javascript、lua已经调通 经测试: javascript SimpleBindings 方式能保证线程安全 lua: SimpleBindings无法保证线程安全 需要通过外部锁,比如 lock、synchronized、redis 分布式锁,防止并发问题

方便判断是 lua 就自动加锁,不方便就让用户自己加锁吧

@TommyLemon
Copy link
Collaborator

我把graalvm js加上去,其他脚步他们自己扩展

默认有一个实现就可以了,其它的用户自己扩展

@TommyLemon
Copy link
Collaborator

TommyLemon commented Jan 13, 2023

@TommyLemon 校验模块好像这两个字端,没有传递到后端 function tag、version 我还没测

可以先提交 PR,然后再继续看这个哈

@spt110
Copy link

spt110 commented Jan 14, 2023

javascript、lua已经调通 经测试: javascript SimpleBindings 方式能保证线程安全 lua: SimpleBindings无法保证线程安全 需要通过外部锁,比如 lock、synchronized、redis 分布式锁,防止并发问题

javascript引擎中 Nashorn引擎是最快的吗?

@cloudAndMonkey
Copy link
Contributor Author

@TommyLemon
GraalVM 是 Nashorn的替代方案
https://www.graalvm.org/latest/reference-manual/js/NashornMigrationGuide/
Nashorn 引擎已作为 JEP 335 的一部分在 JDK 11 中被弃用,并已作为 JEP 372 的一部分从 JDK15 中删除。GraalVM 可以替代之前在 Nashorn 引擎上执行的 JavaScript 代码。

官网测试:
测试证明 GraalVM JavaScript 大约比 Nashorn Community Edition 快四倍。 并且比 Nashorn Enterprise Edition 快六倍。
Nashorn 引擎: 配置 --global-per-engine 参数能提高性能
image

@cloudAndMonkey
Copy link
Contributor Author

cloudAndMonkey commented Jan 16, 2023

1、目前支持脚步引擎
jdk默认实现 Nashorn 引擎
Nashorn(--global-per-engine)
graalvm
lua
其他脚步引擎,业务侧可以扩展
2、扩展脚步引擎,支持更多脚步执行器
image
image
3、脚本 线程安全问题
业务侧按照需求,进行锁颗粒度控制
目前测试, lua Bindings无法保证线程安全 需要通过外部锁,比如 lock、synchronized、redis 分布式锁,防止并发问题
建议锁颗粒度: 脚本名.
举例:
脚本: length, testArray_lua
锁: length
锁: testArray_lua
ConcurrentHashMap 写key实现原理差不多
image

image
4、支持传递 apijson元数据 参数:
version、tag、args
5、支持业务侧扩展参数
extParam
image
image
6、脚本编写规范
案例一:
function getType() {
var curObj = _meta.args[0];
var val = _meta.args[1];
var index = _meta.args[2];
return curObj[val] instanceof Array ? 'array' : typeof curObj[val];
}
getType()
案例二:
function getType(curObj, val, index) {
return curObj[val] instanceof Array ? 'array' : typeof curObj[val];
}
getType(_meta.args[0], _meta.args[1], _meta.args[2])
7、支持二次处理脚步
格式化等
image
8、数据库测试脚本

DROP TABLE IF EXISTS `function`;
CREATE TABLE `function` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `debug` tinyint(4) NOT NULL DEFAULT '0' COMMENT '是否为 DEBUG 调试数据,只允许在开发环境使用,测试和线上环境禁用:0-否,1-是。',
  `userId` varchar(36) NOT NULL COMMENT '管理员用户Id',
  `type` tinyint(4) NOT NULL DEFAULT '0' COMMENT '''0'' COMMENT ''类型:0-远程函数;1- SQL 函数''',
  `name` varchar(50) NOT NULL COMMENT '方法名',
  `returnType` varchar(50) NOT NULL 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(4) 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 '创建时间',
  `appId` varchar(36) DEFAULT NULL COMMENT '应用编号',
  `return` varchar(45) DEFAULT NULL COMMENT '返回值示例',
  `language` varchar(255) 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 中注册',
  PRIMARY KEY (`id`) USING BTREE,
  UNIQUE KEY `name_UNIQUE` (`name`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=37 DEFAULT CHARSET=utf8 COMMENT='远程函数。强制在启动时校验所有demo是否能正常运行通过';

-- ----------------------------
-- Records of function
-- ----------------------------
BEGIN;
INSERT INTO `function` (`id`, `debug`, `userId`, `type`, `name`, `returnType`, `arguments`, `demo`, `detail`, `version`, `tag`, `methods`, `date`, `appId`, `return`, `language`) VALUES (18, 0, '0', 1, 'getType', 'String', 'var,index', '{\"var\": [1, 2, 3], \"index\": 0}', '系统', 0, NULL, NULL, '2022-12-13 14:17:43', NULL, NULL, 'js');
INSERT INTO `function` (`id`, `debug`, `userId`, `type`, `name`, `returnType`, `arguments`, `demo`, `detail`, `version`, `tag`, `methods`, `date`, `appId`, `return`, `language`) VALUES (28, 0, '0', 0, 'testArray', 'Object', 'var,index', '{\"var\": [1, 2, 3], \"index\": 0}', '测试', 0, NULL, NULL, '2022-12-20 18:42:39', NULL, NULL, 'js');
INSERT INTO `function` (`id`, `debug`, `userId`, `type`, `name`, `returnType`, `arguments`, `demo`, `detail`, `version`, `tag`, `methods`, `date`, `appId`, `return`, `language`) VALUES (31, 0, '0', 0, 'isContainJs', 'Object', 'var,valKey', '{\"var\": \"a, b,c\", \"valKey\": \"1\"}', '测试', 0, NULL, NULL, '2023-01-12 14:29:11', NULL, NULL, 'nashornJS');
INSERT INTO `function` (`id`, `debug`, `userId`, `type`, `name`, `returnType`, `arguments`, `demo`, `detail`, `version`, `tag`, `methods`, `date`, `appId`, `return`, `language`) VALUES (32, 0, '0', 0, 'length', 'Object', 'var', '{\"var\": [1, 2, 3]}', '测试', 0, NULL, NULL, '2023-01-12 17:28:47', NULL, NULL, 'js');
INSERT INTO `function` (`id`, `debug`, `userId`, `type`, `name`, `returnType`, `arguments`, `demo`, `detail`, `version`, `tag`, `methods`, `date`, `appId`, `return`, `language`) VALUES (33, 0, '0', 0, 'lengthExtendParameter', 'Object', 'var', '{\"var\": [1, 2, 3]}', '测试', 0, NULL, NULL, '2023-01-12 18:18:31', NULL, NULL, 'nashornJS');
INSERT INTO `function` (`id`, `debug`, `userId`, `type`, `name`, `returnType`, `arguments`, `demo`, `detail`, `version`, `tag`, `methods`, `date`, `appId`, `return`, `language`) VALUES (34, 0, '0', 0, 'length_lua', 'Object', 'var', '{\"var\": [1, 2, 3]}', '测试', 0, NULL, NULL, '2023-01-13 15:59:47', NULL, NULL, 'luaj');
INSERT INTO `function` (`id`, `debug`, `userId`, `type`, `name`, `returnType`, `arguments`, `demo`, `detail`, `version`, `tag`, `methods`, `date`, `appId`, `return`, `language`) VALUES (35, 0, '0', 0, 'testArray_lua', 'Object', 'var,index', '{\"var\": [1, 2, 3], \"index\": 0}', '测试', 0, NULL, NULL, '2023-01-13 16:02:00', NULL, NULL, 'luaj');
INSERT INTO `function` (`id`, `debug`, `userId`, `type`, `name`, `returnType`, `arguments`, `demo`, `detail`, `version`, `tag`, `methods`, `date`, `appId`, `return`, `language`) VALUES (36, 0, '0', 0, 'length_graalJS', 'Object', 'var', '{\"var\": [1, 2, 3]}', '测试', 0, NULL, NULL, '2023-01-13 18:03:25', NULL, NULL, 'graalJS');
COMMIT;

SET FOREIGN_KEY_CHECKS = 1;

SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;

-- ----------------------------
-- Table structure for script
-- ----------------------------
DROP TABLE IF EXISTS `script`;
CREATE TABLE `script` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `documentId` bigint(20) NOT NULL,
  `randomId` bigint(20) NOT NULL,
  `simple` tinyint(4) NOT NULL DEFAULT '0' COMMENT '是否为可直接执行的简单代码段:0-否 1-是',
  `name` varchar(100) NOT NULL,
  `script` text NOT NULL,
  `date` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
  `ahead` tinyint(4) NOT NULL DEFAULT '0' COMMENT '是否为前置脚本',
  PRIMARY KEY (`id`) USING BTREE,
  UNIQUE KEY `id_UNIQUE` (`id`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=10 DEFAULT CHARSET=utf8 COMMENT='脚本,前置预处理脚本、后置断言和恢复脚本等';

-- ----------------------------
-- Records of script
-- ----------------------------
BEGIN;
INSERT INTO `script` (`id`, `documentId`, `randomId`, `simple`, `name`, `script`, `date`, `ahead`) VALUES (1, 0, 0, 0, 'getType', 'function getType() {\n	var curObj = _meta.args[0];\n	var val = _meta.args[1];\n	var index = _meta.args[2];\n	return curObj[val] instanceof Array ? \'array\' : typeof curObj[val];\n}\ngetType()', '2022-11-16 16:01:23', 0);
INSERT INTO `script` (`id`, `documentId`, `randomId`, `simple`, `name`, `script`, `date`, `ahead`) VALUES (2, 0, 0, 0, 'isContainJs', 'function isContainJs() {\n	var curObj = _meta.args[0];\n	var key = _meta.args[1];\n	var valKey = _meta.args[2];\n    var arr = curObj == null ? null : curObj[key];\n    var val = curObj == null ? null : curObj[valKey];\n    return arr != null && arr.indexOf(val) >=0;\n}\nisContainJs()\n', '2022-11-16 16:02:48', 0);
INSERT INTO `script` (`id`, `documentId`, `randomId`, `simple`, `name`, `script`, `date`, `ahead`) VALUES (3, 0, 0, 1, 'init', 'var i = 1;\n\"init done \"  + i;', '2022-11-16 16:41:35', 0);
INSERT INTO `script` (`id`, `documentId`, `randomId`, `simple`, `name`, `script`, `date`, `ahead`) VALUES (4, 0, 0, 0, 'length', 'function length() {\n	var curObj = _meta.args[0];\n	var key = _meta.args[1];\n    var val = curObj == null ? null : curObj[key];\n    return val == null ? 0 : val.length;\n}\nlength()', '2022-11-16 17:18:43', 0);
INSERT INTO `script` (`id`, `documentId`, `randomId`, `simple`, `name`, `script`, `date`, `ahead`) VALUES (5, 0, 0, 0, 'testArray', 'function testArray() {\n	var curObj = _meta.args[0];\n	var key = _meta.args[1];\n	var index = _meta.args[2];\n    var val = curObj == null ? null : curObj[key][curObj[index]];\n    return val;\n}\ntestArray()', '2022-12-20 18:44:09', 0);
INSERT INTO `script` (`id`, `documentId`, `randomId`, `simple`, `name`, `script`, `date`, `ahead`) VALUES (6, 0, 0, 0, 'lengthExtendParameter', 'function lengthExtendParameter() {\n	var data = extParam.data;\n	print(\'data:\'+data);\n	var curObj = _meta.args[0];\n	var key = _meta.args[1];\n    var val = curObj == null ? null : curObj[key];\n    return val == null ? 0 : val.length;\n}\nlengthExtendParameter()', '2023-01-12 18:23:45', 0);
INSERT INTO `script` (`id`, `documentId`, `randomId`, `simple`, `name`, `script`, `date`, `ahead`) VALUES (7, 0, 0, 0, 'length_lua', 'function length_lua()\n	local extParam = extParam:getUsername()\n	local version = _meta:get(\'version\')\n	local args = _meta:get(\'args\')\n	local curObj = args[1];\n	local key = args[2]\n	print(\'curObj:get(key):\'..tostring(curObj:get(key)))\n	print(\'ret type:\'..type(curObj:get(key)))\n	print(\'ret:\'..tostring(curObj:get(key):size()))\n	return curObj:get(key):size()\nend\nreturn length_lua()', '2023-01-13 16:00:34', 0);
INSERT INTO `script` (`id`, `documentId`, `randomId`, `simple`, `name`, `script`, `date`, `ahead`) VALUES (8, 0, 0, 0, 'testArray_lua', 'function testArray_lua()\n	local args = _meta:get(\'args\')\n	local curObj = args[1];\n	print(\'curObj:\'..tostring(curObj)) \n	local key = args[2];\n	local index = args[3];\n	local valIndex = curObj:get(index)\n	print(\'valIndex:\'..tostring(valIndex)) \n	print(\'ret:\'..tostring(curObj:get(key)))\n	return curObj:get(key):get(valIndex)\nend\nreturn testArray_lua()', '2023-01-13 16:01:35', 0);
INSERT INTO `script` (`id`, `documentId`, `randomId`, `simple`, `name`, `script`, `date`, `ahead`) VALUES (9, 0, 0, 0, 'length_graalJS', 'function length() {\n	var curObj = _meta.args[0];\n	var key = _meta.args[1];\n    var val = curObj == null ? null : curObj[key];\n    return val == null ? 0 : val.length;\n}\nlength()', '2023-01-13 18:04:25', 0);
COMMIT;

SET FOREIGN_KEY_CHECKS = 1;

9、apijson 测试json

#js
{
    "func": {
    	"var": [1,2,3],
    	"index": 0,
        "id()": "getType(var,index)"
    }
}

{
    "func": {
    	"var": "a,b,c",
    	"valKey": "a1",
        "id()": "isContainJs(var,valKey)"
    }
}

{
    "func": {
    	"var": [1,2,3],
        "length()": "length(var)"
    }
}

{
    "func": {
    	"var": [1,2,3],
        "index": 1,
        "testArray()": "testArray(var,index)"
    }
}
#lua
{
    "func": {
    	"var": [1,2,3,'dd'],
        "length_lua()": "length_lua(var)"
    }
}

{
    "func": {
    	"var": [1,2,3],
        "index": 2,
        "testArray_lua()": "testArray_lua(var,index)"
    }
}

# graalJS
{
    "func": {
    	"var": [1,2,3],
        "length_graalJS()": "length_graalJS(var)"
    }
}

@TommyLemon
Copy link
Collaborator

赞!

@cloudAndMonkey
Copy link
Contributor Author

cloudAndMonkey commented Feb 2, 2023

@TommyLemon

framework APIJSONApplication 代码合并缺失
image

请问你是觉得放在这里不合理吗?
如果不合并, 业务侧 扩展 脚步处理器,需要实现 addScriptExecutor 方法
https://github.com/APIJSON/APIJSON-Demo/blob/master/APIJSON-Java-Server/APIJSONDemo-Script/src/main/java/apijson/demo/DemoApplication.java#L58-L74

变为:
image

@TommyLemon
Copy link
Collaborator

TommyLemon commented Feb 2, 2023

@cloudAndMonkey 没有手动移除,应该是 GitHub 客户端自动合并时丢掉了,可以重新加上哈

@TommyLemon
Copy link
Collaborator

TommyLemon commented Feb 2, 2023

看了下这次 PR,没有包含 APIJSONApplication 的代码变更,只有 APIJSONFunctionParser.java 的变更,可能是你漏提交了哈
https://github.com/APIJSON/apijson-framework/pull/17/files

等你提交后,我合并 PR 再发一个补丁版本

@cloudAndMonkey
Copy link
Contributor Author

看了下这次 PR,没有包含 APIJSONApplication 的代码变更,只有 APIJSONFunctionParser.java 的变更,可能是你漏提交了哈 https://github.com/APIJSON/apijson-framework/pull/17/files

等你提交后,我合并 PR 再发一个补丁版本

哈哈,不好意思

@TommyLemon
Copy link
Collaborator

TommyLemon commented Feb 2, 2023

@cloudAndMonkey 发了
https://github.com/APIJSON/apijson-framework/releases/tag/6.0.1

https://github.com/APIJSON/apijson-router/releases/tag/1.5.1

预计脚本引擎这个新功能短期内用的人很少,而且大部分用户都是直接下载最新 Demo 源码而不是 Release 中的源码压缩包,
所以 APIJSON-Demo 暂不发新版,但更新了 APIJSONDemo-Script 的依赖
APIJSON/APIJSON-Demo@965aa9a
以及所有 Demo 的 jar 包
APIJSON/APIJSON-Demo@4cf107f

@cloudAndMonkey
Copy link
Contributor Author

@cloudAndMonkey 发了 https://github.com/APIJSON/apijson-framework/releases/tag/6.0.1https://github.com/APIJSON/apijson-router/releases/tag/1.5.1

预计脚本引擎这个新功能短期内用的人很少,而且大部分用户都是直接下载最新 Demo 源码而不是 Release 中的源码压缩包, 所以 APIJSON-Demo 暂不发新版,但更新了 APIJSONDemo-Script 的依赖 APIJSON/APIJSON-Demo@965aa9a 以及所有 Demo 的 jar 包 APIJSON/APIJSON-Demo@4cf107f

嗯嗯

@spt110
Copy link

spt110 commented Feb 5, 2023

怎么使用,有demo吗?

@cloudAndMonkey
Copy link
Contributor Author

@TommyLemon TommyLemon added the Enhancement 增强 增强功能、提高性能等 label Feb 16, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Enhancement 增强 增强功能、提高性能等
Projects
None yet
Development

No branches or pull requests

3 participants