소스 검색

Merge branch 'develop-v3.0.0' of http://39.106.8.246:3003/FM-dev/fm-basics into develop-v3.0.0

lijie 3 년 전
부모
커밋
40543d4b6a
21개의 변경된 파일1031개의 추가작업 그리고 27개의 파일을 삭제
  1. 4 1
      fm-common/src/main/java/com/persagy/fm/common/config/CommonWebConfigurer.java
  2. 295 0
      fm-common/src/main/java/com/persagy/fm/common/helper/LockHelper.java
  3. 1 1
      fm-common/src/main/java/com/persagy/fm/common/model/entity/BaseEntity.java
  4. 2 2
      fm-common/src/main/java/com/persagy/fm/common/model/entity/IBaseEntity.java
  5. 5 5
      fm-common/src/main/java/com/persagy/fm/common/model/entity/ITreeEntity.java
  6. 15 0
      fm-common/src/main/java/com/persagy/fm/common/model/vo/SimpleObjVO.java
  7. 1 1
      fm-common/src/main/java/com/persagy/fm/common/response/FmResponseUpsertVO.java
  8. 159 0
      fm-mybatis/README.md
  9. 4 2
      fm-mybatis/src/main/java/com/persagy/fm/mybatis/config/MyBatisWebConfigurer.java
  10. 17 0
      fm-mybatis/src/main/java/com/persagy/fm/mybatis/dao/DbDao.java
  11. 30 0
      fm-mybatis/src/main/java/com/persagy/fm/mybatis/dao/SimpleTreeCodeDao.java
  12. 29 14
      fm-mybatis/src/main/java/com/persagy/fm/mybatis/handler/DynamicDataSourceHandler.java
  13. 137 0
      fm-mybatis/src/main/java/com/persagy/fm/mybatis/helper/TreeCodeHelper.java
  14. 15 0
      fm-mybatis/src/main/java/com/persagy/fm/mybatis/service/IDbService.java
  15. 72 0
      fm-mybatis/src/main/java/com/persagy/fm/mybatis/service/ITreeCodeService.java
  16. 24 0
      fm-mybatis/src/main/java/com/persagy/fm/mybatis/service/impl/DbServiceImpl.java
  17. 191 0
      fm-mybatis/src/main/java/com/persagy/fm/mybatis/service/impl/SimpleTreeCodeServiceImpl.java
  18. 11 0
      fm-mybatis/src/main/resources/mapper/DbDao.xml
  19. 17 0
      fm-mybatis/src/main/resources/mapper/SimpleTreeCodeDao.xml
  20. 1 1
      fm-translate/src/main/resources/mapper/adder/translate/ITranslateDao.xml
  21. 1 0
      pom.xml

+ 4 - 1
fm-common/src/main/java/com/persagy/fm/common/config/CommonWebConfigurer.java

@@ -3,6 +3,7 @@ package com.persagy.fm.common.config;
 import com.persagy.fm.common.handler.AppContextHandler;
 import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.Configuration;
+import org.springframework.core.annotation.Order;
 import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
 import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
 
@@ -14,6 +15,7 @@ import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
  * @version: V1.0
  **/
 @Configuration
