Browse Source

提交版本

李云瑞 1 year ago
parent
commit
aeb9079035
50 changed files with 2540 additions and 0 deletions
  1. 13 0
      db/Dockerfile
  2. 42 0
      docker-compose.yml
  3. 429 0
      jeecg-boot-base-core/src/main/java/org/jeecg/common/aspect/DictAspect.java
  4. 42 0
      jeecg-boot-base-core/src/main/java/org/jeecg/common/aspect/annotation/Dict.java
  5. 53 0
      jeecg-boot-base-core/src/main/java/org/jeecg/common/system/vo/DictModel.java
  6. 19 0
      jeecg-boot-base-core/src/main/java/org/jeecg/common/system/vo/DictModelMany.java
  7. 35 0
      jeecg-boot-base-core/src/main/java/org/jeecg/common/system/vo/DictQuery.java
  8. 58 0
      jeecg-boot-base-core/src/main/java/org/jeecg/common/system/vo/DynamicDataSourceModel.java
  9. 344 0
      jeecg-boot-base-core/src/main/java/org/jeecg/common/util/dynamic/db/DynamicDBUtil.java
  10. 87 0
      jeecg-boot-base-core/src/main/java/org/jeecg/config/DruidConfig.java
  11. 55 0
      jeecg-boot-base-core/src/main/java/org/jeecg/config/mybatis/interceptor/DynamicDatasourceInterceptor.java
  12. 15 0
      jeecg-boot-base-core/src/main/java/org/jeecg/config/vo/DomainUrl.java
  13. BIN
      jeecg-boot-base-core/target/classes/org/jeecg/common/aspect/DictAspect.class
  14. BIN
      jeecg-boot-base-core/target/classes/org/jeecg/common/aspect/annotation/Dict.class
  15. BIN
      jeecg-boot-base-core/target/classes/org/jeecg/common/system/vo/DictModel.class
  16. BIN
      jeecg-boot-base-core/target/classes/org/jeecg/common/system/vo/DictModelMany.class
  17. BIN
      jeecg-boot-base-core/target/classes/org/jeecg/common/system/vo/DictQuery.class
  18. BIN
      jeecg-boot-base-core/target/classes/org/jeecg/common/system/vo/DynamicDataSourceModel.class
  19. BIN
      jeecg-boot-base-core/target/classes/org/jeecg/common/util/dynamic/db/DynamicDBUtil.class
  20. BIN
      jeecg-boot-base-core/target/classes/org/jeecg/config/DruidConfig.class
  21. BIN
      jeecg-boot-base-core/target/classes/org/jeecg/config/mybatis/interceptor/DynamicDatasourceInterceptor.class
  22. BIN
      jeecg-boot-base-core/target/classes/org/jeecg/config/vo/DomainUrl.class
  23. 0 0
      jeecg-module-demo/src/main/java/org/jeecg/modules/demo/mock/vxe/json/dlglong.json
  24. 412 0
      jeecg-module-demo/src/main/java/org/jeecg/modules/dlglong/controller/DlMockController.java
  25. 0 0
      jeecg-module-demo/src/main/java/org/jeecg/modules/dlglong/json/dlglong.json
  26. 0 0
      jeecg-module-demo/target/classes/org/jeecg/modules/demo/mock/vxe/json/dlglong.json
  27. BIN
      jeecg-module-demo/target/classes/org/jeecg/modules/dlglong/controller/DlMockController.class
  28. 0 0
      jeecg-module-demo/target/classes/org/jeecg/modules/dlglong/json/dlglong.json
  29. 105 0
      jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/system/controller/DuplicateCheckController.java
  30. 98 0
      jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/system/model/DepartIdModel.java
  31. 44 0
      jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/system/model/DuplicateCheckVo.java
  32. 60 0
      jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/system/security/DictQueryBlackListHandler.java
  33. 23 0
      jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/system/vo/lowapp/DepartAndUserInfo.java
  34. 26 0
      jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/system/vo/lowapp/DepartInfo.java
  35. BIN
      jeecg-module-system/jeecg-system-biz/target/classes/org/jeecg/modules/system/controller/DuplicateCheckController.class
  36. BIN
      jeecg-module-system/jeecg-system-biz/target/classes/org/jeecg/modules/system/model/DepartIdModel.class
  37. BIN
      jeecg-module-system/jeecg-system-biz/target/classes/org/jeecg/modules/system/model/DuplicateCheckVo.class
  38. BIN
      jeecg-module-system/jeecg-system-biz/target/classes/org/jeecg/modules/system/security/DictQueryBlackListHandler.class
  39. BIN
      jeecg-module-system/jeecg-system-biz/target/classes/org/jeecg/modules/system/vo/lowapp/DepartAndUserInfo.class
  40. BIN
      jeecg-module-system/jeecg-system-biz/target/classes/org/jeecg/modules/system/vo/lowapp/DepartInfo.class
  41. 16 0
      jeecg-module-system/jeecg-system-start/Dockerfile
  42. 40 0
      jeecg-server-cloud/docker-compose-base.yml
  43. 66 0
      jeecg-server-cloud/docker-compose.yml
  44. 15 0
      jeecg-server-cloud/jeecg-cloud-gateway/Dockerfile
  45. 367 0
      jeecg-server-cloud/jeecg-cloud-gateway/src/main/java/org/jeecg/loader/DynamicRouteLoader.java
  46. 15 0
      jeecg-server-cloud/jeecg-cloud-nacos/Dockerfile
  47. 15 0
      jeecg-server-cloud/jeecg-demo-cloud-start/Dockerfile
  48. 15 0
      jeecg-server-cloud/jeecg-system-cloud-start/Dockerfile
  49. 15 0
      jeecg-server-cloud/jeecg-visual/jeecg-cloud-sentinel/Dockerfile
  50. 16 0
      jeecg-server-cloud/jeecg-visual/jeecg-cloud-xxljob/Dockerfile

+ 13 - 0
db/Dockerfile

@@ -0,0 +1,13 @@
+FROM mysql:8.0.19
+
+MAINTAINER jeecgos@163.com
+
+ENV TZ=Asia/Shanghai
+
+RUN ln -sf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone
+
+COPY ./tables_nacos.sql /docker-entrypoint-initdb.d
+
+COPY ./jeecgboot-mysql-5.7.sql /docker-entrypoint-initdb.d
+
+COPY ./tables_xxl_job.sql /docker-entrypoint-initdb.d

+ 42 - 0
docker-compose.yml

@@ -0,0 +1,42 @@
+version: '2'
+services:
+  jeecg-boot-mysql:
+    build:
+      context: ./db
+    environment:
+      MYSQL_ROOT_PASSWORD: root
+      MYSQL_ROOT_HOST: '%'
+      TZ: Asia/Shanghai
+    restart: always
+    container_name: jeecg-boot-mysql
+    image: jeecg-boot-mysql
+    command:
+      --character-set-server=utf8mb4
+      --collation-server=utf8mb4_general_ci
+      --explicit_defaults_for_timestamp=true
+      --lower_case_table_names=1
+      --max_allowed_packet=128M
+      --default-authentication-plugin=caching_sha2_password
+    ports:
+      - 3306:3306
+
+  jeecg-boot-redis:
+    image: redis:5.0
+    ports:
+      - 6379:6379
+    restart: always
+    hostname: jeecg-boot-redis
+    container_name: jeecg-boot-redis
+
+  jeecg-boot-system:
+    build:
+      context: ./jeecg-module-system/jeecg-system-start
+    restart: on-failure
+    depends_on:
+      - jeecg-boot-mysql
+      - jeecg-boot-redis
+    container_name: jeecg-boot-system
+    image: jeecg-boot-system
+    hostname: jeecg-boot-system
+    ports:
+      - 8080:8080

+ 429 - 0
jeecg-boot-base-core/src/main/java/org/jeecg/common/aspect/DictAspect.java

