Browse Source

新增 表结构自动刷新组件、在线文档组件

18339543638 2 years ago
parent
commit
293d8e87fb
31 changed files with 2635 additions and 12 deletions
  1. 31 0
      tr-dependencies/pom.xml
  2. 16 0
      tr-framework/src/main/java/cn/tr/core/annotation/Comment.java
  3. 8 1
      tr-framework/src/main/java/cn/tr/core/utils/ServletUtils.java
  4. 3 0
      tr-plugins/pom.xml
  5. 28 0
      tr-plugins/tr-spring-boot-starter-plugin-doc/pom.xml
  6. 58 0
      tr-plugins/tr-spring-boot-starter-plugin-doc/src/main/java/cn/tr/plugin/doc/TrDocAutoConfiguration.java
  7. 1 0
      tr-plugins/tr-spring-boot-starter-plugin-doc/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
  8. 2 2
      tr-plugins/tr-spring-boot-starter-plugin-eventbus/src/test/java/cn/tr/plugin/eventbus/SpringBootSubPubTest.java
  9. 51 0
      tr-plugins/tr-spring-boot-starter-plugin-mp-enhance-actable/pom.xml
  10. 71 0
      tr-plugins/tr-spring-boot-starter-plugin-mp-enhance-actable/src/main/java/cn/tr/plugin/mp/enhance/actable/ActableAutoConfiguration.java
  11. 69 0
      tr-plugins/tr-spring-boot-starter-plugin-mp-enhance-actable/src/main/java/cn/tr/plugin/mp/enhance/actable/handler/TrStartUpHandlerImpl.java
  12. 939 0
      tr-plugins/tr-spring-boot-starter-plugin-mp-enhance-actable/src/main/java/cn/tr/plugin/mp/enhance/actable/manager/TrSysMysqlCreateTableManagerImpl.java
  13. 250 0
      tr-plugins/tr-spring-boot-starter-plugin-mp-enhance-actable/src/main/java/cn/tr/plugin/mp/enhance/actable/proxy/MybatisConfigurationProxy.java
  14. 407 0
      tr-plugins/tr-spring-boot-starter-plugin-mp-enhance-actable/src/main/java/cn/tr/plugin/mp/enhance/actable/utils/ColumnUtils.java
  15. 1 0
      tr-plugins/tr-spring-boot-starter-plugin-mp-enhance-actable/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
  16. 2 0
      tr-plugins/tr-spring-boot-starter-plugin-mp-enhance-actable/src/main/resources/mp-actable.properties
  17. 5 0
      tr-plugins/tr-spring-boot-starter-plugin-mybatis/pom.xml
  18. 7 0
      tr-plugins/tr-spring-boot-starter-plugin-mybatis/src/main/java/cn/tr/plugin/mybatis/pojo/BaseDO.java
  19. 43 0
      tr-plugins/tr-spring-boot-starter-plugin-operatelog/pom.xml
  20. 19 0
      tr-plugins/tr-spring-boot-starter-plugin-operatelog/src/main/java/cn/tr/plugin/operatelog/TrOperateLogAutoConfiguration.java
  21. 62 0
      tr-plugins/tr-spring-boot-starter-plugin-operatelog/src/main/java/cn/tr/plugin/operatelog/annotation/OperateLog.java
  22. 96 0
      tr-plugins/tr-spring-boot-starter-plugin-operatelog/src/main/java/cn/tr/plugin/operatelog/bo/OperateLogBO.java
  23. 326 0
      tr-plugins/tr-spring-boot-starter-plugin-operatelog/src/main/java/cn/tr/plugin/operatelog/config/OperateLogAspect.java
  24. 54 0
      tr-plugins/tr-spring-boot-starter-plugin-operatelog/src/main/java/cn/tr/plugin/operatelog/enums/OperateTypeEnum.java
  25. 27 0
      tr-plugins/tr-spring-boot-starter-plugin-operatelog/src/main/java/cn/tr/plugin/operatelog/strategy/OperateStrategy.java
  26. 1 0
      tr-plugins/tr-spring-boot-starter-plugin-operatelog/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
  27. 5 1
      tr-plugins/tr-spring-boot-starter-plugin-satoken/src/main/java/cn/tr/plugin/security/config/LoginUserStrategyConfig.java
  28. 10 0
      tr-test/pom.xml
  29. 17 7
      tr-test/src/main/java/cn/tr/test/TestController.java
  30. 23 0
      tr-test/src/main/resources/application-doc.yml
  31. 3 1
      tr-test/src/main/resources/application.yml

+ 31 - 0
tr-dependencies/pom.xml

@@ -23,8 +23,11 @@
         <mapstruct.version>1.5.3.Final</mapstruct.version>
         <lombok.version>1.18.20</lombok.version>
         <ip2region.version>2.6.6</ip2region.version>
+
+        <!--数据库-->
         <mybatis-plus.version>3.5.3.1</mybatis-plus.version>
         <mybatis-plus-generator.version>3.5.3.1</mybatis-plus-generator.version>
+        <mysql-connector.version>8.0.27</mysql-connector.version>
         <druid.version>1.2.8</druid.version>
         <pagehelper.boot.version>1.4.5</pagehelper.boot.version>
         <!--权限相关-->
@@ -51,6 +54,9 @@
 
         <!--flink版本-->
         <flink.version>1.16.1</flink.version>
+
+        <!--在线文档-->
+        <knife4j.verison>4.0.0</knife4j.verison>
     </properties>
 
 
@@ -241,6 +247,13 @@
                 <version>${flink.version}</version>
             </dependency>
 
+            <!--在线文档-->
+            <dependency>
+                <groupId>com.github.xiaoymin</groupId>
+                <artifactId>knife4j-openapi2-spring-boot-starter</artifactId>
+                <version>${knife4j.verison}</version>
+            </dependency>
+
             <!--业务组件-->
             <dependency>
                 <groupId>cn.tr</groupId>
@@ -260,6 +273,11 @@
                 <version>${revision}</version>
             </dependency>
 
+            <dependency>
+                <groupId>mysql</groupId>
+                <artifactId>mysql-connector-java</artifactId>
+                <version>${mysql-connector.version}</version>
+            </dependency>
 
             <dependency>
                 <groupId>cn.tr</groupId>
@@ -304,6 +322,19 @@
                 <version>${revision}</version>
             </dependency>
 
+            <!--日志记录插件-->
+            <dependency>
+                <groupId>cn.tr</groupId>
+                <artifactId>tr-spring-boot-starter-plugin-operatelog</artifactId>
+                <version>${revision}</version>
+            </dependency>
+
+            <!--在线文档插件-->
+            <dependency>
+                <groupId>cn.tr</groupId>
+                <artifactId>tr-spring-boot-starter-plugin-doc</artifactId>
+                <version>${revision}</version>
+            </dependency>
 
             <!--websocket插件-->
             <dependency>

+ 16 - 0
tr-framework/src/main/java/cn/tr/core/annotation/Comment.java

@@ -0,0 +1,16 @@
+package cn.tr.core.annotation;
+
+import java.lang.annotation.*;
+
+/**
+ * @@interface : Comment
+ * @Description : 字段注释
+ * @Author : LF
+ * @Date: 2023年03月23日
+ */
+@Target({ElementType.FIELD})
+@Retention(RetentionPolicy.RUNTIME)
+@Documented
+public @interface Comment {
+    String value() default "";
+}

+ 8 - 1
tr-framework/src/main/java/cn/tr/core/utils/ServletUtils.java