+@Order(1)
 public class CommonWebConfigurer implements WebMvcConfigurer {
 
     @Bean
@@ -24,6 +26,7 @@ public class CommonWebConfigurer implements WebMvcConfigurer {
     @Override
     public void addInterceptors(InterceptorRegistry registry) {
         // 设置拦截的路径、不拦截的路径、优先级等等
-        registry.addInterceptor(appContextHandler()).addPathPatterns("/**");
+        registry.addInterceptor(appContextHandler()).order(10).addPathPatterns("/**");
     }
+
 }

+ 295 - 0
fm-common/src/main/java/com/persagy/fm/common/helper/LockHelper.java

@@ -0,0 +1,295 @@
+package com.persagy.fm.common.helper;
+
+import com.persagy.common.exception.BusinessException;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.data.redis.connection.RedisConnection;
+import org.springframework.data.redis.core.RedisCallback;
+import org.springframework.data.redis.core.RedisTemplate;
+
+import java.io.UnsupportedEncodingException;
+import java.nio.charset.StandardCharsets;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 锁助手
+ * @author Charlie Yu
+ * @version 1.0 2021-03-30
+ */
+@Slf4j
+public class LockHelper {
+
+    /**
+     * jedis模板
+     */
+    private static RedisTemplate redisTemplate = null;
+
+    /**
+     * 加锁标志
+     */
+    private static final byte[] LOCKED = new byte[]{'Y'};
+
+    /**
+     * 一毫秒等于的纳秒数
+     */
+    public static final long ONE_MILLI_NANOS = 1000000L;
+
+    /**
+     * 默认超时时间(毫秒)
+     */
+    public static final long DEFAULT_TIME_OUT = 3000;
+
+    /**
+     * 锁的超时时间(秒),过期删除
+     */
+    public static final int EXPIRE = 5 * 60;
+
+
+    /**线程变量*/
+    private static ThreadLocal<Map<String,Integer>> locks = new ThreadLocal<>();
+
+
+    /**
+     * 按键值增加锁
+     * @param key     锁的键值
+     * @param timeout 超时时长
+     * @param expire  锁过期时间
+     * @return 是否锁定成功
+     */
+    public static boolean lock(String key,long timeout,int expire){
+        //解决嵌套加锁
+        Map<String,Integer> ls = locks.get();
+        if(ls != null){
+            Integer c =  ls.get(key);
+            if(c != null){
+                c++;
+                ls.put(key,c);
+                return true;
+            }
+        }else{
+            ls = new HashMap<>(16);
+            locks.set(ls);
+        }
+        Boolean rs = (Boolean) getRedisTemplate().execute(new LockRedisCallback(key,timeout,expire));
+        boolean flag = rs!=null && rs;
+        if(flag){
+            ls.put(key,1);
+        }
+        return flag;
+    }
+
+    /**
+     * 按键值增加锁
+     * @param key     锁的键值
+     * @param timeout 超时时长
+     * @return 是否锁定成功
+     */
+    public static boolean lock(String key,long timeout){
+        return lock(key,timeout,EXPIRE);
+    }
+    /**
+     * 按键值增加锁
+     * @param key     锁的键值
+     * @return 是否锁定成功
+     */
+    public static boolean lock(String key){
+        return lock(key,DEFAULT_TIME_OUT);
+    }
+
+    /**
+     * 解锁指定的键值
+     */
+    public static void unlock(String key){
+        //解决嵌套加锁
+        Map<String,Integer> ls = locks.get();
+        if(ls == null) {
+            return;
+        }
+        Integer c =  ls.get(key);
+        if(c == null) {
+            return;
+        }
+        if(c > 1) {
+            c--;
+            ls.put(key,c);
+            return;
+        }
+        ls.remove(key);
+        if(ls.isEmpty()) {
+            locks.remove();
+        }
+
+        getRedisTemplate().execute(new UnLockRedisCallback(key));
+    }
+
+    /**
+     * 清除线程占用的所有的锁
+     */
+    public void clearLock(){
+        Map<String,Integer> ls = locks.get();
+        for(String key:ls.keySet()){
+            unlock(key);
+        }
+        locks.remove();
+    }
+
+    /**
+     * 排他独占锁
+     * @param key 独占键值
+     * @param expire 超期时长
+     * @return 布尔值
+     */
+    public static boolean exclusive(String key, int expire){
+        Boolean rs = (Boolean) getRedisTemplate().execute(new ExclusiveRedisCallback(key, expire));
+        return rs!=null && rs;
+    }
+
+    /**
+     * 排他独占锁
+     * @param key 键值
+     * @return 布尔值
+     */
+    public static boolean exclusive(String key){
+        return exclusive(key,EXPIRE);
+    }
+
+    /**
+     * 释放排他独占锁
+     * @param key 键值
+     */
+    public static void releaseExcusive(String key){
+        getRedisTemplate().execute(new UnLockRedisCallback(key));
+    }
+
+    /**
+     * 获取redis访问模板
+     * @return redis访问模板
+     */
+    public static RedisTemplate getRedisTemplate(){
+        if(redisTemplate == null) {
+            redisTemplate = SpringHelper.getBean("redisTemplate",RedisTemplate.class);
+            if(redisTemplate == null){
+                Map<String, RedisTemplate> beans = SpringHelper.getBeansOfType(RedisTemplate.class);
+                if(!beans.isEmpty()) {
+                    redisTemplate = beans.values().iterator().next();
+                }
+            }
+        }
+        if(redisTemplate == null) {
+            log.error("redis config is error! please init RedisTemplate bean!");
+            throw new RuntimeException("redis config is error! please init RedisTemplate bean!");
+        }
+        return redisTemplate;
+    }
+    /**
+     * 加锁回调指令
+     */
+    public static class LockRedisCallback implements RedisCallback<Boolean> {
+        private long timeout;
+        private byte[] key;
+        private int expire;
+
+        public LockRedisCallback(String key,long timeout,int expire) {
+            this.timeout = timeout;
+            this.expire = expire;
+            this.key = redisTemplate.getStringSerializer().serialize(key);
+        }
+
+        /**
+         * 加锁方法
+         * @param connection redis连接
+         * @return 布尔值
+         */
+        @Override
+        public Boolean doInRedis(RedisConnection connection) {
+            long nano = System.nanoTime();
+            timeout *= ONE_MILLI_NANOS;
+            try {
+                while ((System.nanoTime() - nano) < timeout) {
+                    connection.watch(key);
+                    // 开启watch之后,如果key的值被修改,则事务失败,exec方法返回null
+                    byte[] value = connection.get(key);
+                    if (value == null) {
+                        connection.multi();
+                        connection.setEx(key, expire, LOCKED);
+                        connection.get(key);
+                        List<Object> rs = connection.exec();
+                        if (rs != null && rs.size() > 0) {
+                            connection.unwatch();
+                            return true;
+                        }
+                    }
+                    connection.unwatch();
+                    // 短暂休眠,nano避免出现活锁
+                    Thread.sleep(10);
+                }
+            } catch (Exception e) {
+                log.error("lock error happened",e);
+            }
+            return false;
+        }
+    }
+
+    /**
+     * 解锁回调指令
+     */
+    public static class UnLockRedisCallback implements RedisCallback<Boolean> {
+        private byte[] key;
+
+        public UnLockRedisCallback(String key) {
+            try {
+                this.key = key.getBytes(StandardCharsets.UTF_8.name());
+            } catch (UnsupportedEncodingException e) {
+                throw new BusinessException(e);
+            }
+        }
+
+        @Override
+        public Boolean doInRedis(RedisConnection connection) {
+            connection.del(key);
+            return true;
+        }
+    }
+    /**
+     * 排他独占锁回调,无需解锁
+     */
+    public static class ExclusiveRedisCallback implements RedisCallback<Boolean> {
+        private byte[] key;
+        private int expire;
+
+        public ExclusiveRedisCallback(String key,int expire) {
+            this.expire = expire;
+            this.key = redisTemplate.getStringSerializer().serialize(key);
+        }
+
+        /**
+         * 加锁方法
+         * @param connection redis连接
+         * @return 布尔值
+         */
+        @Override
+        public Boolean doInRedis(RedisConnection connection) {
+            try {
+                connection.watch(key);
+                // 开启watch之后,如果key的值被修改,则事务失败,exec方法返回null
+                byte[] value = connection.get(key);
+                if (value != null) {
+                    return false;
+                }
+                connection.multi();
+                connection.setEx(key, expire, LOCKED);
+                connection.get(key);
+                List<Object> rs = connection.exec();
+                if (rs != null && rs.size() > 0) {
+                    return true;
+                }
+            } catch (Exception e) {
+                log.error("lock error happened",e);
+            } finally {
+                connection.unwatch();
+            }
+            return false;
+        }
+    }
+}

+ 1 - 1
fm-common/src/main/java/com/persagy/fm/common/model/entity/BaseEntity.java

@@ -42,7 +42,7 @@ public abstract class BaseEntity<T> extends Model implements IBaseEntity,Seriali
 	/** 实体类必须有的属性 */
 
 	@TableId(type = IdType.ASSIGN_ID)
-	protected Long id;
+	protected String id;
 
 	/** 时间戳 */
 	protected Date ts;

+ 2 - 2
fm-common/src/main/java/com/persagy/fm/common/model/entity/IBaseEntity.java

@@ -14,13 +14,13 @@ public interface IBaseEntity extends Comparable<IBaseEntity>{
 	 * 取得实体主键
 	 * @return 实体主键
 	 */
-	Long getId();
+	String getId();
 
 	/**
 	 * 设置实体主键
 	 * @param id 实体主键
 	 */
-	void setId(Long id);
+	void setId(String id);
 
 	/**
 	 * 取得时间戳

+ 5 - 5
fm-common/src/main/java/com/persagy/fm/common/model/entity/ITreeEntity.java

@@ -8,25 +8,25 @@ package com.persagy.fm.common.model.entity;
 public interface ITreeEntity extends IBaseEntity {
 
     /** 实体属性树形编码 */
-    String PROP_INNERCODE = "innercode";
+    String PROP_INNER_CODE = "innerCode";
 
     /**
      * 取得树形编码
      * @return 树形编码
      */
-    String getInnercode();
+    String getInnerCode();
 
     /**
      * 设置树形编码
-     * @param innercode 设置树形编码
+     * @param innerCode 设置树形编码
      */
-    void setInnercode(String innercode);
+    void setInnerCode(String innerCode);
 
     /**
      * 取得innercode属性名
      * @return
      */
-    String getInnercodeField();
+    String getInnerCodeField();
 
     /**
      * 取得当前实体表名

+ 15 - 0
fm-common/src/main/java/com/persagy/fm/common/model/vo/SimpleObjVO.java

@@ -0,0 +1,15 @@
+package com.persagy.fm.common.model.vo;
+
+import lombok.Data;
+
+/**
+ * 简单对象vo类
+ *
+ * @author lixing
+ * @version V1.0 2021/3/27 5:18 下午
+ **/
+@Data
+public class SimpleObjVO {
+    private String id;
+    private String name;
+}

+ 1 - 1
fm-common/src/main/java/com/persagy/fm/common/response/FmResponseUpsertVO.java

@@ -20,5 +20,5 @@ public class FmResponseUpsertVO {
     /**
      * 响应码
      */
-    private Long id;
+    private String id;
 }

+ 159 - 0
fm-mybatis/README.md

@@ -1,9 +1,168 @@
 fm-mybatis 关系数据库
 ============ 
+mybatis-plus封装
+- 支持多数据源自动切换
+- 支持预置建库脚本
+- 提供树型内部码生成工具
 
 说明
 ---------------
+多数据源
+* 配置文件参考:com.baomidou.dynamic.datasource.spring.boot.autoconfigure.DynamicDataSourceProperties
+* [配置文件示例](https://github.com/dynamic-datasource/dynamic-datasource-doc/blob/master/docs/guide/integration/Druid.md)
+```
+spring:
+  datasource:
+    druid:
+      stat-view-servlet:
+        enabled: true
+        loginUsername: admin
+        loginPassword: 123456
+    dynamic:
+      druid: #以下是支持的全局默认值
+        initial-size:
+        max-active:
+        min-idle:
+        max-wait:
+        time-between-eviction-runs-millis:
+        time-between-log-stats-millis:
+        stat-sqlmax-size:
+        min-evictable-idle-time-millis:
+        max-evictable-idle-time-millis:
+        test-while-idle:
+        test-on-borrow:
+        test-on-return:
+        validation-query:
+        validation-query-timeout:
+        use-global-datasource-stat:
+        async-init:
+        clear-filters-enable:
+        reset-stat-enable:
+        not-full-timeout-retry-count:
+        max-wait-thread-count:
+        fail-fast:
+        phyTimeout-millis:
+        keep-alive:
+        pool-prepared-statements:
+        init-variants:
+        init-global-variants:
+        use-unfair-lock:
+        kill-when-socket-read-timeout:
+        connection-properties:
+        max-pool-prepared-statement-per-connection-size:
+        init-connection-sqls:
+        share-prepared-statements:
+        connection-errorretry-attempts:
+        break-after-acquire-failure:
+        filters: stat,wall # 注意这个值和druid原生不一致,默认启动了stat,wall
+        wall:
+            noneBaseStatementAllow:
+            callAllow:
+            selectAllow:
+            selectIntoAllow:
+            selectIntoOutfileAllow:
+            selectWhereAlwayTrueCheck:
+            selectHavingAlwayTrueCheck:
+            selectUnionCheck:
+            selectMinusCheck:
+            selectExceptCheck:
+            selectIntersectCheck:
+            createTableAllow:
+            dropTableAllow:
+            alterTableAllow:
+            renameTableAllow:
+            hintAllow:
+            lockTableAllow:
+            startTransactionAllow:
+            blockAllow:
+            conditionAndAlwayTrueAllow:
+            conditionAndAlwayFalseAllow:
+            conditionDoubleConstAllow:
+            conditionLikeTrueAllow:
+            selectAllColumnAllow:
+            deleteAllow:
+            deleteWhereAlwayTrueCheck:
+            deleteWhereNoneCheck:
+            updateAllow:
+            updateWhereAlayTrueCheck:
+            updateWhereNoneCheck:
+            insertAllow:
+            mergeAllow:
+            minusAllow:
+            intersectAllow:
+            replaceAllow:
+            setAllow:
+            commitAllow:
+            rollbackAllow:
+            useAllow:
+            multiStatementAllow:
+            truncateAllow:
+            commentAllow:
+            strictSyntaxCheck:
+            constArithmeticAllow:
+            limitZeroAllow:
+            describeAllow:
+            showAllow:
+            schemaCheck:
+            tableCheck:
+            functionCheck:
+            objectCheck:
+            variantCheck:
+            mustParameterized:
+            doPrivilegedAllow:
+            dir:
+            tenantTablePattern:
+            tenantColumn:
+            wrapAllow:
+            metadataAllow:
+            conditionOpXorAllow:
+            conditionOpBitwseAllow:
+            caseConditionConstAllow:
+            completeInsertValuesCheck:
+            insertValuesCheckSize:
+            selectLimit:
+        stat:
+          merge-sql:
+          log-slow-sql:
+          slow-sql-millis: 
+      datasource:
+        master:
+          username: root
+          password: 123456
+          driver-class-name: com.mysql.jdbc.Driver
+          url: jdbc:mysql://xx.xx.xx.xx:3306/dynamic?characterEncoding=utf8&useSSL=false
+          druid: # 以下是独立参数,每个库可以重新设置
+            initial-size:
+            validation-query: select 1 FROM DUAL #比如oracle就需要重新设置这个
+            public-key: #(非全局参数)设置即表示启用加密,底层会自动帮你配置相关的连接参数和filter,推荐使用本项目自带的加密方法。
+#           ......
 
+# 生成 publickey 和密码,推荐使用本项目自带的加密方法。
+# java -cp druid-1.1.10.jar com.alibaba.druid.filter.config.ConfigTools youpassword
+```
+
+预置建库脚本
+* [参考配置](https://github.com/dynamic-datasource/dynamic-datasource-doc/blob/master/docs/guide/advance/Init-Schema-Data.md)
+* 脚本文件放在resources目录,在默认数据源中增加配置:
+  ```
+  spring:
+    datasource:
+      dynamic:
+        primary: order
+        datasource:
+          order:
+            # 基础配置省略...
+            schema: db/order/schema.sql # 配置则生效,自动初始化表结构。
+            data: db/order/data.sql # 配置则生效,自动初始化数据。
+            continue-on-error: true # 默认true,初始化失败是否继续
+            separator: ";" # sql默认分号分隔符,一般无需更改
+  ```
+* 注意事项:
+  - schema: 建立连接时会执行,因此建表应使用CREATE TABLE IF NOT EXISTS确保不会重复创建。
+  - data: 建立连接时会执行,应确保数据不会重复插入。
+
+树型内部码工具
+* 可对实现ITreeEntity的model自动生成innerCode内部码。
 	
 最新变化
 ---------------

+ 4 - 2
fm-mybatis/src/main/java/com/persagy/fm/mybatis/config/MyBatisWebConfigurer.java

@@ -3,6 +3,7 @@ package com.persagy.fm.mybatis.config;
 import com.persagy.fm.mybatis.handler.DynamicDataSourceHandler;
 import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.Configuration;
+import org.springframework.core.annotation.Order;
 import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
 import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
 
@@ -12,6 +13,7 @@ import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
  * @date 2021-03-29
  */
 @Configuration
+@Order(2)
 public class MyBatisWebConfigurer implements WebMvcConfigurer {
 
     @Bean
@@ -21,7 +23,7 @@ public class MyBatisWebConfigurer implements WebMvcConfigurer {
 
     @Override
     public void addInterceptors(InterceptorRegistry registry) {
-        // 设置拦截的路径、不拦截的路径、优先级等等
-        registry.addInterceptor(dynamicDataSourceHandler()).addPathPatterns("/**");
+        // 设置拦截的路径、不拦截的路径、优先级等等 -- 在contextHandler之后执行
+        registry.addInterceptor(dynamicDataSourceHandler()).order(20).addPathPatterns("/**");
     }
 }

+ 17 - 0
fm-mybatis/src/main/java/com/persagy/fm/mybatis/dao/DbDao.java

@@ -0,0 +1,17 @@
+package com.persagy.fm.mybatis.dao;
+
+import org.apache.ibatis.annotations.Param;
+
+/**
+ * 数据库操作dao
+ * @author Charlie Yu
+ * @date 2021-03-30
+ */
+public interface DbDao {
+
+    /**
+     * 创建schema
+     * @param schema 实例名
+     */
+    void createDataBase(@Param("schema") String schema);
+}

+ 30 - 0
fm-mybatis/src/main/java/com/persagy/fm/mybatis/dao/SimpleTreeCodeDao.java

@@ -0,0 +1,30 @@
+package com.persagy.fm.mybatis.dao;
+
+import org.apache.ibatis.annotations.Param;
+
+
+/**
+ * 树编码使用的dao
+ * @author Charlie Yu
+ * @date 2021-03-29
+ */
+public interface SimpleTreeCodeDao {
+
+    /**
+     * 取得指定表指定树层次的最大编码
+     * @param tableName      表名
+     * @param parentCode     父节点编码
+     * @param innerCodeField 内码字段名
+     * @return 最大编码
+     */
+    String getMaxCode(@Param("tableName") String tableName, @Param("parentCode") String parentCode, @Param("innerCodeField") String innerCodeField);
+
+    /**
+     * 更新指定表的指定上级的实体的树编码
+     * @param tableName     表名
+     * @param parentCode    新的上级编码
+     * @param oldParentCode 旧的上级编码
+     * @param innerCodeField 内码字段名
+     */
+    void updateSubTreeCode(@Param("tableName") String tableName, @Param("parentCode") String parentCode, @Param("oldParentCode") String oldParentCode, @Param("innerCodeField") String innerCodeField);
+}

+ 29 - 14
fm-mybatis/src/main/java/com/persagy/fm/mybatis/handler/DynamicDataSourceHandler.java

@@ -1,12 +1,13 @@
 package com.persagy.fm.mybatis.handler;
 
 import com.baomidou.dynamic.datasource.DynamicRoutingDataSource;
-import com.baomidou.dynamic.datasource.creator.DruidDataSourceCreator;
+import com.baomidou.dynamic.datasource.creator.DefaultDataSourceCreator;
 import com.baomidou.dynamic.datasource.spring.boot.autoconfigure.DataSourceProperty;
 import com.baomidou.dynamic.datasource.spring.boot.autoconfigure.DynamicDataSourceProperties;
 import com.baomidou.dynamic.datasource.toolkit.DynamicDataSourceContextHolder;
 import com.persagy.fm.common.context.AppContext;
 import com.persagy.fm.common.helper.SpringHelper;
+import com.persagy.fm.mybatis.service.IDbService;
 import org.apache.commons.lang3.StringUtils;
 import org.springframework.beans.BeanUtils;
 import org.springframework.beans.factory.annotation.Autowired;
@@ -15,8 +16,6 @@ import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 import javax.sql.DataSource;
-import java.sql.SQLException;
-import java.util.Set;
 
 /**
  * 动态数据源拦截器
@@ -31,9 +30,11 @@ public class DynamicDataSourceHandler extends HandlerInterceptorAdapter {
     @Autowired
     private DynamicRoutingDataSource dataSource;
     @Autowired
-    private DruidDataSourceCreator dataSourceCreator;
+    private DefaultDataSourceCreator dataSourceCreator;
     @Autowired
     private DynamicDataSourceProperties defaultProperty;
+    @Autowired
+    private IDbService dbService;
 
     @Override
     public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
@@ -48,25 +49,22 @@ public class DynamicDataSourceHandler extends HandlerInterceptorAdapter {
     /**
      * 多租户处理,数据源切换
      */
-    private void resetDataSource() throws SQLException {
+    public void resetDataSource() {
         // 是否启用多数据源
         boolean isDynamic = SpringHelper.getBoolean("spring.datasource.dynamic.enabled", false);
         if(!isDynamic) {
             return;
         }
-
         // 默认为应用名
         String dbNameDefault = SpringHelper.getString("spring.application.name");
+        // 创建的数据源名称: 集团编码_应用名
         String newDsName = AppContext.getContext().getGroupCode() + "_" + dbNameDefault;
-        Set<String> dsNames = dataSource.getCurrentDataSources().keySet();
-        if(!dsNames.contains(newDsName)) {
-            // 取默认数据源
-            DataSourceProperty srcProperty = defaultProperty.getDatasource().get(dbNameDefault);
+        // 数据源中是否已存在
+        if(!dataSource.getCurrentDataSources().keySet().contains(newDsName)) {
             // 设置新数据源
-            DataSourceProperty property = new DataSourceProperty();
-            BeanUtils.copyProperties(srcProperty, property);
-            property.setPoolName(newDsName);
-            property.setUrl(StringUtils.replace(property.getUrl(), dbNameDefault, newDsName));
+            DataSourceProperty property = copyProperty(dbNameDefault, newDsName);
+            // 创建数据库实例
+            dbService.createDataBase(newDsName);
             // 创建数据源
             DataSource currDs = dataSourceCreator.createDataSource(property);
             dataSource.addDataSource(newDsName, currDs);
@@ -74,4 +72,21 @@ public class DynamicDataSourceHandler extends HandlerInterceptorAdapter {
         // 设置当前数据源
         DynamicDataSourceContextHolder.push(newDsName);
     }
+
+    /**
+     * 复制新的数据源
+     * @param dbNameDefault 默认连接的实例名
+     * @param newDsName 新创建的实例名
+     * @return
+     */
+    private DataSourceProperty copyProperty(String dbNameDefault, String newDsName) {
+        // 取默认数据源
+        DataSourceProperty srcProperty = defaultProperty.getDatasource().get(defaultProperty.getPrimary());
+        // 设置新数据源
+        DataSourceProperty property = new DataSourceProperty();
+        BeanUtils.copyProperties(srcProperty, property);
+        property.setPoolName(newDsName);
+        property.setUrl(StringUtils.replace(property.getUrl(), dbNameDefault, newDsName));
+        return property;
+    }
 }

+ 137 - 0
fm-mybatis/src/main/java/com/persagy/fm/mybatis/helper/TreeCodeHelper.java

@@ -0,0 +1,137 @@
+package com.persagy.fm.mybatis.helper;
+
+import com.persagy.fm.common.helper.SpringHelper;
+import com.persagy.fm.common.model.entity.ITreeEntity;
+import com.persagy.fm.mybatis.service.ITreeCodeService;
+import org.apache.commons.lang3.StringUtils;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+
+/**
+ * 树编码助手
+ * @author Charlie Yu
+ * @date 2021-03-29
+ */
+public final class TreeCodeHelper {
+
+    /**
+     * 编码引擎
+     */
+    private static ITreeCodeService service = null;
+
+    /**
+     * 取得编码服务
+     * @return 编码服务
+     */
+    public static ITreeCodeService getService() {
+        if (service == null) {
+            service = SpringHelper.getBean(ITreeCodeService.class);
+        }
+        return service;
+    }
+
+    /**
+     * 为指定实体生成树编码
+     * @param entity     树形实体
+     * @param parentCode 上级编码
+     * @return 树形实体
+     */
+    public static <T extends ITreeEntity> T generateTreeCode(T entity, String parentCode) {
+        return getService().generateTreeCode(entity, parentCode);
+    }
+
+    /**
+     * 根据上级实体生成下级编码,如果上级实体为空则传入空实体
+     * @param parent 上级实体
+     * @return 树形编码
+     */
+    public static <T extends ITreeEntity> String generateTreeCodeByParent(T parent) {
+        return getService().generateTreeCodeByParent(parent);
+    }
+
+    /**
+     * 根据表名上级节点id上级节点编码生成新的下级编码
+     * @param tableName      表名
+     * @param parentCode     上级节点编码
+     * @param extendWhereSql 扩展条件
+     * @return 新的编码
+     */
+    public static String generateTreeCode(String tableName, String parentCode, String extendWhereSql) {
+        return getService().generateTreeCode(tableName, parentCode, extendWhereSql);
+    }
+
+    /**
+     * 为一组相同上级的实体生成树编码
+     * @param entitys    树形实体数组
+     * @param parentCode 上级编码
+     * @return 树形实体数组
+     */
+    public static <T extends ITreeEntity> List<T> generateTreeCodes(List<T> entitys, String parentCode) {
+        return getService().generateTreeCodes(entitys, parentCode);
+    }
+
+    /**
+     * 根据上级实体生成多个下级编码,如果上级实体为空则传入空实体
+     * @param parent 上级实体
+     * @param length 编码个数
+     * @return 编码数组
+     */
+    public static <T extends ITreeEntity> String[] generateTreeCodesByParent(T parent, int length) {
+        return getService().generateTreeCodesByParent(parent, length);
+    }
+
+    /**
+     * 根据实体树编码同步其所有子节点编码
+     * @param entity      需要同步子节点编码的节点
+     * @param oldTreeCode 原有的实体树编码
+     */
+    public static <T extends ITreeEntity> void synchronizeSubTree(T entity, String oldTreeCode) {
+        getService().synchronizeSubTree(entity, oldTreeCode);
+    }
+
+    /**
+     * 树形实体更改上级后的处理
+     * @param entity     树形实体
+     * @param parentCode 上级编码
+     */
+    public static <T extends ITreeEntity> ITreeEntity afterMoveNodeToParent(T entity, String parentCode) {
+        return getService().afterMoveNodeToParent(entity, parentCode);
+    }
+
+    /**
+     * 取得当前编码的所有上级节点编码数组
+     * @param treeCode 树编码
+     * @return 上级节点编码数组
+     */
+    public static String[] getSuppriorTreeCodes(String treeCode, boolean includeSelf) {
+        if (StringUtils.isBlank(treeCode) || treeCode.length() == ITreeCodeService.TREE_CODE_LENGTH) {
+            if (treeCode != null && treeCode.length() == ITreeCodeService.TREE_CODE_LENGTH && includeSelf) {
+                return new String[]{treeCode};
+            }
+            return null;
+        }
+        if (treeCode.length() % ITreeCodeService.TREE_CODE_LENGTH != 0) {
+            throw new IllegalArgumentException();
+        }
+
+        List<String> parentCodes = new ArrayList<String>();
+        for (int i = ITreeCodeService.TREE_CODE_LENGTH; i < treeCode.length(); i += ITreeCodeService.TREE_CODE_LENGTH) {
+            String code = treeCode.substring(0, i);
+            parentCodes.add(code);
+        }
+        if (includeSelf) {
+            parentCodes.add(treeCode);
+        }
+        return parentCodes.toArray(new String[parentCodes.size()]);
+    }
+
+    public static void main(String[] args) {
+        Arrays.stream(getSuppriorTreeCodes("00001", true)).forEach(s -> {
+            System.out.println(s);
+        });
+    }
+
+}

+ 15 - 0
fm-mybatis/src/main/java/com/persagy/fm/mybatis/service/IDbService.java

@@ -0,0 +1,15 @@
+package com.persagy.fm.mybatis.service;
+
+/**
+ * 数据库操作 Service
+ * @author Charlie Yu
+ * @date 2021-03-30
+ */
+public interface IDbService {
+
+    /**
+     * 创建数据库
+     * @param schema 实例名
+     */
+    void createDataBase(String schema);
+}

+ 72 - 0
fm-mybatis/src/main/java/com/persagy/fm/mybatis/service/ITreeCodeService.java

@@ -0,0 +1,72 @@
+package com.persagy.fm.mybatis.service;
+
+
+import com.persagy.fm.common.model.entity.ITreeEntity;
+
+import java.util.List;
+
+/**
+ * 树代码生成引擎
+ * @author Charlie Yu
+ * @date 2021-03-29
+ */
+public interface ITreeCodeService {
+
+    /** 树编码每级码长 */
+    int TREE_CODE_LENGTH = 5;
+
+    /**
+     * 为指定实体生成树编码
+     * @param entity     树形实体
+     * @param parentCode 上级编码
+     * @return 树形实体
+     */
+    <T extends ITreeEntity> T generateTreeCode(T entity, String parentCode);
+
+    /**
+     * 根据上级实体生成下级编码,如果上级实体为空则传入空实体
+     * @param parent 上级实体
+     * @return 树形编码
+     */
+    <T extends ITreeEntity> String generateTreeCodeByParent(T parent);
+
+    /**
+     * 根据表名上级节点id上级节点编码生成新的下级编码
+     * @param tableName      表名
+     * @param parentCode     上级节点编码
+     * @param innerCodeField
+     * @return 新的编码
+     */
+    String generateTreeCode(String tableName, String parentCode, String innerCodeField);
+
+    /**
+     * 为一组相同上级的实体生成树编码
+     * @param entitys    树形实体数组
+     * @param parentCode 櫖上级编码
+     * @return 树形实体数组
+     */
+    <T extends ITreeEntity> List<T> generateTreeCodes(List<T> entitys, String parentCode);
+
+    /**
+     * 根据上级实体生成多个下级编码,如果上级实体为空则传入空实体
+     * @param parent 上级实体
+     * @param length 编码个数
+     * @return 编码数组
+     */
+    <T extends ITreeEntity> String[] generateTreeCodesByParent(T parent, int length);
+
+    /**
+     * 根据实体树编码同步其所有子节点编码
+     * @param entity      需要同步子节点编码的节点
+     * @param oldTreeCode 原有的实体树编码
+     */
+    <T extends ITreeEntity> void synchronizeSubTree(T entity, String oldTreeCode);
+
+    /**
+     * 树形实体更改上级后的处理
+     * @param entity     树形实体
+     * @param parentCode 新的上级编码
+     * @return ITreeEntity 树实体对象
+     */
+    <T extends ITreeEntity> T afterMoveNodeToParent(T entity, String parentCode);
+}

+ 24 - 0
fm-mybatis/src/main/java/com/persagy/fm/mybatis/service/impl/DbServiceImpl.java

@@ -0,0 +1,24 @@
+package com.persagy.fm.mybatis.service.impl;
+
+import com.persagy.fm.mybatis.dao.DbDao;
+import com.persagy.fm.mybatis.service.IDbService;
+import org.springframework.stereotype.Service;
+
+import javax.annotation.Resource;
+
+/**
+ * 数据库操作 Impl
+ * @author Charlie Yu
+ * @date 2021-03-30
+ */
+@Service
+public class DbServiceImpl implements IDbService {
+
+    @Resource
+    private DbDao dao;
+
+    @Override
+    public void createDataBase(String schema) {
+        dao.createDataBase(schema);
+    }
+}

+ 191 - 0
fm-mybatis/src/main/java/com/persagy/fm/mybatis/service/impl/SimpleTreeCodeServiceImpl.java

@@ -0,0 +1,191 @@
+package com.persagy.fm.mybatis.service.impl;
+
+import com.persagy.common.exception.BusinessException;
+import com.persagy.fm.common.context.AppContext;
+import com.persagy.fm.common.helper.LockHelper;
+import com.persagy.fm.common.model.entity.ITreeEntity;
+import com.persagy.fm.mybatis.dao.SimpleTreeCodeDao;
+import com.persagy.fm.mybatis.service.ITreeCodeService;
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.stereotype.Service;
+
+import javax.annotation.Resource;
+import java.util.List;
+
+
+/**
+ * 树形编码服务简单实现,目前支持单节点,集群时增加一位节点代码
+ * @author Charlie Yu
+ * @date 2021-03-29
+ */
+@Service
+public class SimpleTreeCodeServiceImpl implements ITreeCodeService {
+
+    /** 数据访问对象 */
+    @Resource
+    private SimpleTreeCodeDao dao = null;
+
+    @Override
+    public <T extends ITreeEntity> T generateTreeCode(T entity, String parentCode) {
+        String tableName = entity.getTreeTableName();
+        String innerCodeField = entity.getInnerCodeField();
+        String code = generateTreeCode(tableName, parentCode, innerCodeField);
+        entity.setInnerCode(code);
+        return entity;
+    }
+
+    @Override
+    public <T extends ITreeEntity> String generateTreeCodeByParent(T parent) {
+        return generateTreeCode(parent.getTreeTableName(), parent.getInnerCode(), parent.getInnerCodeField());
+    }
+
+    @Override
+    public String generateTreeCode(String tableName, String parentCode, String innerCodeField) {
+        if (parentCode == null) {
+            parentCode = StringUtils.EMPTY;
+        }
+        String key = AppContext.getContext().getGroupCode() + "-" + tableName + parentCode;
+        if (LockHelper.lock(key)) {
+            try {
+                String maxCode = dao.getMaxCode(tableName, parentCode, innerCodeField);
+                if (maxCode != null) {
+                    maxCode = maxCode.substring(parentCode.length());
+                }
+//			if(StringUtils.isBlank(maxCode)){
+//				maxCode="00000";
+//			}
+                maxCode = increase(maxCode);
+                return parentCode + maxCode;
+            } finally {
+                LockHelper.unlock(key);
+            }
+        }
+        throw new BusinessException("编码生成失败,请重新操作");
+    }
+
+    @Override
+    public <T extends ITreeEntity> List<T> generateTreeCodes(List<T> entitys, String parentCode) {
+        if (entitys == null || entitys.size() == 0) {
+            return entitys;
+        }
+        if (parentCode == null) {
+            parentCode = StringUtils.EMPTY;
+        }
+        int length = entitys.size();
+        ITreeEntity entity = entitys.get(0);
+
+        String tableName = entity.getTreeTableName();
+        String innerCodeField = entity.getInnerCodeField();
+        String key = AppContext.getContext().getGroupCode() + "-" + tableName + "-" + parentCode;
+        if (LockHelper.lock(key)) {
+            try {
+                String maxCode = dao.getMaxCode(tableName, parentCode, innerCodeField);
+                if (maxCode != null) {
+                    maxCode = maxCode.substring(parentCode.length());
+                }
+                for (int i = 0; i < length; i++) {
+                    maxCode = increase(maxCode);
+                    entitys.get(i).setInnerCode(parentCode + maxCode);
+                }
+                return entitys;
+            } finally {
+                LockHelper.unlock(key);
+            }
+        }
+        throw new BusinessException("编码生成失败,请重新操作");
+    }
+
+    @Override
+    public <T extends ITreeEntity> String[] generateTreeCodesByParent(T parent, int length) {
+        String parentCode = parent.getInnerCode();
+        if (parentCode == null) {
+            parentCode = StringUtils.EMPTY;
+        }
+        String tableName = parent.getTreeTableName();
+        String[] codes = new String[length];
+        String key = getLockKey(tableName);
+        if (LockHelper.lock(key)) {
+            try {
+                String maxCode = dao.getMaxCode(tableName, parentCode, parent.getInnerCodeField());
+                if (maxCode != null) {
+                    maxCode = maxCode.substring(parentCode.length());
+                }
+                for (int i = 0; i < length; i++) {
+                    maxCode = increase(maxCode);
+                    codes[i] = parentCode + maxCode;
+                }
+                return codes;
+            } finally {
+                LockHelper.unlock(key);
+            }
+        }
+        throw new BusinessException("编码生成失败,请重新操作");
+    }
+
+    @Override
+    public <T extends ITreeEntity> void synchronizeSubTree(T entity, String oldTreeCode) {
+        if (entity == null || StringUtils.isEmpty(entity.getInnerCode())) {
+            return;
+        }
+        String tableName = entity.getTreeTableName();
+        String parentCode = entity.getInnerCode();
+        dao.updateSubTreeCode(tableName, parentCode, oldTreeCode, entity.getInnerCodeField());
+    }
+
+    @Override
+    public <T extends ITreeEntity> T afterMoveNodeToParent(T entity, String parentCode) {
+        if (entity == null || StringUtils.isEmpty(entity.getInnerCode())) {
+            return entity;
+        }
+        String oldTreeCode = entity.getInnerCode();
+        String key = getLockKey(entity.getTreeTableName());
+        if (LockHelper.lock(key)) {
+            try {
+                generateTreeCode(entity, parentCode);
+                synchronizeSubTree(entity, oldTreeCode);
+                return entity;
+            } finally {
+                LockHelper.unlock(key);
+            }
+        }
+        throw new BusinessException("节点移动失败,请重试操作");
+    }
+
+    /**
+     * 递增最大编码
+     * @param maxCode 原有最大编码
+     * @return 递增后编码
+     */
+    public String increase(String maxCode) {
+        long v = 0;
+        if (maxCode != null) {
+            v = Long.parseLong(maxCode, Character.MAX_RADIX);
+            v++;
+        }
+        maxCode = Long.toString(v, Character.MAX_RADIX);
+        int digit = TREE_CODE_LENGTH - maxCode.length();
+        if (digit < 0) {
+            throw new RuntimeException("Tree Code digit overflow!");
+        }
+        char[] chars = new char[TREE_CODE_LENGTH];
+        for (int i = 0; i < digit; i++) {
+            chars[i] = '0';
+        }
+        System.arraycopy(maxCode.toCharArray(), 0, chars, digit, maxCode.length());
+        return new String(chars);
+    }
+
+    /**
+     * 取得锁的键值
+     * @param tableName 表名
+     * @return 键值
+     */
+    private String getLockKey(String tableName) {
+        String tenant = AppContext.getContext().getGroupCode();
+        if (StringUtils.isEmpty(tenant)) {
+            throw new BusinessException("未指定租户id");
+        }
+        return tenant + "-" + tableName;
+    }
+
+}

+ 11 - 0
fm-mybatis/src/main/resources/mapper/DbDao.xml

@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE mapper PUBLIC
+	"-//mybatis.org//DTD Mapper 3.0//EN"
+	"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+
+<mapper namespace="com.persagy.fm.mybatis.dao.DbDao">
+	<select id="createDataBase">
+		create schema if not exists `${schema}` default character set utf8 collate utf8_general_ci
+	</select>
+
+</mapper>

+ 17 - 0
fm-mybatis/src/main/resources/mapper/SimpleTreeCodeDao.xml

@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE mapper PUBLIC
+	"-//mybatis.org//DTD Mapper 3.0//EN"
+	"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+
+<mapper namespace="com.persagy.fm.mybatis.dao.SimpleTreeCodeDao">
+	<select id="getMaxCode" resultType="java.lang.String">
+		select max(${innerCodeField}) as code
+		from ${tableName}
+		where ${innerCodeField} like '${parentCode}_____'
+	</select>
+
+	<select id="updateSubTreeCode">
+		update ${tableName} set ${innerCodeField} = (concat('${parentCode}',substr(${innerCodeField},${oldParentCode.length()+1})))
+		where ${innerCodeField} like '${oldParentCode}%'
+	</select>
+</mapper>

+ 1 - 1
fm-translate/src/main/resources/mapper/adder/translate/ITranslateDao.xml

@@ -3,7 +3,7 @@
 	"-//mybatis.org//DTD Mapper 3.0//EN"
 	"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
 
-<mapper namespace="cn.crcc.ccit.chassis.translate.dao.ITranslateDao">
+<mapper namespace="com.persagy.fm.translate.dao.ITranslateDao">
 	<select id="findTransBySql" resultType="map">
 		${sql}
 	</select>

+ 1 - 0
pom.xml

@@ -17,5 +17,6 @@
         <module>fm-server</module>
         <module>fm-common</module>
         <module>fm-translate</module>
+        <module>fm-mybatis</module>
     </modules>
 </project>