@@ -0,0 +1,429 @@
+package org.jeecg.common.aspect;
+
+import com.alibaba.fastjson.JSON;
+import com.alibaba.fastjson.JSONObject;
+import com.alibaba.fastjson.parser.Feature;
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import lombok.extern.slf4j.Slf4j;
+import org.aspectj.lang.ProceedingJoinPoint;
+import org.aspectj.lang.annotation.Around;
+import org.aspectj.lang.annotation.Aspect;
+import org.aspectj.lang.annotation.Pointcut;
+import org.jeecg.common.api.CommonAPI;
+import org.jeecg.common.api.vo.Result;
+import org.jeecg.common.aspect.annotation.Dict;
+import org.jeecg.common.constant.CommonConstant;
+import org.jeecg.common.system.vo.DictModel;
+import org.jeecg.common.util.oConvertUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Lazy;
+import org.springframework.data.redis.core.RedisTemplate;
+import org.springframework.stereotype.Component;
+import org.springframework.util.StringUtils;
+
+import java.lang.reflect.Field;
+import java.util.*;
+import java.util.concurrent.TimeUnit;
+import java.util.stream.Collectors;
+
+/**
+ * @Description: 字典aop类
+ * @Author: dangzhenghui
+ * @Date: 2019-3-17 21:50
+ * @Version: 1.0
+ */
+@Aspect
+@Component
+@Slf4j
+public class DictAspect {
+    @Lazy
+    @Autowired
+    private CommonAPI commonApi;
+    @Autowired
+    public RedisTemplate redisTemplate;
+
+    @Autowired
+    private ObjectMapper objectMapper;
+
+    private static final String JAVA_UTIL_DATE = "java.util.Date";
+
+    /**
+     * 定义切点Pointcut
+     */
+    @Pointcut("execution(public * org.jeecg.modules..*.*Controller.*(..)) || @annotation(org.jeecg.common.aspect.annotation.AutoDict)")
+    public void excudeService() {
+    }
+
+    @Around("excudeService()")
+    public Object doAround(ProceedingJoinPoint pjp) throws Throwable {
+    	long time1=System.currentTimeMillis();	
+        Object result = pjp.proceed();
+        long time2=System.currentTimeMillis();
+        log.debug("获取JSON数据 耗时:"+(time2-time1)+"ms");
+        long start=System.currentTimeMillis();
+        result=this.parseDictText(result);
+        long end=System.currentTimeMillis();
+        log.debug("注入字典到JSON数据  耗时"+(end-start)+"ms");
+        return result;
+    }
+
+    /**
+     * 本方法针对返回对象为Result 的IPage的分页列表数据进行动态字典注入
+     * 字典注入实现 通过对实体类添加注解@dict 来标识需要的字典内容,字典分为单字典code即可 ,table字典 code table text配合使用与原来jeecg的用法相同
+     * 示例为SysUser   字段为sex 添加了注解@Dict(dicCode = "sex") 会在字典服务立马查出来对应的text 然后在请求list的时候将这个字典text,已字段名称加_dictText形式返回到前端
+     * 例输入当前返回值的就会多出一个sex_dictText字段
+     * {
+     *      sex:1,
+     *      sex_dictText:"男"
+     * }
+     * 前端直接取值sext_dictText在table里面无需再进行前端的字典转换了
+     *  customRender:function (text) {
+     *               if(text==1){
+     *                 return "男";
+     *               }else if(text==2){
+     *                 return "女";
+     *               }else{
+     *                 return text;
+     *               }
+     *             }
+     *             目前vue是这么进行字典渲染到table上的多了就很麻烦了 这个直接在服务端渲染完成前端可以直接用
+     * @param result
+     */
+    private Object parseDictText(Object result) {
+        if (result instanceof Result) {
+            if (((Result) result).getResult() instanceof IPage) {
+                List<JSONObject> items = new ArrayList<>();
+
+                //step.1 筛选出加了 Dict 注解的字段列表
+                List<Field> dictFieldList = new ArrayList<>();
+                // 字典数据列表, key = 字典code,value=数据列表
+                Map<String, List<String>> dataListMap = new HashMap<>(5);
+                //取出结果集
+                List<Object> records=((IPage) ((Result) result).getResult()).getRecords();
+                //update-begin--Author:zyf -- Date:20220606 ----for:【VUEN-1230】 判断是否含有字典注解,没有注解返回-----
+                Boolean hasDict= checkHasDict(records);
+                if(!hasDict){
+                    return result;
+                }
+
+                log.debug(" __ 进入字典翻译切面 DictAspect —— " );
+                //update-end--Author:zyf -- Date:20220606 ----for:【VUEN-1230】 判断是否含有字典注解,没有注解返回-----
+                for (Object record : records) {
+                    String json="{}";
+                    try {
+                        //update-begin--Author:zyf -- Date:20220531 ----for:【issues/#3629】 DictAspect Jackson序列化报错-----
+                        //解决@JsonFormat注解解析不了的问题详见SysAnnouncement类的@JsonFormat
+                         json = objectMapper.writeValueAsString(record);
+                        //update-end--Author:zyf -- Date:20220531 ----for:【issues/#3629】 DictAspect Jackson序列化报错-----
+                    } catch (JsonProcessingException e) {
+                        log.error("json解析失败"+e.getMessage(),e);
+                    }
+                    //update-begin--Author:scott -- Date:20211223 ----for:【issues/3303】restcontroller返回json数据后key顺序错乱 -----
+                    JSONObject item = JSONObject.parseObject(json, Feature.OrderedField);
+                    //update-end--Author:scott -- Date:20211223 ----for:【issues/3303】restcontroller返回json数据后key顺序错乱 -----
+
+                    //update-begin--Author:scott -- Date:20190603 ----for:解决继承实体字段无法翻译问题------
+                    //for (Field field : record.getClass().getDeclaredFields()) {
+                    // 遍历所有字段,把字典Code取出来,放到 map 里
+                    for (Field field : oConvertUtils.getAllFields(record)) {
+                        String value = item.getString(field.getName());
+                        if (oConvertUtils.isEmpty(value)) {
+                            continue;
+                        }
+                    //update-end--Author:scott  -- Date:20190603 ----for:解决继承实体字段无法翻译问题------
+                        if (field.getAnnotation(Dict.class) != null) {
+                            if (!dictFieldList.contains(field)) {
+                                dictFieldList.add(field);
+                            }
+                            String code = field.getAnnotation(Dict.class).dicCode();
+                            String text = field.getAnnotation(Dict.class).dicText();
+                            String table = field.getAnnotation(Dict.class).dictTable();
+
+                            List<String> dataList;
+                            String dictCode = code;
+                            if (!StringUtils.isEmpty(table)) {
+                                dictCode = String.format("%s,%s,%s", table, text, code);
+                            }
+                            dataList = dataListMap.computeIfAbsent(dictCode, k -> new ArrayList<>());
+                            this.listAddAllDeduplicate(dataList, Arrays.asList(value.split(",")));
+                        }
+                        //date类型默认转换string格式化日期
+                        //update-begin--Author:zyf -- Date:20220531 ----for:【issues/#3629】 DictAspect Jackson序列化报错-----
+                        //if (JAVA_UTIL_DATE.equals(field.getType().getName())&&field.getAnnotation(JsonFormat.class)==null&&item.get(field.getName())!=null){
+                            //SimpleDateFormat aDate=new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
+                            // item.put(field.getName(), aDate.format(new Date((Long) item.get(field.getName()))));
+                        //}
+                        //update-end--Author:zyf -- Date:20220531 ----for:【issues/#3629】 DictAspect Jackson序列化报错-----
+                    }
+                    items.add(item);
+                }
+
+                //step.2 调用翻译方法,一次性翻译
+                Map<String, List<DictModel>> translText = this.translateAllDict(dataListMap);
+
+                //step.3 将翻译结果填充到返回结果里
+                for (JSONObject record : items) {
+                    for (Field field : dictFieldList) {
+                        String code = field.getAnnotation(Dict.class).dicCode();
+                        String text = field.getAnnotation(Dict.class).dicText();
+                        String table = field.getAnnotation(Dict.class).dictTable();
+
+                        String fieldDictCode = code;
+                        if (!StringUtils.isEmpty(table)) {
+                            fieldDictCode = String.format("%s,%s,%s", table, text, code);
+                        }
+
+                        String value = record.getString(field.getName());
+                        if (oConvertUtils.isNotEmpty(value)) {
+                            List<DictModel> dictModels = translText.get(fieldDictCode);
+                            if(dictModels==null || dictModels.size()==0){
+                                continue;
+                            }
+
+                            String textValue = this.translDictText(dictModels, value);
+                            log.debug(" 字典Val : " + textValue);
+                            log.debug(" __翻译字典字段__ " + field.getName() + CommonConstant.DICT_TEXT_SUFFIX + ": " + textValue);
+
+                            // TODO-sun 测试输出,待删
+                            log.debug(" ---- dictCode: " + fieldDictCode);
+                            log.debug(" ---- value: " + value);
+                            log.debug(" ----- text: " + textValue);
+                            log.debug(" ---- dictModels: " + JSON.toJSONString(dictModels));
+
+                            record.put(field.getName() + CommonConstant.DICT_TEXT_SUFFIX, textValue);
+                        }
+                    }
+                }
+
+                ((IPage) ((Result) result).getResult()).setRecords(items);
+            }
+
+        }
+        return result;
+    }
+
+    /**
+     * list 去重添加
+     */
+    private void listAddAllDeduplicate(List<String> dataList, List<String> addList) {
+        // 筛选出dataList中没有的数据
+        List<String> filterList = addList.stream().filter(i -> !dataList.contains(i)).collect(Collectors.toList());
+        dataList.addAll(filterList);
+    }
+
+    /**
+     * 一次性把所有的字典都翻译了
+     * 1.  所有的普通数据字典的所有数据只执行一次SQL
+     * 2.  表字典相同的所有数据只执行一次SQL
+     * @param dataListMap
+     * @return
+     */
+    private Map<String, List<DictModel>> translateAllDict(Map<String, List<String>> dataListMap) {
+        // 翻译后的字典文本,key=dictCode
+        Map<String, List<DictModel>> translText = new HashMap<>(5);
+        // 需要翻译的数据(有些可以从redis缓存中获取,就不走数据库查询)
+        List<String> needTranslData = new ArrayList<>();
+        //step.1 先通过redis中获取缓存字典数据
+        for (String dictCode : dataListMap.keySet()) {
+            List<String> dataList = dataListMap.get(dictCode);
+            if (dataList.size() == 0) {
+                continue;
+            }
+            // 表字典需要翻译的数据
+            List<String> needTranslDataTable = new ArrayList<>();
+            for (String s : dataList) {
+                String data = s.trim();
+                if (data.length() == 0) {
+                    continue; //跳过循环
+                }
+                if (dictCode.contains(",")) {
+                    String keyString = String.format("sys:cache:dictTable::SimpleKey [%s,%s]", dictCode, data);
+                    if (redisTemplate.hasKey(keyString)) {
+                        try {
+                            String text = oConvertUtils.getString(redisTemplate.opsForValue().get(keyString));
+                            List<DictModel> list = translText.computeIfAbsent(dictCode, k -> new ArrayList<>());
+                            list.add(new DictModel(data, text));
+                        } catch (Exception e) {
+                            log.warn(e.getMessage());
+                        }
+                    } else if (!needTranslDataTable.contains(data)) {
+                        // 去重添加
+                        needTranslDataTable.add(data);
+                    }
+                } else {
+                    String keyString = String.format("sys:cache:dict::%s:%s", dictCode, data);
+                    if (redisTemplate.hasKey(keyString)) {
+                        try {
+                            String text = oConvertUtils.getString(redisTemplate.opsForValue().get(keyString));
+                            List<DictModel> list = translText.computeIfAbsent(dictCode, k -> new ArrayList<>());
+                            list.add(new DictModel(data, text));
+                        } catch (Exception e) {
+                            log.warn(e.getMessage());
+                        }
+                    } else if (!needTranslData.contains(data)) {
+                        // 去重添加
+                        needTranslData.add(data);
+                    }
+                }
+
+            }
+            //step.2 调用数据库翻译表字典
+            if (needTranslDataTable.size() > 0) {
+                String[] arr = dictCode.split(",");
+                String table = arr[0], text = arr[1], code = arr[2];
+                String values = String.join(",", needTranslDataTable);
+                log.debug("translateDictFromTableByKeys.dictCode:" + dictCode);
+                log.debug("translateDictFromTableByKeys.values:" + values);
+                List<DictModel> texts = commonApi.translateDictFromTableByKeys(table, text, code, values);
+                log.debug("translateDictFromTableByKeys.result:" + texts);
+                List<DictModel> list = translText.computeIfAbsent(dictCode, k -> new ArrayList<>());
+                list.addAll(texts);
+
+                // 做 redis 缓存
+                for (DictModel dict : texts) {
+                    String redisKey = String.format("sys:cache:dictTable::SimpleKey [%s,%s]", dictCode, dict.getValue());
+                    try {
+                        // update-begin-author:taoyan date:20211012 for: 字典表翻译注解缓存未更新 issues/3061
+                        // 保留5分钟
+                        redisTemplate.opsForValue().set(redisKey, dict.getText(), 300, TimeUnit.SECONDS);
+                        // update-end-author:taoyan date:20211012 for: 字典表翻译注解缓存未更新 issues/3061
+                    } catch (Exception e) {
+                        log.warn(e.getMessage(), e);
+                    }
+                }
+            }
+        }
+
+        //step.3 调用数据库进行翻译普通字典
+        if (needTranslData.size() > 0) {
+            List<String> dictCodeList = Arrays.asList(dataListMap.keySet().toArray(new String[]{}));
+            // 将不包含逗号的字典code筛选出来,因为带逗号的是表字典,而不是普通的数据字典
+            List<String> filterDictCodes = dictCodeList.stream().filter(key -> !key.contains(",")).collect(Collectors.toList());
+            String dictCodes = String.join(",", filterDictCodes);
+            String values = String.join(",", needTranslData);
+            log.debug("translateManyDict.dictCodes:" + dictCodes);
+            log.debug("translateManyDict.values:" + values);
+            Map<String, List<DictModel>> manyDict = commonApi.translateManyDict(dictCodes, values);
+            log.debug("translateManyDict.result:" + manyDict);
+            for (String dictCode : manyDict.keySet()) {
+                List<DictModel> list = translText.computeIfAbsent(dictCode, k -> new ArrayList<>());
+                List<DictModel> newList = manyDict.get(dictCode);
+                list.addAll(newList);
+
+                // 做 redis 缓存
+                for (DictModel dict : newList) {
+                    String redisKey = String.format("sys:cache:dict::%s:%s", dictCode, dict.getValue());
+                    try {
+                        redisTemplate.opsForValue().set(redisKey, dict.getText());
+                    } catch (Exception e) {
+                        log.warn(e.getMessage(), e);
+                    }
+                }
+            }
+        }
+        return translText;
+    }
+
+    /**
+     * 字典值替换文本
+     *
+     * @param dictModels
+     * @param values
+     * @return
+     */
+    private String translDictText(List<DictModel> dictModels, String values) {
+        List<String> result = new ArrayList<>();
+
+        // 允许多个逗号分隔,允许传数组对象
+        String[] splitVal = values.split(",");
+        for (String val : splitVal) {
+            String dictText = val;
+            for (DictModel dict : dictModels) {
+                if (val.equals(dict.getValue())) {
+                    dictText = dict.getText();
+                    break;
+                }
+            }
+            result.add(dictText);
+        }
+        return String.join(",", result);
+    }
+
+    /**
+     *  翻译字典文本
+     * @param code
+     * @param text
+     * @param table
+     * @param key
+     * @return
+     */
+    @Deprecated
+    private String translateDictValue(String code, String text, String table, String key) {
+    	if(oConvertUtils.isEmpty(key)) {
+    		return null;
+    	}
+        StringBuffer textValue=new StringBuffer();
+        String[] keys = key.split(",");
+        for (String k : keys) {
+            String tmpValue = null;
+            log.debug(" 字典 key : "+ k);
+            if (k.trim().length() == 0) {
+                continue; //跳过循环
+            }
+            //update-begin--Author:scott -- Date:20210531 ----for: !56 优化微服务应用下存在表字段需要字典翻译时加载缓慢问题-----
+            if (!StringUtils.isEmpty(table)){
+                log.debug("--DictAspect------dicTable="+ table+" ,dicText= "+text+" ,dicCode="+code);
+                String keyString = String.format("sys:cache:dictTable::SimpleKey [%s,%s,%s,%s]",table,text,code,k.trim());
+                    if (redisTemplate.hasKey(keyString)){
+                    try {
+                        tmpValue = oConvertUtils.getString(redisTemplate.opsForValue().get(keyString));
+                    } catch (Exception e) {
+                        log.warn(e.getMessage());
+                    }
+                }else {
+                    tmpValue= commonApi.translateDictFromTable(table,text,code,k.trim());
+                }
+            }else {
+                String keyString = String.format("sys:cache:dict::%s:%s",code,k.trim());
+                if (redisTemplate.hasKey(keyString)){
+                    try {
+                        tmpValue = oConvertUtils.getString(redisTemplate.opsForValue().get(keyString));
+                    } catch (Exception e) {
+                       log.warn(e.getMessage());
+                    }
+                }else {
+                    tmpValue = commonApi.translateDict(code, k.trim());
+                }
+            }
+            //update-end--Author:scott -- Date:20210531 ----for: !56 优化微服务应用下存在表字段需要字典翻译时加载缓慢问题-----
+
+            if (tmpValue != null) {
+                if (!"".equals(textValue.toString())) {
+                    textValue.append(",");
+                }
+                textValue.append(tmpValue);
+            }
+
+        }
+        return textValue.toString();
+    }
+
+    /**
+     * 检测返回结果集中是否包含Dict注解
+     * @param records
+     * @return
+     */
+    private Boolean checkHasDict(List<Object> records){
+        if(oConvertUtils.isNotEmpty(records) && records.size()>0){
+            for (Field field : oConvertUtils.getAllFields(records.get(0))) {
+                if (oConvertUtils.isNotEmpty(field.getAnnotation(Dict.class))) {
+                    return true;
+                }
+            }
+        }
+        return false;
+    }
+
+}

