diff --git a/APIJSONDemo-MultiDataSource-Elasticsearch/README.md b/APIJSONDemo-MultiDataSource-Elasticsearch/README.md new file mode 100644 index 00000000..0e61dbb9 --- /dev/null +++ b/APIJSONDemo-MultiDataSource-Elasticsearch/README.md @@ -0,0 +1,383 @@ +# APIJSONDemo + +## 1、支持多数据源 + +数据源解析顺序: + +- [ ] 对象@datasource + +- [ ] 全局 @datasource + +- [ ] 应用默认 @datasource + + ```json + { + // "@datasource": "db2" 全局 + "@post": { + "User:aa": { + "@datasource": "db2" // 对象 + }, + "User_address[]": { + "@datasource": "db2" + } + }, + "User:aa":{ + "username":"test-3", + "password": "233223", + "state": 1 + }, + "ES_blog:a": { + "@datasource": "elasticSearch", + "title.keyword": "test-2" + }, + "User_address[]": [ + { + "user_id@": "User:aa/id", + "addr": "ddd", + "count@": "ES_blog:a/count" + }, + { + "user_id@": "User:aa/id", + "addr": "ddd1", + "count@": "ES_blog:a/count" + } + ], + "@explain": true + } + ``` + + ![image](https://user-images.githubusercontent.com/12228225/207245545-277ba9a6-e87f-42b3-af55-9d6a37384a1e.png) + +## 2、集成elasticsearch-sql + +换成xpack, 也一样 + +应用导入: elasticsearch-sql-7.17.5.0.jar + +## 3、apijson支持elasticsearch功能点 + +新增、修改、删除、查询 + +## 4、elasticsearch-sql不支持RLIKE + +![image](https://user-images.githubusercontent.com/12228225/207245701-ea2560a9-2389-4953-a568-9e85adfb15ad.png) + +## 5、apijson支持字段 .keyword + +``` +{ + "@datasource": "elasticSearch", + "ES_blog:aa":{ + "title.keyword$": "%test-2", + "content": "u-c-2", + "url": "u-u-2", + "postdate": "2008-12-11", + "count": 1 + }, + "tag": "ES_blog", + "@explain": true +} +``` + + + +## 4、示例 + +### 单条插入 + +``` +http://localhost:8080/post + +{ + "@datasource": "elasticSearch", + "ES_blog:aa":{ + "title":"test-1", + "author": "a-1", + "content": "c-1", + "url": "u-1", + "postdate": "2018-12-11", + "count": 1 + }, + "tag": "ES_blog", + "@explain": true +} + +``` + + + + +elasticsearch查询插入的数据: + +GET /es_blog/_doc/5b77b103-0231-42c3-a6cf-a0cb933d3dda + + +### 批量插入 + +```json +http://localhost:8080/post + +{ + "@datasource": "elasticSearch", + "ES_blog:aa[]": [ + { + "title":"test-1", + "author": "a-1", + "content": "c-1", + "url": "u-1", + "postdate": "2018-12-11", + "count": 1 + }, + { + "title":"test-2", + "author": "a-2", + "content": "c-2", + "url": "u-2", + "postdate": "2018-12-11", + "count": 2 + }, + { + "title":"test-3", + "author": "a-3", + "content": "c-3", + "url": "u-3", + "postdate": "2018-12-11", + "count": 3 + } + ], + "tag": "ES_blog[]", + "@explain": true +} + +``` + +elasticsearch查询插入的数据: + +GET /es_blog/_search + + +### id修改 + +```json +http://localhost:8080/put + +{ + "@datasource": "elasticSearch", + "ES_blog:aa":{ + "id": "5b77b103-0231-42c3-a6cf-a0cb933d3dda", + "title":"u-test-1", + "author": "u-a-1", + "content": "u-c-1", + "url": "u-u-1", + "postdate": "2018-12-10", + "count": 9 + }, + "tag": "ES_blog", + "@explain": true +} +``` + + +### 非id修改 + +``` +http://localhost:8080/put +{ + "@datasource": "elasticSearch", + "ES_blog:aa":{ + "title~":"u-test-1", + "author": "u1-a-2", + "content": "u1-c-2", + "url": "u1-u-2", + "postdate": "2028-12-11", + "count": 1 + }, + "tag": "ES_blog", + "@explain": true +} + + +{ + "@datasource": "elasticSearch", + "ES_blog:aa":{ + "title~":"test-3", + "author~": "u3-a-2", + "content": "u1-c-2", + "url": "u1-u-2", + "postdate": "2028-12-11", + "count": 1, + "@combine":"title~ | author~" + }, + "tag": "ES_blog", + "@explain": true +} + +{ + "@datasource": "elasticSearch", + "ES_blog:aa":{ + "count{}":[1,4], + "content": "u-c-2", + "url": "u-u-2", + "postdate": "2008-12-11", + "count": 1 + }, + "tag": "ES_blog", + "@explain": true +} + +{ + "@datasource": "elasticSearch", + "ES_blog:aa":{ + "title$": "%test", + "content": "u-c-2", + "url": "u-u-2", + "postdate": "2008-12-11", + "count": 1 + }, + "tag": "ES_blog", + "@explain": true +} + + +{ + "@datasource": "elasticSearch", + "ES_blog:aa":{ + "postdate%":"2007-10-01,2018-10-01", + "content": "u-c-2", + "url": "u-u-2", + "postdate": "2008-12-11", + "count": 1 + }, + "tag": "ES_blog", + "@explain": true +} +``` + + +### 批量修改 + +``` +http://localhost:8080/put +{ + "@datasource": "elasticSearch", + "ES_blog:aa[]": [ + { + "title~":"test-1", + "author": "u3-a-2", + "content": "u3-c-2", + "url": "u3-u-2", + "postdate": "2038-12-11", + "count": 1 + }, + { + "title~":"test-2", + "author": "u-a-3", + "content": "u-c-3", + "url": "u-u-3", + "postdate": "2008-12-11", + "count": 4 + } + ], + "tag": "ES_blog[]", + "explain": true +} +``` + +### id删除 + +``` +{ + "@datasource": "elasticSearch", + "ES_blog:del": { + "id": "043a7511-296b-43b5-9f12-966dd86299d1" + }, + "tag": "ES_blog", + "explain": true +} +``` + + + +### 非id条件删除 + +``` +http://localhost:8080/delete + +{ + "@datasource": "elasticSearch", + "ES_blog:del": { + "title": "test-2" + }, + "tag": "ES_blog", + "explain": true +} + +{ + "@datasource": "elasticSearch", + "ES_blog:del": { + "count{}":[2,4] + }, + "tag": "ES_blog", + "explain": true +} +``` + + +### 批量删除 + +``` +{ + "@datasource": "elasticSearch", + "ES_blog:del": { + "id{}": ["f41e3010-c410-45a0-b41a-33afbc1e4ef8","d765de31-2fc8-40e5-9430-277bf7e5f91b"] + }, + "tag": "ES_blog", + "explain": true +} +``` + +### 查询单条记录 + +``` +{ + "ES_blog:a": { + "@datasource": "elasticSearch", + "id": "4862927d-9a38-47c9-9cfc-5b3e9db38d30" + }, + "@explain": true +} +``` + +### 分页查询 + +``` +{ + "[]": { + "ES_blog": { + "@datasource": "elasticSearch" + }, + "page": 0, + "count": 2, + "query": 2 + }, + "total@": "/[]/total" +} + +``` + + +### 分组查询 + +``` +{ + "@datasource": "elasticSearch", + "[]": { + "count": 5, + "ES_blog":{ + "@column":"count;sum(count):sum", + "@group":"count" + } + } +} +``` + diff --git a/APIJSONDemo-MultiDataSource-Elasticsearch/libs/elasticsearch-sql-7.17.5.0.jar b/APIJSONDemo-MultiDataSource-Elasticsearch/libs/elasticsearch-sql-7.17.5.0.jar new file mode 100644 index 00000000..e09ac639 Binary files /dev/null and b/APIJSONDemo-MultiDataSource-Elasticsearch/libs/elasticsearch-sql-7.17.5.0.jar differ diff --git a/APIJSONDemo-MultiDataSource-Elasticsearch/pom.xml b/APIJSONDemo-MultiDataSource-Elasticsearch/pom.xml new file mode 100644 index 00000000..5003dda4 --- /dev/null +++ b/APIJSONDemo-MultiDataSource-Elasticsearch/pom.xml @@ -0,0 +1,234 @@ + + + 4.0.0 + + org.springframework.boot + spring-boot-starter-parent + 2.5.13 + + + apijson.demo + apijsondemo-multidatasource-elasticsearch + 5.5.0 + + apijsondemo-multidatasource-elasticsearch + Demo project for testing APIJSON server based on SpringBoot + + + UTF-8 + UTF-8 + 7.17.5 + 3.12.0 + 1.1.16 + 3.5.1 + 2.3.3 + 4.4 + 1.10 + 30.1.1-jre + 1.2.72 + 4.1.1 + 1.18.4 + 3.12.0 + 2.5 + 1.10 + 4.4 + 1.10 + 5.5.0 + 8.0.31 + 5.3.18 + 2.6.6 + 3.5.2 + 1.8 + + + + + + javax.activation + activation + 1.1.1 + + + + + com.github.Tencent + APIJSON + ${apijson.version} + + + com.github.APIJSON + apijson-framework + ${apijson.version} + + + + + + + + + org.springframework.boot + spring-boot-starter-web + + + org.springframework + spring-context-support + ${spring-context-support.version} + + + org.springframework.boot + spring-boot-configuration-processor + ${spring-boot-configuration-processor.version} + true + + + com.alibaba + druid-spring-boot-starter + ${druid.version} + + + com.baomidou + dynamic-datasource-spring-boot-starter + ${dynamic-datasource-spring-boot-starter.version} + + + com.baomidou + mybatis-plus-boot-starter + ${mybatisplus.version} + + + com.baomidou + mybatis-plus-generator + + + + + com.baomidou + mybatis-plus-support + ${mybatis-plus-support.version} + + + org.apache.commons + commons-collections4 + ${commons-collections4.version} + + + mysql + mysql-connector-java + ${mysql.version} + + + org.elasticsearch.client + elasticsearch-rest-high-level-client + ${elasticsearch.version} + + + org.elasticsearch + elasticsearch + ${elasticsearch.version} + + + + org.elasticsearch.client + x-pack-transport + ${elasticsearch.version} + + + com.google.guava + guava + ${guava.version} + + + org.elasticsearch.plugin + reindex-client + ${elasticsearch.version} + + + org.elasticsearch.plugin + parent-join-client + ${elasticsearch.version} + + + com.alibaba + fastjson + ${fastjson.version} + + + cn.hutool + hutool-all + ${hutool.version} + + + org.projectlombok + lombok + ${lombok.version} + + + commons-io + commons-io + ${commons.io.version} + + + commons-codec + commons-codec + ${commons.codec.version} + + + commons-configuration + commons-configuration + ${commons.configuration.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 + + + + \ No newline at end of file diff --git a/APIJSONDemo-MultiDataSource-Elasticsearch/src/main/java/apijson/demo/DataBaseConfig.java b/APIJSONDemo-MultiDataSource-Elasticsearch/src/main/java/apijson/demo/DataBaseConfig.java new file mode 100644 index 00000000..6e261537 --- /dev/null +++ b/APIJSONDemo-MultiDataSource-Elasticsearch/src/main/java/apijson/demo/DataBaseConfig.java @@ -0,0 +1,22 @@ +package apijson.demo; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class DataBaseConfig { + private String primary; + + @Value("${spring.datasource.dynamic.primary}") + public void setPrimary(String primary) { + this.primary = primary; + } + + public String getPrimary() { + return primary; + } + + public static DataBaseConfig getInstence() { + return SpringContextUtils.getBean(DataBaseConfig.class); + } +} diff --git a/APIJSONDemo-MultiDataSource-Elasticsearch/src/main/java/apijson/demo/DataBaseUtil.java b/APIJSONDemo-MultiDataSource-Elasticsearch/src/main/java/apijson/demo/DataBaseUtil.java new file mode 100644 index 00000000..fc736df8 --- /dev/null +++ b/APIJSONDemo-MultiDataSource-Elasticsearch/src/main/java/apijson/demo/DataBaseUtil.java @@ -0,0 +1,54 @@ +package apijson.demo; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import lombok.extern.log4j.Log4j2; + +@Log4j2 +public class DataBaseUtil { + + /** + * 根据url获取库名 + * @param url + * @return + */ + public static String getLibname(String url) { + Pattern p = Pattern.compile("jdbc:(?\\w+):.*((//)|@)(?.+):(?\\d+)(/|(;DatabaseName=)|:)(?\\w+)\\??.*"); + Matcher m = p.matcher(url); + if(m.find()) { + return m.group("dbName"); + } + return null; + } + + /*** + * primary: master + * strict: false + * @param datasource: 匹配不成功, 自动匹配默认数据库 + * @return + */ + public static javax.sql.DataSource getDataSource(String datasource) { + try { + return DynamicJdbcDataSource.getDetail(datasource).getDataSource(); // 数据源 + } catch (Exception e) { + throw new IllegalArgumentException("动态数据源配置错误 " + datasource); + } + } + + public static String getDruidUrl(String datasource) { + return DynamicJdbcDataSource.getDetail(datasource).getUrl(); // 数据库连接url + } + + public static String getDruidSchema(String datasource) { + return getLibname(DynamicJdbcDataSource.getDetail(datasource).getUrl()); // 数据库名; + } + + public static String getDruidDBAccount(String datasource) { + return DynamicJdbcDataSource.getDetail(datasource).getDbAccount(); // 数据库用户名 + } + + public static String getDruidDBPassword(String datasource) { + return DynamicJdbcDataSource.getDetail(datasource).getDbPassword(); // 数据库密码 + } +} diff --git a/APIJSONDemo-MultiDataSource-Elasticsearch/src/main/java/apijson/demo/DemoApplication.java b/APIJSONDemo-MultiDataSource-Elasticsearch/src/main/java/apijson/demo/DemoApplication.java new file mode 100644 index 00000000..8e125b8e --- /dev/null +++ b/APIJSONDemo-MultiDataSource-Elasticsearch/src/main/java/apijson/demo/DemoApplication.java @@ -0,0 +1,106 @@ +/*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.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.framework.APIJSONApplication; +import apijson.framework.APIJSONCreator; +import apijson.framework.APIJSONSQLConfig; +import apijson.orm.AbstractFunctionParser; +import apijson.orm.AbstractVerifier; +import apijson.orm.FunctionParser; +import apijson.orm.Parser; +import apijson.orm.SQLConfig; +import apijson.orm.SQLExecutor; + +/** + * 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 { + public static final String TAG = "DemoApplication"; + + public static void main(String[] args) throws Exception { + SpringApplication.run(DemoApplication.class, args); + Log.DEBUG = true; + AbstractFunctionParser.ENABLE_SCRIPT_FUNCTION = false; + AbstractVerifier.IS_UPDATE_MUST_HAVE_ID_CONDITION = false; + APIJSONApplication.init(false); // 4.4.0 以上需要这句来保证以上 static 代码块中给 DEFAULT_APIJSON_CREATOR 赋值会生效 + // 表名映射,隐藏真实表名,对安全要求很高的表可以这么做 + APIJSONSQLConfig.TABLE_KEY_MAP.put("ES_blog", "es_blog"); + } + + // 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 Parser createParser() { + return new DemoParser(); + } + + @Override + public SQLConfig createSQLConfig() { + return new DemoSQLConfig(); + } + + @Override + public FunctionParser createFunctionParser() { + return new DemoFunctionParser(); + } + + @Override + public SQLExecutor createSQLExecutor() { + return new DemoSQLExecutor(); + } + }; + } + +} diff --git a/APIJSONDemo-MultiDataSource-Elasticsearch/src/main/java/apijson/demo/DemoController.java b/APIJSONDemo-MultiDataSource-Elasticsearch/src/main/java/apijson/demo/DemoController.java new file mode 100644 index 00000000..c91c58f3 --- /dev/null +++ b/APIJSONDemo-MultiDataSource-Elasticsearch/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 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 java.net.URLDecoder; +import java.util.Map; + +import javax.servlet.http.HttpSession; + +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/APIJSONDemo-MultiDataSource-Elasticsearch/src/main/java/apijson/demo/DemoFunctionParser.java b/APIJSONDemo-MultiDataSource-Elasticsearch/src/main/java/apijson/demo/DemoFunctionParser.java new file mode 100644 index 00000000..ba5d230b --- /dev/null +++ b/APIJSONDemo-MultiDataSource-Elasticsearch/src/main/java/apijson/demo/DemoFunctionParser.java @@ -0,0 +1,125 @@ +package apijson.demo; + +import javax.servlet.http.HttpSession; + +import com.alibaba.fastjson.JSONObject; + +import apijson.NotNull; +import apijson.RequestMethod; +import apijson.StringUtil; +import apijson.framework.APIJSONFunctionParser; +import apijson.framework.APIJSONVerifier; +import lombok.extern.slf4j.Slf4j; + +@Slf4j +public class DemoFunctionParser extends APIJSONFunctionParser { + public DemoFunctionParser() { + this(null, null, 0, null, null); + } + + // 展示在远程函数内部可以用 this 拿到的东西 + public DemoFunctionParser(RequestMethod method, String tag, int version, JSONObject request, HttpSession session) { + super(method, tag, version, request, session); + } + + /*** + * 获取当前用户id + * + * @param current + * @return + */ + public String getCurrentUserId(@NotNull JSONObject current) { + if (this.getSession() == null) { + return "test"; // 启动时的自动测试 + } + return APIJSONVerifier.getVisitorId(getSession()); + } + + /** + * 一个最简单的远程函数示例,返回一个前面拼接了 Hello 的字符串 + * + * @param current + * @param name + * @return + * @throws Exception + */ + public String sayHello(@NotNull JSONObject current, @NotNull String name) throws Exception { + // 注意这里参数 name 是 key,不是 value + Object obj = current.get(name); + + if (this.getSession() == null) { + return "test"; // 启动时的自动测试 + } + + if (obj == null) { + throw new IllegalArgumentException(); + } + if (!(obj instanceof String)) { + throw new IllegalArgumentException(); + } + + // 之后可以用 this.getSession 拿到当前的 HttpSession + return "Hello, " + obj.toString(); + } + + /*** + * 密码加密 + * + * @param current + * @param id 添加id生成 + * @param password 密码字段名 + * @return + * @throws Exception + */ + public void pwdEncrypt(@NotNull JSONObject current, @NotNull String id, @NotNull String password) + throws Exception { + String c_password = current.getString(password); + current.put(password, c_password + "_" + System.currentTimeMillis()); + } + + /*** + * 业务表-插入不同表1:n
+ * 业务表-批量修改不同表1:n
+ * 测试 子表 前置函数调用,修改值是否成果
+ * user_address 表 addr字段 + * + * @param current + * @param password + * @return + * @throws Exception + */ + public void childFunTest(@NotNull JSONObject current, @NotNull String addr) throws Exception { + String c_addr = current.getString(addr); + current.put(addr, c_addr + "_" + System.currentTimeMillis()); + } + + /*** + * 没有在外层事物里面 + * + * @throws Exception + */ +// private void insertTest() throws Exception { +// String json = "{\n" + " \"User\":{\n" + " \"username\":\"test\",\n" +// + " \"password\": \"233223\",\n" + " \"state\": 1\n" + " },\n" + " \"tag\": \"User\"\n" +// + "}"; +// com.alibaba.fastjson.JSONObject requestObject = AbstractParser.parseRequest(json); +// JSONResponse response = new JSONResponse(new FormParser(POST, false).parseResponse(requestObject)); +// log.info(response.toJSONString()); +// } +// +// private void selectTest() throws Exception { +// String json = "{\n" + " \"User\": {\n" + " \"id\": \"4732209c-5785-4827-b532-5092f154fd94\"\n" +// + " },\n" + " \"tag\": \"User\"\n" + "}"; +// com.alibaba.fastjson.JSONObject requestObject = AbstractParser.parseRequest(json); +// JSONResponse response = new JSONResponse(new FormParser(GET, false).parseResponse(requestObject)); +// log.info(response.toJSONString()); +// } + + public void removeKeys(@NotNull JSONObject current, String keys) { + String[] ks = StringUtil.split(keys, ";"); // 用分号 ; 分割 + // 根据 ks remove 掉 current 里的字段 + for (int i = 0; i < ks.length; i++) { + current.remove(ks[i]); + } + } +} diff --git a/APIJSONDemo-MultiDataSource-Elasticsearch/src/main/java/apijson/demo/DemoObjectParser.java b/APIJSONDemo-MultiDataSource-Elasticsearch/src/main/java/apijson/demo/DemoObjectParser.java new file mode 100644 index 00000000..627de64c --- /dev/null +++ b/APIJSONDemo-MultiDataSource-Elasticsearch/src/main/java/apijson/demo/DemoObjectParser.java @@ -0,0 +1,26 @@ +package apijson.demo; + +import java.util.List; + +import javax.servlet.http.HttpSession; + +import com.alibaba.fastjson.JSONObject; + +import apijson.NotNull; +import apijson.RequestMethod; +import apijson.framework.APIJSONObjectParser; +import apijson.orm.Join; +import apijson.orm.SQLConfig; + +public class DemoObjectParser extends APIJSONObjectParser { + + public DemoObjectParser(HttpSession session, @NotNull JSONObject request, String parentPath, SQLConfig arrayConfig + , boolean isSubquery, boolean isTable, boolean isArrayMainTable) throws Exception { + super(session, request, parentPath, arrayConfig, isSubquery, isTable, isArrayMainTable); + } + + @Override + public SQLConfig newSQLConfig(RequestMethod method, String table, String alias, JSONObject request, List joinList, boolean isProcedure) throws Exception { + return DemoSQLConfig.newSQLConfig(method, table, alias, request, joinList, isProcedure); + } +} diff --git a/APIJSONDemo-MultiDataSource-Elasticsearch/src/main/java/apijson/demo/DemoParser.java b/APIJSONDemo-MultiDataSource-Elasticsearch/src/main/java/apijson/demo/DemoParser.java new file mode 100644 index 00000000..e12333e6 --- /dev/null +++ b/APIJSONDemo-MultiDataSource-Elasticsearch/src/main/java/apijson/demo/DemoParser.java @@ -0,0 +1,35 @@ +package apijson.demo; + +import com.alibaba.fastjson.JSONObject; + +import apijson.RequestMethod; +import apijson.framework.APIJSONObjectParser; +import apijson.framework.APIJSONParser; +import apijson.orm.SQLConfig; + +public class DemoParser extends APIJSONParser { + public DemoParser() { + super(); + } + + public DemoParser(RequestMethod method) { + super(method); + } + + public DemoParser(RequestMethod method, boolean needVerify) { + super(method, needVerify); + } + + // 可重写来设置最大查询数量 + // @Override + // public int getMaxQueryCount() { + // return 50; + // } + + @Override + public APIJSONObjectParser createObjectParser(JSONObject request, String parentPath, SQLConfig arrayConfig, boolean isSubquery, boolean isTable, boolean isArrayMainTable) throws Exception { + return new DemoObjectParser(getSession(), request, parentPath, arrayConfig, isSubquery, isTable, isArrayMainTable).setMethod(getMethod()).setParser(this); + } + + +} \ No newline at end of file diff --git a/APIJSONDemo-MultiDataSource-Elasticsearch/src/main/java/apijson/demo/DemoSQLConfig.java b/APIJSONDemo-MultiDataSource-Elasticsearch/src/main/java/apijson/demo/DemoSQLConfig.java new file mode 100644 index 00000000..f094883b --- /dev/null +++ b/APIJSONDemo-MultiDataSource-Elasticsearch/src/main/java/apijson/demo/DemoSQLConfig.java @@ -0,0 +1,135 @@ +/*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.util.UUID; + +import com.alibaba.fastjson.annotation.JSONField; + +import apijson.RequestMethod; +import apijson.framework.APIJSONSQLConfig; +import apijson.orm.AbstractSQLConfig; + + +/**SQL 配置 + * 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 { + + public DemoSQLConfig() { + super(); + } + + public DemoSQLConfig(RequestMethod method, String table) { + super(method, table); + } + + static { +// DEFAULT_DATABASE = DATABASE_ELASTICSEARCH; // 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("ES_blog", "es_blog"); + SIMPLE_CALLBACK = new SimpleCallback() { + + @Override + public AbstractSQLConfig getSQLConfig(RequestMethod method, String database, String schema, + String datasource, String table) { + return new DemoSQLConfig(method, table); + } + + // 取消注释来实现数据库自增 id + @Override + public String newId(RequestMethod method, String database, String schema, String datasource, String table) { + if(table.equals("Access") || table.equals("Request") || table.equals("Function")){ + return null; + } + return UUID.randomUUID().toString(); // return null 则不生成 id,一般用于数据库自增 id + } + }; + } + + @JSONField(serialize = false) // 不在日志打印 账号/密码 等敏感信息,用了 UnitAuto 则一定要加 + @Override + public String getDBVersion() { + return DynamicJdbcDataSource.getDetail(this.getDatasource()).getDbVersion(); + } + + @JSONField(serialize = false) // 不在日志打印 账号/密码 等敏感信息,用了 UnitAuto 则一定要加 + @Override + public String getDatabase() { + if (super.getDatabase() != null) { + return super.getDatabase(); + } + try { + return DynamicJdbcDataSource.getDetail(this.getDatasource()).getDatabase(); + } catch (Exception e) { + throw new IllegalArgumentException("动态数据源配置错误 " + this.getDatasource()); + } + } + + @JSONField(serialize = false) // 不在日志打印 账号/密码 等敏感信息,用了 UnitAuto 则一定要加 + @Override + public String getSchema() { + if (super.getSchema() != null) { + return super.getSchema(); + } + try { + return DynamicJdbcDataSource.getDetail(this.getDatasource()).getSchema(); + } catch (Exception e) { + throw new IllegalArgumentException("动态数据源配置错误 " + this.getDatasource()); + } + } + + @JSONField(serialize = false) // 不在日志打印 账号/密码 等敏感信息,用了 UnitAuto 则一定要加 + @Override + public String getDBUri() { + try { + return DynamicJdbcDataSource.getDetail(this.getDatasource()).getUrl(); // 数据库连接url + } catch (Exception e) { + throw new IllegalArgumentException("动态数据源配置错误 " + this.getDatasource()); + } + } + + @JSONField(serialize = false) // 不在日志打印 账号/密码 等敏感信息,用了 UnitAuto 则一定要加 + @Override + public String getDBAccount() { + try { + return DynamicJdbcDataSource.getDetail(this.getDatasource()).getDbAccount(); + } catch (Exception e) { + throw new IllegalArgumentException("动态数据源配置错误 " + this.getDatasource()); + } + } + + @JSONField(serialize = false) // 不在日志打印 账号/密码 等敏感信息,用了 UnitAuto 则一定要加 + @Override + public String getDBPassword() { + try { + return DynamicJdbcDataSource.getDetail(this.getDatasource()).getDbPassword(); + } catch (Exception e) { + throw new IllegalArgumentException("动态数据源配置错误 " + this.getDatasource()); + } + } + +} diff --git a/APIJSONDemo-MultiDataSource-Elasticsearch/src/main/java/apijson/demo/DemoSQLExecutor.java b/APIJSONDemo-MultiDataSource-Elasticsearch/src/main/java/apijson/demo/DemoSQLExecutor.java new file mode 100644 index 00000000..ac30e20f --- /dev/null +++ b/APIJSONDemo-MultiDataSource-Elasticsearch/src/main/java/apijson/demo/DemoSQLExecutor.java @@ -0,0 +1,118 @@ +/*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.Log; +import apijson.NotNull; +import apijson.StringUtil; +import apijson.framework.APIJSONSQLExecutor; +import apijson.orm.SQLConfig; +import lombok.extern.log4j.Log4j2; + +/** + * SQL 执行器,支持连接池及多数据源 具体见 https://github.com/Tencent/APIJSON/issues/151 + * + * @author Lemon + */ +@Log4j2 +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 { + String datasource = config.getDatasource(); + Log.d(TAG, "getConnection config.getDatasource() = " + datasource); + + String key = datasource + "-" + config.getDatabase(); + Connection conn = connectionMap.get(key); + if (conn == null || conn.isClosed()) { + DataSource dataSource = DataBaseUtil.getDataSource(datasource); + connectionMap.put(key, dataSource == null ? null : dataSource.getConnection()); + } + return super.getConnection(config); + } + + @SuppressWarnings("incomplete-switch") + @Override + public int executeUpdate(@NotNull SQLConfig config, String sql) throws Exception { + if (config.getDatasource() != null && StringUtil.equals(config.getDatabase(), SQLConfig.DATABASE_ELASTICSEARCH)) { + // TODO 调用 非 jdbc数据源,执行相关语句 + ESOptions esOptions = new ESOptions(); + switch (config.getMethod()) { + case POST: + return esOptions.insert(config, config.getDatasource()); + case PUT: + return esOptions.updateBySql(config, config.getDatasource(), sql); + case DELETE: + return esOptions.deleteBySql(config, config.getDatasource(), sql); + } + } + return super.executeUpdate(config, sql); + } + /*** + * 查询返回字段值进行二次处理 + */ +// @Override +// protected JSONObject onPutColumn(@NotNull SQLConfig config, @NotNull ResultSet rs, @NotNull ResultSetMetaData rsmd +// , final int tablePosition, @NotNull JSONObject table, final int columnIndex, Join join, Map childMap) throws Exception { +// if (table == null) { // 对应副表 viceSql 不能生成正常 SQL, 或者是 ! - Outer, ( - ANTI JOIN 的副表这种不需要缓存及返回的数据 +// Log.i(TAG, "onPutColumn table == null >> return table;"); +// return table; +// } +// +// if (isHideColumn(config, rs, rsmd, tablePosition, table, columnIndex, childMap)) { +// Log.i(TAG, "onPutColumn isHideColumn(config, rs, rsmd, tablePosition, table, columnIndex, childMap) >> return table;"); +// return table; +// } +// +// String label = getKey(config, rs, rsmd, tablePosition, table, columnIndex, childMap); +// Object value = getValue(config, rs, rsmd, tablePosition, table, columnIndex, label, childMap); +// +// // TODO +// if(StringUtils.equals(config.getTable(), "User") && StringUtils.equals(label, "addr_id")) { +// value = "1-1-1"; +// } +// // 主表必须 put 至少一个 null 进去,否则全部字段为 null 都不 put 会导致中断后续正常返回值 +// if (value != null || (join == null && table.isEmpty())) { +// table.put(label, value); +// } +// +// return table; +// } + + // 取消注释支持 !key 反选字段 和 字段名映射,需要先依赖插件 https://github.com/APIJSON/apijson-column + // @Override + // protected String getKey(SQLConfig config, ResultSet rs, ResultSetMetaData + // rsmd, int tablePosition, JSONObject table, + // int columnIndex, Map childMap) throws Exception { + // return ColumnUtil.compatOutputKey(super.getKey(config, rs, rsmd, + // tablePosition, table, columnIndex, childMap), config.getTable(), + // config.getMethod()); + // } + + // 不需要隐藏字段这个功能时,取消注释来提升性能 + // @Override + // protected boolean isHideColumn(SQLConfig config, ResultSet rs, + // ResultSetMetaData rsmd, int tablePosition, + // JSONObject table, int columnIndex, Map childMap) throws + // SQLException { + // return false; + // } + +} diff --git a/APIJSONDemo-MultiDataSource-Elasticsearch/src/main/java/apijson/demo/DynamicJdbcDataSource.java b/APIJSONDemo-MultiDataSource-Elasticsearch/src/main/java/apijson/demo/DynamicJdbcDataSource.java new file mode 100644 index 00000000..75cd4755 --- /dev/null +++ b/APIJSONDemo-MultiDataSource-Elasticsearch/src/main/java/apijson/demo/DynamicJdbcDataSource.java @@ -0,0 +1,209 @@ +package apijson.demo; + +import static com.alibaba.druid.pool.DruidDataSourceFactory.PROP_CONNECTIONPROPERTIES; +import static com.alibaba.druid.pool.DruidDataSourceFactory.PROP_URL; + +import java.net.InetAddress; +import java.sql.Connection; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; +import java.util.HashMap; +import java.util.Map; +import java.util.Properties; + +import javax.sql.DataSource; + +import org.apache.http.HttpHost; +import org.elasticsearch.common.transport.TransportAddress; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.ApplicationArguments; +import org.springframework.boot.ApplicationRunner; +import org.springframework.core.annotation.Order; +import org.springframework.stereotype.Component; + +import com.alibaba.druid.pool.DruidDataSource; +import com.alibaba.druid.pool.ElasticSearchDruidDataSourceFactory; +import com.baomidou.dynamic.datasource.DynamicRoutingDataSource; +import com.baomidou.dynamic.datasource.ds.ItemDataSource; +import com.baomidou.mybatisplus.extension.toolkit.JdbcUtils; + +import apijson.JSONObject; +import apijson.StringUtil; +import apijson.orm.SQLConfig; +import lombok.Data; +import lombok.extern.log4j.Log4j2; + +/*** + * 不存在并发问题 + * 缓存 jdbc 数据源,供apijson调用 + * 1、应用启动添加数据源 + * 2、页面动态添加数据源(数据库存储数据源信息) + * + * @author xy + * + */ +@Data +@Order(value = 10) +@Component +@Log4j2 +public class DynamicJdbcDataSource implements ApplicationRunner { + // value: 数据源相关信息 + private static Map dataSourceMap = new HashMap<>(); + private String database; // 表所在的数据库类型 + private String schema; // 表所在的数据库名 + private String datasourceName; // 数据源 + private String url; // jdbc url + private String dbAccount; // 数据库用户名 + private String dbPassword; // 数据库密码 + private String dbVersion; // 数据库版本号 + private String clusterName; // 集群名称 + private TransportAddress[] transportAddresss; // elasticSearch tcp地址 + private HttpHost[] httpHosts; // elasticSearch http地址 + + @Autowired + private DataSource dataSource; // 数据源 + + public static void addDataSource(DynamicJdbcDataSource detail) { + dataSourceMap.put(detail.getDatasourceName(), detail); + } + + /*** + * 获取数据源详细信息 + * + * @return + */ + public static DynamicJdbcDataSource getDetail(String datasource) { + if (datasource == null) { + // 默认数据源 + datasource = DataBaseConfig.getInstence().getPrimary(); + } + // 不存在交给框架处理 + return dataSourceMap.get(datasource); + } + + @Override + public void run(ApplicationArguments args) throws Exception { + initJdbcDataSource(); // 初始化spring application.xml 数据库连接池 + // 从数据库初始化 动态数据源 + initElasticDataSource(); + } + + /*** + * 后面替换为从数据库加载 + * + * @throws Exception + */ + public void initElasticDataSource() throws Exception { + String datasourceName = "elasticSearch"; + String urlPrefix = "jdbc:elasticsearch://"; + String httpAddress = "47.108.49.213:9201,47.108.49.213:9202,47.108.49.213:9203"; + String[] httpAddressArr = httpAddress.split(","); + HttpHost[] httpHosts = new HttpHost[httpAddressArr.length]; + for (int i = 0; i < httpAddressArr.length; i++) { + String val = httpAddressArr[i]; + String[] data = val.split(":"); + HttpHost httpHost = new HttpHost(InetAddress.getByName(data[0]), Integer.parseInt(data[1])); + httpHosts[i] = httpHost; + } + + String tcpAddress = "47.108.49.213:9301,47.108.49.213:9302,47.108.49.213:9303"; + String url = urlPrefix + tcpAddress; + String[] tcpAddressArr = tcpAddress.split(","); + TransportAddress[] transportAddresss = new TransportAddress[tcpAddressArr.length]; + for (int i = 0; i < tcpAddressArr.length; i++) { + String val = tcpAddressArr[i]; + String[] data = val.split(":"); + TransportAddress transportAddress = new TransportAddress(InetAddress.getByName(data[0]), Integer.parseInt(data[1])); + transportAddresss[i] = transportAddress; + } + log.info("elasticSearch数据源:{},ip地址:{} ", datasourceName, JSONObject.toJSONString(transportAddresss)); + String database = SQLConfig.DATABASE_ELASTICSEARCH; // 数据库类型 + String dbAccount = null; // 数据库用户名 + String dbPassword = null; // 数据库密码 + Properties properties = new Properties(); + properties.put(PROP_URL, url); + String connectionStr = "client.transport.ignore_cluster_name=true"; + if (StringUtil.isNotEmpty(dbAccount) && StringUtil.isNotEmpty(dbPassword)) { + connectionStr += ";xpack.security.user="+dbAccount+":"+dbPassword; + } + // properties.put(PROP_CONNECTIONPROPERTIES, "client.transport.ignore_cluster_name=true;xpack.security.user=elastic:5laftq1NilavFTibKOaZ"); + properties.put(PROP_CONNECTIONPROPERTIES, connectionStr); + DataSource dataSource = ElasticSearchDruidDataSourceFactory.createDataSource(properties); + + DynamicJdbcDataSource dynDataSource = new DynamicJdbcDataSource(); + dynDataSource.setDatasourceName(datasourceName); + dynDataSource.setDatabase(database); + dynDataSource.setDataSource(dataSource); + dynDataSource.setSchema(""); // 不需要配置数据库名 + // APIJSONSQLConfig.TABLE_KEY_MAP.put("ES_blog", "es_blog/dd"); 来支持 index/doc + dynDataSource.setUrl(url); + if (StringUtil.isNotEmpty(dbAccount) && StringUtil.isNotEmpty(dbPassword)) { + dynDataSource.setDbAccount(dbAccount); + dynDataSource.setDbPassword(dbPassword); + } + dynDataSource.setDbVersion("7.17.5"); + dynDataSource.setClusterName("docker-cluster"); + dynDataSource.setTransportAddresss(transportAddresss); + dynDataSource.setHttpHosts(httpHosts); + dataSourceMap.put(datasourceName, dynDataSource); + } + + /*** + * 初始化数据库连接池 + */ + private void initJdbcDataSource() { + DynamicRoutingDataSource dataSourceList = (DynamicRoutingDataSource) this.dataSource; + for (String datasourceName : dataSourceList.getDataSources().keySet()) { + ItemDataSource dataSource = (ItemDataSource) dataSourceList.getDataSources().get(datasourceName); + DruidDataSource druid = (DruidDataSource) dataSource.getRealDataSource(); + String url = druid.getDataSourceStat().getUrl(); // 数据库连接url + String schema = DataBaseUtil.getLibname(url); // 数据库名; + String database = JdbcUtils.getDbType(url).getDb().toUpperCase(); // 数据库类型 + String dbAccount = druid.getUsername(); // 数据库用户名 + String dbPassword = druid.getPassword(); // 数据库密码 + String dbVersion = getDBVersion(dataSource); + + DynamicJdbcDataSource dynDataSource = new DynamicJdbcDataSource(); + dynDataSource.setDatasourceName(datasourceName); + dynDataSource.setDatabase(database); + dynDataSource.setDataSource(druid); + dynDataSource.setSchema(schema); + dynDataSource.setUrl(url); + dynDataSource.setDbAccount(dbAccount); + dynDataSource.setDbPassword(dbPassword); + dynDataSource.setDbVersion(dbVersion); + dataSourceMap.put(datasourceName, dynDataSource); + } + } + + public String getDBVersion(DataSource dataSource) { + Connection connection = null; + Statement statement = null; + ResultSet resultSet = null; + try { + connection = dataSource.getConnection(); + statement = connection.createStatement(); + resultSet = statement.executeQuery("select version() as version"); + while (resultSet.next()) { + return resultSet.getString("version"); + } + } catch (Exception e) { + e.printStackTrace(); + } finally { + try { + if (resultSet != null) { + resultSet.close(); + } + if (statement != null) { + statement.close(); + } + if (connection != null) { + connection.close(); + } + } catch (SQLException throwables) { + } + } + return null; + } +} diff --git a/APIJSONDemo-MultiDataSource-Elasticsearch/src/main/java/apijson/demo/ESOptions.java b/APIJSONDemo-MultiDataSource-Elasticsearch/src/main/java/apijson/demo/ESOptions.java new file mode 100644 index 00000000..13ad4455 --- /dev/null +++ b/APIJSONDemo-MultiDataSource-Elasticsearch/src/main/java/apijson/demo/ESOptions.java @@ -0,0 +1,274 @@ +package apijson.demo; + +import java.io.IOException; +import java.util.List; + +import org.apache.http.HttpHost; +import org.apache.http.impl.nio.client.HttpAsyncClientBuilder; +import org.elasticsearch.action.bulk.BulkItemResponse; +import org.elasticsearch.action.bulk.BulkRequestBuilder; +import org.elasticsearch.action.bulk.BulkResponse; +import org.elasticsearch.action.index.IndexRequestBuilder; +import org.elasticsearch.client.RequestOptions; +import org.elasticsearch.client.RestClient; +import org.elasticsearch.client.RestClientBuilder; +import org.elasticsearch.client.RestHighLevelClient; +import org.elasticsearch.client.transport.TransportClient; +import org.elasticsearch.common.settings.Settings; +import org.elasticsearch.common.transport.TransportAddress; +import org.elasticsearch.index.query.QueryBuilder; +import org.elasticsearch.index.query.QueryBuilders; +import org.elasticsearch.index.reindex.BulkByScrollResponse; +import org.elasticsearch.index.reindex.UpdateByQueryRequest; +import org.elasticsearch.script.Script; +import org.elasticsearch.transport.client.PreBuiltTransportClient; +import org.elasticsearch.xcontent.XContentBuilder; +import org.elasticsearch.xcontent.XContentFactory; +import org.nlpcn.es4sql.SearchDao; +import org.nlpcn.es4sql.domain.Where; +import org.nlpcn.es4sql.parse.SqlParser; +import org.nlpcn.es4sql.parse.WhereParser; +import org.nlpcn.es4sql.query.ESActionFactory; +import org.nlpcn.es4sql.query.SqlElasticRequestBuilder; +import org.nlpcn.es4sql.query.maker.QueryMaker; + +import com.alibaba.druid.sql.ast.statement.SQLDeleteStatement; +import com.alibaba.druid.sql.parser.SQLStatementParser; + +import apijson.StringUtil; +import apijson.orm.SQLConfig; +import lombok.extern.log4j.Log4j2; + +@Log4j2 +public class ESOptions { + + public TransportClient getTransportClient(String dataSource) { + try { + String clusterName = DynamicJdbcDataSource.getDetail(dataSource).getClusterName(); + Settings settings = Settings.builder().put("cluster.name", clusterName).build(); +// Settings settings = Settings.builder() +// .put("cluster.name", clusterName) +// // 设置xpack权限用户 +// .put("xpack.security.user", userName + ":" + password) +// .build(); + TransportAddress[] transportAddresss = DynamicJdbcDataSource.getDetail(dataSource).getTransportAddresss(); + TransportClient client = new PreBuiltTransportClient(settings); + client.addTransportAddresses(transportAddresss); // 通讯端口 而不是服务端口 + return client; + } catch (Exception e) { + e.printStackTrace(); + throw new IllegalArgumentException("动态数据源配置错误 " + dataSource); + } + } + + private RestHighLevelClient getRestHighLevelClient(String dataSource) { + try { + HttpHost[] httpHosts = DynamicJdbcDataSource.getDetail(dataSource).getHttpHosts(); + // final CredentialsProvider credentialsProvider = new + // BasicCredentialsProvider(); + // credentialsProvider.setCredentials(AuthScope.ANY, new + // UsernamePasswordCredentials("", "")); + RestClientBuilder builder = RestClient.builder(httpHosts).setHttpClientConfigCallback(new RestClientBuilder.HttpClientConfigCallback() { + @Override + public HttpAsyncClientBuilder customizeHttpClient(HttpAsyncClientBuilder httpClientBuilder) { + // return httpClientBuilder.setDefaultCredentialsProvider(credentialsProvider); + return httpClientBuilder; + } + }); + return new RestHighLevelClient(builder); + } catch (Exception e) { + e.printStackTrace(); + throw new IllegalArgumentException("动态数据源配置错误 " + dataSource); + } + } + + /*** + * sql语句解析为dsl + * 支持 查询、删除, 不支持 新增、修改. + * 可以将sql语句转换一下 + * @param dataSource + * @param sql + * @return + * @throws Exception + */ + private String explain(String dataSource, String sql) throws Exception { + // 获取client + TransportClient client = getTransportClient(dataSource); + SearchDao searchDao = new SearchDao(client); + SqlElasticRequestBuilder requestBuilder = searchDao.explain(sql).explain(); + return requestBuilder.explain(); + } + + @SuppressWarnings("deprecation") + public int updateBySql(SQLConfig config, String dataSource, String sql) { + // 获取client + RestHighLevelClient restHighLevelClient = getRestHighLevelClient(dataSource); + try { + sql = StringUtil.isEmpty(sql) ? config.getSQL(false) : sql; + String sqlTable = config.getSQLTable(); // 截取 + int index = sqlTable.indexOf("/"); + String indexName = null; + String type = null; + if (index > 0) { + indexName = sqlTable.substring(0, index); + type = sqlTable.substring(index + 1); + } else { + indexName = sqlTable; + } + + index = sql.indexOf("WHERE"); + UpdateByQueryRequest updateByQuery = new UpdateByQueryRequest(indexName); + if (index > 0) { + String whereSql = sql.substring(index); + index = whereSql.indexOf("RLIKE"); + if(index > 0) { + whereSql = whereSql.replace("RLIKE", "="); + } + String tmpSearchSql = "DELETE from " + indexName + " " + whereSql; + // 获取sql 转换为 elasticSearch api 某一部分,比如where查询等等 + SQLStatementParser parser = ESActionFactory.createSqlStatementParser(tmpSearchSql); + SQLDeleteStatement deleteStatement = parser.parseDeleteStatement(); + WhereParser whereParser = new WhereParser(new SqlParser(), deleteStatement); + Where where = whereParser.findWhere(); + if (where != null) { + QueryBuilder whereQuery = QueryMaker.explan(where); + updateByQuery.setQuery(whereQuery); + } else { + updateByQuery.setQuery(QueryBuilders.matchAllQuery()); + } + } + if(StringUtil.isNotEmpty(type)) { + updateByQuery.setDocTypes(type); + } + // 设置版本冲突时继续执行 + updateByQuery.setConflicts("proceed"); + // 设置更新完成后刷新索引 ps很重要如果不加可能数据不会实时刷新 + updateByQuery.setRefresh(true); + + StringBuffer sb = new StringBuffer(); + for (String column : config.getContent().keySet()) { + Object value = config.getContent().get(column); + // 最后多一个;不会影响,elasticsearch能正确解析 + if(StringUtil.isNumer(value.toString())) { + sb.append("ctx._source['" + column + "'] = " + value + ";"); + }else { + sb.append("ctx._source['" + column + "'] = '" + value + "';"); + } + } + updateByQuery.setScript(new Script(sb.toString())); + BulkByScrollResponse response = restHighLevelClient.updateByQuery(updateByQuery, RequestOptions.DEFAULT); + return response.getStatus().getUpdated() > 0 ? (int)response.getStatus().getUpdated() : 0; + } catch (Exception e) { + e.printStackTrace(); + } finally { + if (restHighLevelClient != null) { + try { + restHighLevelClient.close(); + } catch (IOException e) { + } + } + } + return 0; + } + + /*** + * sql语句转换为dsl执行 + * + * @param config + * @param dataSource + * @return + */ + @SuppressWarnings("deprecation") + public int deleteBySql(SQLConfig config, String dataSource, String sql) { + TransportClient client = getTransportClient(dataSource); + try { + sql = StringUtil.isEmpty(sql) ? config.getSQL(false) : sql; + String sqlTable = config.getSQLTable(); // 截取 + int index = sqlTable.indexOf("/"); + String indexName = null; + if (index > 0) { + indexName = sqlTable.substring(0, index); + } else { + indexName = sqlTable; + } + + SearchDao searchDao = new SearchDao(client); + client.admin().indices().prepareRefresh(indexName).get(); + SqlElasticRequestBuilder requestBuilder = searchDao.explain(sql).explain(); + log.info("sql explain: {}", requestBuilder.explain()); // 线上去掉 + BulkByScrollResponse response = (BulkByScrollResponse) requestBuilder.get(); + if (response.getStatus().getDeleted() > 0) { + searchDao.getClient().admin().indices().prepareRefresh(indexName).get(); + return (int)response.getStatus().getDeleted(); + } + } catch (Exception e) { + e.printStackTrace(); + } finally { + if (client != null) { + client.close(); + } + } + return 0; + } + + /** + * 支持批量插入,apijson框架目前还未放开批量操作 + * @param config + * @param dataSource + * @return + */ + @SuppressWarnings("deprecation") + public int insert(SQLConfig config, String dataSource) { + TransportClient client = getTransportClient(dataSource); + StringBuffer errorMsg = new StringBuffer(); + try { + BulkRequestBuilder builder = client.prepareBulk(); + String sqlTable = config.getSQLTable(); // 截取 + int index = sqlTable.indexOf("/"); + String indexName = null; + String type = null; + if (index > 0) { + indexName = sqlTable.substring(0, index); + type = sqlTable.substring(index + 1); + } else { + indexName = sqlTable; + } + // 支持批量插入[[],[]] + for (int i = 0; i < config.getValues().size(); i++) { + List values = config.getValues().get(i); + XContentBuilder xContentBuilder = XContentFactory.jsonBuilder().startObject(); + for (int j = 0; j < values.size(); j++) { + xContentBuilder.field(config.getColumn().get(j), values.get(j)); + } + if (values.size() > 0) { + xContentBuilder.endObject(); + IndexRequestBuilder request = client.prepareIndex(indexName, type, config.getId().toString()).setSource(xContentBuilder); + builder.add(request); + } + } + BulkResponse response = builder.get(); + if (response.hasFailures()) { + for (BulkItemResponse br : response.getItems()) { + errorMsg.append(br.getId() + ";" + br.getFailureMessage() + "\n"); + } + log.error("数据源 {}, 插入操作失败:{}", dataSource, errorMsg); + } else { + // 立刻刷新索引 + client.admin().indices().prepareRefresh(indexName).get(); + return 1; + } + } catch (Exception e) { + e.printStackTrace(); + } finally { + if (client != null) { + client.close(); + } + } + + if (errorMsg.length() > 0) { + throw new IllegalArgumentException("数据源 " + dataSource + ", 插入操作失败 ! " + errorMsg); + } + return 0; + } + +} diff --git a/APIJSONDemo-MultiDataSource-Elasticsearch/src/main/java/apijson/demo/ElasticSearchSqlTest.java b/APIJSONDemo-MultiDataSource-Elasticsearch/src/main/java/apijson/demo/ElasticSearchSqlTest.java new file mode 100644 index 00000000..79b3d954 --- /dev/null +++ b/APIJSONDemo-MultiDataSource-Elasticsearch/src/main/java/apijson/demo/ElasticSearchSqlTest.java @@ -0,0 +1,233 @@ +package apijson.demo; + +import static com.alibaba.druid.pool.DruidDataSourceFactory.PROP_CONNECTIONPROPERTIES; +import static com.alibaba.druid.pool.DruidDataSourceFactory.PROP_URL; + +import java.net.InetAddress; +import java.net.UnknownHostException; +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.util.Properties; + +import javax.sql.DataSource; + +import org.apache.http.HttpHost; +import org.apache.http.auth.AuthScope; +import org.apache.http.auth.UsernamePasswordCredentials; +import org.apache.http.client.CredentialsProvider; +import org.apache.http.impl.client.BasicCredentialsProvider; +import org.apache.http.impl.nio.client.HttpAsyncClientBuilder; +import org.elasticsearch.action.admin.cluster.node.info.NodesInfoResponse; +import org.elasticsearch.client.RequestOptions; +import org.elasticsearch.client.RestClient; +import org.elasticsearch.client.RestClientBuilder; +import org.elasticsearch.client.RestHighLevelClient; +import org.elasticsearch.client.transport.TransportClient; +import org.elasticsearch.common.settings.Settings; +import org.elasticsearch.common.transport.TransportAddress; +import org.elasticsearch.index.query.QueryBuilder; +import org.elasticsearch.index.query.QueryBuilders; +import org.elasticsearch.index.query.WrapperQueryBuilder; +import org.elasticsearch.index.reindex.BulkByScrollResponse; +import org.elasticsearch.index.reindex.DeleteByQueryAction; +import org.elasticsearch.index.reindex.DeleteByQueryRequestBuilder; +import org.elasticsearch.index.reindex.UpdateByQueryRequest; +import org.elasticsearch.script.Script; +import org.elasticsearch.transport.client.PreBuiltTransportClient; +import org.elasticsearch.xpack.client.PreBuiltXPackTransportClient; +import org.junit.Test; +import org.nlpcn.es4sql.SearchDao; +import org.nlpcn.es4sql.domain.Where; +import org.nlpcn.es4sql.exception.SqlParseException; +import org.nlpcn.es4sql.parse.SqlParser; +import org.nlpcn.es4sql.parse.WhereParser; +import org.nlpcn.es4sql.query.ESActionFactory; +import org.nlpcn.es4sql.query.SqlElasticRequestBuilder; +import org.nlpcn.es4sql.query.maker.QueryMaker; + +import com.alibaba.druid.pool.DruidDataSource; +import com.alibaba.druid.pool.ElasticSearchDruidDataSourceFactory; +import com.alibaba.druid.sql.ast.statement.SQLDeleteStatement; +import com.alibaba.druid.sql.parser.SQLStatementParser; +import com.alibaba.fastjson.JSONObject; + +import apijson.StringUtil; +import lombok.extern.slf4j.Slf4j; + +/*** + * + * @author xy + * + */ +@Slf4j +public class ElasticSearchSqlTest { + + public static void main(String[] args) throws Exception { + Properties properties = new Properties(); + properties.put(PROP_URL, "jdbc:elasticsearch://47.108.49.213:9302"); + properties.put(PROP_CONNECTIONPROPERTIES, "client.transport.ignore_cluster_name=true"); + DruidDataSource dds = (DruidDataSource) ElasticSearchDruidDataSourceFactory.createDataSource(properties); + Connection connection = dds.getConnection(); + // SELECT count(*) as count FROM es_blog + PreparedStatement ps = connection.prepareStatement("SELECT count, count(*) as total FROM es_blog/dd group by count"); + ResultSet resultSet = ps.executeQuery(); + while (resultSet.next()) { + System.out.println(resultSet.getObject("count") + ";" + resultSet.getObject("total")); + } + + ps.close(); + connection.close(); + dds.close(); + } + + /** + * 将sql语句转换未dsl语句 + * + * @throws Exception + * @throws SqlParseException + */ + @Test + public void sqlExplainTest() throws Exception, SqlParseException { + String sql = "SELECT count, count(*) as total FROM es_blog group by count"; + String explain = explain(sql); + log.info("explain:{}", explain); + } + + private String explain(String sql) throws Exception { + // 获取client + Settings settings = Settings.builder().put("cluster.name", "docker-cluster").build(); + TransportClient transportClient = new PreBuiltTransportClient(settings); + transportClient.addTransportAddress(getTransportAddress()); + SearchDao searchDao = new SearchDao(transportClient); + SqlElasticRequestBuilder requestBuilder = searchDao.explain(sql).explain(); + return requestBuilder.explain(); + } + + String TEST_INDEX = "es_blog"; + + /*** + * 非jdbc方式 实现delete + * + * @throws Exception + * @throws SqlParseException + */ + @Test + public void deleteTest() throws Exception, SqlParseException { + // String deleteStatement = "delete from " + TEST_INDEX +" where title.keyword = + // \"Linux安装\""; + String deleteStatement = "DELETE FROM es_blog WHERE id IN ('c7890a84-1b3c-464d-9c40-9d2cf5a1d292','322323') "; + // String deleteStatement = "DELETE FROM es_blog WHERE title.keyword = 'test-3' + // and count = 3"; +// String deleteStatement = "DELETE FROM es_blog WHERE title.keyword like 'test%'"; + // 获取client + Settings settings = Settings.builder().put("client.transport.ignore_cluster_name", true).build(); + TransportClient client = new PreBuiltTransportClient(settings).addTransportAddress(getTransportAddress()); + NodesInfoResponse nodeInfos = client.admin().cluster().prepareNodesInfo().get(); + String clusterName = nodeInfos.getClusterName().value(); + log.info(String.format("Found cluster... cluster name: %s", clusterName)); + SearchDao searchDao = new SearchDao(client); + client.admin().indices().prepareRefresh(TEST_INDEX + "*").get(); + + // searchDao.explain(deleteStatement).explain().get(); + + SqlElasticRequestBuilder requestBuilder = searchDao.explain(deleteStatement).explain(); + log.info("sql explain: {}", requestBuilder.explain()); + BulkByScrollResponse reponse = (BulkByScrollResponse) requestBuilder.get(); + searchDao.getClient().admin().indices().prepareRefresh(TEST_INDEX).get(); + } + + protected static TransportAddress getTransportAddress() throws UnknownHostException { + String host = "47.108.49.213"; + String port = "9301"; + return new TransportAddress(InetAddress.getByName(host), Integer.parseInt(port)); + } + + protected static TransportAddress getHttpTransportAddress() throws UnknownHostException { + String host = "47.108.49.213"; + String port = "9201"; + return new TransportAddress(InetAddress.getByName(host), Integer.parseInt(port)); + } + + + @Test + public void delete() throws Exception { + deleteQuery(TEST_INDEX, null); + } + + public static void deleteQuery(String indexName, String typeName) throws Exception { + // 获取client +// Settings settings = Settings.builder().put("cluster.name", "docker-cluster").build(); +// TransportClient client = new PreBuiltTransportClient(settings); + Settings settings = Settings.builder().put("cluster.name", "docker-cluster") + // 设置xpack权限用户 + .put("xpack.security.user", "elastic:ztzh@smart666") + .put("xpack.security.transport.ssl.enabled",false)//设置xpack权限用户 + .put("client.transport.sniff", true) + .build(); + TransportClient client = new PreBuiltXPackTransportClient(settings); + client.addTransportAddress(getTransportAddress()); + DeleteByQueryRequestBuilder deleteQueryBuilder = new DeleteByQueryRequestBuilder(client, DeleteByQueryAction.INSTANCE); + deleteQueryBuilder.filter(QueryBuilders.matchQuery("title.keyword", "u-test-1")).source(indexName); + if (typeName != null) { + deleteQueryBuilder.request().getSearchRequest().types(typeName); + } + deleteQueryBuilder.get(); + // deleteQueryBuilder.filter(QueryBuilders.matchAllQuery()).get(); + System.out.println(String.format("Deleted index %s and type %s", indexName, typeName)); + } + + private RestHighLevelClient buildClient() { + final CredentialsProvider credentialsProvider = new BasicCredentialsProvider(); + credentialsProvider.setCredentials(AuthScope.ANY, new UsernamePasswordCredentials("elastic", "ztzh@smart666")); + RestClientBuilder builder = RestClient.builder(new HttpHost("47.108.49.213", 9201)).setHttpClientConfigCallback(new RestClientBuilder.HttpClientConfigCallback() { + @Override + public HttpAsyncClientBuilder customizeHttpClient(HttpAsyncClientBuilder httpClientBuilder) { + return httpClientBuilder.setDefaultCredentialsProvider(credentialsProvider); +// return httpClientBuilder; + } + }); + + RestHighLevelClient restHighLevelClient = new RestHighLevelClient(builder); + return restHighLevelClient; + } + + /*** + * 沿用 elastic-sql实现 + * + * @throws Exception + */ + @Test + public void update() throws Exception { + // 获取client + RestHighLevelClient restHighLevelClient = buildClient(); + String updateSql = "UPDATE es_blog set title = 'u-test-2' WHERE id = '82f812fb-72e2-491e-865a-1bfbd251e5cd'"; + int index = updateSql.indexOf("WHERE"); + if (index > 0) { + String tmpSearchSql = "DELETE from es_blog " + updateSql.substring(index); + UpdateByQueryRequest request = new UpdateByQueryRequest("es_blog"); + SQLStatementParser parser = ESActionFactory.createSqlStatementParser(tmpSearchSql); + SQLDeleteStatement deleteStatement = parser.parseDeleteStatement(); + WhereParser whereParser = new WhereParser(new SqlParser(), deleteStatement); + Where where = whereParser.findWhere(); + if (where != null) { + QueryBuilder whereQuery = QueryMaker.explan(where); + request.setQuery(whereQuery); + } else { + request.setQuery(QueryBuilders.matchAllQuery()); + } + request.setScript(new Script("ctx._source['title'] = 'test-2'")); + restHighLevelClient.updateByQuery(request, RequestOptions.DEFAULT); + restHighLevelClient.close(); + } + } + + @Test + public void testClusterName() throws Exception { + Settings settings = Settings.builder().put("client.transport.ignore_cluster_name", true).build(); + TransportClient client = new PreBuiltXPackTransportClient(settings).addTransportAddress(getTransportAddress()); + NodesInfoResponse nodeInfos = client.admin().cluster().prepareNodesInfo().get(); + String clusterName = nodeInfos.getClusterName().value(); + log.info(clusterName); + } +} diff --git a/APIJSONDemo-MultiDataSource-Elasticsearch/src/main/java/apijson/demo/SpringContextUtils.java b/APIJSONDemo-MultiDataSource-Elasticsearch/src/main/java/apijson/demo/SpringContextUtils.java new file mode 100644 index 00000000..7a8e8f4f --- /dev/null +++ b/APIJSONDemo-MultiDataSource-Elasticsearch/src/main/java/apijson/demo/SpringContextUtils.java @@ -0,0 +1,60 @@ +package apijson.demo; + +import java.util.Map; + +import org.springframework.beans.BeansException; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationContextAware; +import org.springframework.stereotype.Component; + +/** + * Spring Context 工具类 + * + * @author + */ +@Component +public class SpringContextUtils implements ApplicationContextAware { + public static ApplicationContext applicationContext; + + @Override + public void setApplicationContext(ApplicationContext applicationContext) + throws BeansException { + SpringContextUtils.applicationContext = applicationContext; + } + + public static Object getBean(String name) { + return applicationContext.getBean(name); + } + + public static T getBean(String name, Class requiredType) { + return applicationContext.getBean(name, requiredType); + } + + /** + * 通过class获取Bean. + * + * @param clazz + * @param + * @return + */ + public static T getBean(Class clazz) { + return applicationContext.getBean(clazz); + } + + public static boolean containsBean(String name) { + return applicationContext.containsBean(name); + } + + public static boolean isSingleton(String name) { + return applicationContext.isSingleton(name); + } + + public static Class getType(String name) { + return applicationContext.getType(name); + } + + public static Map getBeansOfType(Class type) { + return applicationContext.getBeansOfType(type); + } + +} diff --git a/APIJSONDemo-MultiDataSource-Elasticsearch/src/main/resources/application.yml b/APIJSONDemo-MultiDataSource-Elasticsearch/src/main/resources/application.yml new file mode 100644 index 00000000..61cacf5f --- /dev/null +++ b/APIJSONDemo-MultiDataSource-Elasticsearch/src/main/resources/application.yml @@ -0,0 +1,55 @@ +spring: + datasource: + type: com.alibaba.druid.pool.DruidDataSource + dynamic: + primary: master + strict: true + druid: + initial-size: 5 + min-idle: 5 + maxActive: 2000 + maxWait: 60000 + timeBetweenEvictionRunsMillis: 60000 + minEvictableIdleTimeMillis: 300000 + validationQuery: SELECT 1 FROM DUAL + testWhileIdle: true + testOnBorrow: false + testOnReturn: false + poolPreparedStatements: true + maxPoolPreparedStatementPerConnectionSize: 20 + filters: stat,slf4j + connectionProperties: druid.stat.mergeSql\=true;druid.stat.slowSqlMillis\=5000 + datasource: + master: + driver-class-name: com.mysql.cj.jdbc.Driver + url: jdbc:mysql://xxx:3306/housekeeping?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&allowMultiQueries=true&useSSL=false + username: + password: + 155db: + driver-class-name: com.mysql.cj.jdbc.Driver + url: jdbc:mysql://xxx:3888/housekeeping?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&allowMultiQueries=true&useSSL=false + username: + password: + 213db: + driver-class-name: com.mysql.cj.jdbc.Driver + url: jdbc:mysql://xxx:3888/housekeeping?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&allowMultiQueries=true&useSSL=false + username: + password: + db1: + driver-class-name: com.mysql.cj.jdbc.Driver + url: jdbc:mysql://xxx:3306/apijsonMuli1?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&allowMultiQueries=true&useSSL=false + username: + password: + db2: + driver-class-name: com.mysql.cj.jdbc.Driver + url: jdbc:mysql://xxx:3306/apijsonMuli2?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&allowMultiQueries=true&useSSL=false + username: + password: + filter: + stat: + log-slow-sql: true + slow-sql-millis: 1000 + merge-sql: false + wall: + config: + multi-statement-allow: true