diff --git a/APIJSON-Java-Server/APIJSONDemo-Script/README.md b/APIJSON-Java-Server/APIJSONDemo-Script/README.md
new file mode 100644
index 00000000..52cd0dc0
--- /dev/null
+++ b/APIJSON-Java-Server/APIJSONDemo-Script/README.md
@@ -0,0 +1,190 @@
+1、目前支持脚步引擎
+ jdk默认实现 Nashorn 引擎
+Nashorn(--global-per-engine)
+graalvm
+lua
+其他脚步引擎,业务侧可以扩展
+2、扩展脚步引擎,支持更多脚步执行器
+
+
+3、脚本 线程安全问题
+业务侧按照需求,进行锁颗粒度控制
+目前测试, lua Bindings无法保证线程安全 需要通过外部锁,比如 lock、synchronized、redis 分布式锁,防止并发问题
+建议锁颗粒度: 脚本名.
+举例:
+脚本: length, testArray_lua
+锁: length
+锁: testArray_lua
+ConcurrentHashMap 写key实现原理差不多
+
+
+
+4、支持传递 apijson元数据 参数:
+version、tag、args
+5、支持业务侧扩展参数
+extParam
+
+
+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、支持二次处理脚步
+格式化等
+
+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)"
+ }
+}
+```
diff --git a/APIJSON-Java-Server/APIJSONDemo-Script/pom.xml b/APIJSON-Java-Server/APIJSONDemo-Script/pom.xml
new file mode 100644
index 00000000..26dadebf
--- /dev/null
+++ b/APIJSON-Java-Server/APIJSONDemo-Script/pom.xml
@@ -0,0 +1,124 @@
+
+
+ 4.0.0
+ apijson.demo
+ apijson-demo-script
+ 6.0.0
+ jar
+
+ APIJSONDemo-Druid
+ Demo project for Testing APIJSON Server based on SpringBoot
+
+
+ UTF-8
+ UTF-8
+ 1.8
+ 21.3.3.1
+ 1.18.4
+
+
+
+
+
+ javax.activation
+ activation
+ 1.1.1
+
+
+
+ com.github.APIJSON
+ apijson-framework
+ 6.0.0
+
+
+
+
+ mysql
+ mysql-connector-java
+ 8.0.29
+
+
+ org.postgresql
+ postgresql
+ 42.3.4
+
+
+
+
+
+ org.springframework.boot
+ spring-boot-starter-web
+ 2.5.13
+
+
+
+
+ com.alibaba
+ druid
+ 1.2.9
+
+
+ org.graalvm.js
+ js
+ ${graalvm.version}
+
+
+ org.projectlombok
+ lombok
+ ${lombok.version}
+
+
+
+
+
+
+ org.springframework.boot
+ spring-boot-maven-plugin
+
+ true
+ apijson.demo.DemoApplication
+
+
+
+
+ repackage
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+
+ 1.8
+ 1.8
+
+
+
+
+
+
+
+
+ jitpack.io
+ https://jitpack.io
+
+ true
+
+
+
+
+ spring-snapshots
+ https://repo.spring.io/snapshot
+
+ true
+
+
+
+ spring-milestones
+ https://repo.spring.io/milestone
+
+
+
+
diff --git a/APIJSON-Java-Server/APIJSONDemo-Script/src/main/java/apijson/demo/DemoApplication.java b/APIJSON-Java-Server/APIJSONDemo-Script/src/main/java/apijson/demo/DemoApplication.java
new file mode 100644
index 00000000..62aaec9f
--- /dev/null
+++ b/APIJSON-Java-Server/APIJSONDemo-Script/src/main/java/apijson/demo/DemoApplication.java
@@ -0,0 +1,132 @@
+/*Copyright ©2016 TommyLemon(https://github.com/TommyLemon/APIJSON)
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.*/
+
+package apijson.demo;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.boot.context.properties.EnableConfigurationProperties;
+import org.springframework.boot.web.server.WebServerFactoryCustomizer;
+import org.springframework.boot.web.servlet.server.ConfigurableServletWebServerFactory;
+import org.springframework.context.ApplicationContext;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.web.servlet.config.annotation.CorsRegistry;
+import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
+
+import apijson.Log;
+import apijson.demo.script.GraalJavaScriptExecutor;
+import apijson.demo.script.LuaScriptExecutor;
+import apijson.demo.script.NashornScriptExecutor;
+import apijson.framework.APIJSONApplication;
+import apijson.framework.APIJSONCreator;
+import apijson.orm.SQLConfig;
+import apijson.orm.SQLExecutor;
+import apijson.orm.script.ScriptExecutor;
+
+/**
+ * Demo SpringBoot Application 主应用程序启动类 右键这个类 > Run As > Java Application 具体见
+ * SpringBoot 文档
+ * https://www.springcloud.cc/spring-boot.html#using-boot-locating-the-main-class
+ *
+ * @author Lemon
+ */
+@Configuration
+@SpringBootApplication
+@EnableAutoConfiguration
+@EnableConfigurationProperties
+public class DemoApplication implements WebServerFactoryCustomizer {
+
+ // 全局 ApplicationContext 实例,方便 getBean 拿到 Spring/SpringBoot 注入的类实例
+ private static ApplicationContext APPLICATION_CONTEXT;
+
+ public static ApplicationContext getApplicationContext() {
+ return APPLICATION_CONTEXT;
+ }
+
+ public static void main(String[] args) throws Exception {
+ APPLICATION_CONTEXT = SpringApplication.run(DemoApplication.class, args);
+
+ Log.DEBUG = true;
+ // 加载扩展脚本执行器
+ extendScriptExecutor();
+ APIJSONApplication.init(true); // 4.4.0 以上需要这句来保证以上 static 代码块中给 DEFAULT_APIJSON_CREATOR 赋值会生效
+ }
+
+ public static void extendScriptExecutor() {
+ ScriptExecutor javaScriptExecutor = new NashornScriptExecutor();
+ ScriptExecutor luaExecutor = new LuaScriptExecutor();
+ ScriptExecutor GraalJSExecutor = new GraalJavaScriptExecutor();
+ APIJSONApplication.addScriptExecutor("nashornJS", javaScriptExecutor);
+ APIJSONApplication.addScriptExecutor("luaj", luaExecutor);
+ APIJSONApplication.addScriptExecutor("graalJS", GraalJSExecutor);
+ }
+
+ // SpringBoot 2.x 自定义端口方式
+ @Override
+ public void customize(ConfigurableServletWebServerFactory server) {
+ server.setPort(8080);
+ }
+
+ // 支持 APIAuto 中 JavaScript 代码跨域请求
+ @Bean
+ public WebMvcConfigurer corsConfigurer() {
+ return new WebMvcConfigurer() {
+ @Override
+ public void addCorsMappings(CorsRegistry registry) {
+ registry.addMapping("/**").allowedOriginPatterns("*").allowedMethods("*").allowCredentials(true).maxAge(3600);
+ }
+ };
+ }
+
+ static {
+ // 使用本项目的自定义处理类
+ APIJSONApplication.DEFAULT_APIJSON_CREATOR = new APIJSONCreator() {
+ @Override
+ public SQLConfig createSQLConfig() {
+ return new DemoSQLConfig();
+ }
+
+ @Override
+ public SQLExecutor createSQLExecutor() {
+ return new DemoSQLExecutor();
+ }
+ };
+
+ // 把以下需要用到的数据库驱动取消注释即可,如果这里没有可以自己新增
+ // try { //加载驱动程序
+ // Log.d(TAG, "尝试加载 SQLServer 驱动 <<<<<<<<<<<<<<<<<<<<< ");
+ // Class.forName("com.microsoft.sqlserver.jdbc.SQLServerDriver");
+ // Log.d(TAG, "成功加载 SQLServer 驱动!>>>>>>>>>>>>>>>>>>>>> ");
+ // }
+ // catch (ClassNotFoundException e) {
+ // e.printStackTrace();
+ // Log.e(TAG, "加载 SQLServer 驱动失败,请检查 pom.xml 中 net.sourceforge.jtds 版本是否存在以及可用
+ // !!!");
+ // }
+ //
+ // try { //加载驱动程序
+ // Log.d(TAG, "尝试加载 Oracle 驱动 <<<<<<<<<<<<<<<<<<<<< ");
+ // Class.forName("oracle.jdbc.driver.OracleDriver");
+ // Log.d(TAG, "成功加载 Oracle 驱动!>>>>>>>>>>>>>>>>>>>>> ");
+ // }
+ // catch (ClassNotFoundException e) {
+ // e.printStackTrace();
+ // Log.e(TAG, "加载 Oracle 驱动失败,请检查 pom.xml 中 com.oracle.jdbc 版本是否存在以及可用 !!!");
+ // }
+
+ }
+
+}
diff --git a/APIJSON-Java-Server/APIJSONDemo-Script/src/main/java/apijson/demo/DemoController.java b/APIJSON-Java-Server/APIJSONDemo-Script/src/main/java/apijson/demo/DemoController.java
new file mode 100644
index 00000000..2e5f213a
--- /dev/null
+++ b/APIJSON-Java-Server/APIJSONDemo-Script/src/main/java/apijson/demo/DemoController.java
@@ -0,0 +1,99 @@
+/*Copyright ©2016 TommyLemon(https://github.com/TommyLemon/APIJSON)
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.*/
+
+package apijson.demo;
+
+import java.net.URLDecoder;
+import java.util.Map;
+
+import javax.servlet.http.HttpSession;
+
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+
+import apijson.RequestMethod;
+import apijson.StringUtil;
+import apijson.framework.APIJSONController;
+import apijson.orm.Parser;
+
+
+/**请求路由入口控制器,包括通用增删改查接口等,转交给 APIJSON 的 Parser 来处理
+ * 具体见 SpringBoot 文档
+ * https://www.springcloud.cc/spring-boot.html#boot-features-spring-mvc
+ * 以及 APIJSON 通用文档 3.设计规范 3.1 操作方法
+ * https://github.com/Tencent/APIJSON/blob/master/Document.md#3.1
+ *
建议全通过HTTP POST来请求:
+ *
1.减少代码 - 客户端无需写HTTP GET,PUT等各种方式的请求代码
+ *
2.提高性能 - 无需URL encode和decode
+ *
3.调试方便 - 建议使用 APIAuto(http://apijson.cn/api) 或 Postman
+ * @author Lemon
+ */
+@RestController
+@RequestMapping("")
+public class DemoController extends APIJSONController {
+
+ @Override
+ public Parser newParser(HttpSession session, RequestMethod method) {
+ return super.newParser(session, method).setNeedVerify(false); // TODO 这里关闭校验,方便新手快速测试,实际线上项目建议开启
+ }
+
+ /**增删改查统一接口,这个一个接口可替代 7 个万能通用接口,牺牲一些路由解析性能来提升一点开发效率
+ * @param method
+ * @param request
+ * @param session
+ * @return
+ */
+ @PostMapping(value = "{method}") // 如果和其它的接口 URL 冲突,可以加前缀,例如改为 crud/{method} 或 Controller 注解 @RequestMapping("crud")
+ @Override
+ public String crud(@PathVariable String method, @RequestBody String request, HttpSession session) {
+ return super.crud(method, request, session);
+ }
+
+ /**增删改查统一接口,这个一个接口可替代 7 个万能通用接口,牺牲一些路由解析性能来提升一点开发效率
+ * @param method
+ * @param tag
+ * @param params
+ * @param request
+ * @param session
+ * @return
+ */
+ @PostMapping("{method}/{tag}") // 如果和其它的接口 URL 冲突,可以加前缀,例如改为 crud/{method}/{tag} 或 Controller 注解 @RequestMapping("crud")
+ @Override
+ public String crudByTag(@PathVariable String method, @PathVariable String tag, @RequestParam Map params, @RequestBody String request, HttpSession session) {
+ return super.crudByTag(method, tag, params, request, session);
+ }
+
+ /**获取
+ * 只为兼容HTTP GET请求,推荐用HTTP POST,可删除
+ * @param request 只用String,避免encode后未decode
+ * @param session
+ * @return
+ * @see {@link RequestMethod#GET}
+ */
+ @GetMapping("get/{request}")
+ public String openGet(@PathVariable String request, HttpSession session) {
+ try {
+ request = URLDecoder.decode(request, StringUtil.UTF_8);
+ } catch (Exception e) {
+ // Parser 会报错
+ }
+ return get(request, session);
+ }
+
+}
\ No newline at end of file
diff --git a/APIJSON-Java-Server/APIJSONDemo-Script/src/main/java/apijson/demo/DemoDataSourceConfig.java b/APIJSON-Java-Server/APIJSONDemo-Script/src/main/java/apijson/demo/DemoDataSourceConfig.java
new file mode 100644
index 00000000..a25cf62f
--- /dev/null
+++ b/APIJSON-Java-Server/APIJSONDemo-Script/src/main/java/apijson/demo/DemoDataSourceConfig.java
@@ -0,0 +1,38 @@
+/*Copyright ©2016 TommyLemon(https://github.com/TommyLemon/APIJSON)
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.*/
+
+package apijson.demo;
+
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+import com.alibaba.druid.pool.DruidDataSource;
+
+
+/**数据源配置,对应 application.yml 的数据库连接池 datasource 配置。
+ * 也可以直接 new 再 set 属性,具体见 Druid 的 DbTestCase
+ * https://github.com/alibaba/druid/blob/master/src/test/java/com/alibaba/druid/DbTestCase.java
+ * @author Lemon
+ */
+@Configuration
+public class DemoDataSourceConfig {
+
+ @Bean
+ @ConfigurationProperties(prefix = "spring.datasource.druid")
+ public DruidDataSource druidDataSource() {
+ return new DruidDataSource();
+ }
+
+}
\ No newline at end of file
diff --git a/APIJSON-Java-Server/APIJSONDemo-Script/src/main/java/apijson/demo/DemoSQLConfig.java b/APIJSON-Java-Server/APIJSONDemo-Script/src/main/java/apijson/demo/DemoSQLConfig.java
new file mode 100644
index 00000000..83bfcdb6
--- /dev/null
+++ b/APIJSON-Java-Server/APIJSONDemo-Script/src/main/java/apijson/demo/DemoSQLConfig.java
@@ -0,0 +1,48 @@
+/*Copyright ©2016 TommyLemon(https://github.com/TommyLemon/APIJSON)
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.*/
+
+package apijson.demo;
+
+import apijson.framework.APIJSONSQLConfig;
+
+
+/**SQL 配置,这里不配置数据库账号密码等信息,改为使用 DemoDataSourceConfig 来配置
+ * TiDB 用法和 MySQL 一致
+ * 具体见详细的说明文档 C.开发说明 C-1-1.修改数据库链接
+ * https://github.com/Tencent/APIJSON/blob/master/%E8%AF%A6%E7%BB%86%E7%9A%84%E8%AF%B4%E6%98%8E%E6%96%87%E6%A1%A3.md#c-1-1%E4%BF%AE%E6%94%B9%E6%95%B0%E6%8D%AE%E5%BA%93%E9%93%BE%E6%8E%A5
+ * @author Lemon
+ */
+public class DemoSQLConfig extends APIJSONSQLConfig {
+
+ static {
+ DEFAULT_DATABASE = DATABASE_MYSQL; // TODO 默认数据库类型,改成你自己的
+ DEFAULT_SCHEMA = "sys"; // TODO 默认数据库名/模式,改成你自己的,默认情况是 MySQL: sys, PostgreSQL: public, SQL Server: dbo, Oracle:
+
+ // 表名和数据库不一致的,需要配置映射关系。只使用 APIJSONORM 时才需要;
+ // 如果用了 apijson-framework 且调用了 APIJSONApplication.init 则不需要
+ // (间接调用 DemoVerifier.init 方法读取数据库 Access 表来替代手动输入配置)。
+ // 但如果 Access 这张表的对外表名与数据库实际表名不一致,仍然需要这里注册。例如
+ // TABLE_KEY_MAP.put(Access.class.getSimpleName(), "access");
+
+ //表名映射,隐藏真实表名,对安全要求很高的表可以这么做
+ TABLE_KEY_MAP.put("User", "apijson_user");
+ TABLE_KEY_MAP.put("Privacy", "apijson_privacy");
+ }
+
+ @Override
+ public String getDBVersion() {
+ return "5.7.22"; // "8.0.11"; // TODO 改成你自己的 MySQL 或 PostgreSQL 数据库版本号 // MYSQL 8 和 7 使用的 JDBC 配置不一样
+ }
+
+}
diff --git a/APIJSON-Java-Server/APIJSONDemo-Script/src/main/java/apijson/demo/DemoSQLExecutor.java b/APIJSON-Java-Server/APIJSONDemo-Script/src/main/java/apijson/demo/DemoSQLExecutor.java
new file mode 100644
index 00000000..fbd774ae
--- /dev/null
+++ b/APIJSON-Java-Server/APIJSONDemo-Script/src/main/java/apijson/demo/DemoSQLExecutor.java
@@ -0,0 +1,50 @@
+/*Copyright ©2016 TommyLemon(https://github.com/TommyLemon/APIJSON)
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.*/
+
+package apijson.demo;
+
+import java.sql.Connection;
+
+import javax.sql.DataSource;
+
+import apijson.framework.APIJSONSQLExecutor;
+import apijson.orm.SQLConfig;
+
+
+/**SQL 执行器,支持连接池及多数据源
+ * 具体见 https://github.com/Tencent/APIJSON/issues/151
+ * @author Lemon
+ */
+public class DemoSQLExecutor extends APIJSONSQLExecutor {
+ public static final String TAG = "DemoSQLExecutor";
+
+ // 适配连接池,如果这里能拿到连接池的有效 Connection,则 SQLConfig 不需要配置 dbVersion, dbUri, dbAccount, dbPassword
+ @Override
+ public Connection getConnection(SQLConfig config) throws Exception {
+ // Log.d(TAG, "getConnection config.getDatasource() = " + config.getDatasource());
+
+ String key = config.getDatasource() + "-" + config.getDatabase();
+ Connection c = connectionMap.get(key);
+ if (c == null || c.isClosed()) {
+ DataSource ds = DemoApplication.getApplicationContext().getBean(DataSource.class);
+ // 另一种方式是 DemoDataSourceConfig 初始化获取到 Datasource 后给静态变量 DATA_SOURCE 赋值: ds = DemoDataSourceConfig.DATA_SOURCE.getConnection();
+ connectionMap.put(key, ds == null ? null : ds.getConnection());
+ }
+
+ // 必须最后执行 super 方法,因为里面还有事务相关处理。
+ // 如果这里是 return c,则会导致 增删改 多个对象时只有第一个会 commit,即只有第一个对象成功插入数据库表
+ return super.getConnection(config);
+ }
+
+}
diff --git a/APIJSON-Java-Server/APIJSONDemo-Script/src/main/java/apijson/demo/UserEntity.java b/APIJSON-Java-Server/APIJSONDemo-Script/src/main/java/apijson/demo/UserEntity.java
new file mode 100644
index 00000000..cdca8bee
--- /dev/null
+++ b/APIJSON-Java-Server/APIJSONDemo-Script/src/main/java/apijson/demo/UserEntity.java
@@ -0,0 +1,21 @@
+package apijson.demo;
+
+import java.util.Date;
+import lombok.Data;
+
+@Data
+public class UserEntity {
+ // 用户编号
+ private String id;
+ // 用户名
+ private String username;
+ // 密码
+ private String password;
+
+ // 状态 已删除:-1,私有:0,公开:1,仅好友可见:2
+ private Integer state;
+ // 创建时间
+ private Date createTime;
+ // 最近更新时间
+ private Date updateTime;
+}
diff --git a/APIJSON-Java-Server/APIJSONDemo-Script/src/main/java/apijson/demo/script/GraalJavaScriptExecutor.java b/APIJSON-Java-Server/APIJSONDemo-Script/src/main/java/apijson/demo/script/GraalJavaScriptExecutor.java
new file mode 100644
index 00000000..f84e525b
--- /dev/null
+++ b/APIJSON-Java-Server/APIJSONDemo-Script/src/main/java/apijson/demo/script/GraalJavaScriptExecutor.java
@@ -0,0 +1,72 @@
+package apijson.demo.script;
+
+import org.graalvm.polyglot.Context;
+import org.graalvm.polyglot.Engine;
+import org.graalvm.polyglot.Source;
+import org.graalvm.polyglot.Value;
+
+import com.alibaba.fastjson.JSONObject;
+
+import apijson.orm.AbstractFunctionParser;
+import apijson.orm.script.ScriptExecutor;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+public class GraalJavaScriptExecutor implements ScriptExecutor {
+
+ private final Map scriptMap = new ConcurrentHashMap<>();
+
+ private Engine engine;
+
+ @Override
+ public ScriptExecutor init() {
+ engine = Engine.create();
+ return this;
+ }
+
+ private Object extendParameter(AbstractFunctionParser parser, JSONObject currentObject, String methodName, Object[] args) {
+ return null;
+ }
+
+ @Override
+ public void load(String name, String script) {
+ try {
+ scriptMap.put(name, Source.create("js", script));
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+
+ @Override
+ public Object execute(AbstractFunctionParser parser, JSONObject currentObject, String methodName, Object[] args) throws Exception {
+ Context context = Context.newBuilder().allowAllAccess(true).engine(this.engine).build();
+ Value bindings = context.getBindings("js");
+ // 加载扩展属性
+ Object extendParameter = this.extendParameter(parser, currentObject, methodName, args);
+ if (extendParameter != null) {
+ bindings.putMember("extParam", extendParameter);
+ }
+
+ Map metaMap = new HashMap<>();
+ metaMap.put("version", parser.getVersion());
+ metaMap.put("tag", parser.getTag());
+ metaMap.put("args", args);
+ bindings.putMember("_meta", metaMap);
+ Value value = context.eval(scriptMap.get(methodName));
+ if (value.isBoolean()) {
+ return value.asBoolean();
+ } else if (value.isNumber()) {
+ return value.asInt();
+ } else if (value.isString()) {
+ return value.asString();
+ }
+ return value;
+ }
+
+ @Override
+ public void cleanCache() {
+ scriptMap.clear();
+ }
+}
diff --git a/APIJSON-Java-Server/APIJSONDemo-Script/src/main/java/apijson/demo/script/LuaScriptExecutor.java b/APIJSON-Java-Server/APIJSONDemo-Script/src/main/java/apijson/demo/script/LuaScriptExecutor.java
new file mode 100644
index 00000000..b981e2bd
--- /dev/null
+++ b/APIJSON-Java-Server/APIJSONDemo-Script/src/main/java/apijson/demo/script/LuaScriptExecutor.java
@@ -0,0 +1,37 @@
+package apijson.demo.script;
+
+import com.alibaba.fastjson.JSONObject;
+
+import apijson.demo.UserEntity;
+import apijson.orm.AbstractFunctionParser;
+import apijson.orm.script.JSR223ScriptExecutor;
+
+/**
+ * Lua脚本执行器
+ */
+public class LuaScriptExecutor extends JSR223ScriptExecutor {
+ @Override
+ protected String scriptEngineName() {
+ return "luaj";
+ }
+
+ @Override
+ protected Object extendParameter(AbstractFunctionParser parser, JSONObject currentObject, String methodName, Object[] args) {
+ UserEntity user = new UserEntity();
+ user.setUsername("ddd");
+ return user;
+ }
+
+ @Override
+ protected boolean isLockScript(String methodName) {
+ return true;
+ }
+
+ @Override
+ public Object execute(AbstractFunctionParser parser, JSONObject currentObject, String methodName, Object[] args) throws Exception {
+ //业务侧控制锁颗粒度,可以通过脚本名进行加锁
+ synchronized (methodName) {
+ return super.execute(parser, currentObject, methodName, args);
+ }
+ }
+}
diff --git a/APIJSON-Java-Server/APIJSONDemo-Script/src/main/java/apijson/demo/script/NashornScriptExecutor.java b/APIJSON-Java-Server/APIJSONDemo-Script/src/main/java/apijson/demo/script/NashornScriptExecutor.java
new file mode 100644
index 00000000..eb2b8645
--- /dev/null
+++ b/APIJSON-Java-Server/APIJSONDemo-Script/src/main/java/apijson/demo/script/NashornScriptExecutor.java
@@ -0,0 +1,42 @@
+package apijson.demo.script;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import com.alibaba.fastjson.JSONObject;
+
+import apijson.orm.AbstractFunctionParser;
+import apijson.orm.script.JSR223ScriptExecutor;
+import apijson.orm.script.ScriptExecutor;
+import jdk.nashorn.api.scripting.NashornScriptEngineFactory;
+
+public class NashornScriptExecutor extends JSR223ScriptExecutor {
+ @SuppressWarnings("restriction")
+ @Override
+ public ScriptExecutor init() {
+ NashornScriptEngineFactory factory = new NashornScriptEngineFactory();
+ String[] stringArray = new String[] { "-doe", "--global-per-engine" };
+ scriptEngine = factory.getScriptEngine(stringArray);
+ return this;
+ }
+
+
+ @Override
+ protected String scriptEngineName() {
+ return "nashornJS";
+ }
+
+
+ @Override
+ protected Object extendParameter(AbstractFunctionParser parser, JSONObject currentObject, String methodName, Object[] args) {
+ Map tmpData = new HashMap<>();
+ tmpData.put("data", "122");
+ return tmpData;
+ }
+
+
+ @Override
+ protected boolean isLockScript(String methodName) {
+ return false;
+ }
+}
diff --git a/APIJSON-Java-Server/APIJSONDemo-Script/src/main/resources/application.properties b/APIJSON-Java-Server/APIJSONDemo-Script/src/main/resources/application.properties
new file mode 100644
index 00000000..e69de29b
diff --git a/APIJSON-Java-Server/APIJSONDemo-Script/src/main/resources/application.yml b/APIJSON-Java-Server/APIJSONDemo-Script/src/main/resources/application.yml
new file mode 100644
index 00000000..941af568
--- /dev/null
+++ b/APIJSON-Java-Server/APIJSONDemo-Script/src/main/resources/application.yml
@@ -0,0 +1,28 @@
+spring:
+ datasource:
+ # type: com.zaxxer.hikari.HikariDataSource
+ # driver-class-name: com.mysql.cj.jdbc.Driver
+# url: jdbc:mysql://localhost:3306?serverTimezone=GMT%2B8&useUnicode=true&characterEncoding=UTF-8
+# username: root
+# password: apijson
+ druid:
+ driverClassName: com.mysql.cj.jdbc.Driver
+ url: jdbc:mysql://localhost:3306?serverTimezone=GMT%2B8&useUnicode=true&characterEncoding=UTF-8
+ username:
+ password:
+ type: com.alibaba.druid.pool.DruidDataSource
+ initialSize: 5
+ minIdle: 5
+ maxActive: 20
+ maxWait: 60000
+ timeBetweenEvictionRunsMillis: 60000
+ minEvictableIdleTimeMillis: 300000
+ validationQuery: SELECT 1 FROM DUAL
+ testWhileIdle: true
+ testOnBorrow: false
+ testOnReturn: false
+ poolPreparedStatements: true
+ maxPoolPreparedStatementPerConnectionSize: 20
+ # filters: stat,wall,log4j
+ connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=5000
+ useGlobalDataSourceStat: true