+ 42 - 0
jeecg-boot-base-core/src/main/java/org/jeecg/common/aspect/annotation/Dict.java

@@ -0,0 +1,42 @@
+package org.jeecg.common.aspect.annotation;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * 字典注解
+ * @author: dangzhenghui
+ * @date: 2019年03月17日-下午9:37:16
+ */
+@Target(ElementType.FIELD)
+@Retention(RetentionPolicy.RUNTIME)
+public @interface Dict {
+    /**
+     * 方法描述:  数据code
+     * 作    者: dangzhenghui
+     * 日    期: 2019年03月17日-下午9:37:16
+     *
+     * @return 返回类型: String
+     */
+    String dicCode();
+
+    /**
+     * 方法描述:  数据Text
+     * 作    者: dangzhenghui
+     * 日    期: 2019年03月17日-下午9:37:16
+     *
+     * @return 返回类型: String
+     */
+    String dicText() default "";
+
+    /**
+     * 方法描述: 数据字典表
+     * 作    者: dangzhenghui
+     * 日    期: 2019年03月17日-下午9:37:16
+     *
+     * @return 返回类型: String
+     */
+    String dictTable() default "";
+}

+ 53 - 0
jeecg-boot-base-core/src/main/java/org/jeecg/common/system/vo/DictModel.java

@@ -0,0 +1,53 @@
+package org.jeecg.common.system.vo;
+
+import java.io.Serializable;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.experimental.Accessors;
+
+/**
+ * @Description: 字典类
+ * @author: jeecg-boot
+ */
+@Data
+@EqualsAndHashCode(callSuper = false)
+@Accessors(chain = true)
+@JsonIgnoreProperties(ignoreUnknown = true)
+public class DictModel implements Serializable{
+	private static final long serialVersionUID = 1L;
+
+	public DictModel() {
+	}
+	
+	public DictModel(String value, String text) {
+		this.value = value;
+		this.text = text;
+	}
+	
+	/**
+	 * 字典value
+	 */
+	private String value;
+	/**
+	 * 字典文本
+	 */
+	private String text;
+
+	/**
+	 * 特殊用途: JgEditableTable
+	 * @return
+	 */
+	public String getTitle() {
+		return this.text;
+	}
+	/**
+	 * 特殊用途: vue3 Select组件
+	 */
+	public String getLabel() {
+		return this.text;
+	}
+
+}

+ 19 - 0
jeecg-boot-base-core/src/main/java/org/jeecg/common/system/vo/DictModelMany.java

@@ -0,0 +1,19 @@
+package org.jeecg.common.system.vo;
+
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+/**
+ * 查询多个字典时用到
+ * @author: jeecg-boot
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+public class DictModelMany extends DictModel {
+
+    /**
+     * 字典code,根据多个字段code查询时才用到,用于区分不同的字典选项
+     */
+    private String dictCode;
+
+}

+ 35 - 0
jeecg-boot-base-core/src/main/java/org/jeecg/common/system/vo/DictQuery.java

@@ -0,0 +1,35 @@
+package org.jeecg.common.system.vo;
+
+import lombok.Data;
+
+/**
+ * 字典查询参数实体
+ * @author: jeecg-boot
+ */
+@Data
+public class DictQuery {
+    /**
+     * 表名
+     */
+    private String table;
+    /**
+     * 存储列
+     */
+    private String code;
+
+    /**
+     * 显示列
+     */
+    private String text;
+
+    /**
+     * 关键字查询
+     */
+    private String keyword;
+
+    /**
+     * 存储列的值 用于回显查询
+     */
+    private String codeValue;
+
+}

+ 58 - 0
jeecg-boot-base-core/src/main/java/org/jeecg/common/system/vo/DynamicDataSourceModel.java

@@ -0,0 +1,58 @@
+package org.jeecg.common.system.vo;
+
+import lombok.Data;
+import org.springframework.beans.BeanUtils;
+
+/**
+ * @Description: 数据源
+ * @author: jeecg-boot
+ */
+@Data
+public class DynamicDataSourceModel {
+
+    public DynamicDataSourceModel() {
+
+    }
+
+    public DynamicDataSourceModel(Object dbSource) {
+        if (dbSource != null) {
+            BeanUtils.copyProperties(dbSource, this);
+        }
+    }
+
+    /**
+     * id
+     */
+    private java.lang.String id;
+    /**
+     * 数据源编码
+     */
+    private java.lang.String code;
+    /**
+     * 数据库类型
+     */
+    private java.lang.String dbType;
+    /**
+     * 驱动类
+     */
+    private java.lang.String dbDriver;
+    /**
+     * 数据源地址
+     */
+    private java.lang.String dbUrl;
+
+//    /**
+//     * 数据库名称
+//     */
+//    private java.lang.String dbName;
+
+    /**
+     * 用户名
+     */
+    private java.lang.String dbUsername;
+    /**
+     * 密码
+     */
+    private java.lang.String dbPassword;
+
+}

+ 344 - 0
jeecg-boot-base-core/src/main/java/org/jeecg/common/util/dynamic/db/DynamicDBUtil.java

@@ -0,0 +1,344 @@
+package org.jeecg.common.util.dynamic.db;
+
+import com.alibaba.druid.pool.DruidDataSource;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.lang3.ArrayUtils;
+import org.jeecg.common.exception.JeecgBootException;
+import org.jeecg.common.exception.JeecgBootException;
+import org.jeecg.common.system.vo.DynamicDataSourceModel;
+import org.jeecg.common.util.ReflectHelper;
+import org.jeecg.common.util.oConvertUtils;
+import org.springframework.jdbc.core.JdbcTemplate;
+import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;
+
+import javax.sql.DataSource;
+import java.sql.SQLException;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Spring JDBC 实时数据库访问
+ *
+ * @author chenguobin
+ * @version 1.0
+ * @date 2014-09-05
+ */
+@Slf4j
+public class DynamicDBUtil {
+
+    /**
+     * 获取数据源【最底层方法,不要随便调用】
+     *
+     * @param dbSource
+     * @return
+     */
+    private static DruidDataSource getJdbcDataSource(final DynamicDataSourceModel dbSource) {
+        DruidDataSource dataSource = new DruidDataSource();
+
+        String driverClassName = dbSource.getDbDriver();
+        String url = dbSource.getDbUrl();
+        String dbUser = dbSource.getDbUsername();
+        String dbPassword = dbSource.getDbPassword();
+        dataSource.setDriverClassName(driverClassName);
+        dataSource.setUrl(url);
+        //dataSource.setValidationQuery("SELECT 1 FROM DUAL");
+        dataSource.setTestWhileIdle(true);
+        dataSource.setTestOnBorrow(false);
+        dataSource.setTestOnReturn(false);
+        dataSource.setBreakAfterAcquireFailure(true);
+        dataSource.setConnectionErrorRetryAttempts(0);
+        dataSource.setUsername(dbUser);
+        dataSource.setMaxWait(30000);
+        dataSource.setPassword(dbPassword);
+
+        log.info("******************************************");
+        log.info("*                                        *");
+        log.info("*====【"+dbSource.getCode()+"】=====Druid连接池已启用 ====*");
+        log.info("*                                        *");
+        log.info("******************************************");
+        return dataSource;
+    }
+
+    /**
+     * 通过 dbKey ,获取数据源
+     *
+     * @param dbKey
+     * @return
+     */
+    public static DruidDataSource getDbSourceByDbKey(final String dbKey) {
+        //获取多数据源配置
+        DynamicDataSourceModel dbSource = DataSourceCachePool.getCacheDynamicDataSourceModel(dbKey);
+        //先判断缓存中是否存在数据库链接
+        DruidDataSource cacheDbSource = DataSourceCachePool.getCacheBasicDataSource(dbKey);
+        if (cacheDbSource != null && !cacheDbSource.isClosed()) {
+            log.debug("--------getDbSourceBydbKey------------------从缓存中获取DB连接-------------------");
+            return cacheDbSource;
+        } else {
+            DruidDataSource dataSource = getJdbcDataSource(dbSource);
+            if(dataSource!=null && dataSource.isEnable()){
+                DataSourceCachePool.putCacheBasicDataSource(dbKey, dataSource);
+            }else{
+                throw new JeecgBootException("动态数据源连接失败,dbKey:"+dbKey);
+            }
+            log.info("--------getDbSourceBydbKey------------------创建DB数据库连接-------------------");
+            return dataSource;
+        }
+    }
+
+    /**
+     * 关闭数据库连接池
+     *
+     * @param dbKey
+     * @return
+     */
+    public static void closeDbKey(final String dbKey) {
+        DruidDataSource dataSource = getDbSourceByDbKey(dbKey);
+        try {
+            if (dataSource != null && !dataSource.isClosed()) {
+                dataSource.getConnection().commit();
+                dataSource.getConnection().close();
+                dataSource.close();
+            }
+        } catch (SQLException e) {
+            e.printStackTrace();
+        }
+    }
+
+
+    private static JdbcTemplate getJdbcTemplate(String dbKey) {
+        DruidDataSource dataSource = getDbSourceByDbKey(dbKey);
+        return new JdbcTemplate(dataSource);
+    }
+
+    /**
+     * 根据数据源获取NamedParameterJdbcTemplate
+     * @param dbKey
+     * @return
+     */
+    private static NamedParameterJdbcTemplate getNamedParameterJdbcTemplate(String dbKey) {
+        DruidDataSource dataSource = getDbSourceByDbKey(dbKey);
+        return new NamedParameterJdbcTemplate(dataSource);
+    }
+
+    /**
+     * Executes the SQL statement in this <code>PreparedStatement</code> object,
+     * which must be an SQL Data Manipulation Language (DML) statement, such as <code>INSERT</code>, <code>UPDATE</code> or
+     * <code>DELETE</code>; or an SQL statement that returns nothing,
+     * such as a DDL statement.
+     */
+    public static int update(final String dbKey, String sql, Object... param) {
+        int effectCount;
+        JdbcTemplate jdbcTemplate = getJdbcTemplate(dbKey);
+        if (ArrayUtils.isEmpty(param)) {
+            effectCount = jdbcTemplate.update(sql);
+        } else {
+            effectCount = jdbcTemplate.update(sql, param);
+        }
+        return effectCount;
+    }
+
+    /**
+     * 支持miniDao语法操作的Update
+     *
+     * @param dbKey 数据源标识
+     * @param sql   执行sql语句,sql支持minidao语法逻辑
+     * @param data  sql语法中需要判断的数据及sql拼接注入中需要的数据
+     * @return
+     */
+    public static int updateByHash(final String dbKey, String sql, HashMap<String, Object> data) {
+        int effectCount;
+        JdbcTemplate jdbcTemplate = getJdbcTemplate(dbKey);
+        //根据模板获取sql
+        sql = FreemarkerParseFactory.parseTemplateContent(sql, data);
+        NamedParameterJdbcTemplate namedParameterJdbcTemplate = new NamedParameterJdbcTemplate(jdbcTemplate.getDataSource());
+        effectCount = namedParameterJdbcTemplate.update(sql, data);
+        return effectCount;
+    }
+
+    public static Object findOne(final String dbKey, String sql, Object... param) {
+        List<Map<String, Object>> list;
+        list = findList(dbKey, sql, param);
+        if (oConvertUtils.listIsEmpty(list)) {
+            log.error("Except one, but not find actually");
+            return null;
+        }
+        if (list.size() > 1) {
+            log.error("Except one, but more than one actually");
+        }
+        return list.get(0);
+    }
+
+    /**
+     * 支持miniDao语法操作的查询 返回HashMap
+     *
+     * @param dbKey 数据源标识
+     * @param sql   执行sql语句,sql支持minidao语法逻辑
+     * @param data  sql语法中需要判断的数据及sql拼接注入中需要的数据
+     * @return
+     */
+    public static Object findOneByHash(final String dbKey, String sql, HashMap<String, Object> data) {
+        List<Map<String, Object>> list;
+        list = findListByHash(dbKey, sql, data);
+        if (oConvertUtils.listIsEmpty(list)) {
+            log.error("Except one, but not find actually");
+        }
+        if (list.size() > 1) {
+            log.error("Except one, but more than one actually");
+        }
+        return list.get(0);
+    }
+
+    /**
+     * 直接sql查询 根据clazz返回单个实例
+     *
+     * @param dbKey 数据源标识
+     * @param sql   执行sql语句
+     * @param clazz 返回实例的Class
+     * @param param
+     * @return
+     */
+    @SuppressWarnings("unchecked")
+    public static <T> Object findOne(final String dbKey, String sql, Class<T> clazz, Object... param) {
+        Map<String, Object> map = (Map<String, Object>) findOne(dbKey, sql, param);
+        return ReflectHelper.setAll(clazz, map);
+    }
+
+    /**
+     * 支持miniDao语法操作的查询 返回单个实例
+     *
+     * @param dbKey 数据源标识
+     * @param sql   执行sql语句,sql支持minidao语法逻辑
+     * @param clazz 返回实例的Class
+     * @param data  sql语法中需要判断的数据及sql拼接注入中需要的数据
+     * @return
+     */
+    @SuppressWarnings("unchecked")
+    public static <T> Object findOneByHash(final String dbKey, String sql, Class<T> clazz, HashMap<String, Object> data) {
+        Map<String, Object> map = (Map<String, Object>) findOneByHash(dbKey, sql, data);
+        return ReflectHelper.setAll(clazz, map);
+    }
+
+    public static List<Map<String, Object>> findList(final String dbKey, String sql, Object... param) {
+        List<Map<String, Object>> list;
+        JdbcTemplate jdbcTemplate = getJdbcTemplate(dbKey);
+
+        if (ArrayUtils.isEmpty(param)) {
+            list = jdbcTemplate.queryForList(sql);
+        } else {
+            list = jdbcTemplate.queryForList(sql, param);
+        }
+        return list;
+    }
+
+    /**
+     * 查询数量
+     * @param dbKey
+     * @param sql
+     * @param param
+     * @return
+     */
+    public static Map<String, Object> queryCount(String dbKey, String sql, Map<String, Object> param){
+        NamedParameterJdbcTemplate npJdbcTemplate = getNamedParameterJdbcTemplate(dbKey);
+        return npJdbcTemplate.queryForMap(sql, param);
+    }
+
+    /**
+     * 查询列表数据
+     * @param dbKey
+     * @param sql
+     * @param param
+     * @return
+     */
+    public static List<Map<String, Object>> findListByNamedParam(final String dbKey, String sql, Map<String, Object> param) {
+        NamedParameterJdbcTemplate npJdbcTemplate = getNamedParameterJdbcTemplate(dbKey);
+        List<Map<String, Object>> list = npJdbcTemplate.queryForList(sql, param);
+        return list;
+    }
+
+    /**
+     * 支持miniDao语法操作的查询
+     *
+     * @param dbKey 数据源标识
+     * @param sql   执行sql语句,sql支持minidao语法逻辑
+     * @param data  sql语法中需要判断的数据及sql拼接注入中需要的数据
+     * @return
+     */
+    public static List<Map<String, Object>> findListByHash(final String dbKey, String sql, HashMap<String, Object> data) {
+        List<Map<String, Object>> list;
+        JdbcTemplate jdbcTemplate = getJdbcTemplate(dbKey);
+        //根据模板获取sql
+        sql = FreemarkerParseFactory.parseTemplateContent(sql, data);
+        NamedParameterJdbcTemplate namedParameterJdbcTemplate = new NamedParameterJdbcTemplate(jdbcTemplate.getDataSource());
+        list = namedParameterJdbcTemplate.queryForList(sql, data);
+        return list;
+    }
+
+    /**
+     * 此方法只能返回单列,不能返回实体类
+     * @param dbKey 数据源的key
+     * @param sql sal
+     * @param clazz 类
+     * @param param 参数
+     * @param <T>
+     * @return
+     */
+    public static <T> List<T> findList(final String dbKey, String sql, Class<T> clazz, Object... param) {
+        List<T> list;
+        JdbcTemplate jdbcTemplate = getJdbcTemplate(dbKey);
+
+        if (ArrayUtils.isEmpty(param)) {
+            list = jdbcTemplate.queryForList(sql, clazz);
+        } else {
+            list = jdbcTemplate.queryForList(sql, clazz, param);
+        }
+        return list;
+    }
+
+    /**
+     * 支持miniDao语法操作的查询 返回单列数据list
+     *
+     * @param dbKey 数据源标识
+     * @param sql   执行sql语句,sql支持minidao语法逻辑
+     * @param clazz 类型Long、String等
+     * @param data  sql语法中需要判断的数据及sql拼接注入中需要的数据
+     * @return
+     */
+    public static <T> List<T> findListByHash(final String dbKey, String sql, Class<T> clazz, HashMap<String, Object> data) {
+        List<T> list;
+        JdbcTemplate jdbcTemplate = getJdbcTemplate(dbKey);
+        //根据模板获取sql
+        sql = FreemarkerParseFactory.parseTemplateContent(sql, data);
+        NamedParameterJdbcTemplate namedParameterJdbcTemplate = new NamedParameterJdbcTemplate(jdbcTemplate.getDataSource());
+        list = namedParameterJdbcTemplate.queryForList(sql, data, clazz);
+        return list;
+    }
+
+    /**
+     * 直接sql查询 返回实体类列表
+     *
+     * @param dbKey 数据源标识
+     * @param sql   执行sql语句,sql支持 minidao 语法逻辑
+     * @param clazz 返回实体类列表的class
+     * @param param sql拼接注入中需要的数据
+     * @return
+     */
+    public static <T> List<T> findListEntities(final String dbKey, String sql, Class<T> clazz, Object... param) {
+        List<Map<String, Object>> queryList = findList(dbKey, sql, param);
+        return ReflectHelper.transList2Entrys(queryList, clazz);
+    }
+
+    /**
+     * 支持miniDao语法操作的查询 返回实体类列表
+     *
+     * @param dbKey 数据源标识
+     * @param sql   执行sql语句,sql支持minidao语法逻辑
+     * @param clazz 返回实体类列表的class
+     * @param data  sql语法中需要判断的数据及sql拼接注入中需要的数据
+     * @return
+     */
+    public static <T> List<T> findListEntitiesByHash(final String dbKey, String sql, Class<T> clazz, HashMap<String, Object> data) {
+        List<Map<String, Object>> queryList = findListByHash(dbKey, sql, data);
+        return ReflectHelper.transList2Entrys(queryList, clazz);
+    }
+}

