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
+ }
+ ```
+
+ 
+
+## 2、集成elasticsearch-sql
+
+换成xpack, 也一样
+
+应用导入: elasticsearch-sql-7.17.5.0.jar
+
+## 3、apijson支持elasticsearch功能点
+
+新增、修改、删除、查询
+
+## 4、elasticsearch-sql不支持RLIKE
+
+
+
+## 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