From fa5517124f3be91bf6b3d06483ed89da718bc416 Mon Sep 17 00:00:00 2001 From: cloudAndMonkey Date: Mon, 16 Jan 2023 14:47:33 +0800 Subject: [PATCH 1/2] =?UTF-8?q?apijson=20function=E6=94=AF=E6=8C=81?= =?UTF-8?q?=E8=84=9A=E6=9C=AC=E5=BC=95=E6=93=8E=EF=BC=8C=E6=AF=94=E5=A6=82?= =?UTF-8?q?JavaScript=E3=80=81lua=E7=AD=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit https://github.com/Tencent/APIJSON/issues/495 --- .../APIJSONDemo-Script/README.md | 7 + .../APIJSONDemo-Script/pom.xml | 124 ++++++++++++++++ .../java/apijson/demo/DemoApplication.java | 132 ++++++++++++++++++ .../java/apijson/demo/DemoController.java | 99 +++++++++++++ .../apijson/demo/DemoDataSourceConfig.java | 38 +++++ .../main/java/apijson/demo/DemoSQLConfig.java | 48 +++++++ .../java/apijson/demo/DemoSQLExecutor.java | 50 +++++++ .../main/java/apijson/demo/UserEntity.java | 21 +++ .../demo/script/GraalJavaScriptExecutor.java | 72 ++++++++++ .../demo/script/LuaScriptExecutor.java | 37 +++++ .../demo/script/NashornScriptExecutor.java | 42 ++++++ .../src/main/resources/application.properties | 0 .../src/main/resources/application.yml | 28 ++++ 13 files changed, 698 insertions(+) create mode 100644 APIJSON-Java-Server/APIJSONDemo-Script/README.md create mode 100644 APIJSON-Java-Server/APIJSONDemo-Script/pom.xml create mode 100644 APIJSON-Java-Server/APIJSONDemo-Script/src/main/java/apijson/demo/DemoApplication.java create mode 100644 APIJSON-Java-Server/APIJSONDemo-Script/src/main/java/apijson/demo/DemoController.java create mode 100644 APIJSON-Java-Server/APIJSONDemo-Script/src/main/java/apijson/demo/DemoDataSourceConfig.java create mode 100644 APIJSON-Java-Server/APIJSONDemo-Script/src/main/java/apijson/demo/DemoSQLConfig.java create mode 100644 APIJSON-Java-Server/APIJSONDemo-Script/src/main/java/apijson/demo/DemoSQLExecutor.java create mode 100644 APIJSON-Java-Server/APIJSONDemo-Script/src/main/java/apijson/demo/UserEntity.java create mode 100644 APIJSON-Java-Server/APIJSONDemo-Script/src/main/java/apijson/demo/script/GraalJavaScriptExecutor.java create mode 100644 APIJSON-Java-Server/APIJSONDemo-Script/src/main/java/apijson/demo/script/LuaScriptExecutor.java create mode 100644 APIJSON-Java-Server/APIJSONDemo-Script/src/main/java/apijson/demo/script/NashornScriptExecutor.java create mode 100644 APIJSON-Java-Server/APIJSONDemo-Script/src/main/resources/application.properties create mode 100644 APIJSON-Java-Server/APIJSONDemo-Script/src/main/resources/application.yml diff --git a/APIJSON-Java-Server/APIJSONDemo-Script/README.md b/APIJSON-Java-Server/APIJSONDemo-Script/README.md new file mode 100644 index 00000000..c80bbfe5 --- /dev/null +++ b/APIJSON-Java-Server/APIJSONDemo-Script/README.md @@ -0,0 +1,7 @@ +# APIJSONDemo + +APIJSON + SpringBoot 初级使用的最简单 Demo + +### 运行 + +右键 DemoApplication > Run As > Java Application 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 From b9a388381dc23685c37158b399a24ca58b12692a Mon Sep 17 00:00:00 2001 From: cloudAndMonkey Date: Mon, 16 Jan 2023 17:48:11 +0800 Subject: [PATCH 2/2] Update README.md --- .../APIJSONDemo-Script/README.md | 191 +++++++++++++++++- 1 file changed, 187 insertions(+), 4 deletions(-) diff --git a/APIJSON-Java-Server/APIJSONDemo-Script/README.md b/APIJSON-Java-Server/APIJSONDemo-Script/README.md index c80bbfe5..52cd0dc0 100644 --- a/APIJSON-Java-Server/APIJSONDemo-Script/README.md +++ b/APIJSON-Java-Server/APIJSONDemo-Script/README.md @@ -1,7 +1,190 @@ -# APIJSONDemo +1、目前支持脚步引擎
+ jdk默认实现 Nashorn 引擎
+Nashorn(--global-per-engine)
+graalvm
+lua
+其他脚步引擎,业务侧可以扩展
+2、扩展脚步引擎,支持更多脚步执行器
+![image](https://user-images.githubusercontent.com/12228225/212584805-12f7c2d0-4b67-4c9e-b4c7-4259e2c7cb04.png) +![image](https://user-images.githubusercontent.com/12228225/212584868-3893f192-8f7c-4612-ac72-d022ff0550ed.png) +3、脚本 线程安全问题
+业务侧按照需求,进行锁颗粒度控制
+目前测试, lua Bindings无法保证线程安全 需要通过外部锁,比如 lock、synchronized、redis 分布式锁,防止并发问题
+建议锁颗粒度: 脚本名.
+举例:
+脚本: length, testArray_lua
+锁: length
+锁: testArray_lua
+ConcurrentHashMap 写key实现原理差不多
+![image](https://user-images.githubusercontent.com/12228225/212581538-a68d677b-bb3f-487b-8e4a-b7415e03666a.png) -APIJSON + SpringBoot 初级使用的最简单 Demo +![image](https://user-images.githubusercontent.com/12228225/212581560-5304baf4-fd80-4809-80cc-168767199986.png) +4、支持传递 apijson元数据 参数:
+version、tag、args
+5、支持业务侧扩展参数
+extParam
+![image](https://user-images.githubusercontent.com/12228225/212581889-edbe0d99-1a0e-401f-ba14-73d81e244382.png) +![image](https://user-images.githubusercontent.com/12228225/212582389-f5de9267-893e-4db1-beae-39f5a3cbdcc7.png) +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() -右键 DemoApplication > Run As > Java Application +``` +案例二:
+ +``` +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](https://user-images.githubusercontent.com/12228225/212583376-52081fee-7c1a-40c2-8a40-3ec04cb6c5f9.png) +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)" + } +} +```