+ 87 - 0
jeecg-boot-base-core/src/main/java/org/jeecg/config/DruidConfig.java

@@ -0,0 +1,87 @@
+package org.jeecg.config;
+
+import java.io.IOException;
+
+import javax.servlet.*;
+
+import org.springframework.boot.autoconfigure.AutoConfigureAfter;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
+import org.springframework.boot.web.servlet.FilterRegistrationBean;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+import com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceAutoConfigure;
+import com.alibaba.druid.spring.boot.autoconfigure.properties.DruidStatProperties;
+import com.alibaba.druid.util.Utils;
+
+/**
+ * @Description: DruidConfig配置类
+ * @author: jeecg-boot
+ */
+@Configuration
+@AutoConfigureAfter(DruidDataSourceAutoConfigure.class)
+public class DruidConfig {
+
+    /**
+     * 带有广告的common.js全路径,druid-1.1.14
+     */
+    private static final String FILE_PATH = "support/http/resources/js/common.js";
+    /**
+     * 原始脚本,触发构建广告的语句
+     */
+    private static final String ORIGIN_JS = "this.buildFooter();";
+    /**
+     * 替换后的脚本
+     */
+    private static final String NEW_JS = "//this.buildFooter();";
+
+    /**
+     * 去除Druid监控页面的广告
+     *
+     * @param properties DruidStatProperties属性集合
+     * @return {@link FilterRegistrationBean}
+     */
+    @Bean
+    @ConditionalOnWebApplication
+    @ConditionalOnProperty(name = "spring.datasource.druid.stat-view-servlet.enabled", havingValue = "true")
+    public FilterRegistrationBean<RemoveAdFilter> removeDruidAdFilter(
+            DruidStatProperties properties) throws IOException {
+        // 获取web监控页面的参数
+        DruidStatProperties.StatViewServlet config = properties.getStatViewServlet();
+        // 提取common.js的配置路径
+        String pattern = config.getUrlPattern() != null ? config.getUrlPattern() : "/druid/*";
+        String commonJsPattern = pattern.replaceAll("\\*", "js/common.js");
+        // 获取common.js
+        String text = Utils.readFromResource(FILE_PATH);
+        // 屏蔽 this.buildFooter(); 不构建广告
+        final String newJs = text.replace(ORIGIN_JS, NEW_JS);
+        FilterRegistrationBean<RemoveAdFilter> registration = new FilterRegistrationBean<>();
+        registration.setFilter(new RemoveAdFilter(newJs));
+        registration.addUrlPatterns(commonJsPattern);
+        return registration;
+    }
+
+    /**
+     * 删除druid的广告过滤器
+     *
+     * @author BBF
+     */
+    private class RemoveAdFilter implements Filter {
+
+        private final String newJs;
+
+        public RemoveAdFilter(String newJs) {
+            this.newJs = newJs;
+        }
+
+        @Override
+        public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
+                throws IOException, ServletException {
+            chain.doFilter(request, response);
+            // 重置缓冲区,响应头不会被重置
+            response.resetBuffer();
+            response.getWriter().write(newJs);
+        }
+    }
+}

+ 55 - 0
jeecg-boot-base-core/src/main/java/org/jeecg/config/mybatis/interceptor/DynamicDatasourceInterceptor.java

@@ -0,0 +1,55 @@
+package org.jeecg.config.mybatis.interceptor;
+
+import com.baomidou.dynamic.datasource.toolkit.DynamicDataSourceContextHolder;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.web.servlet.HandlerInterceptor;
+import org.springframework.web.servlet.ModelAndView;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+/**
+ * 动态数据源切换拦截器
+ *
+ * 测试:拦截参数,自动切换数据源
+ * 未来规划:后面通过此机制,实现多租户切换数据源功能
+ * @author zyf
+ */
+@Slf4j
+public class DynamicDatasourceInterceptor implements HandlerInterceptor {
+
+    /**
+     * 在请求处理之前进行调用(Controller方法调用之前)
+     */
+    @Override
+    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
+        String requestURI = request.getRequestURI();
+        log.info("经过多数据源Interceptor,当前路径是{}", requestURI);
+        //获取动态数据源名称
+        String dsName = request.getParameter("dsName");
+        String dsKey = "master";
+        if (StringUtils.isNotEmpty(dsName)) {
+            dsKey = dsName;
+        }
+        DynamicDataSourceContextHolder.push(dsKey);
+        return true;
+    }
+
+    /**
+     * 请求处理之后进行调用,但是在视图被渲染之前(Controller方法调用之后)
+     */
+    @Override
+    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) {
+
+    }
+
+    /**
+     * 在整个请求结束之后被调用,也就是在DispatcherServlet 渲染了对应的视图之后执行(主要是用于进行资源清理工作)
+     */
+    @Override
+    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
+        DynamicDataSourceContextHolder.clear();
+    }
+
+}

+ 15 - 0
jeecg-boot-base-core/src/main/java/org/jeecg/config/vo/DomainUrl.java

@@ -0,0 +1,15 @@
+package org.jeecg.config.vo;
+
+import lombok.Data;
+
+/**
+ * @Author taoYan
+ * @Date 2022/7/5 21:16
+ **/
+@Data
+public class DomainUrl {
+
+    private String pc;
+
+    private String app;
+}

BIN
jeecg-boot-base-core/target/classes/org/jeecg/common/aspect/DictAspect.class


BIN
jeecg-boot-base-core/target/classes/org/jeecg/common/aspect/annotation/Dict.class


BIN
jeecg-boot-base-core/target/classes/org/jeecg/common/system/vo/DictModel.class


BIN
jeecg-boot-base-core/target/classes/org/jeecg/common/system/vo/DictModelMany.class


BIN
jeecg-boot-base-core/target/classes/org/jeecg/common/system/vo/DictQuery.class


BIN
jeecg-boot-base-core/target/classes/org/jeecg/common/system/vo/DynamicDataSourceModel.class


BIN
jeecg-boot-base-core/target/classes/org/jeecg/common/util/dynamic/db/DynamicDBUtil.class


BIN
jeecg-boot-base-core/target/classes/org/jeecg/config/DruidConfig.class


BIN
jeecg-boot-base-core/target/classes/org/jeecg/config/mybatis/interceptor/DynamicDatasourceInterceptor.class


BIN
jeecg-boot-base-core/target/classes/org/jeecg/config/vo/DomainUrl.class


File diff suppressed because it is too large
+ 0 - 0
jeecg-module-demo/src/main/java/org/jeecg/modules/demo/mock/vxe/json/dlglong.json


+ 412 - 0
jeecg-module-demo/src/main/java/org/jeecg/modules/dlglong/controller/DlMockController.java