@@ -121,7 +121,14 @@ public class ServletUtils {
      * @return
      */
     public static String getClientIP() {
-        HttpServletRequest request = getRequest();
+        return getClientIP(getRequest());
+    }
+
+    /**
+     * 获取客户端ip地址
+     * @return
+     */
+    public static String getClientIP(  HttpServletRequest request ) {
         if (request == null) {
             return null;
         }

+ 3 - 0
tr-plugins/pom.xml

@@ -31,6 +31,9 @@
         <module>tr-spring-boot-starter-plugin-mq</module>
         <module>tr-spring-boot-starter-plugin-flink</module>
         <module>tr-spring-boot-starter-plugin-desensitize</module>
+        <module>tr-spring-boot-starter-plugin-operatelog</module>
+        <module>tr-spring-boot-starter-plugin-doc</module>
+        <module>tr-spring-boot-starter-plugin-mp-enhance-actable</module>
     </modules>
 
 </project>

+ 28 - 0
tr-plugins/tr-spring-boot-starter-plugin-doc/pom.xml

@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <parent>
+        <artifactId>tr-plugins</artifactId>
+        <groupId>cn.tr</groupId>
+        <version>${revision}</version>
+    </parent>
+    <modelVersion>4.0.0</modelVersion>
+    <version>${revision}</version>
+    <artifactId>tr-spring-boot-starter-plugin-doc</artifactId>
+
+
+    <dependencies>
+        <dependency>
+            <groupId>com.github.xiaoymin</groupId>
+            <artifactId>knife4j-openapi2-spring-boot-starter</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-web</artifactId>
+            <scope>provided</scope>
+        </dependency>
+    </dependencies>
+
+</project>

+ 58 - 0
tr-plugins/tr-spring-boot-starter-plugin-doc/src/main/java/cn/tr/plugin/doc/TrDocAutoConfiguration.java

@@ -0,0 +1,58 @@
+package cn.tr.plugin.doc;
+
+import org.springframework.beans.BeansException;
+import org.springframework.beans.factory.config.BeanPostProcessor;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Import;
+import org.springframework.util.ReflectionUtils;
+import org.springframework.web.servlet.mvc.method.RequestMappingInfoHandlerMapping;
+import springfox.bean.validators.configuration.BeanValidatorPluginsConfiguration;
+import springfox.documentation.spring.web.plugins.WebMvcRequestHandlerProvider;
+import springfox.documentation.swagger2.annotations.EnableSwagger2WebMvc;
+
+import java.lang.reflect.Field;
+import java.util.List;
+import java.util.stream.Collectors;
+
+/**
+ * @ClassName : TrDocAutoConfiguration
+ * @Description : 解决doc和springboot版本不兼容问题
+ * @Author : LF
+ * @Date: 2023年03月21日
+ */
+@EnableSwagger2WebMvc
+@Import(BeanValidatorPluginsConfiguration.class)
+public class TrDocAutoConfiguration {
+    @Bean
+    public static BeanPostProcessor springfoxHandlerProviderBeanPostProcessor() {
+        return new BeanPostProcessor() {
+
+            @Override
+            public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
+                if (bean instanceof WebMvcRequestHandlerProvider) {
+                    customizeSpringfoxHandlerMappings(getHandlerMappings(bean));
+                }
+                return bean;
+            }
+
+            private <T extends RequestMappingInfoHandlerMapping> void customizeSpringfoxHandlerMappings(List<T> mappings) {
+                List<T> copy = mappings.stream()
+                        .filter(mapping -> mapping.getPatternParser() == null)
+                        .collect(Collectors.toList());
+                mappings.clear();
+                mappings.addAll(copy);
+            }
+
+            @SuppressWarnings("unchecked")
+            private List<RequestMappingInfoHandlerMapping> getHandlerMappings(Object bean) {
+                try {
+                    Field field = ReflectionUtils.findField(bean.getClass(), "handlerMappings");
+                    field.setAccessible(true);
+                    return (List<RequestMappingInfoHandlerMapping>) field.get(bean);
+                } catch (IllegalArgumentException | IllegalAccessException e) {
+                    throw new IllegalStateException(e);
+                }
+            }
+        };
+    }
+}

+ 1 - 0
tr-plugins/tr-spring-boot-starter-plugin-doc/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports

@@ -0,0 +1 @@
+cn.tr.plugin.doc.TrDocAutoConfiguration

+ 2 - 2
tr-plugins/tr-spring-boot-starter-plugin-eventbus/src/test/java/cn/tr/plugin/eventbus/SpringBootSubPubTest.java

@@ -42,7 +42,7 @@ public class SpringBootSubPubTest extends BaseRedisUnitTest {
     @Test
     public void pub(){
         for (int i = 0; i < 10; i++) {
-            eventBus.publish("test",User.of("123","测试"));
+            eventBus.publish("test"+i,User.of("123","测试"));
         }
         while (true){
 
@@ -52,7 +52,7 @@ public class SpringBootSubPubTest extends BaseRedisUnitTest {
     @Slf4j
     public static class Sub{
         private static AtomicInteger atomicInteger=new AtomicInteger(0);
-        @Subscribe("test")
+        @Subscribe("test*")
         public void sub(TopicPayload user){
             int i = atomicInteger.incrementAndGet();
             log.info("第:"+i+"次接到数据:"+user);

+ 51 - 0
tr-plugins/tr-spring-boot-starter-plugin-mp-enhance-actable/pom.xml

@@ -0,0 +1,51 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <parent>
+        <artifactId>tr-plugins</artifactId>
+        <groupId>cn.tr</groupId>
+        <version>${revision}</version>
+    </parent>
+    <modelVersion>4.0.0</modelVersion>
+    <version>${revision}</version>
+    <artifactId>tr-spring-boot-starter-plugin-mp-enhance-actable</artifactId>
+
+
+    <description>和mybatis结合实现自动建表</description>
+    <dependencies>
+        <dependency>
+            <groupId>com.gitee.sunchenbin.mybatis.actable</groupId>
+            <artifactId>mybatis-enhance-actable</artifactId>
+            <version>1.5.0.RELEASE</version>
+            <exclusions>
+                <exclusion>
+                    <artifactId>mybatis-plus-annotation</artifactId>
+                    <groupId>com.baomidou</groupId>
+                </exclusion>
+            </exclusions>
+        </dependency>
+
+        <dependency>
+            <groupId>cn.tr</groupId>
+            <artifactId>tr-spring-boot-starter-plugin-web</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>cn.tr</groupId>
+            <artifactId>tr-spring-boot-starter-plugin-mybatis</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>cn.tr</groupId>
+            <artifactId>tr-spring-boot-starter-plugin-test</artifactId>
+            <scope>test</scope>
+            <exclusions>
+                <exclusion>
+                    <groupId>org.junit.vintage</groupId>
+                    <artifactId>junit-vintage-engine</artifactId>
+                </exclusion>
+            </exclusions>
+        </dependency>
+    </dependencies>
+</project>

+ 71 - 0
tr-plugins/tr-spring-boot-starter-plugin-mp-enhance-actable/src/main/java/cn/tr/plugin/mp/enhance/actable/ActableAutoConfiguration.java

@@ -0,0 +1,71 @@
+package cn.tr.plugin.mp.enhance.actable;
+
+import cn.hutool.core.util.ReflectUtil;
+import cn.hutool.extra.spring.SpringUtil;
+import cn.tr.plugin.mp.enhance.actable.handler.TrStartUpHandlerImpl;
+import cn.tr.plugin.mp.enhance.actable.manager.TrSysMysqlCreateTableManagerImpl;
+import cn.tr.plugin.mp.enhance.actable.proxy.MybatisConfigurationProxy;
+import com.gitee.sunchenbin.mybatis.actable.manager.handler.StartUpHandlerImpl;
+import com.gitee.sunchenbin.mybatis.actable.manager.system.SysMysqlCreateTableManagerImpl;
+import org.apache.ibatis.session.SqlSessionFactory;
+import org.mybatis.spring.annotation.MapperScan;
+import org.springframework.beans.BeansException;
+import org.springframework.beans.factory.config.BeanPostProcessor;
+import org.springframework.beans.factory.config.PropertiesFactoryBean;
+import org.springframework.context.annotation.*;
+import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
+
+/**
+ * @ClassName : ActatbleAutoConfiguration
+ * @Description :
+ * @Author : LF
+ * @Date: 2023年03月22日
+ */
+@ComponentScan(value = "com.gitee.sunchenbin.mybatis.actable.manager.*"
+        ,excludeFilters = {@ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE,classes = {SysMysqlCreateTableManagerImpl.class, StartUpHandlerImpl.class})}
+        )
+@MapperScan({"cn.tr.plugin.mp.enhance.actable","com.gitee.sunchenbin.mybatis.actable.dao.*"})
+@PropertySources({@org.springframework.context.annotation.PropertySource("classpath:mp-actable.properties")})
+public class ActableAutoConfiguration implements BeanPostProcessor {
+    private boolean runActable;
+    private SqlSessionFactory sqlSessionFactory;
+
+    @Override
+    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
+        if(!runActable){
+            try {
+                sqlSessionFactory=sqlSessionFactory==null?SpringUtil.getBean(SqlSessionFactory.class):sqlSessionFactory;
+            }catch (Exception e){
+
+            }
+        }
+        if(sqlSessionFactory!=null){
+            if(!runActable){
+                runActable=true;
+                MybatisConfigurationProxy configurationProxy = new MybatisConfigurationProxy(sqlSessionFactory.getConfiguration());
+                TrStartUpHandlerImpl trStartUpHandler = new TrStartUpHandlerImpl(new TrSysMysqlCreateTableManagerImpl());
+                configurationProxy.addListener(aClass -> {
+                    trStartUpHandler
+                    .startHandler();
+                });
+                ReflectUtil.setFieldValue(sqlSessionFactory,"configuration",configurationProxy);
+                trStartUpHandler.startHandler();
+            }
+        }
+        return bean;
+    }
+
+
+
+    @Bean
+    public PropertiesFactoryBean configProperties() throws Exception{
+        PropertiesFactoryBean propertiesFactoryBean = new PropertiesFactoryBean();
+        PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
+        propertiesFactoryBean.setLocations(resolver.getResources("classpath*:application.properties"));
+        return propertiesFactoryBean;
+    }
+
+
+
+
+}

+ 69 - 0
tr-plugins/tr-spring-boot-starter-plugin-mp-enhance-actable/src/main/java/cn/tr/plugin/mp/enhance/actable/handler/TrStartUpHandlerImpl.java

@@ -0,0 +1,69 @@
+package cn.tr.plugin.mp.enhance.actable.handler;
+
+
+import cn.hutool.extra.spring.SpringUtil;
+import com.gitee.sunchenbin.mybatis.actable.manager.handler.StartUpHandler;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import com.gitee.sunchenbin.mybatis.actable.constants.Constants;
+import com.gitee.sunchenbin.mybatis.actable.manager.system.SysMysqlCreateTableManager;
+import com.gitee.sunchenbin.mybatis.actable.manager.util.ConfigurationUtil;
+
+/**
+ * 启动时进行处理的实现类
+ * @author chenbin.sun
+ *
+ */
+@SuppressWarnings("restriction")
+public class TrStartUpHandlerImpl implements StartUpHandler {
+
+	private static final Logger	log	= LoggerFactory.getLogger(TrStartUpHandlerImpl.class);
+
+	@Autowired
+	private ConfigurationUtil springContextUtil;
+
+	/** 数据库类型:mysql */
+	public static String MYSQL = "mysql";
+
+	/** 数据库类型:oracle */
+	public static String ORACLE = "oracle";
+
+	/** 数据库类型:sqlserver */
+	public static String SQLSERVER = "sqlserver";
+
+	/** 数据库类型:postgresql */
+	public static String POSTGRESQL = "postgresql";
+
+	/** 数据库类型  */
+	private static String databaseType = null;
+
+	private SysMysqlCreateTableManager sysMysqlCreateTableManager;
+
+	public TrStartUpHandlerImpl( SysMysqlCreateTableManager sysMysqlCreateTableManager) {
+		this.springContextUtil = SpringUtil.getBean(ConfigurationUtil.class);
+		this.sysMysqlCreateTableManager = sysMysqlCreateTableManager;
+	}
+
+	@Override
+	public void startHandler() {
+		// 获取配置信息
+		databaseType = springContextUtil.getConfig(Constants.DATABASE_TYPE_KEY) == null ? MYSQL : springContextUtil.getConfig(Constants.DATABASE_TYPE_KEY);
+
+		// 执行mysql的处理方法
+		if (MYSQL.equals(databaseType)) {
+			log.info("databaseType=mysql,开始执行mysql的处理方法");
+			sysMysqlCreateTableManager.createMysqlTable();
+		}else if (ORACLE.equals(databaseType)) {
+			log.info("databaseType=oracle,开始执行oracle的处理方法");
+		}else if (SQLSERVER.equals(databaseType)) {
+			log.info("databaseType=sqlserver,开始执行sqlserver的处理方法");
+		}else if (POSTGRESQL.equals(databaseType)) {
+			log.info("databaseType=postgresql,开始执行postgresql的处理方法");
+		}else{
+			log.info("没有找到符合条件的处理方法!");
+		}
+	}
+
+
+}

+ 939 - 0
tr-plugins/tr-spring-boot-starter-plugin-mp-enhance-actable/src/main/java/cn/tr/plugin/mp/enhance/actable/manager/TrSysMysqlCreateTableManagerImpl.java

@@ -0,0 +1,939 @@
+package cn.tr.plugin.mp.enhance.actable.manager;
+
+import cn.hutool.core.collection.CollectionUtil;
+import cn.hutool.extra.spring.SpringUtil;
+import cn.tr.plugin.mp.enhance.actable.utils.ColumnUtils;
+import com.gitee.sunchenbin.mybatis.actable.annotation.IgnoreUpdate;
+import com.gitee.sunchenbin.mybatis.actable.annotation.Index;
+import com.gitee.sunchenbin.mybatis.actable.annotation.Unique;
+import com.gitee.sunchenbin.mybatis.actable.command.CreateTableParam;
+import com.gitee.sunchenbin.mybatis.actable.command.MySqlTypeAndLength;
+import com.gitee.sunchenbin.mybatis.actable.command.SysMysqlColumns;
+import com.gitee.sunchenbin.mybatis.actable.command.SysMysqlTable;
+import com.gitee.sunchenbin.mybatis.actable.command.TableConfig;
+import com.gitee.sunchenbin.mybatis.actable.constants.Constants;
+import com.gitee.sunchenbin.mybatis.actable.constants.MySqlCharsetConstant;
+import com.gitee.sunchenbin.mybatis.actable.constants.MySqlEngineConstant;
+import com.gitee.sunchenbin.mybatis.actable.constants.MySqlTypeConstant;
+import com.gitee.sunchenbin.mybatis.actable.dao.system.CreateMysqlTablesMapper;
+import com.gitee.sunchenbin.mybatis.actable.manager.system.SysMysqlCreateTableManager;
+import com.gitee.sunchenbin.mybatis.actable.manager.util.ConfigurationUtil;
+import com.gitee.sunchenbin.mybatis.actable.utils.ClassTools;
+
+import lombok.extern.slf4j.Slf4j;
+import org.apache.ibatis.mapping.MappedStatement;
+import org.apache.ibatis.mapping.ResultMap;
+import org.apache.ibatis.session.SqlSessionFactory;
+import org.springframework.transaction.annotation.Transactional;
+import org.springframework.util.ReflectionUtils;
+import org.springframework.util.StringUtils;
+
+import java.lang.reflect.Field;
+import java.util.*;
+import java.util.Map.Entry;
+
+/**
+ * 项目启动时自动扫描配置的目录中的model,根据配置的规则自动创建或更新表 该逻辑只适用于mysql,其他数据库尚且需要另外扩展,因为sql的语法不同
+ *
+ * @author sunchenbin, Spet
+ * @version 2019/07/06
+ */
+@Transactional
+@Slf4j
+public class TrSysMysqlCreateTableManagerImpl implements SysMysqlCreateTableManager {
+	/**
+	 * 已经解析过得statement
+	 */
+	private static Set<MappedStatement> hasParseMappedStatement=new HashSet<>();
+
+
+	private CreateMysqlTablesMapper createMysqlTablesMapper;
+
+	private ConfigurationUtil springContextUtil;
+
+	private SqlSessionFactory sqlSessionFactory;
+
+
+	public TrSysMysqlCreateTableManagerImpl() {
+		this.springContextUtil = SpringUtil.getBean(ConfigurationUtil.class);
+		this.sqlSessionFactory = SpringUtil.getBean(SqlSessionFactory.class);
+		this.createMysqlTablesMapper =SpringUtil.getBean(CreateMysqlTablesMapper.class);
+	}
+
+	/**
+	 * 自动创建模式:update表示更新,create表示删除原表重新创建
+	 */
+	private static String tableAuto = null;
+
+	/**
+	 * 读取配置文件的三种状态(创建表、更新表、不做任何事情)
+	 */
+	@Override
+	public void createMysqlTable() {
+		// 读取配置信息
+		tableAuto = springContextUtil.getConfig(Constants.TABLE_AUTO_KEY);
+
+		// 不做任何事情
+		if (!"none".equals(tableAuto) && !"update".equals(tableAuto) && !"create".equals(tableAuto) && !"add".equals(tableAuto)) {
+			log.warn("配置mybatis.table.auto错误无法识别,当前配置只支持[none/update/create/add]三种类型!");
+			return;
+		}
+
+		// 不做任何事情
+		if ("none".equals(tableAuto)) {
+			log.info("配置mybatis.table.auto=none,不需要做任何事情");
+			return;
+		}
+
+		// 从包package中获取所有的Class
+		//从Mp中获取所有的class
+		Set<Class> classes =new HashSet<>();
+		Collection<MappedStatement> mappedStatements = sqlSessionFactory.getConfiguration().getMappedStatements();
+		if (CollectionUtil.isNotEmpty(mappedStatements)) {
+			for (Object obj : mappedStatements) {
+				if(obj.getClass() != MappedStatement.class){
+					continue;
+				}
+				MappedStatement statement= (MappedStatement) obj;
+				if(hasParseMappedStatement.contains(statement)){
+					continue;
+				}
+				List<ResultMap> resultMaps = statement.getResultMaps();
+				if (CollectionUtil.isNotEmpty(resultMaps)) {
+					resultMaps.stream().map(ResultMap::getType).forEach(classes::add);
+				}
+				hasParseMappedStatement.add(statement);
+			}
+		}
+
+		// 初始化用于存储各种操作表结构的容器
+		Map<String, Map<String, TableConfig>> baseTableMap = initTableMap();
+
+		// 表名集合
+		List<String> tableNames = new ArrayList<String>();
+
+		// 循环全部的model
+		for (Class<?> clas : classes) {
+			if(clas.getName() .startsWith("java.")){
+				continue;
+			}
+			// 没有打注解不需要创建表 或者配置了忽略建表的注解
+			if (!ColumnUtils.hasTableAnnotation(clas) || ColumnUtils.hasIgnoreTableAnnotation(clas)) {
+				log.warn("{},没有@Table或配置了@IgnoreTable注解直接跳过", clas.getName());
+				continue;
+			}
+			// 禁止出现重名表
+			checkTableName(tableNames, clas);
+			// 构建出全部表的增删改的map
+			buildTableMapConstruct(clas, baseTableMap);
+		}
+
+		// 根据传入的map,分别去创建或修改表结构
+		createOrModifyTableConstruct(baseTableMap);
+	}
+
+	private void checkTableName(List<String> tableNames, Class<?> clas) {
+		String tableName = ColumnUtils.getTableName(clas);
+		if (tableNames.contains(tableName)){
+			throw new RuntimeException(tableName + "表名出现重复,禁止创建!");
+		}
+		tableNames.add(tableName);
+	}
+
+	/**
+	 * 初始化用于存储各种操作表结构的容器
+	 *
+	 * @return 初始化map
+	 */
+	private Map<String, Map<String, TableConfig>> initTableMap() {
+		Map<String, Map<String, TableConfig>> baseTableMap = new HashMap<String, Map<String, TableConfig>>();
+		// 1.用于存需要创建的表名+(字段结构/表信息)
+		baseTableMap.put(Constants.NEW_TABLE_MAP, new HashMap<String, TableConfig>());
+		// 2.用于存需要更新字段类型等的表名+结构
+		baseTableMap.put(Constants.MODIFY_TABLE_MAP, new HashMap<String, TableConfig>());
+		// 3.用于存需要增加字段的表名+结构
+		baseTableMap.put(Constants.ADD_TABLE_MAP, new HashMap<String, TableConfig>());
+		// 4.用于存需要删除字段的表名+结构
+		baseTableMap.put(Constants.REMOVE_TABLE_MAP, new HashMap<String, TableConfig>());
+		// 5.用于存需要删除主键的表名+结构
+		baseTableMap.put(Constants.DROPKEY_TABLE_MAP, new HashMap<String, TableConfig>());
+		// 6.用于存需要删除唯一约束的表名+结构
+		baseTableMap.put(Constants.DROPINDEXANDUNIQUE_TABLE_MAP, new HashMap<String, TableConfig>());
+		// 7.用于存需要增加的索引
+		baseTableMap.put(Constants.ADDINDEX_TABLE_MAP, new HashMap<String, TableConfig>());
+		// 8.用于存需要增加的唯一约束
+		baseTableMap.put(Constants.ADDUNIQUE_TABLE_MAP, new HashMap<String, TableConfig>());
+		// 9.更新表注释
+		baseTableMap.put(Constants.MODIFY_TABLE_PROPERTY_MAP, new HashMap<String, TableConfig>());
+		return baseTableMap;
+	}
+
+	/**
+	 * 构建出全部表的增删改的map
+	 *
+	 * @param clas
+	 *            package中的model的Class
+	 * @param baseTableMap
+	 *            用于存储各种操作表结构的容器
+	 */
+	private void buildTableMapConstruct(Class<?> clas, Map<String, Map<String, TableConfig>> baseTableMap) {
+
+		// 获取model的tablename
+		String tableName = ColumnUtils.getTableName(clas);
+
+		// 获取表注释
+		String tableComment = ColumnUtils.getTableComment(clas);
+
+		// 获取表字符集
+		MySqlCharsetConstant tableCharset = ColumnUtils.getTableCharset(clas);
+
+		// 获取表引擎
+		MySqlEngineConstant tableEngine = ColumnUtils.getTableEngine(clas);
+
+		// 1. 用于存表的全部字段
+		List<Object> allFieldList;
+		try{
+			allFieldList = getAllFields(clas);
+			if (allFieldList.size() == 0) {
+				log.warn("扫描发现" + clas.getName() + "没有建表字段请检查!");
+				return;
+			}
+		}catch (Exception e){
+			log.error("表:{},初始化字段结构失败!", tableName);
+			throw new RuntimeException(e);
+		}
+
+		// 如果配置文件配置的是create,表示将所有的表删掉重新创建
+		if ("create".equals(tableAuto)) {
+			log.info("由于配置的模式是create,因此先删除表后续根据结构重建,删除表:{}", tableName);
+			createMysqlTablesMapper.dropTableByName(tableName);
+		}
+
+		// 先查该表是否以存在
+		SysMysqlTable table = createMysqlTablesMapper.findTableByTableName(tableName);
+
+		// 不存在时
+		if (table == null) {
+			Map<String, Object> map = new HashMap<String, Object>();
+			if (!StringUtils.isEmpty(tableComment)){
+				map.put(SysMysqlTable.TABLE_COMMENT_KEY, tableComment);
+			}
+			if (tableCharset != null && tableCharset != MySqlCharsetConstant.DEFAULT){
+				map.put(SysMysqlTable.TABLE_COLLATION_KEY, tableCharset.toString().toLowerCase());
+			}
+			if (tableEngine != null && tableEngine != MySqlEngineConstant.DEFAULT){
+				map.put(SysMysqlTable.TABLE_ENGINE_KEY, tableEngine.toString());
+			}
+			baseTableMap.get(Constants.NEW_TABLE_MAP).put(tableName, new TableConfig(allFieldList, map));
+			baseTableMap.get(Constants.ADDINDEX_TABLE_MAP).put(tableName, new TableConfig(getAddIndexList(null, allFieldList)));
+			baseTableMap.get(Constants.ADDUNIQUE_TABLE_MAP).put(tableName, new TableConfig(getAddUniqueList(null, allFieldList)));
+			return;
+		}else{
+			Map<String, Object> map = new HashMap<String, Object>();
+			// 判断表注释是否要更新
+			if (!StringUtils.isEmpty(tableComment) && !tableComment.equals(table.getTable_comment())){
+				map.put(SysMysqlTable.TABLE_COMMENT_KEY, tableComment);
+			}
+			// 判断表字符集是否要更新
+			if (tableCharset != null && tableCharset != MySqlCharsetConstant.DEFAULT && !tableCharset.toString().toLowerCase().equals(table.getTable_collation().replace(SysMysqlTable.TABLE_COLLATION_SUFFIX, ""))){
+				map.put(SysMysqlTable.TABLE_COLLATION_KEY, tableCharset.toString().toLowerCase());
+			}
+			// 判断表引擎是否要更新
+			if (tableEngine != null && tableEngine != MySqlEngineConstant.DEFAULT && !tableEngine.toString().equals(table.getEngine())){
+				map.put(SysMysqlTable.TABLE_ENGINE_KEY, tableEngine.toString());
+			}
+			baseTableMap.get(Constants.MODIFY_TABLE_PROPERTY_MAP).put(tableName, new TableConfig(map));
+		}
+
+		// 已存在时理论上做修改的操作,这里查出该表的结构
+		List<SysMysqlColumns> tableColumnList = createMysqlTablesMapper.findTableEnsembleByTableName(tableName);
+
+		// 从sysColumns中取出我们需要比较的列的List
+		// 先取出name用来筛选出增加和删除的字段
+		List<String> columnNames = ClassTools.getPropertyValueList(tableColumnList, SysMysqlColumns.COLUMN_NAME_KEY);
+
+		// 验证对比从model中解析的allFieldList与从数据库查出来的columnList
+		// 2. 找出增加的字段
+		List<Object> addFieldList = getAddFieldList(allFieldList, columnNames);
+
+		// 3. 找出删除的字段
+		List<Object> removeFieldList = getRemoveFieldList(columnNames, allFieldList);
+
+		// 4. 找出更新的字段
+		List<Object> modifyFieldList = getModifyFieldList(tableColumnList, allFieldList);
+
+		// 5. 找出需要删除主键的字段
+		List<Object> dropKeyFieldList = getDropKeyFieldList(tableColumnList, allFieldList);
+
+		String uniPrefix = springContextUtil.getConfig(Constants.TABLE_UNIQUE_KEY);
+		String idxPrefix = springContextUtil.getConfig(Constants.TABLE_INDEX_KEY);
+		Map<String, String> paramMap = new HashMap<String, String>();
+		paramMap.put("tableName", tableName);
+		paramMap.put("uniquePrefix",uniPrefix);
+		paramMap.put("indexPrefix",idxPrefix);
+		// 查询当前表中全部acteble创建的索引和唯一约束,也就是名字前缀是actable_和actable_的
+		Set<String> allIndexAndUniqueNames = createMysqlTablesMapper.findTableIndexByTableName(paramMap);
+
+		// 6. 找出需要删除的索引和唯一约束
+		List<Object> dropIndexAndUniqueFieldList = getDropIndexAndUniqueList(allIndexAndUniqueNames, allFieldList);
+
+		// 7. 找出需要新增的索引
+		List<Object> addIndexFieldList = getAddIndexList(allIndexAndUniqueNames, allFieldList);
+
+		// 8. 找出需要新增的唯一约束
+		List<Object> addUniqueFieldList = getAddUniqueList(allIndexAndUniqueNames, allFieldList);
+
+		if (addFieldList.size() != 0) {
+			baseTableMap.get(Constants.ADD_TABLE_MAP).put(tableName, new TableConfig(addFieldList));
+		}
+		if (removeFieldList.size() != 0) {
+			baseTableMap.get(Constants.REMOVE_TABLE_MAP).put(tableName, new TableConfig(removeFieldList));
+		}
+		if (modifyFieldList.size() != 0) {
+			baseTableMap.get(Constants.MODIFY_TABLE_MAP).put(tableName, new TableConfig(modifyFieldList));
+		}
+		if (dropKeyFieldList.size() != 0) {
+			baseTableMap.get(Constants.DROPKEY_TABLE_MAP).put(tableName, new TableConfig(dropKeyFieldList));
+		}
+		if (dropIndexAndUniqueFieldList.size() != 0) {
+			baseTableMap.get(Constants.DROPINDEXANDUNIQUE_TABLE_MAP).put(tableName, new TableConfig(dropIndexAndUniqueFieldList));
+		}
+		if (addIndexFieldList.size() != 0) {
+			baseTableMap.get(Constants.ADDINDEX_TABLE_MAP).put(tableName, new TableConfig(addIndexFieldList));
+		}
+		if (addUniqueFieldList.size() != 0) {
+			baseTableMap.get(Constants.ADDUNIQUE_TABLE_MAP).put(tableName, new TableConfig(addUniqueFieldList));
+		}
+	}
+
+	/**
+	 * 找出需要新建的索引
+	 *
+	 * @param allIndexAndUniqueNames
+	 *            当前数据库的索引很约束名
+	 * @param allFieldList
+	 *            model中的所有字段
+	 * @return 需要新建的索引
+	 */
+	private List<Object> getAddIndexList(Set<String> allIndexAndUniqueNames, List<Object> allFieldList) {
+		List<Object> addIndexFieldList = new ArrayList<Object>();
+		if (null == allIndexAndUniqueNames) {
+			allIndexAndUniqueNames = new HashSet<String>();
+		}
+		for (Object obj : allFieldList) {
+			CreateTableParam createTableParam = (CreateTableParam) obj;
+			if (null != createTableParam.getFiledIndexName()
+					&& !allIndexAndUniqueNames.contains(createTableParam.getFiledIndexName())) {
+				addIndexFieldList.add(createTableParam);
+			}
+		}
+		return addIndexFieldList;
+	}
+
+	/**
+	 * 找出需要新建的唯一约束
+	 *
+	 * @param allIndexAndUniqueNames
+	 *            当前数据库的索引很约束名
+	 * @param allFieldList
+	 *            model中的所有字段
+	 * @return 需要新建的唯一约束
+	 */
+	private List<Object> getAddUniqueList(Set<String> allIndexAndUniqueNames, List<Object> allFieldList) {
+		List<Object> addUniqueFieldList = new ArrayList<Object>();
+		if (null == allIndexAndUniqueNames) {
+			allIndexAndUniqueNames = new HashSet<String>();
+		}
+		for (Object obj : allFieldList) {
+			CreateTableParam createTableParam = (CreateTableParam) obj;
+			if (null != createTableParam.getFiledUniqueName()
+					&& !allIndexAndUniqueNames.contains(createTableParam.getFiledUniqueName())) {
+				addUniqueFieldList.add(createTableParam);
+			}
+		}
+		return addUniqueFieldList;
+	}
+
+	/**
+	 * 找出需要删除的索引和唯一约束
+	 *
+	 * @param allIndexAndUniqueNames
+	 *            当前数据库的索引很约束名
+	 * @param allFieldList
+	 *            model中的所有字段
+	 * @return 需要删除的索引和唯一约束
+	 */
+	private List<Object> getDropIndexAndUniqueList(Set<String> allIndexAndUniqueNames, List<Object> allFieldList) {
+		List<Object> dropIndexAndUniqueFieldList = new ArrayList<Object>();
+		if (null == allIndexAndUniqueNames || allIndexAndUniqueNames.size() == 0) {
+			return dropIndexAndUniqueFieldList;
+		}
+		List<String> currentModelIndexAndUnique = new ArrayList<String>();
+		for (Object obj : allFieldList) {
+			CreateTableParam createTableParam = (CreateTableParam) obj;
+			if (null != createTableParam.getFiledIndexName()) {
+				currentModelIndexAndUnique.add(createTableParam.getFiledIndexName());
+			}
+			if (null != createTableParam.getFiledUniqueName()) {
+				currentModelIndexAndUnique.add(createTableParam.getFiledUniqueName());
+			}
+		}
+		for (String string : allIndexAndUniqueNames) {
+			if (!currentModelIndexAndUnique.contains(string)) {
+				dropIndexAndUniqueFieldList.add(string);
+			}
+		}
+		return dropIndexAndUniqueFieldList;
+	}
+
+	/**
+	 * 返回需要删除主键的字段
+	 *
+	 * @param tableColumnList
+	 *            表结构
+	 * @param allFieldList
+	 *            model中的所有字段
+	 * @return 需要删除主键的字段
+	 */
+	private List<Object> getDropKeyFieldList(List<SysMysqlColumns> tableColumnList, List<Object> allFieldList) {
+		Map<String, CreateTableParam> fieldMap = getAllFieldMap(allFieldList);
+		List<Object> dropKeyFieldList = new ArrayList<Object>();
+		for (SysMysqlColumns sysColumn : tableColumnList) {
+			// 数据库中有该字段时
+			CreateTableParam createTableParam = fieldMap.get(sysColumn.getColumn_name().toLowerCase());
+			if (createTableParam != null) {
+				// 原本是主键,现在不是了,那么要去做删除主键的操作
+				if ("PRI".equals(sysColumn.getColumn_key()) && !createTableParam.isFieldIsKey()) {
+					dropKeyFieldList.add(createTableParam);
+				}
+
+			}
+		}
+		return dropKeyFieldList;
+	}
+
+	/**
+	 * 根据数据库中表的结构和model中表的结构对比找出修改类型默认值等属性的字段
+	 *
+	 *            数据库中的结构
+	 * @param tableColumnList
+	 *            表结构
+	 * @param allFieldList
+	 *            model中的所有字段
+	 * @return 需要修改的字段
+	 */
+	private List<Object> getModifyFieldList(List<SysMysqlColumns> tableColumnList, List<Object> allFieldList) {
+		Map<String, CreateTableParam> fieldMap = getAllFieldMap(allFieldList);
+		List<Object> modifyFieldList = new ArrayList<Object>();
+		for (SysMysqlColumns sysColumn : tableColumnList) {
+			// 数据库中有该字段时,验证是否有更新
+			CreateTableParam createTableParam = fieldMap.get(sysColumn.getColumn_name().toLowerCase());
+			if (createTableParam != null && !createTableParam.getIgnoreUpdate()) {
+				// 该复制操作时为了解决multiple primary key defined的同时又不会drop primary key
+				CreateTableParam modifyTableParam = createTableParam.clone();
+				// 1.验证主键
+				// 原本不是主键,现在变成了主键,那么要去做更新
+				if (!"PRI".equals(sysColumn.getColumn_key()) && createTableParam.isFieldIsKey()) {
+					modifyFieldList.add(modifyTableParam);
+					continue;
+				}
+				// 原本是主键,现在依然主键,坚决不能在alter语句后加primary key,否则会报multiple primary
+				// key defined
+				if ("PRI".equals(sysColumn.getColumn_key()) && createTableParam.isFieldIsKey()) {
+					modifyTableParam.setFieldIsKey(false);
+				}
+				// 2.验证类型
+				if (!sysColumn.getData_type().toLowerCase().equals(createTableParam.getFieldType().toLowerCase())) {
+					modifyFieldList.add(modifyTableParam);
+					continue;
+				}
+				// 3.验证长度个小数点位数
+				String typeAndLength = createTableParam.getFieldType().toLowerCase();
+				if (createTableParam.getFileTypeLength() == 1) {
+					// 拼接出类型加长度,比如varchar(1)
+					typeAndLength = typeAndLength + "(" + createTableParam.getFieldLength() + ")";
+				} else if (createTableParam.getFileTypeLength() == 2) {
+					// 拼接出类型加长度,比如varchar(1)
+					typeAndLength = typeAndLength + "(" + createTableParam.getFieldLength() + ","
+							+ createTableParam.getFieldDecimalLength() + ")";
+				}
+
+				// 判断类型+长度是否相同
+				if (!sysColumn.getColumn_type().toLowerCase().equals(typeAndLength)) {
+					modifyFieldList.add(modifyTableParam);
+					continue;
+				}
+				// 5.验证自增
+				if ("auto_increment".equals(sysColumn.getExtra()) && !createTableParam.isFieldIsAutoIncrement()) {
+					modifyFieldList.add(modifyTableParam);
+					continue;
+				}
+				if (!"auto_increment".equals(sysColumn.getExtra()) && createTableParam.isFieldIsAutoIncrement()) {
+					modifyFieldList.add(modifyTableParam);
+					continue;
+				}
+				// 6.验证默认值
+				if (sysColumn.getColumn_default() == null || sysColumn.getColumn_default().equals("")) {
+					// 数据库默认值是null,model中注解设置的默认值不为NULL时,那么需要更新该字段
+					if (createTableParam.getFieldDefaultValue() != null && !ColumnUtils.DEFAULTVALUE.equals(createTableParam.getFieldDefaultValue())) {
+						modifyFieldList.add(modifyTableParam);
+						continue;
+					}
+				} else if (!sysColumn.getColumn_default().equals(createTableParam.getFieldDefaultValue())) {
+					if (MySqlTypeConstant.BIT.toString().toLowerCase().equals(createTableParam.getFieldType()) && !createTableParam.isFieldDefaultValueNative()){
+						if(("true".equals(createTableParam.getFieldDefaultValue()) || "1".equals(createTableParam.getFieldDefaultValue()))
+								&& !"b'1'".equals(sysColumn.getColumn_default())){
+							// 两者不相等时,需要更新该字段
+							modifyFieldList.add(modifyTableParam);
+							continue;
+						}
+						if(("false".equals(createTableParam.getFieldDefaultValue()) || "0".equals(createTableParam.getFieldDefaultValue()))
+								&& !"b'0'".equals(sysColumn.getColumn_default())){
+							// 两者不相等时,需要更新该字段
+							modifyFieldList.add(modifyTableParam);
+							continue;
+						}
+					}else{
+						// 两者不相等时,需要更新该字段
+						modifyFieldList.add(modifyTableParam);
+						continue;
+					}
+				}
+				// 7.验证是否可以为null(主键不参与是否为null的更新)
+				if (sysColumn.getIs_nullable().equals("NO") && !createTableParam.isFieldIsKey()) {
+					if (createTableParam.isFieldIsNull()) {
+						// 一个是可以一个是不可用,所以需要更新该字段
+						modifyFieldList.add(modifyTableParam);
+						continue;
+					}
+				} else if (sysColumn.getIs_nullable().equals("YES") && !createTableParam.isFieldIsKey()) {
+					if (!createTableParam.isFieldIsNull()) {
+						// 一个是可以一个是不可用,所以需要更新该字段
+						modifyFieldList.add(modifyTableParam);
+						continue;
+					}
+				}
+				// 8.验证注释
+				if (!sysColumn.getColumn_comment().equals(createTableParam.getFieldComment())) {
+					modifyFieldList.add(modifyTableParam);
+				}
+			}
+		}
+		return modifyFieldList;
+	}
+
+	/**
+	 * 将allFieldList转换为Map结构
+	 *
+	 * @param allFieldList
+	 * @return
+	 */
+	private Map<String, CreateTableParam> getAllFieldMap(List<Object> allFieldList) {
+		// 将fieldList转成Map类型,字段名作为主键
+		Map<String, CreateTableParam> fieldMap = new HashMap<String, CreateTableParam>();
+		for (Object obj : allFieldList) {
+			CreateTableParam createTableParam = (CreateTableParam) obj;
+			fieldMap.put(createTableParam.getFieldName().toLowerCase(), createTableParam);
+		}
+		return fieldMap;
+	}
+
+	/**
+	 * 根据数据库中表的结构和model中表的结构对比找出删除的字段
+	 *
+	 * @param columnNames
+	 *            数据库中的结构
+	 * @param allFieldList
+	 *            model中的所有字段
+	 */
+	private List<Object> getRemoveFieldList(List<String> columnNames, List<Object> allFieldList) {
+		List<String> toLowerCaseColumnNames = ClassTools.toLowerCase(columnNames);
+		Map<String, CreateTableParam> fieldMap = getAllFieldMap(allFieldList);
+		// 用于存删除的字段
+		List<Object> removeFieldList = new ArrayList<Object>();
+		for (String fieldNm : toLowerCaseColumnNames) {
+			// 判断该字段在新的model结构中是否存在
+			if (fieldMap.get(fieldNm) == null) {
+				// 不存在,做删除处理
+				removeFieldList.add(fieldNm);
+			}
+		}
+		return removeFieldList;
+	}
+
+	/**
+	 * 根据数据库中表的结构和model中表的结构对比找出新增的字段
+	 *
+	 * @param allFieldList
+	 *            model中的所有字段
+	 * @param columnNames
+	 *            数据库中的结构
+	 * @return 新增的字段
+	 */
+	private List<Object> getAddFieldList(List<Object> allFieldList, List<String> columnNames) {
+		List<String> toLowerCaseColumnNames = ClassTools.toLowerCase(columnNames);
+		List<Object> addFieldList = new ArrayList<Object>();
+		for (Object obj : allFieldList) {
+			CreateTableParam createTableParam = (CreateTableParam) obj;
+			// 循环新的model中的字段,判断是否在数据库中已经存在
+			if (!toLowerCaseColumnNames.contains(createTableParam.getFieldName().toLowerCase())) {
+				// 不存在,表示要在数据库中增加该字段
+				addFieldList.add(obj);
+			}
+		}
+		return addFieldList;
+	}
+
+	/**
+	 * 迭代出所有model的所有fields
+	 *
+	 * @param clas
+	 *            准备做为创建表依据的class
+	 * @return 表的全部字段
+	 */
+	@Override
+	public List<Object> getAllFields(Class<?> clas) {
+		String idxPrefix = springContextUtil.getConfig(Constants.TABLE_INDEX_KEY);
+		String uniPrefix = springContextUtil.getConfig(Constants.TABLE_UNIQUE_KEY);
+		List<Object> fieldList = new ArrayList<Object>();
+		ReflectionUtils.doWithFields(clas,field->{
+			// 判断方法中是否有指定注解类型的注解
+			if (ColumnUtils.hasColumnAnnotation(field,clas)) {
+				CreateTableParam param = new CreateTableParam();
+				param.setFieldName(ColumnUtils.getColumnName(field,clas));
+				MySqlTypeAndLength mySqlTypeAndLength = ColumnUtils.getMySqlTypeAndLength(field,clas);
+				param.setFieldType(mySqlTypeAndLength.getType().toLowerCase());
+				param.setFileTypeLength(mySqlTypeAndLength.getLengthCount());
+				if (mySqlTypeAndLength.getLengthCount() == 1) {
+					param.setFieldLength(mySqlTypeAndLength.getLength());
+				} else if (mySqlTypeAndLength.getLengthCount() == 2) {
+					param.setFieldLength(mySqlTypeAndLength.getLength());
+					param.setFieldDecimalLength(mySqlTypeAndLength.getDecimalLength());
+				}
+				param.setFieldIsNull(ColumnUtils.isNull(field,clas));
+				param.setFieldIsKey(ColumnUtils.isKey(field,clas));
+				param.setFieldIsAutoIncrement(ColumnUtils.isAutoIncrement(field,clas));
+				param.setFieldDefaultValue(ColumnUtils.getDefaultValue(field,clas));
+				param.setFieldDefaultValueNative(ColumnUtils.getDefaultValueNative(field,clas));
+				param.setFieldComment( ColumnUtils.getComment(field, clas));
+				// 获取当前字段的@Index注解
+				Index index = field.getAnnotation(Index.class);
+				if (null != index) {
+					String[] indexValue = index.columns();
+					param.setFiledIndexName((index.value() == null || index.value().equals(""))
+							? (idxPrefix + ((indexValue.length == 0) ? ColumnUtils.getColumnName(field,clas) : stringArrFormat(indexValue)))
+							: idxPrefix + index.value());
+					param.setFiledIndexValue(
+							indexValue.length == 0 ? Arrays.asList(ColumnUtils.getColumnName(field,clas)) : Arrays.asList(indexValue));
+				}
+				// 获取当前字段的@Unique注解
+				Unique unique = field.getAnnotation(Unique.class);
+				if (null != unique) {
+					String[] uniqueValue = unique.columns();
+					param.setFiledUniqueName((unique.value() == null || unique.value().equals(""))
+							? (uniPrefix
+							+ ((uniqueValue.length == 0) ? ColumnUtils.getColumnName(field,clas) : stringArrFormat(uniqueValue)))
+							: uniPrefix + unique.value());
+					param.setFiledUniqueValue(
+							uniqueValue.length == 0 ? Arrays.asList(ColumnUtils.getColumnName(field,clas)) : Arrays.asList(uniqueValue));
+				}
+				// 获取当前字段的@IgnoreUpdate注解
+				IgnoreUpdate ignoreUpdate = field.getAnnotation(IgnoreUpdate.class);
+				if (null != ignoreUpdate){
+					param.setIgnoreUpdate(ignoreUpdate.value());
+				}
+				fieldList.add(param);
+			}
+		});
+		return fieldList;
+	}
+
+	/**
+	 * String[] to format xxx_yyy_sss
+	 * @param arr
+	 * @return
+	 */
+	private String stringArrFormat(String[] arr) {
+		return String.valueOf(Arrays.toString(arr)).replaceAll(",", "_").replaceAll(" ", "").replace("[", "")
+				.replace("]", "");
+	}
+
+	/**
+	 * 递归扫描父类的fields
+	 *
+	 * @param clas
+	 *            类
+	 * @param fields
+	 *            属性
+	 */
+	@SuppressWarnings("rawtypes")
+	private Field[] recursionParents(Class<?> clas, Field[] fields) {
+		if (clas.getSuperclass() != null) {
+			Class clsSup = clas.getSuperclass();
+			List<Field> fieldList = new ArrayList<Field>();
+			fieldList.addAll(Arrays.asList(fields));
+			// 获取当前class的所有fields的name列表
+			List<String> fdNames = getFieldNames(fieldList);
+			for (Field pfd : Arrays.asList(clsSup.getDeclaredFields())){
+				// 避免重载属性
+				if (fdNames.contains(pfd.getName())){
+					continue;
+				}
+				fieldList.add(pfd);
+			}
+			fields = new Field[fieldList.size()];
+			int i = 0;
+			for (Object field : fieldList.toArray()) {
+				fields[i] = (Field) field;
+				i++;
+			}
+			fields = recursionParents(clsSup, fields);
+		}
+		return fields;
+	}
+
+	private List<String> getFieldNames(List<Field> fieldList) {
+		List<String> fdNames = new ArrayList<String>();
+		for (Field fd : fieldList){
+			fdNames.add(fd.getName());
+		}
+		return fdNames;
+	}
+
+	/**
+	 * 根据传入的map创建或修改表结构
+	 *
+	 * @param baseTableMap
+	 *            操作sql的数据结构
+	 */
+	private void createOrModifyTableConstruct(Map<String, Map<String, TableConfig>> baseTableMap) {
+
+		// 1. 创建表
+		createTableByMap(baseTableMap.get(Constants.NEW_TABLE_MAP));
+
+		// add是追加模式不做删除和修改操作
+		if(!"add".equals(tableAuto)){
+			// 2. 删除要变更主键的表的原来的字段的主键
+			dropFieldsKeyByMap(baseTableMap.get(Constants.DROPKEY_TABLE_MAP));
+		}
+
+		// add是追加模式不做删除和修改操作
+		if(!"add".equals(tableAuto)) {
+			// 3. 删除索引和约束
+			dropIndexAndUniqueByMap(baseTableMap.get(Constants.DROPINDEXANDUNIQUE_TABLE_MAP));
+			// 4. 删除字段
+			removeFieldsByMap(baseTableMap.get(Constants.REMOVE_TABLE_MAP));
+			// 5. 修改表注释
+			modifyTableCommentByMap(baseTableMap.get(Constants.MODIFY_TABLE_PROPERTY_MAP));
+			// 6. 修改字段类型等
+			modifyFieldsByMap(baseTableMap.get(Constants.MODIFY_TABLE_MAP));
+		}
+
+		// 7. 添加新的字段
+		addFieldsByMap(baseTableMap.get(Constants.ADD_TABLE_MAP));
+
+		// 8. 创建索引
+		addIndexByMap(baseTableMap.get(Constants.ADDINDEX_TABLE_MAP));
+
+		// 9. 创建约束
+		addUniqueByMap(baseTableMap.get(Constants.ADDUNIQUE_TABLE_MAP));
+
+	}
+
+	/**
+	 * 根据map结构删除索引和唯一约束
+	 *
+	 * @param dropIndexAndUniqueMap
+	 *            用于删除索引和唯一约束
+	 */
+	private void dropIndexAndUniqueByMap(Map<String, TableConfig> dropIndexAndUniqueMap) {
+		if (dropIndexAndUniqueMap.size() > 0) {
+			for (Entry<String, TableConfig> entry : dropIndexAndUniqueMap.entrySet()) {
+				for (Object obj : entry.getValue().getList()) {
+					Map<String, Object> map = new HashMap<String, Object>();
+					map.put(entry.getKey(), obj);
+					log.info("开始删除表" + entry.getKey() + "中的索引" + obj);
+					createMysqlTablesMapper.dropTabelIndex(map);
+					log.info("完成删除表" + entry.getKey() + "中的索引" + obj);
+				}
+			}
+		}
+	}
+
+	/**
+	 * 根据map结构创建索引
+	 *
+	 * @param addIndexMap
+	 *            用于创建索引和唯一约束
+	 */
+	private void addIndexByMap(Map<String, TableConfig> addIndexMap) {
+		if (addIndexMap.size() > 0) {
+			for (Entry<String, TableConfig> entry : addIndexMap.entrySet()) {
+				for (Object obj : entry.getValue().getList()) {
+					Map<String, Object> map = new HashMap<String, Object>();
+					map.put(entry.getKey(), obj);
+					CreateTableParam fieldProperties = (CreateTableParam) obj;
+					if (null != fieldProperties.getFiledIndexName()) {
+						log.info("开始创建表" + entry.getKey() + "中的索引" + fieldProperties.getFiledIndexName());
+						createMysqlTablesMapper.addTableIndex(map);
+						log.info("完成创建表" + entry.getKey() + "中的索引" + fieldProperties.getFiledIndexName());
+					}
+				}
+			}
+		}
+	}
+
+	/**
+	 * 根据map结构创建唯一约束
+	 *
+	 * @param addUniqueMap
+	 *            用于创建索引和唯一约束
+	 */
+	private void addUniqueByMap(Map<String, TableConfig> addUniqueMap) {
+		if (addUniqueMap.size() > 0) {
+			for (Entry<String, TableConfig> entry : addUniqueMap.entrySet()) {
+				for (Object obj : entry.getValue().getList()) {
+					Map<String, Object> map = new HashMap<String, Object>();
+					map.put(entry.getKey(), obj);
+					CreateTableParam fieldProperties = (CreateTableParam) obj;
+					if (null != fieldProperties.getFiledUniqueName()) {
+						log.info("开始创建表" + entry.getKey() + "中的唯一约束" + fieldProperties.getFiledUniqueName());
+						createMysqlTablesMapper.addTableUnique(map);
+						log.info("完成创建表" + entry.getKey() + "中的唯一约束" + fieldProperties.getFiledUniqueName());
+					}
+				}
+			}
+		}
+	}
+
+	/**
+	 * 根据map结构修改表中的字段类型等
+	 *
+	 * @param modifyTableMap
+	 *            用于存需要更新字段类型等的表名+结构
+	 */
+	private void modifyFieldsByMap(Map<String, TableConfig> modifyTableMap) {
+		// 做修改字段操作
+		if (modifyTableMap.size() > 0) {
+			for (Entry<String, TableConfig> entry : modifyTableMap.entrySet()) {
+				for (Object obj : entry.getValue().getList()) {
+					Map<String, Object> map = new HashMap<String, Object>();
+					map.put(entry.getKey(), obj);
+					CreateTableParam fieldProperties = (CreateTableParam) obj;
+					log.info("开始修改表" + entry.getKey() + "中的字段" + fieldProperties.getFieldName());
+					createMysqlTablesMapper.modifyTableField(map);
+					log.info("完成修改表" + entry.getKey() + "中的字段" + fieldProperties.getFieldName());
+				}
+			}
+		}
+	}
+
+	/**
+	 * 根据map结构删除表中的字段
+	 *
+	 * @param removeTableMap
+	 *            用于存需要删除字段的表名+结构
+	 */
+	private void removeFieldsByMap(Map<String, TableConfig> removeTableMap) {
+		// 做删除字段操作
+		if (removeTableMap.size() > 0) {
+			for (Entry<String, TableConfig> entry : removeTableMap.entrySet()) {
+				for (Object obj : entry.getValue().getList()) {
+					Map<String, Object> map = new HashMap<String, Object>();
+					map.put(entry.getKey(), obj);
+					String fieldName = (String) obj;
+					log.info("开始删除表" + entry.getKey() + "中的字段" + fieldName);
+					createMysqlTablesMapper.removeTableField(map);
+					log.info("完成删除表" + entry.getKey() + "中的字段" + fieldName);
+				}
+			}
+		}
+	}
+
+	/**
+	 * 根据map结构更新表的注释
+	 *
+	 * @param modifyTableCommentMap
+	 *            用于存需要更新表名+注释
+	 */
+	private void modifyTableCommentByMap(Map<String, TableConfig> modifyTableCommentMap) {
+		// 做更新的表注释
+		if (modifyTableCommentMap.size() > 0) {
+			for (Entry<String, TableConfig> entry : modifyTableCommentMap.entrySet()) {
+				for (String property : entry.getValue().getMap().keySet()) {
+					Map<String, TableConfig> map = new HashMap<String, TableConfig>();
+					Map<String, Object> tcMap = new HashMap<String, Object>();
+					Object value = entry.getValue().getMap().get(property);
+					tcMap.put(property, value);
+					map.put(entry.getKey(), new TableConfig(tcMap));
+					log.info("开始更新表" + entry.getKey() + "的" + property + "为" + value);
+					createMysqlTablesMapper.modifyTableProperty(map);
+					log.info("完成更新表" + entry.getKey() + "的" + property + "为" + value);
+				}
+			}
+		}
+	}
+
+	/**
+	 * 根据map结构对表中添加新的字段
+	 *
+	 * @param addTableMap
+	 *            用于存需要增加字段的表名+结构
+	 */
+	private void addFieldsByMap(Map<String, TableConfig> addTableMap) {
+		// 做增加字段操作
+		if (addTableMap.size() > 0) {
+			for (Entry<String, TableConfig> entry : addTableMap.entrySet()) {
+				for (Object obj : entry.getValue().getList()) {
+					Map<String, Object> map = new HashMap<String, Object>();
+					map.put(entry.getKey(), obj);
+					CreateTableParam fieldProperties = (CreateTableParam) obj;
+					log.info("开始为表" + entry.getKey() + "增加字段" + fieldProperties.getFieldName());
+					createMysqlTablesMapper.addTableField(map);
+					log.info("完成为表" + entry.getKey() + "增加字段" + fieldProperties.getFieldName());
+				}
+			}
+		}
+	}
+
+	/**
+	 * 根据map结构删除要变更表中字段的主键
+	 *
+	 * @param dropKeyTableMap
+	 *            用于存需要删除主键的表名+结构
+	 */
+	private void dropFieldsKeyByMap(Map<String, TableConfig> dropKeyTableMap) {
+		// 先去做删除主键的操作,这步操作必须在增加和修改字段之前!
+		if (dropKeyTableMap.size() > 0) {
+			for (Entry<String, TableConfig> entry : dropKeyTableMap.entrySet()) {
+				for (Object obj : entry.getValue().getList()) {
+					Map<String, Object> map = new HashMap<String, Object>();
+					map.put(entry.getKey(), obj);
+					CreateTableParam fieldProperties = (CreateTableParam) obj;
+					log.info("开始为表" + entry.getKey() + "删除主键" + fieldProperties.getFieldName());
+					createMysqlTablesMapper.dropKeyTableField(map);
+					log.info("完成为表" + entry.getKey() + "删除主键" + fieldProperties.getFieldName());
+				}
+			}
+		}
+	}
+
+	/**
+	 * 根据map结构创建表
+	 *
+	 * @param newTableMap
+	 *            用于存需要创建的表名+结构
+	 */
+	private void createTableByMap(Map<String, TableConfig> newTableMap) {
+		// 做创建表操作
+		if (newTableMap.size() > 0) {
+			for (Entry<String, TableConfig> entry : newTableMap.entrySet()) {
+				Map<String, TableConfig> map = new HashMap<String, TableConfig>();
+				map.put(entry.getKey(), entry.getValue());
+				log.info("开始创建表:" + entry.getKey());
+				createMysqlTablesMapper.createTable(map);
+				log.info("完成创建表:" + entry.getKey());
+			}
+		}
+	}
+}

+ 250 - 0
tr-plugins/tr-spring-boot-starter-plugin-mp-enhance-actable/src/main/java/cn/tr/plugin/mp/enhance/actable/proxy/MybatisConfigurationProxy.java

@@ -0,0 +1,250 @@
+package cn.tr.plugin.mp.enhance.actable.proxy;
+
+import com.baomidou.mybatisplus.core.MybatisConfiguration;
+import org.apache.ibatis.binding.MapperRegistry;
+import org.apache.ibatis.cache.Cache;
+import org.apache.ibatis.executor.Executor;
+import org.apache.ibatis.executor.keygen.KeyGenerator;
+import org.apache.ibatis.mapping.MappedStatement;
+import org.apache.ibatis.mapping.ParameterMap;
+import org.apache.ibatis.mapping.ResultMap;
+import org.apache.ibatis.parsing.XNode;
+import org.apache.ibatis.scripting.LanguageDriver;
+import org.apache.ibatis.session.Configuration;
+import org.apache.ibatis.session.ExecutorType;
+import org.apache.ibatis.session.SqlSession;
+import org.apache.ibatis.transaction.Transaction;
+import org.apache.ibatis.type.TypeHandler;
+
+import java.util.*;
+import java.util.function.Consumer;
+
+/**
+ * @ClassName : MybatisConfigurationProxy
+ * @Description :
+ * @Author : LF
+ * @Date: 2023年03月22日
+ */
+public class MybatisConfigurationProxy extends Configuration {
+    private final Configuration target;
+    private List<Consumer<Class<?>>> addMapperListener=new ArrayList<>();
+    public MybatisConfigurationProxy(Configuration target) {
+        super(target.getEnvironment());
+        this.target = target;
+    }
+
+
+    public void addListener(Consumer<Class<?>> classConsumer){
+        addMapperListener.add(classConsumer);
+    }
+    /**
+     * MybatisPlus 加载 SQL 顺序:
+     * <p> 1、加载 XML中的 SQL </p>
+     * <p> 2、加载 SqlProvider 中的 SQL </p>
+     * <p> 3、XmlSql 与 SqlProvider不能包含相同的 SQL </p>
+     * <p>调整后的 SQL优先级:XmlSql > sqlProvider > CurdSql </p>
+     */
+    @Override
+    public void addMappedStatement(MappedStatement ms) {
+        target.addMappedStatement(ms);
+    }
+
+    /**
+     * 使用自己的 MybatisMapperRegistry
+     */
+    @Override
+    public MapperRegistry getMapperRegistry() {
+        return target.getMapperRegistry();
+    }
+
+    /**
+     * 使用自己的 MybatisMapperRegistry
+     */
+    @Override
+    public <T> void addMapper(Class<T> type) {
+        target.addMapper(type);
+        addMapperListener.forEach(listener->listener.accept(type));
+    }
+
+    /**
+     * 使用自己的 MybatisMapperRegistry
+     */
+    @Override
+    public void addMappers(String packageName, Class<?> superType) {
+        target.addMappers(packageName,superType);
+    }
+
+    /**
+     * 使用自己的 MybatisMapperRegistry
+     */
+    @Override
+    public void addMappers(String packageName) {
+        target.addMappers(packageName);
+    }
+
+    /**
+     * 使用自己的 MybatisMapperRegistry
+     */
+    @Override
+    public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
+        return target.getMapper(type, sqlSession);
+    }
+
+    /**
+     * 使用自己的 MybatisMapperRegistry
+     */
+    @Override
+    public boolean hasMapper(Class<?> type) {
+        return target.hasMapper(type);
+    }
+
+    /**
+     * 指定动态SQL生成的默认语言
+     *
+     * @param driver LanguageDriver
+     */
+    @Override
+    public void setDefaultScriptingLanguage(Class<? extends LanguageDriver> driver) {
+        target.setDefaultScriptingLanguage(driver);
+    }
+
+    @Override
+    public void setDefaultEnumTypeHandler(Class<? extends TypeHandler> typeHandler) {
+        target.setDefaultEnumTypeHandler(typeHandler);
+    }
+
+    @Override
+    public void addKeyGenerator(String id, KeyGenerator keyGenerator) {
+        target.addKeyGenerator(id,keyGenerator);
+    }
+
+    @Override
+    public Collection<String> getKeyGeneratorNames() {
+        return target.getKeyGeneratorNames();
+    }
+
+    @Override
+    public Collection<KeyGenerator> getKeyGenerators() {
+        return target.getKeyGenerators();
+    }
+
+    @Override
+    public KeyGenerator getKeyGenerator(String id) {
+        return target.getKeyGenerator(id);
+    }
+
+    @Override
+    public boolean hasKeyGenerator(String id) {
+        return target.hasKeyGenerator(id);
+    }
+
+    @Override
+    public void addCache(Cache cache) {
+        target.addCache(cache);
+    }
+
+    @Override
+    public Collection<String> getCacheNames() {
+        return target.getCacheNames();
+    }
+
+    @Override
+    public Collection<Cache> getCaches() {
+        return target.getCaches();
+    }
+
+    @Override
+    public Cache getCache(String id) {
+        return target.getCache(id);
+    }
+
+    @Override
+    public boolean hasCache(String id) {
+        return target.hasCache(id);
+    }
+
+    @Override
+    public void addResultMap(ResultMap rm) {
+        target.addResultMap(rm);
+    }
+
+    @Override
+    public Collection<String> getResultMapNames() {
+        return target.getResultMapNames();
+    }
+
+    @Override
+    public Collection<ResultMap> getResultMaps() {
+        return target.getResultMaps();
+    }
+
+    @Override
+    public ResultMap getResultMap(String id) {
+        return target.getResultMap(id);
+    }
+
+    @Override
+    public boolean hasResultMap(String id) {
+        return target.hasResultMap(id);
+    }
+
+    @Override
+    public void addParameterMap(ParameterMap pm) {
+        target.addParameterMap(pm);
+    }
+
+    @Override
+    public Collection<String> getParameterMapNames() {
+        return target.getParameterMapNames();
+    }
+
+    @Override
+    public Collection<ParameterMap> getParameterMaps() {
+        return target.getParameterMaps();
+    }
+
+    @Override
+    public ParameterMap getParameterMap(String id) {
+        return target.getParameterMap(id);
+    }
+
+    @Override
+    public boolean hasParameterMap(String id) {
+        return target.hasParameterMap(id);
+    }
+
+    @Override
+    public Map<String, XNode> getSqlFragments() {
+        return target.getSqlFragments();
+    }
+
+    @Override
+    public Collection<String> getMappedStatementNames() {
+        return target.getMappedStatementNames();
+    }
+
+    @Override
+    public Collection<MappedStatement> getMappedStatements() {
+        return target.getMappedStatements();
+    }
+
+    @Override
+    public MappedStatement getMappedStatement(String id) {
+        return target.getMappedStatement(id);
+    }
+
+    @Override
+    public MappedStatement getMappedStatement(String id, boolean validateIncompleteStatements) {
+        return target.getMappedStatement(id,validateIncompleteStatements);
+    }
+
+    @Override
+    public boolean hasStatement(String statementName, boolean validateIncompleteStatements) {
+        return target.hasStatement(statementName,validateIncompleteStatements);
+    }
+
+    @Override
+    public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
+        return target.newExecutor(transaction, executorType);
+    }
+}

+ 407 - 0
tr-plugins/tr-spring-boot-starter-plugin-mp-enhance-actable/src/main/java/cn/tr/plugin/mp/enhance/actable/utils/ColumnUtils.java

@@ -0,0 +1,407 @@
+package cn.tr.plugin.mp.enhance.actable.utils;
+
+import cn.hutool.core.date.DateUtil;
+import cn.tr.core.annotation.Comment;
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import com.gitee.sunchenbin.mybatis.actable.annotation.*;
+import com.gitee.sunchenbin.mybatis.actable.annotation.impl.ColumnImpl;
+import com.gitee.sunchenbin.mybatis.actable.command.JavaToMysqlType;
+import com.gitee.sunchenbin.mybatis.actable.command.MySqlTypeAndLength;
+import com.gitee.sunchenbin.mybatis.actable.constants.MySqlCharsetConstant;
+import com.gitee.sunchenbin.mybatis.actable.constants.MySqlEngineConstant;
+import com.gitee.sunchenbin.mybatis.actable.constants.MySqlTypeConstant;
+import com.google.common.base.CaseFormat;
+import org.apache.ibatis.type.JdbcType;
+import org.springframework.beans.BeanUtils;
+import org.springframework.util.StringUtils;
+
+import javax.persistence.Id;
+import java.lang.reflect.Field;
+import java.util.Arrays;
+import java.util.Date;
+
+public class ColumnUtils {
+
+    public static final String DEFAULTVALUE = "DEFAULT";
+
+    public static final String SQL_ESCAPE_CHARACTER = "`";
+
+    public static String getTableName(Class<?> clasz){
+        Table tableName = clasz.getAnnotation(Table.class);
+        javax.persistence.Table tableNameCommon = clasz.getAnnotation(javax.persistence.Table.class);
+        TableName tableNamePlus = clasz.getAnnotation(TableName.class);
+        EnableTimeSuffix enableTimeSuffix = clasz.getAnnotation(EnableTimeSuffix.class);
+        if (!hasTableAnnotation(clasz)){
+            return null;
+        }
+        String finalTableName = "";
+        if (tableName != null && !StringUtils.isEmpty(tableName.name())) {
+            finalTableName = tableName.name();
+        }
+        if (tableName != null && !StringUtils.isEmpty(tableName.value())) {
+            finalTableName = tableName.value();
+        }
+        if (tableNameCommon != null && !StringUtils.isEmpty(tableNameCommon.name())) {
+            finalTableName = tableNameCommon.name();
+        }
+        if (tableNamePlus != null && !StringUtils.isEmpty(tableNamePlus.value())) {
+            finalTableName = tableNamePlus.value();
+        }
+        if (StringUtils.isEmpty(finalTableName)) {
+            // 都为空时采用类名按照驼峰格式转会为表名
+            finalTableName = getBuildLowerName(clasz.getSimpleName());
+        }
+        if(null != enableTimeSuffix && enableTimeSuffix.value()){
+            finalTableName = appendTimeSuffix(finalTableName, enableTimeSuffix.pattern());
+        }
+        return finalTableName;
+    }
+
+    public static String getTableComment(Class<?> clasz){
+        Table table = clasz.getAnnotation(Table.class);
+        TableComment tableComment = clasz.getAnnotation(TableComment.class);
+        if (!hasTableAnnotation(clasz)){
+            return "";
+        }
+        if (table != null && !StringUtils.isEmpty(table.comment())){
+            return table.comment();
+        }
+        if (tableComment != null && !StringUtils.isEmpty(tableComment.value())){
+            return tableComment.value();
+        }
+        return "";
+    }
+
+    public static MySqlCharsetConstant getTableCharset(Class<?> clasz){
+        Table table = clasz.getAnnotation(Table.class);
+        TableCharset charset = clasz.getAnnotation(TableCharset.class);
+        if (!hasTableAnnotation(clasz)){
+            return null;
+        }
+        if (table != null && table.charset() != MySqlCharsetConstant.DEFAULT){
+            return table.charset();
+        }
+        if (charset != null && !StringUtils.isEmpty(charset.value())){
+            return charset.value();
+        }
+        return null;
+    }
+
+    public static MySqlEngineConstant getTableEngine(Class<?> clasz){
+        Table table = clasz.getAnnotation(Table.class);
+        TableEngine engine = clasz.getAnnotation(TableEngine.class);
+        if (!hasTableAnnotation(clasz)){
+            return null;
+        }
+        if (table != null && table.engine() != MySqlEngineConstant.DEFAULT){
+            return table.engine();
+        }
+        if (engine != null && !StringUtils.isEmpty(engine.value())){
+            return engine.value();
+        }
+        return null;
+    }
+
+    public static String getColumnName(Field field, Class<?> clasz){
+        Column column = getColumn(field, clasz);
+        javax.persistence.Column columnCommon = field.getAnnotation(javax.persistence.Column.class);
+        TableField tableField = field.getAnnotation(TableField.class);
+        TableId tableId = field.getAnnotation(TableId.class);
+        if(!hasColumnAnnotation(field, clasz)){
+            return null;
+        }
+        if (column != null && !StringUtils.isEmpty(column.name())){
+            return column.name().toLowerCase().replace(SQL_ESCAPE_CHARACTER, "");
+        }
+        if (column != null && !StringUtils.isEmpty(column.value())){
+            return column.value().toLowerCase().replace(SQL_ESCAPE_CHARACTER, "");
+        }
+        if (columnCommon != null && !StringUtils.isEmpty(columnCommon.name())){
+            return columnCommon.name().toLowerCase().replace(SQL_ESCAPE_CHARACTER, "");
+        }
+        if (tableField != null && !StringUtils.isEmpty(tableField.value()) && tableField.exist()){
+            return tableField.value().toLowerCase().replace(SQL_ESCAPE_CHARACTER, "");
+        }
+        if (tableId != null && !StringUtils.isEmpty(tableId.value())){
+            return tableId.value().replace(SQL_ESCAPE_CHARACTER, "");
+        }
+        return getBuildLowerName(field.getName()).replace(SQL_ESCAPE_CHARACTER, "");
+    }
+
+    private static String getBuildLowerName(String name) {
+        return CaseFormat.LOWER_CAMEL.to(CaseFormat.LOWER_UNDERSCORE,
+                name).toLowerCase();
+    }
+
+    public static boolean isKey(Field field, Class<?> clasz){
+        Column column = getColumn(field, clasz);
+        if(!hasColumnAnnotation(field,clasz)){
+            return false;
+        }
+        IsKey isKey = field.getAnnotation(IsKey.class);
+        Id id = field.getAnnotation(Id.class);
+        TableId tableId = field.getAnnotation(TableId.class);
+        if(null != isKey){
+            return true;
+        }else if(column != null && column.isKey()){
+            return true;
+        }else if(null != id){
+            return true;
+        }else if(null != tableId){
+            return true;
+        }
+        return false;
+    }
+
+    public static boolean isAutoIncrement(Field field, Class<?> clasz){
+        Column column = getColumn(field, clasz);
+        if(!hasColumnAnnotation(field, clasz)){
+            return false;
+        }
+        IsAutoIncrement isAutoIncrement = field.getAnnotation(IsAutoIncrement.class);
+        if(null != isAutoIncrement){
+            return true;
+        }else if(column != null && column.isAutoIncrement()){
+            return true;
+        }
+        return false;
+    }
+
+    public static Boolean isNull(Field field, Class<?> clasz){
+        Column column = getColumn(field, clasz);
+        javax.persistence.Column columnCommon = field.getAnnotation(javax.persistence.Column.class);
+        if(!hasColumnAnnotation(field,clasz)){
+            return true;
+        }
+        boolean iskey = isKey(field, clasz);
+        // 主键默认为非空
+        if (iskey){
+            return false;
+        }
+
+        IsNotNull isNotNull = field.getAnnotation(IsNotNull.class);
+        if(null != isNotNull){
+            return false;
+        }else if(column != null){
+            return column.isNull();
+        }else if(columnCommon != null){
+            return columnCommon.nullable();
+        }
+        return true;
+    }
+
+    public static String getComment(Field field, Class<?> clasz){
+        Column column = getColumn(field, clasz);
+        ColumnComment comment = field.getAnnotation(ColumnComment.class);
+        Comment columnComment = field.getAnnotation(Comment.class);
+        if(!hasColumnAnnotation(field,clasz)){
+            return null;
+        }
+        if (column != null && !StringUtils.isEmpty(column.comment())){
+            return column.comment();
+        }
+        if (comment != null && !StringUtils.isEmpty(comment.value())){
+            return comment.value();
+        }
+        if (columnComment != null && !StringUtils.isEmpty(columnComment.value())){
+            return columnComment.value();
+        }
+        return "";
+    }
+
+    public static String getDefaultValue(Field field, Class<?> clasz){
+        Column column = getColumn(field,clasz);
+        DefaultValue defaultValue = field.getAnnotation(DefaultValue.class);
+        if(!hasColumnAnnotation(field,clasz)){
+            return null;
+        }
+        if (column != null && !DEFAULTVALUE.equals(column.defaultValue())){
+            return column.defaultValue();
+        }
+        if (defaultValue != null){
+            return defaultValue.value();
+        }
+        return null;
+    }
+
+    public static boolean getDefaultValueNative(Field field, Class<?> clasz){
+        IsNativeDefValue isNativeDefValue = field.getAnnotation(IsNativeDefValue.class);
+        if (isNativeDefValue != null){
+            return isNativeDefValue.value();
+        }
+        if(field.getGenericType().toString().equals("class java.lang.String")
+                || field.getGenericType().toString().equals("char")
+                || field.getGenericType().toString().equals("class java.lang.Boolean")
+                || field.getGenericType().toString().equals("boolean")){
+            return false;
+        }
+        return true;
+    }
+
+    public static MySqlTypeAndLength getMySqlTypeAndLength(Field field, Class<?> clasz){
+        Column column = getColumn(field,clasz);
+        javax.persistence.Column columnCommon = field.getAnnotation(javax.persistence.Column.class);
+        ColumnType type = field.getAnnotation(ColumnType.class);
+        TableField tableField = field.getAnnotation(TableField.class);
+        if(!hasColumnAnnotation(field, clasz)){
+            throw new RuntimeException("字段名:" + field.getName() +"没有字段标识的注解,异常抛出!");
+        }
+        if (column != null && column.type() != MySqlTypeConstant.DEFAULT){
+            return buildMySqlTypeAndLength(field, column.type().toString().toLowerCase(), column.length(), column.decimalLength());
+        }
+        if (tableField != null && tableField.jdbcType() !=  JdbcType.UNDEFINED){
+            if(tableField.jdbcType()==JdbcType.VARCHAR){
+                return buildMySqlTypeAndLength(field, tableField.jdbcType().toString().toLowerCase(),255, 0);
+            }
+            if(tableField.jdbcType()==JdbcType.TINYINT){
+                return buildMySqlTypeAndLength(field, tableField.jdbcType().toString().toLowerCase(),1, 0);
+            }
+            return buildMySqlTypeAndLength(field, tableField.jdbcType().toString().toLowerCase(),255, 0);
+        }
+        if (type != null && type.value() != null && type.value() != MySqlTypeConstant.DEFAULT){
+            return buildMySqlTypeAndLength(field, type.value().toString().toLowerCase(), type.length(), type.decimalLength());
+        }
+        if (type != null && columnCommon != null && type.value() != null  && type.value() != MySqlTypeConstant.DEFAULT){
+            return buildMySqlTypeAndLength(field, type.value().toString().toLowerCase(), columnCommon.length(), columnCommon.scale());
+        }
+        // 类型为空根据字段类型去默认匹配类型
+        MySqlTypeConstant mysqlType = JavaToMysqlType.javaToMysqlTypeMap.get(field.getGenericType().toString());
+        if (mysqlType == null){
+            throw new RuntimeException("字段名:" + field.getName() +"不支持" + field.getGenericType().toString() + "类型转换到mysql类型,仅支持JavaToMysqlType类中的类型默认转换,异常抛出!");
+        }
+        String sqlType = mysqlType.toString().toLowerCase();
+        // 默认类型可以使用column来设置长度
+        if (column != null){
+            return buildMySqlTypeAndLength(field, sqlType, column.length(), column.decimalLength());
+        }
+        // 默认类型可以使用type来设置长度
+        if (type != null){
+            return buildMySqlTypeAndLength(field, sqlType, type.length(), type.decimalLength());
+        }
+        // 默认类型可以使用columnCommon来设置长度
+        if (columnCommon != null){
+            return buildMySqlTypeAndLength(field, sqlType, columnCommon.length(), columnCommon.scale());
+        }
+        return buildMySqlTypeAndLength(field, sqlType, 255, 0);
+    }
+
+    private static MySqlTypeAndLength buildMySqlTypeAndLength(Field field, String type, int length, int decimalLength) {
+        MySqlTypeAndLength mySqlTypeAndLength = MySqlTypeConstant.mySqlTypeAndLengthMap.get(type);
+        if (mySqlTypeAndLength == null) {
+            throw new RuntimeException("字段名:" + field.getName() + "使用的" + type + "类型,没有配置对应的MySqlTypeConstant,只支持创建MySqlTypeConstant中类型的字段,异常抛出!");
+        }
+        MySqlTypeAndLength targetMySqlTypeAndLength = new MySqlTypeAndLength();
+        BeanUtils.copyProperties(mySqlTypeAndLength, targetMySqlTypeAndLength);
+        if (length != 255) {
+            targetMySqlTypeAndLength.setLength(length);
+        }
+        if (decimalLength != 0) {
+            targetMySqlTypeAndLength.setDecimalLength(decimalLength);
+        }
+        return targetMySqlTypeAndLength;
+    }
+
+    public static boolean hasTableAnnotation(Class<?> clasz){
+        Table tableName = clasz.getAnnotation(Table.class);
+        javax.persistence.Table tableNameCommon = clasz.getAnnotation(javax.persistence.Table.class);
+        TableName tableNamePlus = clasz.getAnnotation(TableName.class);
+        if (tableName == null && tableNameCommon == null && tableNamePlus == null){
+            return false;
+        }
+        return true;
+    }
+
+    public static boolean hasIgnoreTableAnnotation(Class<?> clasz){
+        IgnoreTable ignoreTable = clasz.getAnnotation(IgnoreTable.class);
+        if (ignoreTable == null){
+            return false;
+        }
+        return true;
+    }
+
+    public static boolean hasColumnAnnotation(Field field, Class<?> clasz){
+        // 是否开启simple模式
+        boolean isSimple = isSimple(clasz);
+        // 不参与建表的字段
+        String[] excludeFields = excludeFields(clasz);
+        // 当前属性名在排除建表的字段内
+        if (Arrays.asList(excludeFields).contains(field.getName())){
+            return false;
+        }
+        Column column = field.getAnnotation(Column.class);
+        javax.persistence.Column columnCommon = field.getAnnotation(javax.persistence.Column.class);
+        TableField tableField = field.getAnnotation(TableField.class);
+        if(tableField!=null&&!tableField.exist()){
+            return false;
+        }
+        IsKey isKey = field.getAnnotation(IsKey.class);
+        Id id = field.getAnnotation(Id.class);
+        TableId tableId = field.getAnnotation(TableId.class);
+        if(column == null && columnCommon == null && (tableField == null || !tableField.exist())
+                && isKey == null && id == null && tableId == null){
+            // 开启了simple模式
+            if (isSimple){
+                return true;
+            }
+            return false;
+        }
+        return true;
+    }
+
+    private static Column getColumn(Field field, Class<?> clasz){
+        // 不参与建表的字段
+        String[] excludeFields = excludeFields(clasz);
+        if (Arrays.asList(excludeFields).contains(field.getName())){
+            return null;
+        }
+        Column column = field.getAnnotation(Column.class);
+        if (column != null){
+            return column;
+        }
+        // 是否开启simple模式
+        boolean isSimple = isSimple(clasz);
+        // 开启了simple模式
+        if (isSimple){
+            return new ColumnImpl();
+        }
+        return null;
+    }
+
+    private static String[] excludeFields(Class<?> clasz) {
+        String[] excludeFields = {};
+        Table tableName = clasz.getAnnotation(Table.class);
+        if (tableName != null){
+            excludeFields = tableName.excludeFields();
+        }
+        return excludeFields;
+    }
+
+    private static boolean isSimple(Class<?> clasz) {
+        boolean isSimple = false;
+        Table tableName = clasz.getAnnotation(Table.class);
+        if (tableName != null){
+            isSimple = tableName.isSimple();
+        }
+        return isSimple;
+    }
+
+
+    /**
+     * 添加时间后缀
+     *
+     * @param tableName 表名
+     * @param pattern 时间格式
+     * @return
+     */
+    public static String appendTimeSuffix(String tableName, String pattern) {
+        String suffix = "";
+        try {
+            suffix = DateUtil.format(new Date(), pattern);
+        } catch (Exception e) {
+            throw new RuntimeException("无法转换时间格式" + pattern);
+        }
+        return tableName + "_" + suffix;
+    }
+}

+ 1 - 0
tr-plugins/tr-spring-boot-starter-plugin-mp-enhance-actable/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports

@@ -0,0 +1 @@
+cn.tr.plugin.mp.enhance.actable.ActableAutoConfiguration

+ 2 - 0
tr-plugins/tr-spring-boot-starter-plugin-mp-enhance-actable/src/main/resources/mp-actable.properties

@@ -0,0 +1,2 @@
+mybatis.table.auto=add
+mybatis.database.type=mysql

+ 5 - 0
tr-plugins/tr-spring-boot-starter-plugin-mybatis/pom.xml

@@ -25,6 +25,11 @@
         </dependency>
 
 
+        <dependency>
+            <groupId>mysql</groupId>
+            <artifactId>mysql-connector-java</artifactId>
+        </dependency>
+
         <dependency>
             <groupId>cn.hutool</groupId>
             <artifactId>hutool-all</artifactId>

+ 7 - 0
tr-plugins/tr-spring-boot-starter-plugin-mybatis/src/main/java/cn/tr/plugin/mybatis/pojo/BaseDO.java

@@ -1,5 +1,6 @@
 package cn.tr.plugin.mybatis.pojo;
 
+import cn.tr.core.annotation.Comment;
 import com.baomidou.mybatisplus.annotation.FieldFill;
 import com.baomidou.mybatisplus.annotation.TableField;
 import com.baomidou.mybatisplus.annotation.TableLogic;
@@ -22,12 +23,14 @@ public class BaseDO implements Serializable {
     /**
      * 创建时间
      */
+    @Comment("创建时间")
     @TableField(fill = FieldFill.INSERT)
     private LocalDateTime createTime;
 
     /**
      * 最后更新时间
      */
+    @Comment("更新时间")
     @TableField(fill = FieldFill.INSERT_UPDATE)
     private LocalDateTime updateTime;
     /**
@@ -35,6 +38,7 @@ public class BaseDO implements Serializable {
      *
      * 使用 String 类型的原因是,未来可能会存在非数值的情况,留好拓展性。
      */
+    @Comment("创建人")
     @TableField(fill = FieldFill.INSERT, jdbcType = JdbcType.VARCHAR)
     private String creator;
     /**
@@ -42,12 +46,15 @@ public class BaseDO implements Serializable {
      *
      * 使用 String 类型的原因是,未来可能会存在非数值的情况,留好拓展性。
      */
+    @Comment("更新人")
     @TableField(fill = FieldFill.INSERT_UPDATE, jdbcType = JdbcType.VARCHAR)
     private String updater;
 
     /**
      * 是否删除
      */
+    @Comment("删除标记")
     @TableLogic
+    @TableField(jdbcType = JdbcType.TINYINT)
     private Boolean deleted;
 }

+ 43 - 0
tr-plugins/tr-spring-boot-starter-plugin-operatelog/pom.xml

@@ -0,0 +1,43 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <parent>
+        <artifactId>tr-plugins</artifactId>
+        <groupId>cn.tr</groupId>
+        <version>${revision}</version>
+    </parent>
+    <modelVersion>4.0.0</modelVersion>
+
+    <version>${revision}</version>
+    <artifactId>tr-spring-boot-starter-plugin-operatelog</artifactId>
+    <description>操作日志记录</description>
+
+    <dependencies>
+        <dependency>
+            <groupId>cn.tr</groupId>
+            <artifactId>tr-framework</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-web</artifactId>
+            <scope>provided</scope>
+        </dependency>
+
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-aop</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>cn.tr</groupId>
+            <artifactId>tr-spring-boot-starter-plugin-doc</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>cn.tr</groupId>
+            <artifactId>tr-spring-boot-starter-plugin-web</artifactId>
+        </dependency>
+    </dependencies>
+</project>

+ 19 - 0
tr-plugins/tr-spring-boot-starter-plugin-operatelog/src/main/java/cn/tr/plugin/operatelog/TrOperateLogAutoConfiguration.java

@@ -0,0 +1,19 @@
+package cn.tr.plugin.operatelog;
+
+import cn.tr.plugin.operatelog.config.OperateLogAspect;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+/**
+ * @ClassName : TrOperateLogAutoConfiguration
+ * @Description :
+ * @Author : LF
+ * @Date: 2023年03月21日
+ */
+public class TrOperateLogAutoConfiguration {
+
+    @Bean
+    public OperateLogAspect operateLogAspect(){
+        return new OperateLogAspect();
+    }
+}

+ 62 - 0
tr-plugins/tr-spring-boot-starter-plugin-operatelog/src/main/java/cn/tr/plugin/operatelog/annotation/OperateLog.java

@@ -0,0 +1,62 @@
+package cn.tr.plugin.operatelog.annotation;
+
+import cn.tr.plugin.operatelog.enums.OperateTypeEnum;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * 操作日志注解
+ *
+ * @author 芋道源码
+ */
+@Target({ElementType.METHOD})
+@Retention(RetentionPolicy.RUNTIME)
+public @interface OperateLog {
+
+    // ========== 模块字段 ==========
+
+    /**
+     * 操作模块
+     *
+     * 为空时,会尝试读取 {@link Api#value()} 属性
+     */
+    String module() default "";
+    /**
+     * 操作名
+     *
+     * 为空时,会尝试读取 {@link ApiOperation#value()} ()} 属性
+     */
+    String name() default "";
+    /**
+     * 操作分类
+     *
+     * 实际并不是数组,因为枚举不能设置 null 作为默认值
+     */
+    OperateTypeEnum[] type() default {};
+
+    // ========== 开关字段 ==========
+
+    /**
+     * 是否记录操作日志
+     */
+    boolean enable() default true;
+    /**
+     * 是否记录方法参数
+     */
+    boolean logArgs() default true;
+    /**
+     * 是否记录方法结果的数据
+     */
+    boolean logResultData() default true;
+
+    /**
+     * 是否为登录日志
+     */
+    boolean isLog() default false;
+
+}

+ 96 - 0
tr-plugins/tr-spring-boot-starter-plugin-operatelog/src/main/java/cn/tr/plugin/operatelog/bo/OperateLogBO.java

@@ -0,0 +1,96 @@
+package cn.tr.plugin.operatelog.bo;
+
+import lombok.Data;
+
+import java.time.LocalDateTime;
+import java.util.Map;
+
+/**
+ * 操作日志
+ *
+ * @author 芋道源码
+ */
+@Data
+public class OperateLogBO {
+
+    /**
+     * 用户id
+     */
+    private String userId;
+
+    /**
+     * 操作模块
+     */
+    private String module;
+
+    /**
+     * 操作名
+     */
+    private String name;
+
+    /**
+     * 操作分类
+     */
+    private Integer type;
+
+    /**
+     * 操作明细
+     */
+    private String content;
+
+    /**
+     * 请求方法名
+     */
+    private String requestMethod;
+
+    /**
+     * 请求地址
+     */
+    private String requestUrl;
+
+    /**
+     * 用户 IP
+     */
+    private String userIp;
+
+    /**
+     * 浏览器 UserAgent
+     */
+    private String userAgent;
+
+    /**
+     * Java 方法名
+     */
+    private String javaMethod;
+
+    /**
+     * Java 方法的参数
+     */
+    private String javaMethodArgs;
+
+    /**
+     * 开始时间
+     */
+    private LocalDateTime startTime;
+
+    /**
+     * 执行时长,单位:毫秒
+     */
+    private Integer duration;
+
+    /**
+     * 结果码
+     */
+    private String resultCode;
+
+    /**
+     * 结果提示
+     */
+    private String resultMsg;
+
+    /**
+     * 结果数据
+     */
+    private String resultData;
+
+}

+ 326 - 0
tr-plugins/tr-spring-boot-starter-plugin-operatelog/src/main/java/cn/tr/plugin/operatelog/config/OperateLogAspect.java

@@ -0,0 +1,326 @@
+package cn.tr.plugin.operatelog.config;
+
+import cn.hutool.core.date.LocalDateTimeUtil;
+import cn.hutool.core.util.ArrayUtil;
+import cn.hutool.core.util.StrUtil;
+import cn.tr.core.exception.TRExcCode;
+import cn.tr.core.pojo.CommonResult;
+import cn.tr.core.strategy.ExceptionStrategy;
+import cn.tr.core.strategy.LoginUserStrategy;
+import cn.tr.core.utils.JsonUtils;
+import cn.tr.core.utils.ServletUtils;
+import cn.tr.plugin.operatelog.annotation.OperateLog;
+import cn.tr.plugin.operatelog.bo.OperateLogBO;
+import cn.tr.plugin.operatelog.enums.OperateTypeEnum;
+import cn.tr.plugin.operatelog.strategy.OperateStrategy;
+import cn.tr.plugin.web.context.RequestContextHolder;
+import io.swagger.annotations.ApiOperation;
+import io.swagger.annotations.Tag;
+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.reflect.MethodSignature;
+import org.springframework.core.annotation.AnnotationUtils;
+import org.springframework.validation.BindingResult;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestMethod;
+import org.springframework.web.multipart.MultipartFile;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Array;
+import java.time.LocalDateTime;
+import java.util.*;
+import java.util.function.Predicate;
+import java.util.stream.IntStream;
+
+/**
+ * @ClassName : OperateLogAspect
+ * @Description :
+ * @Author : LF
+ * @Date: 2023年03月21日
+ */
+@Aspect
+@Slf4j
+public class OperateLogAspect {
+    @Around("@annotation(operation)")
+    public Object around(ProceedingJoinPoint joinPoint, ApiOperation operation) throws Throwable {
+        // 可能也添加了 @ApiOperation 注解
+        OperateLog operateLog = getMethodAnnotation(joinPoint,
+                OperateLog.class);
+        return around0(joinPoint, operateLog, operation);
+    }
+
+    @Around("!@annotation(io.swagger.annotations.ApiOperation) && @annotation(operateLog)")
+    // 兼容处理,只添加 @OperateLog 注解的情况
+    public Object around(ProceedingJoinPoint joinPoint,
+                         OperateLog operateLog) throws Throwable {
+        return around0(joinPoint, operateLog, null);
+    }
+
+    private Object around0(ProceedingJoinPoint joinPoint,
+                           OperateLog operateLog,
+                           ApiOperation apiOperation) throws Throwable {
+        // 记录开始时间
+        LocalDateTime startTime = LocalDateTime.now();
+        try {
+            // 执行原有方法
+            Object result = joinPoint.proceed();
+            // 记录正常执行时的操作日志
+            this.log(joinPoint, operateLog, apiOperation, startTime, result, null);
+            return result;
+        } catch (Throwable exception) {
+            this.log(joinPoint, operateLog, apiOperation, startTime, null, exception);
+            throw exception;
+        }
+    }
+
+
+    private void log(ProceedingJoinPoint joinPoint,
+                     OperateLog operateLog,
+                     ApiOperation apiOperation,
+                     LocalDateTime startTime, Object result, Throwable exception) {
+        try {
+            // 判断不记录的情况
+            if (!isLogEnable(joinPoint, operateLog)) {
+                return;
+            }
+            // 真正记录操作日志
+            this.log0(joinPoint, operateLog, apiOperation, startTime, result, exception);
+        } catch (Throwable ex) {
+            log.error("[log][记录操作日志时,发生异常,其中参数是 joinPoint({}) operateLog({}) apiOperation({}) result({}) exception({}) ]",
+                    joinPoint, operateLog, apiOperation, result, exception, ex);
+            throw ex;
+        }
+    }
+
+    private void log0(ProceedingJoinPoint joinPoint, OperateLog operateLog, ApiOperation apiOperation, LocalDateTime startTime, Object result, Throwable exception) {
+        OperateLogBO log = new OperateLogBO();
+        // 补全通用字段
+        log.setStartTime(startTime);
+        // 补充用户信息
+        fillUserFields(log);
+        // 补全模块信息
+        fillModuleFields(log, joinPoint, operateLog, apiOperation);
+        // 补全请求信息
+        fillRequestFields(log);
+        // 补全方法信息
+        fillMethodFields(log, joinPoint, operateLog, startTime, result, exception);
+
+        // 异步记录日志
+        OperateStrategy.tr.saveLog(log);
+    }
+
+
+    private static void fillMethodFields(OperateLogBO operateLogObj,
+                                         ProceedingJoinPoint joinPoint,
+                                         OperateLog operateLog,
+                                         LocalDateTime startTime, Object result, Throwable exception) {
+        MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
+        operateLogObj.setJavaMethod(methodSignature.toString());
+        if (operateLog == null || operateLog.logArgs()) {
+            operateLogObj.setJavaMethodArgs(obtainMethodArgs(joinPoint));
+        }
+        if (operateLog == null || operateLog.logResultData()) {
+            operateLogObj.setResultData(obtainResultData(result));
+        }
+        operateLogObj.setDuration((int) (LocalDateTimeUtil.between(startTime, LocalDateTime.now()).toMillis()));
+        // (正常)处理 resultCode 和 resultMsg 字段
+        if (result instanceof CommonResult) {
+            CommonResult<?> commonResult = (CommonResult<?>) result;
+            operateLogObj.setResultCode(commonResult.getCode());
+            operateLogObj.setResultMsg(commonResult.getErrorMsg());
+        } else {
+            operateLogObj.setResultCode(TRExcCode.SUCCESS.getErrCode());
+        }
+        // (异常)处理 resultCode 和 resultMsg 字段
+        if (exception != null) {
+            CommonResult<?> commonResult = ExceptionStrategy.tr.exceptionHandle(ServletUtils.getRequest(),exception);
+            operateLogObj.setResultCode(commonResult.getCode());
+            operateLogObj.setResultMsg(commonResult.getErrorMsg());
+        }
+    }
+
+    private static String obtainResultData(Object result) {
+        if (result instanceof CommonResult) {
+            result = ((CommonResult<?>) result).getData();
+        }
+        return JsonUtils.toJsonString(result);
+    }
+
+    private static String obtainMethodArgs(ProceedingJoinPoint joinPoint) {
+        MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
+        String[] argNames = methodSignature.getParameterNames();
+        Object[] argValues = joinPoint.getArgs();
+        // 拼接参数
+        Map<String, Object> args = new HashMap<>();
+        for (int i = 0; i < argNames.length; i++) {
+            String argName = argNames[i];
+            Object argValue = argValues[i];
+            // 被忽略时,标记为 ignore 字符串,避免和 null 混在一起
+            args.put(argName, !isIgnoreArgs(argValue) ? argValue : "[ignore]");
+        }
+        return JsonUtils.toJsonString(args);
+    }
+
+    private static boolean isIgnoreArgs(Object object) {
+        Class<?> clazz = object.getClass();
+        // 处理数组的情况
+        if (clazz.isArray()) {
+            return IntStream.range(0, Array.getLength(object))
+                    .anyMatch(index -> isIgnoreArgs(Array.get(object, index)));
+        }
+        // 递归,处理数组、Collection、Map 的情况
+        if (Collection.class.isAssignableFrom(clazz)) {
+            return ((Collection<?>) object).stream()
+                    .anyMatch((Predicate<Object>) OperateLogAspect::isIgnoreArgs);
+        }
+        if (Map.class.isAssignableFrom(clazz)) {
+            return isIgnoreArgs(((Map<?, ?>) object).values());
+        }
+        // obj
+        return object instanceof MultipartFile
+                || object instanceof HttpServletRequest
+                || object instanceof HttpServletResponse
+                || object instanceof BindingResult;
+    }
+
+    private static void fillRequestFields(OperateLogBO operateLogObj) {
+        // 获得 Request 对象
+        HttpServletRequest request = ServletUtils.getRequest();
+        if (request == null) {
+            return;
+        }
+        // 补全请求信息
+        operateLogObj.setRequestMethod(request.getMethod());
+        operateLogObj.setRequestUrl(request.getRequestURI());
+        operateLogObj.setUserIp(ServletUtils.getClientIP(request));
+        operateLogObj.setUserAgent(ServletUtils.getUserAgent(request));
+    }
+
+    private void fillUserFields(OperateLogBO log) {
+        log.setUserId(LoginUserStrategy.tr.getCurrentUserId());
+    }
+
+    private static void fillModuleFields(OperateLogBO operateLogObj,
+                                         ProceedingJoinPoint joinPoint,
+                                         OperateLog operateLog,
+                                         ApiOperation apiOperation) {
+        // module 属性
+        if (operateLog != null) {
+            operateLogObj.setModule(operateLog.module());
+        }
+        if (StrUtil.isEmpty(operateLogObj.getModule())) {
+            Tag tag = getClassAnnotation(joinPoint, Tag.class);
+            if (tag != null) {
+                // 优先读取 @Tag 的 name 属性
+                if (StrUtil.isNotEmpty(tag.name())) {
+                    operateLogObj.setModule(tag.name());
+                }
+                // 没有的话,读取 @API 的 description 属性
+                if (StrUtil.isEmpty(operateLogObj.getModule()) && ArrayUtil.isNotEmpty(tag.description())) {
+                    operateLogObj.setModule(tag.description());
+                }
+            }
+        }
+        // name 属性
+        if (operateLog != null) {
+            operateLogObj.setName(operateLog.name());
+        }
+        if (StrUtil.isEmpty(operateLogObj.getName()) && apiOperation != null) {
+            operateLogObj.setName(apiOperation.value());
+        }
+        // type 属性
+        if (operateLog != null && ArrayUtil.isNotEmpty(operateLog.type())) {
+            operateLogObj.setType(operateLog.type()[0].getType());
+        }
+        if (operateLogObj.getType() == null) {
+            RequestMethod requestMethod = obtainFirstMatchRequestMethod(obtainRequestMethod(joinPoint));
+            OperateTypeEnum operateLogType = convertOperateLogType(requestMethod);
+            operateLogObj.setType(operateLogType != null ? operateLogType.getType() : null);
+        }
+        // content
+        operateLogObj.setContent(RequestContextHolder.getRequestBody());
+    }
+
+
+    private static RequestMethod obtainFirstMatchRequestMethod(RequestMethod[] requestMethods) {
+        if (ArrayUtil.isEmpty(requestMethods)) {
+            return null;
+        }
+        // 优先,匹配最优的 POST、PUT、DELETE
+        RequestMethod result = obtainFirstLogRequestMethod(requestMethods);
+        if (result != null) {
+            return result;
+        }
+        // 然后,匹配次优的 GET
+        result = Arrays.stream(requestMethods).filter(requestMethod -> requestMethod == RequestMethod.GET)
+                .findFirst().orElse(null);
+        if (result != null) {
+            return result;
+        }
+        // 兜底,获得第一个
+        return requestMethods[0];
+    }
+
+    private static OperateTypeEnum convertOperateLogType(RequestMethod requestMethod) {
+        if (requestMethod == null) {
+            return null;
+        }
+        switch (requestMethod) {
+            case GET:
+                return OperateTypeEnum.GET;
+            case POST:
+                return OperateTypeEnum.CREATE;
+            case PUT:
+                return OperateTypeEnum.UPDATE;
+            case DELETE:
+                return OperateTypeEnum.DELETE;
+            default:
+                return OperateTypeEnum.OTHER;
+        }
+    }
+
+    private static boolean isLogEnable(ProceedingJoinPoint joinPoint,
+                                       OperateLog operateLog) {
+        // 有 @OperateLog 注解的情况下
+        if (operateLog != null) {
+            return operateLog.enable();
+        }
+        // 没有 @ApiOperation 注解的情况下,只记录 GET、POST、PUT、DELETE 的情况
+        return obtainFirstLogRequestMethod(obtainRequestMethod(joinPoint)) != null;
+    }
+
+    private static RequestMethod obtainFirstLogRequestMethod(RequestMethod[] requestMethods) {
+        if (ArrayUtil.isEmpty(requestMethods)) {
+            return null;
+        }
+        return Arrays.stream(requestMethods).filter(requestMethod ->
+                requestMethod == RequestMethod.POST
+                        ||requestMethod == RequestMethod.GET
+                        || requestMethod == RequestMethod.PUT
+                        || requestMethod == RequestMethod.DELETE)
+                .findFirst().orElse(null);
+    }
+
+
+    private static RequestMethod[] obtainRequestMethod(ProceedingJoinPoint joinPoint) {
+        // 使用 Spring 的工具类,可以处理 @RequestMapping 别名注解
+        RequestMapping requestMapping = AnnotationUtils.getAnnotation(
+                ((MethodSignature) joinPoint.getSignature()).getMethod(), RequestMapping.class);
+        return requestMapping != null ? requestMapping.method() : new RequestMethod[]{};
+    }
+
+
+    private static <T extends Annotation> T getMethodAnnotation(ProceedingJoinPoint joinPoint, Class<T> annotationClass) {
+        return ((MethodSignature) joinPoint.getSignature()).getMethod().getAnnotation(annotationClass);
+    }
+
+    @SuppressWarnings("SameParameterValue")
+    private static <T extends Annotation> T getClassAnnotation(ProceedingJoinPoint joinPoint, Class<T> annotationClass) {
+        return ((MethodSignature) joinPoint.getSignature()).getMethod().getDeclaringClass().getAnnotation(annotationClass);
+    }
+
+
+}

+ 54 - 0
tr-plugins/tr-spring-boot-starter-plugin-operatelog/src/main/java/cn/tr/plugin/operatelog/enums/OperateTypeEnum.java

@@ -0,0 +1,54 @@
+package cn.tr.plugin.operatelog.enums;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+/**
+ * 操作日志的操作类型
+ *
+ * @author ruoyi
+ */
+@Getter
+@AllArgsConstructor
+public enum OperateTypeEnum {
+
+    /**
+     * 查询
+     *
+     * 绝大多数情况下,不会记录查询动作,因为过于大量显得没有意义。
+     * 在有需要的时候,通过声明 {@link io.swagger.annotations.ApiOperation} 注解来记录
+     */
+    GET(1),
+    /**
+     * 新增
+     */
+    CREATE(2),
+    /**
+     * 修改
+     */
+    UPDATE(3),
+    /**
+     * 删除
+     */
+    DELETE(4),
+    /**
+     * 导出
+     */
+    EXPORT(5),
+    /**
+     * 导入
+     */
+    IMPORT(6),
+    /**
+     * 其它
+     *
+     * 在无法归类时,可以选择使用其它。因为还有操作名可以进一步标识
+     */
+    OTHER(0);
+
+    /**
+     * 类型
+     */
+    private final Integer type;
+
+}

+ 27 - 0
tr-plugins/tr-spring-boot-starter-plugin-operatelog/src/main/java/cn/tr/plugin/operatelog/strategy/OperateStrategy.java

@@ -0,0 +1,27 @@
+package cn.tr.plugin.operatelog.strategy;
+
+import cn.tr.plugin.operatelog.bo.OperateLogBO;
+
+import java.util.function.Consumer;
+
+/**
+ * @ClassName : OperateStrategy
+ * @Description :
+ * @Author : LF
+ * @Date: 2023年03月21日
+ */
+
+public class OperateStrategy {
+    private OperateStrategy(){
+
+    }
+
+    public Consumer<OperateLogBO> logSaveConsumer= log->{};
+    public static OperateStrategy tr=new OperateStrategy();
+
+    public void saveLog(OperateLogBO log){
+        if(logSaveConsumer!=null){
+            logSaveConsumer.accept(log);
+        }
+    }
+}

+ 1 - 0
tr-plugins/tr-spring-boot-starter-plugin-operatelog/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports

@@ -0,0 +1 @@
+cn.tr.plugin.operatelog.TrOperateLogAutoConfiguration

+ 5 - 1
tr-plugins/tr-spring-boot-starter-plugin-satoken/src/main/java/cn/tr/plugin/security/config/LoginUserStrategyConfig.java

@@ -1,7 +1,9 @@
 package cn.tr.plugin.security.config;
 
+import cn.dev33.satoken.session.SaSession;
 import cn.dev33.satoken.stp.StpLogic;
 import cn.dev33.satoken.stp.StpUtil;
+import cn.hutool.core.util.ObjectUtil;
 import cn.tr.core.pojo.LoginResult;
 import cn.tr.core.strategy.ILoginUser;
 import cn.tr.core.strategy.LoginUserStrategy;
@@ -24,7 +26,9 @@ public class LoginUserStrategyConfig {
         LoginUserStrategy.tr.loginUserSupplier=()->{
             LoginUserBO user = LoginUserContextHolder.getUser();
             if(user==null){
-                user = StpUtil.getTokenSession().getModel(SecurityConstant.LOGIN_USER, LoginUserBO.class);
+                SaSession tokenSession = StpUtil.getTokenSession();
+
+                user = ObjectUtil.isNull(tokenSession)?null:tokenSession.getModel(SecurityConstant.LOGIN_USER, LoginUserBO.class);
             }
             String userId=user==null?null:user.getUserId();
             String username=user==null?null:user.getUsername();

+ 10 - 0
tr-test/pom.xml

@@ -48,6 +48,16 @@
             <artifactId>tr-spring-boot-starter-plugin-dict</artifactId>
         </dependency>
 
+        <dependency>
+            <groupId>cn.tr</groupId>
+            <artifactId>tr-spring-boot-starter-plugin-operatelog</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>cn.tr</groupId>
+            <artifactId>tr-spring-boot-starter-plugin-doc</artifactId>
+        </dependency>
+
         <dependency>
             <groupId>org.springframework.boot</groupId>
             <artifactId>spring-boot-starter-data-redis</artifactId>

+ 17 - 7
tr-test/src/main/java/cn/tr/test/TestController.java

@@ -1,24 +1,24 @@
 package cn.tr.test;
 
 import cn.tr.core.enums.IEnum;
-import cn.tr.core.utils.JsonUtils;
 import cn.tr.plugin.desensitize.config.slider.annotation.ChineseNameDesensitize;
 import cn.tr.plugin.dict.annotation.Dict;
 import cn.tr.plugin.dict.bo.DictBO;
 import cn.tr.plugin.dict.config.cache.DictManager;
-import cn.tr.plugin.eventbus.annotation.Subscribe;
+import com.github.xiaoymin.knife4j.annotations.ApiOperationSupport;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import io.swagger.annotations.Authorization;
 import lombok.AllArgsConstructor;
 import lombok.Data;
 import lombok.Getter;
 import lombok.NoArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.web.bind.annotation.GetMapping;
-import org.springframework.web.bind.annotation.RequestMapping;
-import org.springframework.web.bind.annotation.RequestParam;
-import org.springframework.web.bind.annotation.RestController;
+import org.springframework.web.bind.annotation.*;
 
 import javax.annotation.PostConstruct;
+import javax.validation.constraints.NotNull;
 import java.util.*;
 /**
  * @ClassName : TestController
@@ -29,6 +29,7 @@ import java.util.*;
 @RestController
 @RequestMapping("/test")
 @Slf4j
+@Api(tags = "测试接口")
 public class TestController {
     @Autowired
     private DictManager dictManager;
@@ -39,7 +40,16 @@ public class TestController {
     }
 
     @GetMapping("/123")
-    public User test(@RequestParam("test") String test){
+    @ApiOperationSupport(author = "lf")
+    @ApiOperation(value = "获取参数",authorizations = {@Authorization("ces")})
+    public User test(@RequestParam("test")@NotNull String test){
+        return User.of("id","1","上官吹雪",false,Grander.high);
+    }
+
+    @PostMapping("/123")
+    @ApiOperationSupport(author = "lf")
+    @ApiOperation(value = "获取参数123",authorizations = {@Authorization("ces")})
+    public User test(@RequestBody User test){
         return User.of("id","1","上官吹雪",false,Grander.high);
     }
 

+ 23 - 0
tr-test/src/main/resources/application-doc.yml

@@ -0,0 +1,23 @@
+knife4j:
+  enable: true
+  openapi:
+    title: Knife4j官方文档
+    description: "`我是测试`,**你知道吗**
+    # aaa"
+    email: xiaoymin@foxmail.com
+    concat: 八一菜刀
+    url: https://docs.xiaominfo.com
+    version: v4.0
+    license: Apache 2.0
+    license-url: https://stackoverflow.com/
+    terms-of-service-url: https://stackoverflow.com/
+    group:
+      test1:
+        group-name: 测试分组
+        api-rule: package
+        api-rule-resources:
+          - cn.tr.test
+  setting:
+    enable-footer: false
+    enable-footer-custom: true
+    footer-custom-content: Apache License 2.0 | Copyright  2019-[驼人控股集团](https://www.tuoren.com/about/index.html)

+ 3 - 1
tr-test/src/main/resources/application.yml

@@ -28,4 +28,6 @@ spring:
         # #连接池最大阻塞等待时间(使用负值表示没有限制)
         max-wait: -1ms
   cache:
-    type: caffeine
+    type: caffeine
+  profiles:
+    include: doc