@@ -0,0 +1,412 @@
+package org.jeecg.modules.dlglong.controller;
+
+import com.alibaba.fastjson.JSON;
+import com.alibaba.fastjson.JSONArray;
+import com.alibaba.fastjson.JSONObject;
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.io.IOUtils;
+import org.apache.commons.lang3.StringUtils;
+import org.jeecg.common.api.vo.Result;
+import org.jeecg.common.system.query.MatchTypeEnum;
+import org.jeecg.common.system.query.QueryCondition;
+import org.jeecg.common.system.query.QueryGenerator;
+import org.jeecg.common.constant.VxeSocketConst;
+import org.jeecg.modules.demo.mock.vxe.websocket.VxeSocket;
+import org.jeecg.modules.dlglong.entity.MockEntity;
+import org.springframework.web.bind.annotation.*;
+
+import javax.servlet.http.HttpServletRequest;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URLDecoder;
+import java.util.*;
+
+/**
+ * @Description: DlMockController
+ * @author: jeecg-boot
+ */
+@Slf4j
+@RestController
+@RequestMapping("/mock/dlglong")
+public class DlMockController {
+
+    /**
+     * 模拟更改状态
+     *
+     * @param id
+     * @param status
+     * @return
+     */
+    @GetMapping("/change1")
+    public Result mockChange1(@RequestParam("id") String id, @RequestParam("status") String status) {
+        /* id 为 行的id(rowId),只要获取到rowId,那么只需要调用 VXESocket.sendMessageToAll() 即可 */
+
+        // 封装行数据
+        JSONObject rowData = new JSONObject();
+        // 这个字段就是要更改的行数据ID
+        rowData.put("id", id);
+        // 这个字段就是要更改的列的key和具体的值
+        rowData.put("status", status);
+        // 模拟更改数据
+        this.mockChange(rowData);
+
+        return Result.ok();
+    }
+
+    /**
+     * 模拟更改拖轮状态
+     *
+     * @param id
+     * @param tugStatus
+     * @return
+     */
+    @GetMapping("/change2")
+    public Result mockChange2(@RequestParam("id") String id, @RequestParam("tug_status") String tugStatus) {
+        /* id 为 行的id(rowId),只要获取到rowId,那么只需要调用 VXESocket.sendMessageToAll() 即可 */
+
+        // 封装行数据
+        JSONObject rowData = new JSONObject();
+        // 这个字段就是要更改的行数据ID
+        rowData.put("id", id);
+        // 这个字段就是要更改的列的key和具体的值
+        JSONObject status = JSON.parseObject(tugStatus);
+        rowData.put("tug_status", status);
+        // 模拟更改数据
+        this.mockChange(rowData);
+
+        return Result.ok();
+    }
+
+    /**
+     * 模拟更改进度条状态
+     *
+     * @param id
+     * @param progress
+     * @return
+     */
+    @GetMapping("/change3")
+    public Result mockChange3(@RequestParam("id") String id, @RequestParam("progress") String progress) {
+        /* id 为 行的id(rowId),只要获取到rowId,那么只需要调用 VXESocket.sendMessageToAll() 即可 */
+
+        // 封装行数据
+        JSONObject rowData = new JSONObject();
+        // 这个字段就是要更改的行数据ID
+        rowData.put("id", id);
+        // 这个字段就是要更改的列的key和具体的值
+        rowData.put("progress", progress);
+        // 模拟更改数据
+        this.mockChange(rowData);
+
+        return Result.ok();
+    }
+
+    private void mockChange(JSONObject rowData) {
+        // 封装socket数据
+        JSONObject socketData = new JSONObject();
+        // 这里的 socketKey 必须要和调度计划页面上写的 socketKey 属性保持一致
+        socketData.put("socketKey", "page-dispatch");
+        // 这里的 args 必须得是一个数组,下标0是行数据,下标1是caseId,一般不用传
+        socketData.put("args", new Object[]{rowData, ""});
+        // 封装消息字符串,这里的 type 必须是 VXESocketConst.TYPE_UVT
+        String message = VxeSocket.packageMessage(VxeSocketConst.TYPE_UVT, socketData);
+        // 调用 sendMessageToAll 发送给所有在线的用户
+        VxeSocket.sendMessageToAll(message);
+    }
+
+    /**
+     * 模拟更改【大船待审】状态
+     *
+     * @param status
+     * @return
+     */
+    @GetMapping("/change4")
+    public Result mockChange4(@RequestParam("status") String status) {
+        // 封装socket数据
+        JSONObject socketData = new JSONObject();
+        // 这里的 key 是前端注册时使用的key,必须保持一致
+        socketData.put("key", "dispatch-dcds-status");
+        // 这里的 args 必须得是一个数组,每一位都是注册方法的参数,按顺序传递
+        socketData.put("args", new Object[]{status});
+
+        // 封装消息字符串,这里的 type 必须是 VXESocketConst.TYPE_UVT
+        String message = VxeSocket.packageMessage(VxeSocketConst.TYPE_CSD, socketData);
+        // 调用 sendMessageToAll 发送给所有在线的用户
+        VxeSocket.sendMessageToAll(message);
+
+        return Result.ok();
+    }
+
+    /**
+     * 【模拟】即时保存单行数据
+     *
+     * @param rowData 行数据,实际使用时可以替换成一个实体类
+     */
+    @PutMapping("/immediateSaveRow")
+    public Result mockImmediateSaveRow(@RequestBody JSONObject rowData) throws Exception {
+        System.out.println("即时保存.rowData:" + rowData.toJSONString());
+        // 延时1.5秒,模拟网慢堵塞真实感
+        Thread.sleep(500);
+        return Result.ok();
+    }
+
+    /**
+     * 【模拟】即时保存整个表格的数据
+     *
+     * @param tableData 表格数据(实际使用时可以替换成一个List实体类)
+     */
+    @PostMapping("/immediateSaveAll")
+    public Result mockImmediateSaveAll(@RequestBody JSONArray tableData) throws Exception {
+        // 【注】:
+        // 1、tableData里包含该页所有的数据
+        // 2、如果你实现了“即时保存”,那么除了新增的数据,其他的都是已经保存过的了,
+        //    不需要再进行一次update操作了,所以可以在前端传数据的时候就遍历判断一下,
+        //    只传新增的数据给后台insert即可,否者将会造成性能上的浪费。
+        // 3、新增的行是没有id的,通过这一点,就可以判断是否是新增的数据
+
+        System.out.println("即时保存.tableData:" + tableData.toJSONString());
+        // 延时1.5秒,模拟网慢堵塞真实感
+        Thread.sleep(1000);
+        return Result.ok();
+    }
+
+    /**
+     * 获取模拟数据
+     *
+     * @param pageNo   页码
+     * @param pageSize 页大小
+     * @param parentId 父ID,不传则查询顶级
+     * @return
+     */
+    @GetMapping("/getData")
+    public Result getMockData(
+            @RequestParam(name = "pageNo", defaultValue = "1") Integer pageNo,
+            @RequestParam(name = "pageSize", defaultValue = "10") Integer pageSize,
+            // 父级id,根据父级id查询子级,如果为空则查询顶级
+            @RequestParam(name = "parentId", required = false) String parentId
+    ) {
+        // 模拟JSON数据路径
+        String path = "classpath:org/jeecg/modules/dlglong/json/dlglong.json";
+        // 读取JSON数据
+        JSONArray dataList = readJsonData(path);
+        if (dataList == null) {
+            return Result.error("读取数据失败!");
+        }
+        IPage<JSONObject> page = this.queryDataPage(dataList, parentId, pageNo, pageSize);
+        return Result.ok(page);
+    }
+
+    /**
+     * 获取模拟“调度计划”页面的数据
+     *
+     * @param pageNo   页码
+     * @param pageSize 页大小
+     * @param parentId 父ID,不传则查询顶级
+     * @return
+     */
+    @GetMapping("/getDdjhData")
+    public Result getMockDdjhData(
+            // SpringMVC 会自动将参数注入到实体里
+            MockEntity mockEntity,
+            @RequestParam(name = "pageNo", defaultValue = "1") Integer pageNo,
+            @RequestParam(name = "pageSize", defaultValue = "10") Integer pageSize,
+            // 父级id,根据父级id查询子级,如果为空则查询顶级
+            @RequestParam(name = "parentId", required = false) String parentId,
+            @RequestParam(name = "status", required = false) String status,
+            // 高级查询条件
+            @RequestParam(name = "superQueryParams", required = false) String superQueryParams,
+            // 高级查询模式
+            @RequestParam(name = "superQueryMatchType", required = false) String superQueryMatchType,
+            HttpServletRequest request
+    ) {
+        // 获取查询条件(前台传递的查询参数)
+        Map<String, String[]> parameterMap = request.getParameterMap();
+        // 遍历输出到控制台
+        System.out.println("\ngetDdjhData - 普通查询条件:");
+        for (String key : parameterMap.keySet()) {
+            System.out.println("-- " + key + ": " + JSON.toJSONString(parameterMap.get(key)));
+        }
+        // 输出高级查询
+        try {
+            System.out.println("\ngetDdjhData - 高级查询条件:");
+            // 高级查询模式
+            MatchTypeEnum matchType = MatchTypeEnum.getByValue(superQueryMatchType);
+            if (matchType == null) {
+                System.out.println("-- 高级查询模式:不识别(" + superQueryMatchType + ")");
+            } else {
+                System.out.println("-- 高级查询模式:" + matchType.getValue());
+            }
+            superQueryParams = URLDecoder.decode(superQueryParams, "UTF-8");
+            List<QueryCondition> conditions = JSON.parseArray(superQueryParams, QueryCondition.class);
+            if (conditions != null) {
+                for (QueryCondition condition : conditions) {
+                    System.out.println("-- " + JSON.toJSONString(condition));
+                }
+            } else {
+                System.out.println("-- 没有传递任何高级查询条件");
+            }
+            System.out.println();
+        } catch (Exception e) {
+            log.error("-- 高级查询操作失败:" + superQueryParams, e);
+            e.printStackTrace();
+        }
+
+        /* 注:实际使用中不用写上面那种繁琐的代码,这里只是为了直观的输出到控制台里而写的示例,
+              使用下面这种写法更简洁方便 */
+
+        // 封装成 MyBatisPlus 能识别的 QueryWrapper,可以直接使用这个对象进行SQL筛选条件拼接
+        // 这个方法也会自动封装高级查询条件,但是高级查询参数名必须是superQueryParams和superQueryMatchType
+        QueryWrapper<MockEntity> queryWrapper = QueryGenerator.initQueryWrapper(mockEntity, parameterMap);
+        System.out.println("queryWrapper: " + queryWrapper.getCustomSqlSegment());
+
+        // 模拟JSON数据路径
+        String path = "classpath:org/jeecg/modules/dlglong/json/ddjh.json";
+        String statusValue = "8";
+        if (statusValue.equals(status)) {
+            path = "classpath:org/jeecg/modules/dlglong/json/ddjh_s8.json";
+        }
+        // 读取JSON数据
+        JSONArray dataList = readJsonData(path);
+        if (dataList == null) {
+            return Result.error("读取数据失败!");
+        }
+
+        IPage<JSONObject> page = this.queryDataPage(dataList, parentId, pageNo, pageSize);
+        // 逐行查询子表数据,用于计算拖轮状态
+        List<JSONObject> records = page.getRecords();
+        for (JSONObject record : records) {
+            Map<String, Integer> tugStatusMap = new HashMap<>(5);
+            String id = record.getString("id");
+            // 查询出主表的拖轮
+            String tugMain = record.getString("tug");
+            // 判断是否有值
+            if (StringUtils.isNotBlank(tugMain)) {
+                // 拖轮根据分号分割
+                String[] tugs = tugMain.split(";");
+                // 查询子表数据
+                List<JSONObject> subRecords = this.queryDataPage(dataList, id, null, null).getRecords();
+                // 遍历子表和拖轮数据,找出进行计算反推拖轮状态
+                for (JSONObject subData : subRecords) {
+                    String subTug = subData.getString("tug");
+                    if (StringUtils.isNotBlank(subTug)) {
+                        for (String tug : tugs) {
+                            if (tug.equals(subTug)) {
+                                // 计算拖轮状态逻辑
+                                int statusCode = 0;
+
+                                /* 如果有发船时间、作业开始时间、作业结束时间、回船时间,则主表中的拖轮列中的每个拖轮背景色要即时变色 */
+
+                                // 有发船时间,状态 +1
+                                String departureTime = subData.getString("departure_time");
+                                if (StringUtils.isNotBlank(departureTime)) {
+                                    statusCode += 1;
+                                }
+                                // 有作业开始时间,状态 +1
+                                String workBeginTime = subData.getString("work_begin_time");
+                                if (StringUtils.isNotBlank(workBeginTime)) {
+                                    statusCode += 1;
+                                }
+                                // 有作业结束时间,状态 +1
+                                String workEndTime = subData.getString("work_end_time");
+                                if (StringUtils.isNotBlank(workEndTime)) {
+                                    statusCode += 1;
+                                }
+                                // 有回船时间,状态 +1
+                                String returnTime = subData.getString("return_time");
+                                if (StringUtils.isNotBlank(returnTime)) {
+                                    statusCode += 1;
+                                }
+                                // 保存拖轮状态,key是拖轮的值,value是状态,前端根据不同的状态码,显示不同的颜色,这个颜色也可以后台计算完之后返回给前端直接使用
+                                tugStatusMap.put(tug, statusCode);
+                                break;
+                            }
+                        }
+                    }
+                }
+            }
+            // 新加一个字段用于保存拖轮状态,不要直接覆盖原来的,这个字段可以不保存到数据库里
+            record.put("tug_status", tugStatusMap);
+        }
+        page.setRecords(records);
+        return Result.ok(page);
+    }
+
+    /**
+     * 模拟查询数据,可以根据父ID查询,可以分页
+     *
+     * @param dataList 数据列表
+     * @param parentId 父ID
+     * @param pageNo   页码
+     * @param pageSize 页大小
+     * @return
+     */
+    private IPage<JSONObject> queryDataPage(JSONArray dataList, String parentId, Integer pageNo, Integer pageSize) {
+        // 根据父级id查询子级
+        JSONArray dataDb = dataList;
+        if (StringUtils.isNotBlank(parentId)) {
+            JSONArray results = new JSONArray();
+            List<String> parentIds = Arrays.asList(parentId.split(","));
+            this.queryByParentId(dataDb, parentIds, results);
+            dataDb = results;
+        }
+        // 模拟分页(实际中应用SQL自带的分页)
+        List<JSONObject> records = new ArrayList<>();
+        IPage<JSONObject> page;
+        long beginIndex, endIndex;
+        // 如果任意一个参数为null,则不分页
+        if (pageNo == null || pageSize == null) {
+            page = new Page<>(0, dataDb.size());
+            beginIndex = 0;
+            endIndex = dataDb.size();
+        } else {
+            page = new Page<>(pageNo, pageSize);
+            beginIndex = page.offset();
+            endIndex = page.offset() + page.getSize();
+        }
+        for (long i = beginIndex; (i < endIndex && i < dataDb.size()); i++) {
+            JSONObject data = dataDb.getJSONObject((int) i);
+            data = JSON.parseObject(data.toJSONString());
+            // 不返回 children
+            data.remove("children");
+            records.add(data);
+        }
+        page.setRecords(records);
+        page.setTotal(dataDb.size());
+        return page;
+    }
+
+    private void queryByParentId(JSONArray dataList, List<String> parentIds, JSONArray results) {
+        for (int i = 0; i < dataList.size(); i++) {
+            JSONObject data = dataList.getJSONObject(i);
+            JSONArray children = data.getJSONArray("children");
+            // 找到了该父级
+            if (parentIds.contains(data.getString("id"))) {
+                if (children != null) {
+                    // addAll 的目的是将多个子表的数据合并在一起
+                    results.addAll(children);
+                }
+            } else {
+                if (children != null) {
+                    queryByParentId(children, parentIds, results);
+                }
+            }
+        }
+        results.addAll(new JSONArray());
+    }
+
+    private JSONArray readJsonData(String path) {
+        try {
+            InputStream stream = getClass().getClassLoader().getResourceAsStream(path.replace("classpath:", ""));
+            if (stream != null) {
+                String json = IOUtils.toString(stream, "UTF-8");
+                return JSON.parseArray(json);
+            }
+        } catch (IOException e) {
+            log.error(e.getMessage(), e);
+        }
+        return null;
+    }
+
+}

File diff suppressed because it is too large
+ 0 - 0
jeecg-module-demo/src/main/java/org/jeecg/modules/dlglong/json/dlglong.json


File diff suppressed because it is too large
+ 0 - 0
jeecg-module-demo/target/classes/org/jeecg/modules/demo/mock/vxe/json/dlglong.json


BIN
jeecg-module-demo/target/classes/org/jeecg/modules/dlglong/controller/DlMockController.class


File diff suppressed because it is too large
+ 0 - 0
jeecg-module-demo/target/classes/org/jeecg/modules/dlglong/json/dlglong.json


+ 105 - 0
jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/system/controller/DuplicateCheckController.java

@@ -0,0 +1,105 @@
+package org.jeecg.modules.system.controller;
+
+import javax.servlet.http.HttpServletRequest;
+
+import org.apache.commons.lang.StringUtils;
+import org.jeecg.common.api.vo.Result;
+import org.jeecg.common.constant.SymbolConstant;
+import org.jeecg.common.util.SqlInjectionUtil;
+import org.jeecg.modules.system.mapper.SysDictMapper;
+import org.jeecg.modules.system.model.DuplicateCheckVo;
+import org.jeecg.modules.system.security.DictQueryBlackListHandler;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.ExceptionHandler;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestMethod;
+import org.springframework.web.bind.annotation.RestController;
+
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import lombok.extern.slf4j.Slf4j;
+
+/**
+ * @Title: DuplicateCheckAction
+ * @Description: 重复校验工具
+ * @Author 张代浩
+ * @Date 2019-03-25
+ * @Version V1.0
+ */
+@Slf4j
+@RestController
+@RequestMapping("/sys/duplicate")
+@Api(tags="重复校验")
+public class DuplicateCheckController {
+
+	@Autowired
+	SysDictMapper sysDictMapper;
+
+	@Autowired
+	DictQueryBlackListHandler dictQueryBlackListHandler;
+
+	/**
+	 * 校验数据是否在系统中是否存在
+	 * 
+	 * @return
+	 */
+	@RequestMapping(value = "/check", method = RequestMethod.GET)
+	@ApiOperation("重复校验接口")
+	public Result<String> doDuplicateCheck(DuplicateCheckVo duplicateCheckVo, HttpServletRequest request) {
+		Long num = null;
+
+		log.debug("----duplicate check------:"+ duplicateCheckVo.toString());
+		//关联表字典(举例:sys_user,realname,id)
+		//SQL注入校验(只限制非法串改数据库)
+		final String[] sqlInjCheck = {duplicateCheckVo.getTableName(),duplicateCheckVo.getFieldName()};
+		SqlInjectionUtil.filterContent(sqlInjCheck);
+		// update-begin-author:taoyan date:20211227 for: JTC-25 【online报表】oracle 操作问题 录入弹框啥都不填直接保存 ①编码不是应该提示必填么?②报错也应该是具体文字提示,不是后台错误日志
+		if(StringUtils.isEmpty(duplicateCheckVo.getFieldVal())){
+			Result rs = new Result();
+			rs.setCode(500);
+			rs.setSuccess(true);
+			rs.setMessage("数据为空,不作处理!");
+			return rs;
+		}
+		//update-begin-author:taoyan date:20220329 for: VUEN-223【安全漏洞】当前被攻击的接口
+		String checkSql = duplicateCheckVo.getTableName() + SymbolConstant.COMMA + duplicateCheckVo.getFieldName() + SymbolConstant.COMMA;
+		if(!dictQueryBlackListHandler.isPass(checkSql)){
+			return Result.error(dictQueryBlackListHandler.getError());
+		}
+		//update-end-author:taoyan date:20220329 for: VUEN-223【安全漏洞】当前被攻击的接口
+		// update-end-author:taoyan date:20211227 for: JTC-25 【online报表】oracle 操作问题 录入弹框啥都不填直接保存 ①编码不是应该提示必填么?②报错也应该是具体文字提示,不是后台错误日志
+		if (StringUtils.isNotBlank(duplicateCheckVo.getDataId())) {
+			// [2].编辑页面校验
+			num = sysDictMapper.duplicateCheckCountSql(duplicateCheckVo);
+		} else {
+			// [1].添加页面校验
+			num = sysDictMapper.duplicateCheckCountSqlNoDataId(duplicateCheckVo);
+		}
+
+		if (num == null || num == 0) {
+			// 该值可用
+			return Result.ok("该值可用!");
+		} else {
+			// 该值不可用
+			log.info("该值不可用,系统中已存在!");
+			return Result.error("该值不可用,系统中已存在!");
+		}
+	}
+
+	/**
+	 * VUEN-2584【issue】平台sql注入漏洞几个问题
+	 * 部分特殊函数 可以将查询结果混夹在错误信息中,导致数据库的信息暴露
+	 * @param e
+	 * @return
+	 */
+	@ExceptionHandler(java.sql.SQLException.class)
+	public Result<?> handleSQLException(Exception e){
+		String msg = e.getMessage();
+		String extractvalue = "extractvalue";
+		String updatexml = "updatexml";
+		if(msg!=null && (msg.toLowerCase().indexOf(extractvalue)>=0 || msg.toLowerCase().indexOf(updatexml)>=0)){
+			return Result.error("校验失败,sql解析异常!");
+		}
+		return Result.error("校验失败,sql解析异常!" + msg);
+	}
+}

+ 98 - 0
jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/system/model/DepartIdModel.java

@@ -0,0 +1,98 @@
+package org.jeecg.modules.system.model;
+
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.jeecg.modules.system.entity.SysDepart;
+
+/**
+ * <p>
+ * 部门表 封装树结构的部门的名称的实体类
+ * <p>
+ * 
+ * @Author Steve
+ * @Since 2019-01-22 
+ *
+ */
+public class DepartIdModel implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 主键ID
+     */
+    private String key;
+
+    /**
+     * 主键ID
+     */
+    private String value;
+
+    /**
+     * 部门名称
+     */
+    private String title;
+    
+    List<DepartIdModel> children = new ArrayList<>();
+    
+    /**
+     * 将SysDepartTreeModel的部分数据放在该对象当中
+     * @param treeModel
+     * @return
+     */
+    public DepartIdModel convert(SysDepartTreeModel treeModel) {
+        this.key = treeModel.getId();
+        this.value = treeModel.getId();
+        this.title = treeModel.getDepartName();
+        return this;
+    }
+    
+    /**
+     * 该方法为用户部门的实现类所使用
+     * @param sysDepart
+     * @return
+     */
+    public DepartIdModel convertByUserDepart(SysDepart sysDepart) {
+        this.key = sysDepart.getId();
+        this.value = sysDepart.getId();
+        this.title = sysDepart.getDepartName();
+        return this;
+    } 
+
+    public List<DepartIdModel> getChildren() {
+        return children;
+    }
+
+    public void setChildren(List<DepartIdModel> children) {
+        this.children = children;
+    }
+
+    public static long getSerialVersionUID() {
+        return serialVersionUID;
+    }
+
+    public String getKey() {
+        return key;
+    }
+
+    public void setKey(String key) {
+        this.key = key;
+    }
+
+    public String getValue() {
+        return value;
+    }
+
+    public void setValue(String value) {
+        this.value = value;
+    }
+
+    public String getTitle() {
+        return title;
+    }
+
+    public void setTitle(String title) {
+        this.title = title;
+    }
+}

+ 44 - 0
jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/system/model/DuplicateCheckVo.java

@@ -0,0 +1,44 @@
+package org.jeecg.modules.system.model;
+
+import java.io.Serializable;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+/**
+ * @Title: DuplicateCheckVo
+ * @Description: 重复校验VO
+ * @Author 张代浩
+ * @Date 2019-03-25
+ * @Version V1.0
+ */
+@Data
+@ApiModel(value="重复校验数据模型",description="重复校验数据模型")
+public class DuplicateCheckVo implements Serializable {
+	private static final long serialVersionUID = 1L;
+
+	/**
+	 * 表名
+	 */
+	@ApiModelProperty(value="表名",name="tableName",example="sys_log")
+	private String tableName;
+	
+	/**
+	 * 字段名
+	 */
+	@ApiModelProperty(value="字段名",name="fieldName",example="id")
+	private String fieldName;
+	
+	/**
+	 * 字段值
+	 */
+	@ApiModelProperty(value="字段值",name="fieldVal",example="1000")
+	private String fieldVal;
+	
+	/**
+	 * 数据ID
+	*/
+	@ApiModelProperty(value="数据ID",name="dataId",example="2000")
+	private String dataId;
+
+}

+ 60 - 0
jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/system/security/DictQueryBlackListHandler.java

@@ -0,0 +1,60 @@
+package org.jeecg.modules.system.security;
+
+import org.jeecg.common.constant.CommonConstant;
+import org.jeecg.common.constant.SymbolConstant;
+import org.jeecg.common.util.oConvertUtils;
+import org.jeecg.common.util.security.AbstractQueryBlackListHandler;
+import org.springframework.stereotype.Component;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * 字典组件 执行sql前校验 只校验表字典
+ * dictCodeString格式如:
+ * table,text,code
+ * table where xxx,text,code
+ * table,text,code, where xxx
+ *
+ * @Author taoYan
+ * @Date 2022/3/23 21:10
+ **/
+@Component("dictQueryBlackListHandler")
+public class DictQueryBlackListHandler extends AbstractQueryBlackListHandler {
+
+    @Override
+    protected List<QueryTable> getQueryTableInfo(String dictCodeString) {
+        if (dictCodeString != null && dictCodeString.indexOf(SymbolConstant.COMMA) > 0) {
+            String[] arr = dictCodeString.split(SymbolConstant.COMMA);
+            if (arr.length != 3 && arr.length != 4) {
+                return null;
+            }
+            String tableName = getTableName(arr[0]);
+            QueryTable table = new QueryTable(tableName, "");
+            // 无论什么场景 第二、三个元素一定是表的字段,直接add
+            table.addField(arr[1].trim());
+            String filed = arr[2].trim();
+            if (oConvertUtils.isNotEmpty(filed)) {
+                table.addField(filed);
+            }
+            List<QueryTable> list = new ArrayList<>();
+            list.add(table);
+            return list;
+        }
+        return null;
+    }
+
+    /**
+     * 取where前面的为:table name
+     *
+     * @param str
+     * @return
+     */
+    private String getTableName(String str) {
+        String[] arr = str.split("\\s+(?i)where\\s+");
+        // sys_user , (sys_user), sys_user%20, %60sys_user%60  issues/4393
+        String reg = "\\s+|\\(|\\)|`";
+        return arr[0].replaceAll(reg, "");
+    }
+
+}

+ 23 - 0
jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/system/vo/lowapp/DepartAndUserInfo.java

@@ -0,0 +1,23 @@
+package org.jeecg.modules.system.vo.lowapp;
+
+import lombok.Data;
+import org.jeecg.modules.system.vo.UserAvatar;
+
+import java.io.Serializable;
+import java.util.List;
+
+/**
+ * 用户或者部门的信息
+ * 用于 成员与部门 的搜索
+ * @Author taoYan
+ * @Date 2022/12/30 10:47
+ **/
+@Data
+public class DepartAndUserInfo implements Serializable {
+    private static final long serialVersionUID = 1L;
+    
+    List<UserAvatar> userList;
+    
+    List<DepartInfo> departList;
+    
+}

+ 26 - 0
jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/system/vo/lowapp/DepartInfo.java

@@ -0,0 +1,26 @@
+package org.jeecg.modules.system.vo.lowapp;
+
+import lombok.Data;
+
+import java.util.List;
+
+/**
+ * @Author taoYan
+ * @Date 2022/12/30 10:52
+ **/
+@Data
+public class DepartInfo {
+
+    private String id;
+
+    /**
+     * 上级名称-下级名称
+     */
+    private List<String> orgName;
+
+    /**
+     * 上级ID-下级ID
+     */
+    private List<String> orgId;
+
+}

BIN
jeecg-module-system/jeecg-system-biz/target/classes/org/jeecg/modules/system/controller/DuplicateCheckController.class


BIN
jeecg-module-system/jeecg-system-biz/target/classes/org/jeecg/modules/system/model/DepartIdModel.class


BIN
jeecg-module-system/jeecg-system-biz/target/classes/org/jeecg/modules/system/model/DuplicateCheckVo.class


BIN
jeecg-module-system/jeecg-system-biz/target/classes/org/jeecg/modules/system/security/DictQueryBlackListHandler.class


BIN
jeecg-module-system/jeecg-system-biz/target/classes/org/jeecg/modules/system/vo/lowapp/DepartAndUserInfo.class


BIN
jeecg-module-system/jeecg-system-biz/target/classes/org/jeecg/modules/system/vo/lowapp/DepartInfo.class


+ 16 - 0
jeecg-module-system/jeecg-system-start/Dockerfile

@@ -0,0 +1,16 @@
+FROM anapsix/alpine-java:8_server-jre_unlimited
+
+MAINTAINER jeecgos@163.com
+
+RUN ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime
+
+#RUN mkdir -p /jeecg-boot/config/jeecg/
+
+WORKDIR /jeecg-boot
+
+EXPOSE 8080
+
+#ADD ./src/main/resources/jeecg ./config/jeecg
+ADD ./target/jeecg-system-start-3.5.0.jar ./
+
+CMD sleep 60;java -Djava.security.egd=file:/dev/./urandom -jar jeecg-system-start-3.5.0.jar

+ 40 - 0
jeecg-server-cloud/docker-compose-base.yml

@@ -0,0 +1,40 @@
+version: '2'
+services:
+  jeecg-boot-mysql:
+    build:
+      context: ../db
+    environment:
+      MYSQL_ROOT_PASSWORD: root
+      MYSQL_ROOT_HOST: '%'
+      TZ: Asia/Shanghai
+    restart: always
+    container_name: jeecg-boot-mysql
+    command:
+      --character-set-server=utf8mb4
+      --collation-server=utf8mb4_general_ci
+      --explicit_defaults_for_timestamp=true
+      --lower_case_table_names=1
+      --max_allowed_packet=128M
+      --default-authentication-plugin=caching_sha2_password
+    ports:
+      - 3306:3306
+
+  jeecg-boot-redis:
+    image: redis:5.0
+    ports:
+      - 6379:6379
+    restart: always
+    container_name: jeecg-boot-redis
+    hostname: jeecg-boot-redis
+
+#  jeecg-boot-rabbitmq:
+#    image: rabbitmq:3.7.7-management
+#    ports:
+#      - 5672:5672
+#      - 15672:15672
+#    restart: always
+#    container_name: jeecg-boot-rabbitmq
+#    hostname: jeecg-boot-rabbitmq
+#    environment:
+#      RABBITMQ_DEFAULT_USER: guest
+#      RABBITMQ_DEFAULT_PASS: guest

+ 66 - 0
jeecg-server-cloud/docker-compose.yml

@@ -0,0 +1,66 @@
+version: '2'
+services:
+  jeecg-boot-nacos:
+    restart: always
+    build:
+      context: ./jeecg-cloud-nacos
+    ports:
+      - 8848:8848
+    container_name: jeecg-boot-nacos
+    hostname: jeecg-boot-nacos
+
+  jeecg-boot-system:
+    depends_on:
+      - jeecg-boot-nacos
+    build:
+      context: ./jeecg-system-cloud-start
+    container_name: jeecg-system-start
+    hostname: jeecg-boot-system
+    restart: on-failure
+    environment:
+      - TZ=Asia/Shanghai
+
+  jeecg-boot-demo:
+    depends_on:
+      - jeecg-boot-nacos
+    build:
+      context: ./jeecg-demo-cloud-start
+    container_name: jeecg-demo-start
+    hostname: jeecg-boot-demo
+    restart: on-failure
+    environment:
+      - TZ=Asia/Shanghai
+
+  jeecg-boot-gateway:
+    restart: on-failure
+    build:
+      context: ./jeecg-cloud-gateway
+    ports:
+      - 9999:9999
+    depends_on:
+      - jeecg-boot-nacos
+      - jeecg-boot-system
+    container_name: jeecg-boot-gateway
+    hostname: jeecg-boot-gateway
+
+#  jeecg-boot-sentinel:
+#    restart: on-failure
+#    build:
+#      context: ./jeecg-visual/jeecg-cloud-sentinel
+#    ports:
+#      - 9000:9000
+#    depends_on:
+#      - jeecg-boot-nacos
+#      - jeecg-boot-demo
+#      - jeecg-boot-system
+#      - jeecg-boot-gateway
+#    container_name: jeecg-boot-sentinel
+#    hostname: jeecg-boot-sentinel
+#
+#  jeecg-boot-xxljob:
+#    build:
+#      context: ./jeecg-visual/jeecg-cloud-xxljob
+#    ports:
+#      - 9080:9080
+#    container_name: jeecg-boot-xxljob
+#    hostname: jeecg-boot-xxljob

+ 15 - 0
jeecg-server-cloud/jeecg-cloud-gateway/Dockerfile

@@ -0,0 +1,15 @@
+FROM anapsix/alpine-java:8_server-jre_unlimited
+
+MAINTAINER jeecgos@163.com
+
+RUN ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime
+
+RUN mkdir -p /jeecg-cloud-gateway
+
+WORKDIR /jeecg-cloud-gateway
+
+EXPOSE 9999
+
+ADD ./target/jeecg-cloud-gateway-3.5.0.jar ./
+
+CMD sleep 1;java -Dfile.encoding=utf-8 -Djava.security.egd=file:/dev/./urandom -jar jeecg-cloud-gateway-3.5.0.jar

+ 367 - 0
jeecg-server-cloud/jeecg-cloud-gateway/src/main/java/org/jeecg/loader/DynamicRouteLoader.java

@@ -0,0 +1,367 @@
+package org.jeecg.loader;
+
+import cn.hutool.core.util.ArrayUtil;
+import cn.hutool.core.util.ObjectUtil;
+import com.alibaba.fastjson.JSON;
+import com.alibaba.fastjson.JSONArray;
+import com.alibaba.fastjson.JSONObject;
+import com.alibaba.nacos.api.NacosFactory;
+import com.alibaba.nacos.api.config.ConfigService;
+import com.alibaba.nacos.api.config.listener.Listener;
+import com.alibaba.nacos.api.exception.NacosException;
+import com.google.common.collect.Lists;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.lang3.ObjectUtils;
+import org.apache.commons.lang3.StringUtils;
+import org.jeecg.common.base.BaseMap;
+import org.jeecg.common.constant.CacheConstant;
+import org.jeecg.common.util.RedisUtil;
+import org.jeecg.config.GatewayRoutersConfig;
+import org.jeecg.config.RouterDataType;
+import org.jeecg.loader.repository.DynamicRouteService;
+import org.jeecg.loader.repository.MyInMemoryRouteDefinitionRepository;
+import org.jeecg.loader.vo.MyRouteDefinition;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.cloud.context.config.annotation.RefreshScope;
+import org.springframework.cloud.gateway.event.RefreshRoutesEvent;
+import org.springframework.cloud.gateway.filter.FilterDefinition;
+import org.springframework.cloud.gateway.handler.predicate.PredicateDefinition;
+import org.springframework.cloud.gateway.route.RouteDefinition;
+import org.springframework.context.ApplicationEventPublisher;
+import org.springframework.context.ApplicationEventPublisherAware;
+import org.springframework.context.annotation.DependsOn;
+import org.springframework.stereotype.Component;
+import reactor.core.publisher.Mono;
+
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.Properties;
+import java.util.concurrent.Executor;
+
+/**
+ * 动态路由加载器
+ *
+ * @author : zyf
+ * @date :2020-11-10
+ */
+@Slf4j
+@Component
+@RefreshScope
+@DependsOn({"gatewayRoutersConfig"})
+public class DynamicRouteLoader implements ApplicationEventPublisherAware {
+
+    public static final long DEFAULT_TIMEOUT = 30000;
+    @Autowired
+    private GatewayRoutersConfig gatewayRoutersConfig;
+    private MyInMemoryRouteDefinitionRepository repository;
+    private ApplicationEventPublisher publisher;
+    private DynamicRouteService dynamicRouteService;
+    private ConfigService configService;
+    private RedisUtil redisUtil;
+
+
+    /**
+     * 需要拼接key的路由条件
+     */
+    private static String[] GEN_KEY_ROUTERS = new String[]{"Path", "Host", "Method", "After", "Before", "Between", "RemoteAddr"};
+
+    public DynamicRouteLoader(MyInMemoryRouteDefinitionRepository repository, DynamicRouteService dynamicRouteService, RedisUtil redisUtil) {
+
+        this.repository = repository;
+        this.dynamicRouteService = dynamicRouteService;
+        this.redisUtil = redisUtil;
+    }
+
+//    @PostConstruct
+//    public void init() {
+//       init(null);
+//    }
+
+
+    public void init(BaseMap baseMap) {
+        log.info("初始化路由模式,dataType:"+ gatewayRoutersConfig.getDataType());
+        if (RouterDataType.nacos.toString().endsWith(gatewayRoutersConfig.getDataType())) {
+            loadRoutesByNacos();
+        }
+        //从数据库加载路由
+        if (RouterDataType.database.toString().endsWith(gatewayRoutersConfig.getDataType())) {
+            loadRoutesByRedis(baseMap);
+        }
+    }
+    /**
+     * 刷新路由
+     *
+     * @return
+     */
+    public Mono<Void> refresh(BaseMap baseMap) {
+        log.info("初始化路由模式,dataType:"+ gatewayRoutersConfig.getDataType());
+        if (!RouterDataType.yml.toString().endsWith(gatewayRoutersConfig.getDataType())) {
+            this.init(baseMap);
+        }
+        return Mono.empty();
+    }
+
+
+    /**
+     * 从nacos中读取路由配置
+     *
+     * @return
+     */
+    private void loadRoutesByNacos() {
+        List<RouteDefinition> routes = Lists.newArrayList();
+        configService = createConfigService();
+        if (configService == null) {
+            log.warn("initConfigService fail");
+        }
+        try {
+            String configInfo = configService.getConfig(gatewayRoutersConfig.getDataId(), gatewayRoutersConfig.getRouteGroup(), DEFAULT_TIMEOUT);
+            if (StringUtils.isNotBlank(configInfo)) {
+                log.info("获取网关当前配置:\r\n{}", configInfo);
+                routes = JSON.parseArray(configInfo, RouteDefinition.class);
+            }else{
+                log.warn("ERROR: 从Nacos获取网关配置为空,请确认Nacos配置是否正确!");
+            }
+        } catch (NacosException e) {
+            log.error("初始化网关路由时发生错误", e);
+            e.printStackTrace();
+        }
+        for (RouteDefinition definition : routes) {
+            log.info("update route : {}", definition.toString());
+            dynamicRouteService.add(definition);
+        }
+        this.publisher.publishEvent(new RefreshRoutesEvent(this));
+        dynamicRouteByNacosListener(gatewayRoutersConfig.getDataId(), gatewayRoutersConfig.getRouteGroup());
+    }
+
+
+    /**
+     * 从redis中读取路由配置
+     *
+     * @return
+     */
+    private void loadRoutesByRedis(BaseMap baseMap) {
+        List<MyRouteDefinition> routes = Lists.newArrayList();
+        configService = createConfigService();
+        if (configService == null) {
+            log.warn("initConfigService fail");
+        }
+        Object configInfo = redisUtil.get(CacheConstant.GATEWAY_ROUTES);
+        if (ObjectUtil.isNotEmpty(configInfo)) {
+            log.info("获取网关当前配置:\r\n{}", configInfo);
+            JSONArray array = JSON.parseArray(configInfo.toString());
+            try {
+                routes = getRoutesByJson(array);
+            } catch (URISyntaxException e) {
+                e.printStackTrace();
+            }
+        }else{
+            log.warn("ERROR: 从Redis获取网关配置为空,请确认system服务是否启动成功!");
+        }
+        
+        for (MyRouteDefinition definition : routes) {
+            log.info("update route : {}", definition.toString());
+            Integer status=definition.getStatus();
+            if(status.equals(0)){
+                dynamicRouteService.delete(definition.getId());
+            }else{
+                dynamicRouteService.add(definition);
+            }
+        }
+        if(ObjectUtils.isNotEmpty(baseMap)){
+            String delRouterId = baseMap.get("delRouterId");
+            if (ObjectUtils.isNotEmpty(delRouterId)) {
+                dynamicRouteService.delete(delRouterId);
+            }
+        }
+        this.publisher.publishEvent(new RefreshRoutesEvent(this));
+    }
+
+    /**
+     * redis中的信息需要处理下 转成RouteDefinition对象
+     * - id: login
+     * uri: lb://cloud-jeecg-system
+     * predicates:
+     * - Path=/jeecg-boot/sys/**,
+     *
+     * @param array
+     * @return
+     */
+
+    public static List<MyRouteDefinition> getRoutesByJson(JSONArray array) throws URISyntaxException {
+        List<MyRouteDefinition> ls = new ArrayList<>();
+        for (int i = 0; i < array.size(); i++) {
+            JSONObject obj = array.getJSONObject(i);
+            MyRouteDefinition route = new MyRouteDefinition();
+            route.setId(obj.getString("routerId"));
+            route.setStatus(obj.getInteger("status"));
+            Object uri = obj.get("uri");
+            if (uri == null) {
+                route.setUri(new URI("lb://" + obj.getString("name")));
+            } else {
+                route.setUri(new URI(obj.getString("uri")));
+            }
+            Object predicates = obj.get("predicates");
+            if (predicates != null) {
+                JSONArray list = JSON.parseArray(predicates.toString());
+                List<PredicateDefinition> predicateDefinitionList = new ArrayList<>();
+                for (Object map : list) {
+                    JSONObject json = (JSONObject) map;
+                    PredicateDefinition predicateDefinition = new PredicateDefinition();
+                    //update-begin-author:zyf date:20220419 for:【VUEN-762】路由条件添加异常问题,原因是部分路由条件参数需要设置固定key
+                    String name=json.getString("name");
+                    predicateDefinition.setName(name);
+                    //路由条件是否拼接Key
+                    if(ArrayUtil.contains(GEN_KEY_ROUTERS,name)) {
+                        JSONArray jsonArray = json.getJSONArray("args");
+                        for (int j = 0; j < jsonArray.size(); j++) {
+                            predicateDefinition.addArg("_genkey" + j, jsonArray.get(j).toString());
+                        }
+                    }else{
+                        JSONObject jsonObject = json.getJSONObject("args");
+                        if(ObjectUtil.isNotEmpty(jsonObject)){
+                            for (Map.Entry<String, Object> entry : jsonObject.entrySet()) {
+                                Object valueObj=entry.getValue();
+                                if(ObjectUtil.isNotEmpty(valueObj)) {
+                                    predicateDefinition.addArg(entry.getKey(), valueObj.toString());
+                                }
+                            }
+                        }
+                    }
+                    //update-end-author:zyf date:20220419 for:【VUEN-762】路由条件添加异常问题,原因是部分路由条件参数需要设置固定key
+                    predicateDefinitionList.add(predicateDefinition);
+                }
+                route.setPredicates(predicateDefinitionList);
+            }
+
+            Object filters = obj.get("filters");
+            if (filters != null) {
+                JSONArray list = JSON.parseArray(filters.toString());
+                List<FilterDefinition> filterDefinitionList = new ArrayList<>();
+                if (ObjectUtil.isNotEmpty(list)) {
+                    for (Object map : list) {
+                        JSONObject json = (JSONObject) map;
+                        JSONArray jsonArray = json.getJSONArray("args");
+                        String name = json.getString("name");
+                        FilterDefinition filterDefinition = new FilterDefinition();
+                        for (Object o : jsonArray) {
+                            JSONObject params = (JSONObject) o;
+                            filterDefinition.addArg(params.getString("key"), params.get("value").toString());
+                        }
+                        filterDefinition.setName(name);
+                        filterDefinitionList.add(filterDefinition);
+                    }
+                    route.setFilters(filterDefinitionList);
+                }
+            }
+            ls.add(route);
+        }
+        return ls;
+    }
+
+
+//    private void loadRoutesByDataBase() {
+//        List<GatewayRouteVo> routeList = jdbcTemplate.query(SELECT_ROUTES, new RowMapper<GatewayRouteVo>() {
+//            @Override
+//            public GatewayRouteVo mapRow(ResultSet rs, int i) throws SQLException {
+//                GatewayRouteVo result = new GatewayRouteVo();
+//                result.setId(rs.getString("id"));
+//                result.setName(rs.getString("name"));
+//                result.setUri(rs.getString("uri"));
+//                result.setStatus(rs.getInt("status"));
+//                result.setRetryable(rs.getInt("retryable"));
+//                result.setPredicates(rs.getString("predicates"));
+//                result.setStripPrefix(rs.getInt("strip_prefix"));
+//                result.setPersist(rs.getInt("persist"));
+//                return result;
+//            }
+//        });
+//        if (ObjectUtil.isNotEmpty(routeList)) {
+//            // 加载路由
+//            routeList.forEach(route -> {
+//                RouteDefinition definition = new RouteDefinition();
+//                List<PredicateDefinition> predicatesList = Lists.newArrayList();
+//                List<FilterDefinition> filtersList = Lists.newArrayList();
+//                definition.setId(route.getId());
+//                String predicates = route.getPredicates();
+//                String filters = route.getFilters();
+//                if (StringUtils.isNotEmpty(predicates)) {
+//                    predicatesList = JSON.parseArray(predicates, PredicateDefinition.class);
+//                    definition.setPredicates(predicatesList);
+//                }
+//                if (StringUtils.isNotEmpty(filters)) {
+//                    filtersList = JSON.parseArray(filters, FilterDefinition.class);
+//                    definition.setFilters(filtersList);
+//                }
+//                URI uri = UriComponentsBuilder.fromUriString(route.getUri()).build().toUri();
+//                definition.setUri(uri);
+//                this.repository.save(Mono.just(definition)).subscribe();
+//            });
+//            log.info("加载路由:{}==============", routeList.size());
+//            Mono.empty();
+//        }
+//    }
+
+
+    /**
+     * 监听Nacos下发的动态路由配置
+     *
+     * @param dataId
+     * @param group
+     */
+    public void dynamicRouteByNacosListener(String dataId, String group) {
+        try {
+            configService.addListener(dataId, group, new Listener() {
+                @Override
+                public void receiveConfigInfo(String configInfo) {
+                    log.info("进行网关更新:\n\r{}", configInfo);
+                    List<MyRouteDefinition> definitionList = JSON.parseArray(configInfo, MyRouteDefinition.class);
+                    for (MyRouteDefinition definition : definitionList) {
+                        log.info("update route : {}", definition.toString());
+                        dynamicRouteService.update(definition);
+                    }
+                }
+
+                @Override
+                public Executor getExecutor() {
+                    log.info("getExecutor\n\r");
+                    return null;
+                }
+            });
+        } catch (Exception e) {
+            log.error("从nacos接收动态路由配置出错!!!", e);
+        }
+    }
+
+    /**
+     * 创建ConfigService
+     *
+     * @return
+     */
+    private ConfigService createConfigService() {
+        try {
+            Properties properties = new Properties();
+            properties.setProperty("serverAddr", gatewayRoutersConfig.getServerAddr());
+            if(StringUtils.isNotBlank(gatewayRoutersConfig.getNamespace())){
+                properties.setProperty("namespace", gatewayRoutersConfig.getNamespace());
+            }
+            if(StringUtils.isNotBlank( gatewayRoutersConfig.getUsername())){
+                properties.setProperty("username", gatewayRoutersConfig.getUsername());
+            }
+            if(StringUtils.isNotBlank(gatewayRoutersConfig.getPassword())){
+                properties.setProperty("password", gatewayRoutersConfig.getPassword());
+            }
+            return configService = NacosFactory.createConfigService(properties);
+        } catch (Exception e) {
+            log.error("创建ConfigService异常", e);
+            return null;
+        }
+    }
+
+    @Override
+    public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
+        this.publisher = applicationEventPublisher;
+    }
+}

+ 15 - 0
jeecg-server-cloud/jeecg-cloud-nacos/Dockerfile

@@ -0,0 +1,15 @@
+FROM anapsix/alpine-java:8_server-jre_unlimited
+
+MAINTAINER jeecgos@163.com
+
+RUN ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime
+
+RUN mkdir -p /jeecg-cloud-nacos
+
+WORKDIR /jeecg-cloud-nacos
+
+EXPOSE 8848
+
+ADD ./target/jeecg-cloud-nacos-3.5.0.jar ./
+
+CMD sleep 1;java -Dfile.encoding=utf-8 -Djava.security.egd=file:/dev/./urandom -jar jeecg-cloud-nacos-3.5.0.jar

+ 15 - 0
jeecg-server-cloud/jeecg-demo-cloud-start/Dockerfile

@@ -0,0 +1,15 @@
+FROM anapsix/alpine-java:8_server-jre_unlimited
+
+MAINTAINER jeecgos@163.com
+
+RUN ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime
+
+RUN mkdir -p /jeecg-demo-cloud
+
+WORKDIR /jeecg-demo-cloud
+
+EXPOSE 7002
+
+ADD ./target/jeecg-demo-cloud-start-3.5.0.jar ./
+
+CMD sleep 1;java -Dfile.encoding=utf-8 -Djava.security.egd=file:/dev/./urandom -jar jeecg-demo-cloud-start-3.5.0.jar

+ 15 - 0
jeecg-server-cloud/jeecg-system-cloud-start/Dockerfile

@@ -0,0 +1,15 @@
+FROM anapsix/alpine-java:8_server-jre_unlimited
+
+MAINTAINER jeecgos@163.com
+
+RUN ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime
+
+RUN mkdir -p /jeecg-system-cloud
+
+WORKDIR /jeecg-system-cloud
+
+EXPOSE 7001
+
+ADD ./target/jeecg-system-cloud-start-3.5.0.jar ./
+
+CMD sleep 1;java -Dfile.encoding=utf-8 -Djava.security.egd=file:/dev/./urandom -jar jeecg-system-cloud-start-3.5.0.jar

+ 15 - 0
jeecg-server-cloud/jeecg-visual/jeecg-cloud-sentinel/Dockerfile

@@ -0,0 +1,15 @@
+FROM anapsix/alpine-java:8_server-jre_unlimited
+
+MAINTAINER jeecgos@163.com
+
+RUN ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime
+
+RUN mkdir -p /jeecg-cloud-sentinel
+
+WORKDIR /jeecg-cloud-sentinel
+
+EXPOSE 8848
+
+ADD ./target/jeecg-cloud-sentinel-3.5.0.jar ./
+
+CMD sleep 5;java -Dfile.encoding=utf-8 -Djava.security.egd=file:/dev/./urandom -jar jeecg-cloud-sentinel-3.5.0.jar

+ 16 - 0
jeecg-server-cloud/jeecg-visual/jeecg-cloud-xxljob/Dockerfile

@@ -0,0 +1,16 @@
+FROM anapsix/alpine-java:8_server-jre_unlimited
+
+MAINTAINER jeecgos@163.com
+
+RUN ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime
+
+RUN mkdir -p /jeecg-cloud-xxljob
+
+WORKDIR /jeecg-cloud-xxljob
+
+EXPOSE 9080
+
+ADD ./target/jeecg-cloud-xxljob-3.5.0.jar ./
+
+CMD java -Dfile.encoding=utf-8 -Djava.security.egd=file:/dev/./urandom -jar jeecg-cloud-xxljob-3.5.0.jar
+

Some files were not shown because too many files changed in this diff