搭建一个SpringBoot项目(自用)

一、创建一个空的SpringBoot项目(就是依赖选个Web就行了)2023-5-23

git仓库
在这里插入图片描述

二、导包

    <dependencies>
        <!--Web-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <!--阿里的json转换jar包-->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.60</version>
        </dependency>

        <!--redis-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>

        <!--mybatis-plus-->
        <!--mybatis-plus是自己开发的,并非官方的-->
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>3.5.1</version>
        </dependency>

        <!--非官方: mybatis-spring-boot-starter-->
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>2.2.2</version>
        </dependency>

        <!--Druid-->
<!--        <dependency>-->
<!--            <groupId>com.baomidou</groupId>-->
<!--            <artifactId>mybatis-plus-boot-starter</artifactId>-->
<!--            <version>3.5.1</version>-->
<!--        </dependency>-->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid-spring-boot-starter</artifactId>
            <version>1.2.16</version>
        </dependency>
        

        <!--Druid会使用log4j的日志记录,使用导入依赖-->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid-spring-boot-starter</artifactId>
            <version>1.2.11</version>
        </dependency>

        <!--JDBC-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-jdbc</artifactId>
        </dependency>

        <!--MySQL-->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>runtime</scope>
        </dependency>

        <!--mybatis-plus-generator 生成器-->
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-generator</artifactId>
            <version>3.5.2</version>
        </dependency>

        <!-- freemarker,作为代码生成器mapper文件的模板引擎使用(当然也可以使用velocity,二选一即可) -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-freemarker</artifactId>
        </dependency>

        <!--thymeleaf-->
<!--        <dependency>-->
<!--            <groupId>org.springframework.boot</groupId>-->
<!--            <artifactId>spring-boot-starter-thymeleaf</artifactId>-->
<!--        </dependency>-->
        
        
        <!--jcakson-->
        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-databind</artifactId>
        </dependency>
        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-core</artifactId>
        </dependency>
        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-annotations</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <scope>runtime</scope>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-configuration-processor</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <configuration>
                    <excludes>
                        <exclude>
                            <groupId>org.projectlombok</groupId>
                            <artifactId>lombok</artifactId>
                        </exclude>
                    </excludes>
                </configuration>
            </plugin>
        </plugins>
    </build>

三、写配置文件(最好,多创建几个application-{}.yaml文件)

application.yaml

spring:
  profiles:
    active: release

application-dev.yaml

spring:
	# thymeleaf 的配置基本上可以抛弃了,使用前段框架开发前段就可以了
  #thymeleaf:
    #cache: false
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    username: root
    password: 123456
    url: jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai&useSSL=true&allowMultiQueries=true

    # Oracle配置
    #url: jdbc:oracle:thin:@127.0.0.1:1521:test
    #username: root
    #password: 123456
    #driver-class-name: oracle.jdbc.driver.OracleDriver

    #    配置druid数据源
    type: com.alibaba.druid.pool.DruidDataSource
    # druid 配置
    druid:
      #    打开我们的内置监控页面,打开后我们才可以有相应的web界面
      stat-view-servlet:
        enabled: true
        url-pattern: /druid/*
        login-username: user
        login-password: 123456
      #        打开我们的内置监控功能,打开之后我们才可以对我们的操作进行统计
      filter:
        stat:
          enabled: true
          slow-sql-millis: 2000
        #      配置防火墙,防止sql语句注入,我们可以设置我们想要那些语句可以执行,哪些不行
        wall:
          enabled: true
          config:
            delete-allow: true
      #            select-all-column-allow: false,如果我们设置了拒绝查询,那么我们查询时就会报错,返回一个whitePage
      #            selelct-allow: false
      #        打开我们的Web关联监控配置,即我们对某些路径进行精确统计
      web-stat-filter:
        enabled: true
      # 初始化时建立的物理连接数。初始化发生在显式调用init方法,或者第一次getConnection时.
      initial-size: 5
      # 连接池最大物理连接数量。
      max-active: 50
      # 连接池最小物理连接数量。
      min-idle: 5
      # 获取连接时最大等待时间,单位为毫秒。
      # 配置之后,缺省启用公平锁,并发效率会有所下降,若需要可以通过配置useUnfairLock属性为true使用非公平锁。
      max-wait: 6000
      # 是否缓存preparedStatement,也就是PSCache。
      # PSCache对支持游标的数据库性能提升巨大,比如说oracle。在mysql下建议关闭。
      pool-prepared-statements: true
      # 要启用PSCache,其值必须大于0,当大于0时,poolPreparedStatements自动触发修改为true。
      # 在Druid中,不会存在Oracle下PSCache占用内存过多的问题,可以把这个数值配置大一些,比如说100。
      max-pool-prepared-statement-per-connection-size: 20
      # 用来检测连接是否有效的sql,要求是一个查询语句,常用select 'x'。
      # 如果validationQuery为null,testOnBorrow、testOnReturn、testWhileIdle都不会起作用。
      validation-query: select 1 from dual
      # 检测连接是否有效的超时时间,单位为秒。
      # 底层调用jdbc Statement对象的void setQueryTimeout(int seconds)方法。
      #      validation-query-timeout: 30
      # 有两个含义:
      #  1) Destroy线程会检测连接的间隔时间,若连接空闲时间大于等于minEvictableIdleTimeMillis则关闭物理连接。
      #  2) testWhileIdle的判断依据,详细看testWhileIdle属性的说明。
      time-between-eviction-runs-millis: 60000
      # 连接保持空闲而不被驱逐的最长时间。
      min-evictable-idle-time-millis: 300000
      # 建议配置为true,不影响性能,并且保证安全性。
      # 申请连接的时候检测,若空闲时间大于timeBetweenEvictionRunsMillis,执行validationQuery检测连接是否有效。
      test-while-idle: true
      # 申请连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能。
      test-on-borrow: false
      # 归还连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能。
      test-on-return: false
      # 类型是字符串,通过别名的方式配置扩展的拦截器插件,常用的拦截器插件有:
      # 监控统计用的filter:stat,日志用的filter:log4j,防御sql注入攻击的filter:wall,三个同时配置的化,用逗号隔开。
      # 注意,Druid中的filter-class-names配置项是不起作用的,必须采用filters配置项才可以。
      filters: stat,wall,log4j2
      # 通过connectProperties属性来打开mergeSql功能;慢SQL记录。
      connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=5000
      # 合并多个DruidDataSource的监控数据
      use-global-data-source-stat: true
  # 时间日期格式化 spring默认提交格式 yyyy/MM/dd
  mvc:
    format:
      date: yyyy-MM-dd HH:mm:ss
  servlet:
    multipart:
      enabled: true
      max-file-size: 30MB
      max-request-size: 30MB
#  redis
  redis:
    host: ip地址 # 默认127.0.0.1
    port: 6379 # 默认6379
    password: 密码


# 整合mybatis
mybatis:
  mapper-locations:
    - classpath:com/zhao/mapper/*.xml

#    配置日志
mybatis-plus:
  configuration:
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
    # 开启驼峰命名
    map-underscore-to-camel-case: true
  # 配置逻辑删除
  global-config:
    db-config:
      logic-delete-value: 1  # 1 为逻辑删除后的字段
      logic-not-delete-value: 0  # 0 为没有逻辑删除的字段
      logic-delete-field: deleted  #对应实体类的字段 写了这个在实体类中就不需要写注解了


application-release.yaml

spring:
  thymeleaf:
    cache: false
  datasource:
    username: 账号
    password: 密码
    #    假如时区报错了加  &serverTimezone=UTC
    url: jdbc:mysql://ip地址:3306/school_vote?useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai&useSSL=true&allowMultiQueries=true
    driver-class-name: com.mysql.cj.jdbc.Driver
    #    配置druid数据源
    type: com.alibaba.druid.pool.DruidDataSource
    #    配置监控统计拦截的filters,stat:监控统计、log4j:日志记录、wall:防御sql注入
    #    如果允许时报错 java.lang.ClassNotFoundExcption :org.apache.log4j.Priority
    #    则导入 log4j 依赖即可,Maven 地址: https://mvnrepository.com/artifact/log4j/log4j
    druid:
      filters: stat,wall,log4j
      maxPoolPreparedStatementPerConnectionSize: 20
      useGlobalDataSourceStat: true
      connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=500
  # 时间日期格式化 spring默认提交格式 yyyy/MM/dd
  mvc:
    format:
      date: yyyy-MM-dd HH:mm:ss
  servlet:
    multipart:
      enabled: true
      max-file-size: 30MB
      max-request-size: 30MB
  #  redis
  redis:
    host: ip地址 # 默认127.0.0.1
    port: 6379 # 默认6379
    password: 密码


# 整合mybatis 下面使用MP也可以配置映射的文件
# mybatis:
  # mapper-locations:
    # - classpath:com/zhao/mapper/*.xml

mybatis-plus:
  configuration:
    # 更新字段的时候设置为null,忽略实体null判断之后,解决Oracle无效的列异常, oracle数据库必须配置
    jdbc-type-for-null: 'null'  # 注意要有单引号
    # mybatis-plus驼峰映射
    map-underscore-to-camel-case: true
    # 这个配置会将执行的sql打印出来,在开发或测试的时候可以用
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
  # mybatis的xml文件地址,如果启动文件找不到xml文件,如下去修改pom.xml文件
  mapper-locations: classpath:mybatis/mapper/*.xml
  global-config:
    # 禁用mybatis-plus的LOGO
    banner: false
    db-config:
      # 逻辑未删除值,(逻辑删除下有效)
      logic-delete-value: 1
      # 逻辑未删除值,(逻辑删除下有效)需要注入逻辑策略LogicSqlInjector,以@Bean方式注入
      logic-not-delete-value: 0
      # 对应实体类的字段,写了这个在实体类中就不需要写注解了
      logic-delete-field: deleted

server:
  port: 9800

三.五、logback.xml(记录日志)

<?xml version="1.0" encoding="UTF-8" ?>
<configuration scan="true" scanPeriod="60 seconds" debug="false">
    <!-- 日志级别从低到高分为TRACE < DEBUG < INFO < WARN < ERROR < FATAL,如果设置为WARN,则低于WARN的信息都不会输出 -->
    <!-- scan:当此属性设置为true时,配置文件如果发生改变,将会被重新加载,默认值为true -->
    <!-- scanPeriod:设置监测配置文件是否有修改的时间间隔,如果没有给出时间单位,默认单位是毫秒。当scan为true时,此属性生效。默认的时间间隔为1分钟。 -->
    <!-- debug:当此属性设置为true时,将打印出logback内部日志信息,实时查看logback运行状态。默认值为false。 -->
    
    <!-- 工程名/项目名 -->
    <contextName>project_name</contextName>
    
    <!-- 路径变量 -->
    <property name="logPath" value="/opt/logs" />
    <!-- 日志格式变量 -->
    <property name="logPattern" value="%date{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{35} - %msg%n" />

    <!--
    1. %d{HH:mm:ss.SSS} 显示的时间
    2. [%thread]        打印线程号,log4j2使用%t]也可以
    3. %-5level         日志级别,并且使用5个字符靠左对齐
    4. %logger{35}      日志输出者的名字,即类的类名
    5. %file	        打印类名,也可用%class,打印的全限定类名
    6. %line	        打印日志所在代码行数
    7. %msg             日志消息
    8. %n               平台的换行符
    -->
    
    <!--把>=debug的日志输出到控制台 -->
    <appender name="SDTOUT" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <pattern>${logPattern}</pattern>
        </encoder>
    </appender>

	<!--输出到文件(>=info的日志)-->
    <appender name="INFO_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
    	<!-- /opt 目录下(linux环境) ,没有的话会自己新建的-->
        <file>${logPath}/info.log</file>
        
        <!-- 日志记录器的滚动策略,按日期,按大小记录 -->
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <!-- 每天日志归档路径以及格式 -->
            <fileNamePattern>${logPath}/info/log-info-%d{yyyy-MM-dd}.%i.log</fileNamePattern>
            <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
                <maxFileSize>100MB</maxFileSize>
            </timeBasedFileNamingAndTriggeringPolicy>
            <!--日志文件保留天数-->
            <maxHistory>30</maxHistory>
        </rollingPolicy>
        <encoder>
            <pattern>${logPattern}</pattern>
        </encoder>
      	<!--临界值日志过滤级别配置 -->
        <filter class="ch.qos.logback.classic.filter.ThresholdFilter">
            <!-- 在日志配置级别的基础上过滤掉info级别以下的日志 -->
            <level>INFO</level> 
        </filter>
    </appender>

	<!--输出到文件(=error的日志)-->
    <appender name="ERROR_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <file>${logPath}/error.log</file>
        
        <!-- 日志记录器的滚动策略,按日期,按大小记录 -->
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <!-- 每天日志归档路径以及格式 -->
            <fileNamePattern>${logPath}/error/log-error-%d{yyyy-MM-dd}.%i.log</fileNamePattern>
            <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
                <maxFileSize>100MB</maxFileSize>
            </timeBasedFileNamingAndTriggeringPolicy>
            <!--日志文件保留天数-->
            <maxHistory>30</maxHistory>
        </rollingPolicy>
        <encoder>
            <pattern>${logPattern}</pattern>
        </encoder>
        <!-- 此日志文件只记录ERROR级别的 -->
        <filter class="ch.qos.logback.classic.filter.LevelFilter">
            <level>ERROR</level>
            <onMatch>ACCEPT</onMatch>
            <onMismatch>DENY</onMismatch>
        </filter>
    </appender>
    <!-- <logger name="org.mybatis" level="INFO"></logger> -->

    <!-- 开发阶段使用DEBUG  生产环境使用INFO -->
     <root level="INFO">
    	<appender-ref ref="SDTOUT" />
      	<appender-ref ref="INFO_FILE" />
   		<appender-ref ref="ERROR_FILE" />
     </root>

</configuration>

四、创建项目结构

在这里插入图片描述

五、404、500页面

resources > error > 404.html(500.html)

在这里插入图片描述

六、工具类

1、JSON返回工具类,也可以自己写

SpringBoot的JSON工具类(java),用于前后端分离_我认不到你的博客-CSDN博客

@AllArgsConstructor
@NoArgsConstructor
@Data
public class Results<T> {
    public static final String ERROR = "500";
    public static final String SUCCESS = "200";

    /**
     * 返回码
     */
    private String code;

    /**
     * 返回消息
     */
    private String message;

    /**
     * 返回实体
     */
    private T data;

    public static <T> Results<T> success(String msg){
        return success(SUCCESS,msg,null);
    }

    public static <T> Results<T> success(T obj){
        return success(SUCCESS,"成功",obj);
    }

    public static <T> Results<T> success(String msg,T obj){
        return success(SUCCESS,msg,obj);
    }

    public static <T> Results<T> success(String resCode,String msg,T obj){
        Results<T> result = new Results<T>();
        result.setCode(resCode);
        result.setMessage(msg);
        result.setData(obj);
        return result;
    }

    public static <T> Results<T> failed(String msg) {
        return failed(ERROR,msg,null);
    }

    public static <T> Results<T> failed(String msg,T obj) {
        return failed(ERROR,msg,obj);
    }

    public static <T> Results<T> failed(String resCode,String msg) {
        return failed(resCode,msg,null);
    }

    public static <T> Results<T> failed(String resCode,String msg,T obj) {
        Results<T> result = new Results<T>();
        result.setCode(resCode);
        result.setMessage(msg);
        result.setData(obj);
        return result;
    }
}

2、Redis工具类(utils包下)

Redis工具类(redisTemplate)以及 redisTemplate 的用法_我认不到你的博客-CSDN博客

SpringBoot整合Rides_我认不到你的博客-CSDN博客

3、MD5工具类(utils包下)

/**
 * MD5工具类
 * @author PING.AN.ZHAO
 * @date   2022年7月10日
 */
public class MD5Util {
	public static String md5(String source) throws NoSuchAlgorithmException {
        String des = "";  
        MessageDigest md = MessageDigest.getInstance("MD5");
        byte[] result = md.digest(source.getBytes());  
        StringBuilder buf = new StringBuilder();
        for (byte b : result) {
            buf.append(String.format("%02X", b));
        }  
        des = buf.toString().toLowerCase();  
        return des;  
    }
}

此工具类转自:Java MD5 加密工具类_LookingTomorrow的博客-CSDN博客_java md5加密工具类

4、字母编号+数字,并且需要自增工具类

/**
 * @Role 字母+数字工具类
 * @Author 赵平安
 * @GMTCreate   2022年7月20日
 * @Modifier  修改bug,且第一个set弃用
 * @GMTModified 赵平安
 * @ModifyContent  2022年7月28日
 */
public class LetterNumberUtil {

    public static String gal(String LetterNumber,String name) {
        //没有的时候填充
        if (LetterNumber==null||LetterNumber.trim().isEmpty()){
            return name+"000001";
        }
        //截取头部字母编号
        StringBuffer head = new StringBuffer();
        String[] split = LetterNumber.split("");
        for (String s : split) {
            if (!s.matches("^[0-9]*$")){
                head.append(s);
            }else break;
        }
        //截取尾部数字
        String tail = LetterNumber.replaceAll(" ","").substring(head.length(), LetterNumber.replaceAll(" ","").length());
        //尾部数字 +1
        int num = Integer.valueOf(tail) + 1;
        //填充 0
        StringBuffer buffer = new StringBuffer();
        for (int i = 0; i < tail.length(); i++) {
            buffer.append(0);
        }
        String number = buffer.toString();
        //合并字符串
        number = number + num;
        //进位
        if (tail.length()<String.valueOf(num).length()){
            number = number.substring(number.length() - tail.length()-1, number.length());
        }else number = number.substring(number.length() - tail.length(), number.length());
        return head + number;
    }
}

5、BeansUtils

使用反射:类中的String如果为null转为"",应用场景,数据库不能为null的字段,再比如Mybatis插入Oracle时,为null会报错

public class BeanUtils extends BeanUtil{
    /**
     * 如果对象中的属性类别为 String 且值为null的话设置为 ""
     * 作用的话,不是很清楚,应该是怕数据库字段设置不能为空吧
     * @param t 对象
     * @param <T> 泛型
     * @throws Exception 往上抛,由调用者解决异常问题
     * @author ZHAOPINGAN
     */
    public static <T> void stringNotNull(T t) throws Exception{
        if (Objects.isNull(t)){
            return;
        }
        Class<?> tClass = t.getClass();
        Field[] fields = tClass.getDeclaredFields();
        for (Field field : fields) {
            field.setAccessible(true);
            if (field.getType() == String.class) {
                Object o = field.get(t);
                if (Objects.isNull(o)){
                    field.set(t,"");
                }
            }
        }
    }
}

6、线程池工具类

/**
 *  线程池工具类,单例模式,全局唯一
 * @author ZHAOPINGAN
 */
public class ThreadPoolUtil {

    private static volatile ExecutorService threadPool = null;

    private ThreadPoolUtil(){}

    public static ExecutorService getThreadPool() {
        if (threadPool == null) {
            synchronized (ThreadPoolUtil.class) {
                if (threadPool == null) {
                    threadPool = new ThreadPoolExecutor(
                            // 核心线程数
                            4,
                            10,
                            // 10s 过期时间
                            10000L, TimeUnit.MILLISECONDS,
                            // 队列 1000
                            new LinkedBlockingQueue<>(1000),
                            // 线程池工厂
                            Executors.defaultThreadFactory(),
                            // 拒绝策略 丢弃任务,并抛出拒绝执行 RejectedExecutionException 异常信息
                            new ThreadPoolExecutor.AbortPolicy()
                    );
                }
            }
        }
        return threadPool;
    }
}

7、生成Token的工具类

JWT详解(文章内嵌jwt工具类)

8、线程缓存工具类(可以用于JWT生成Token后拦截token并缓存用户数据,可以直接通过getUserInfo,获取用户信息,当然UserInfo是我用来保存用户的数据,你们可以自定义)

/**
 * 线程缓存工具类
 *
 * @author ZPA
 */
public class ThreadLocalUtils {
    private static final ThreadLocal<Map<String, Object>> cache = new ThreadLocal<Map<String, Object>>();
    private static final String KEY = "userInfo";

    /**
     * 向ThreadLocal缓存用户登录信息
     * @param userInfo 要缓存的VALUE
     */
    public static void setUserInfo(UserInfo userInfo) {
        if (!isCaheIsNull()) {
            cache.get().put(KEY, userInfo);
        } else {
            Map<String, Object> map = new HashMap<>();
            map.put(KEY, userInfo);
            cache.set(map);
        }
    }

    /**
     * 向ThreadLocal缓存用户登录信息
     */
    public static UserInfo getUserInfo() {
        Map<String, Object> map = cache.get();
        if (isCaheIsNull()) {
            return null;
        }
        return (UserInfo)map.getOrDefault(KEY, null);
    }

    /**
     * 向ThreadLocal缓存值
     *
     * @param key   要缓存的KEY
     * @param value 要缓存的VALUE
     */
    public static void set(String key, Object value) {
        if (!isCaheIsNull()) {
            cache.get().put(key, value);
        } else {
            Map<String, Object> map = new HashMap<>();
            map.put(key, value);
            cache.set(map);
        }
    }

    /**
     * 从ThreadLocal里获取缓存的值
     *
     * @param key 要获取的数据的KEY
     * @return 要获取的值
     */
    public static Object getCache(String key) {
        Map<String, Object> map = cache.get();
        if (isCaheIsNull()) {
            return null;
        }
        return map.getOrDefault(key, null);
    }

    /**
     * 根据KEY移除缓存里的数据
     *
     * @param key
     */
    public static void removeByKey(String key) {
        if (isCaheIsNull()) {
            return;
        } else {
            cache.get().remove(key);
        }
    }

    /**
     * 移除当前线程缓存
     * 用于释放当前线程threadlocal资源
     */
    public static void remove() {
        cache.remove();
    }

    private static boolean isCaheIsNull() {
        return cache.get() == null;
    }
}

9、RSA加密工具类

/**
 * RSA加密解密工具类
 */
public class RSAUtils {
    /**
     * 常量字符串
     */
    private static final String RSA = "RSA";

    /**
     * 私钥
     */
    private static final String PRIVATE_KEY = "MIICdQIBADANBgkqhkiG9w0BAQEFAASCAl8wggJbAgEAAoGBAItn9b5IvrTh1rnhzJJFNQ4UnJBL9kRitVHHklkz28ykYB5IWWM4lvZ7VzBP01AFG/I+otjr62Xx+gurInr22/F8FA4uKOmiYCPPf5h1mEvRK0y20ShYtDiwu9eM8hYkCqBvBHDKYjNAFmw3+HRzgCmGFEnmAG44bKCnZiMqUEf9AgMBAAECgYA05uCksypbrhA0PfHJ2CWIEF5Ri+IKlYLFY/yviTRx9jbbhw0U0BbJtoihtskz5pxyUz6tHuoXp7oBz5GoJCHWcvUGHTfJx8aq2IJwfJKGfi5V1I7ke3H6A8fI9lE8Y62lFSJmZITbUv3TsBiec/GhotYn3x5cu329MFX6miY7QQJBANwuq/jRM9RB+d8NUdO9XsWHVqCVp59J1KyKR7UHTgshH5ahHUGiSBtqM9yirKPnPHJLZpDsloNUDm7xhoLfULkCQQCiFWw7VZ7gKwY6q4nxBcb5BXDb3Fa5JKr7ggduvF8C90nue7TMbVMr0z8HHDvqhjzD06XTYG2P4+xPBwm5E2dlAkAMHkl6xVDb8tGk1B/Xzoljx8Idzn7ORor9ABNYRFGoTSdm6/EnRp4/XAYEs7NaxgROqhW4Dj1udvbgZkyn8VCJAkAJPV9mIoNkFA/O2GiMrN+i4oSEhBMNiuGUZN03mtVvvdkhFzw/Sxwqq2g0Z4+i1vQv1ajmW+DjCwM1nhkXy9thAkBPh46mKPQ58yURs9hlhLlUziUFjq83sSzXJkZQx7EWOud0U8Nriq+xsrVKJRkzntmIhLBacdwQSyImIWkStd7v";

    /**
     * 公钥
     */
    private static final String PUBLIC_KEY = "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCLZ/W+SL604da54cySRTUOFJyQS/ZEYrVRx5JZM9vMpGAeSFljOJb2e1cwT9NQBRvyPqLY6+tl8foLqyJ69tvxfBQOLijpomAjz3+YdZhL0StMttEoWLQ4sLvXjPIWJAqgbwRwymIzQBZsN/h0c4AphhRJ5gBuOGygp2YjKlBH/QIDAQAB";
    
    /**
     * 获取密钥对象
     * @param keySize RSA算法模长(个人理解应该是模长越大加密安全性越高,但加密过程可能也越长)
     * @return List
     */
    public static List<Key> getRsaObject(int keySize) throws NoSuchAlgorithmException {
        //创建list用来接收公钥对象和私钥对象
        List<Key> keyList = new ArrayList<>();
        //创建RSA密钥生成器
        KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance(RSA);
        //设置密钥大小,RSA算法的模长=最大加密数据的大小
        keyPairGenerator.initialize(keySize);
        //调用函数生成公钥私钥对象(以对生成密钥)
        KeyPair keyPair = keyPairGenerator.generateKeyPair();
        //获取公钥放入list
        keyList.add(keyPair.getPublic());
        //获取私钥放入list
        keyList.add(keyPair.getPrivate());
        //返回list
        return keyList;
    }

    /**
     * 生成公钥私钥的字符串
     * @param keySize 模长
     * @return List
     */
    public static List<String> getRsaKeyString(int keySize) throws NoSuchAlgorithmException {
        //创建list用来接收公钥对象和私钥对象
        List<String> keyList = new ArrayList<>();
        //创建RSA密钥生成器
        KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance(RSA);
        //设置密钥大小,RSA算法的模长=最大加密数据的大小
        keyPairGenerator.initialize(keySize);
        //调用函数生成公钥私钥对象(以对生成密钥)
        KeyPair keyPair = keyPairGenerator.generateKeyPair();
        //将公钥对象转换为字符串通过base64加密
        String publicKey = Base64.getEncoder().encodeToString(keyPair.getPublic().getEncoded());
        //将私钥对象转换为字符串通过base64加密
        String privateKey = Base64.getEncoder().encodeToString(keyPair.getPrivate().getEncoded());
        //获取公钥放入list
        keyList.add(publicKey);
        //获取私钥放入list
        keyList.add(privateKey);
        //返回list
        return keyList;
    }

    /**
     * 通过公钥字符串生成公钥对象(RSAPublicKey类型)
     * X509EncodeKeySpec方式(字符串公钥转为RSAPublicKey公钥)
     * @param publicKeyStr  公钥字符串
     * @return 返回RSAPublicKey类型的公钥对象
     */
    public static RSAPublicKey getRSAPublicKeyByX509(String publicKeyStr) throws NoSuchAlgorithmException, InvalidKeySpecException {
        //密钥工厂创建
        KeyFactory keyFactory = KeyFactory.getInstance(RSA);
        //公钥字符解密为bytes数组
        byte[] keyBytes = Base64.getDecoder().decode(publicKeyStr);
        //公钥字符串转x509
        X509EncodedKeySpec x509EncodedKeySpec = new X509EncodedKeySpec(keyBytes);
        //x509转RSAPublicKey
        return (RSAPublicKey) keyFactory.generatePublic(x509EncodedKeySpec);
    }

    /**
     * 通过私钥字符串生成私钥对象(RSAPrivateKey类型)
     * PKCS8EncodedKeySpec方式(字符串私钥转为RSAPrivateKey公钥)
     * @param privateKey 私钥字符串
     * @return 返回RSAPrivateKey类型的私钥对象
     */
    public static RSAPrivateKey getRSAPrivateKeyByPKCS8(String privateKey) throws NoSuchAlgorithmException, InvalidKeySpecException {
        //密钥工厂创建
        KeyFactory keyFactory = KeyFactory.getInstance(RSA);
        //私钥字符串解密为bytes数组
        byte[] keyBytes = Base64.getDecoder().decode(privateKey);
        //私钥字符串转pkcs8
        PKCS8EncodedKeySpec pkcs8EncodedKeySpec = new PKCS8EncodedKeySpec(keyBytes);
        //pkcs8转RSAPrivateKey
        return (RSAPrivateKey) keyFactory.generatePrivate(pkcs8EncodedKeySpec);
    }

    /**
     * 公钥加密
     * @param message 需要加密的信息
     * @param rsaPublicKey rsa公钥对象
     * @return 返回信息被加密后的字符串
     */
    public static String encryptByPublicKey(String message, RSAPublicKey rsaPublicKey) throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidKeyException, IllegalBlockSizeException, BadPaddingException, IOException {
        //RSA加密实例
        Cipher cipher = Cipher.getInstance(RSA);
        //初始化公钥
        cipher.init(Cipher.ENCRYPT_MODE, rsaPublicKey);
        //模长转为字节数
        int modulusSize = rsaPublicKey.getModulus().bitLength()/8;
        //PKCS PADDING长度为11字节,解密数据是除去这11byte
        int maxSingleSize = modulusSize-11;
        //切分字节数,每段不大于maxSingleSize
        byte[][] dataArray = splitArray(message.getBytes(), maxSingleSize);
        //字节数组输出流
        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
        //分组加密,加密后内容写入输出字节流
        for (byte[] s : dataArray){
            byteArrayOutputStream.write(cipher.doFinal(s));
        }
        //使用base64将字节数组转为string类型
        return Base64.getEncoder().encodeToString(byteArrayOutputStream.toByteArray());
    }

    /**
     * 公钥加密
     * @param message 需要加密的信息
     * @param publicKey rsa公钥字符串
     * @return 返回信息被加密后的字符串
     */
    public static String encryptByPublicKey(String message, String publicKey) throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidKeyException, IllegalBlockSizeException, BadPaddingException, IOException, InvalidKeySpecException {
        RSAPublicKey rsaPublicKeyByX509 = getRSAPublicKeyByX509(publicKey);
        return encryptByPublicKey(message,rsaPublicKeyByX509);
    }
    
    /**
     * 私钥解密密
     * @param encryptedMessage 信息加密后的字符串
     * @param rsaPrivateKey rsa私钥对象
     * @return 返回解密后的字符串
     */
    public static String decryptByPrivateKey(String encryptedMessage, RSAPrivateKey rsaPrivateKey) throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidKeyException, IllegalBlockSizeException, BadPaddingException, IOException {
        //RSA加密实例
        Cipher cipher = Cipher.getInstance(RSA);
        //初始化公钥
        cipher.init(Cipher.DECRYPT_MODE, rsaPrivateKey);
        //加密算法模长
        int modulusSize = rsaPrivateKey.getModulus().bitLength()/8;
        byte[] dataBytes = encryptedMessage.getBytes();
        //加密做了转码,这里也要用base64转回来
        byte[] decodeData = Base64.getDecoder().decode(dataBytes);
        //切分字节数,每段不大于maxSingleSize
        byte[][] dataArray = splitArray(decodeData, modulusSize);
        //字节数组输出流
        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
        //分组解密,解密后内容写入输出字节流
        for (byte[] s : dataArray){
            byteArrayOutputStream.write(cipher.doFinal(s));
        }
        //使用base64将字节数组转为string类型
        return byteArrayOutputStream.toString();
    }

    /**
     * 私钥解密密
     * @param encryptedMessage 信息加密后的字符串
     * @param privateKey rsa私钥字符串
     * @return 返回解密后的字符串
     */
    public static String decryptByPrivateKey(String encryptedMessage, String privateKey) throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidKeyException, IllegalBlockSizeException, BadPaddingException, IOException, InvalidKeySpecException {
        RSAPrivateKey rsaPublicKeyByX509 = getRSAPrivateKeyByPKCS8(privateKey);
        return decryptByPrivateKey(encryptedMessage,rsaPublicKeyByX509);
    }

    /**
     * 私钥解密密
     * @param encryptedMessage 信息加密后的字符串
     * @return 返回解密后的字符串
     */
    public static String decryptByPrivateKey(String encryptedMessage) throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidKeyException, IllegalBlockSizeException, BadPaddingException, IOException, InvalidKeySpecException {
        return decryptByPrivateKey(encryptedMessage,PRIVATE_KEY);
    }
    
    /**
     * 按指定长度切分数组
     * @param byteArrayInfo 需要切分的byte数组
     * @param maxSize 单个字节数组长度
     * @return byte[][]
     */
    private static byte[][] splitArray(byte[] byteArrayInfo, int maxSize){
        int dataLen = byteArrayInfo.length;
        if(dataLen<=maxSize){
            return new byte[][]{byteArrayInfo};
        }
        byte[][] result = new byte[(dataLen-1)/maxSize+1][];
        int resultLen = result.length;
        for (int i = 0; i < resultLen; i++) {
            if(i==resultLen-1){
                int sLen = dataLen-maxSize*i;
                byte[] single = new byte[sLen];
                System.arraycopy(byteArrayInfo, maxSize*i, single, 0, sLen);
                result[i] = single;
                break;
            }
            byte[] single = new byte[maxSize];
            System.arraycopy(byteArrayInfo, maxSize*i, single, 0, maxSize);
            result[i] = single;
        }
        return result;
    }

    /**
     * 返回公钥
     * @return String
     */
    public static String getPublicKey(){
        return PUBLIC_KEY;
    }

    public static void main(String[]args) throws NoSuchAlgorithmException, NoSuchPaddingException, IllegalBlockSizeException, BadPaddingException, IOException, InvalidKeyException, InvalidKeySpecException {
                String message = "fdsihufdiuheiuhasfd";
        System.out.println("加密前的信息内容:"+message);

        System.out.println("-----------------------RSA密钥对象加密解密--------------------------------");
        //生成密钥对象
        List<Key> keyList = RSAUtils.getRsaObject(1024);
        //RSA类型的公钥对象
        RSAPublicKey rsaPublicKey = (RSAPublicKey) keyList.get(0);
        //RSA类型的私钥对象
        RSAPrivateKey rsaPrivateKey = (RSAPrivateKey) keyList.get(1);
        //通过RSA类型的公钥对象加密信息
        String encryptedMsg = RSAUtils.encryptByPublicKey(message, rsaPublicKey);
        System.out.println("加密后的字符串encryptedMsg:\r\n"+encryptedMsg);
        //通过RSA类型的私钥对象解密
        String  decryptedMsg = RSAUtils.decryptByPrivateKey(encryptedMsg, rsaPrivateKey);
        System.out.println("------");
        System.out.println("解密后的字符串decryptedMsg:\r\n"+decryptedMsg);

        System.out.println();
        System.out.println("-----------------------字符串类型的密钥对象转为RSA密钥对象后的加密解密--------------------------");
        //生成密钥(字符串类型的密钥)
        List<String> keyStringList = RSAUtils.getRsaKeyString(1024);
        //字符串类型的公钥
        String publicKeyString = keyStringList.get(0);
        System.out.println("生成的公钥,可以把 PUBLIC_KEY 改为此值:\r\n"+publicKeyString);
        //字符串类型的私钥
        String privateKeyString = keyStringList.get(1);
        System.out.println("生成的私钥,可以把 PRIVATE_KEY 改为此值:\r\n"+publicKeyString);
        //将字符串类型的公钥私钥转为RSA类型
        RSAPublicKey rsaPublicKey1 = RSAUtils.getRSAPublicKeyByX509(publicKeyString);
        RSAPrivateKey rsaPrivateKey1 = RSAUtils.getRSAPrivateKeyByPKCS8(privateKeyString);
        //通过字符串公钥转为RSA类型的公钥 加密
        String encryptedMsg1 = RSAUtils.encryptByPublicKey(message, rsaPublicKey1);
        System.out.println("使用字符串转的rsa公钥对象加密后的字符串encryptedMsg1:\r\n"+encryptedMsg1);
        //通过字符串私钥转为RSA类型的私钥 解密
        String decryptedMsg1 = RSAUtils.decryptByPrivateKey(encryptedMsg1, rsaPrivateKey1);
        System.out.println("------");
        System.out.println("使用字符串转的私钥对象解密后的字符串decryptedMsg1:\r\n"+decryptedMsg1);
    }
}

10、校验图片的静态方法

图片文件的二进制数据也包含了一些特定的标识信息,通过解析这些标识信息可以判断出图片的格式。以下是一些常见图片格式的标识信息:

图片格式标识信息(十六进制)
JPEGFF D8
PNG89 50 4E 47 0D 0A 1A 0A
GIF47 49 46 38
BMP42 4D
TIFF49 49 2A 00
	/**
     * 通过二进制数据校验上传图片时的格式
     * 支持:jpeg|jpg、png、gif、bmp、tiff 格式的图片
     * @param file 文件
     * @return Boolean
     * @throws IOException IO异常
     */
    public static Boolean verifyImageFormat(MultipartFile file) throws IOException {
        if (file == null){
            return false;
        }
        FileInputStream inputStream = (FileInputStream) file.getInputStream();
        byte[] buffer = new byte[8];
        inputStream.read(buffer);
        // jpeg|jpg
        if (buffer[0] == (byte) 0xFF && buffer[1] == (byte) 0xD8) {
            return true;
        }
        // png
        if (buffer[0] == (byte) 0x89 && buffer[1] == (byte) 0x50 && buffer[2] == (byte) 0x4E && buffer[3] == (byte) 0x47
                && buffer[4] == (byte) 0x0D && buffer[5] == (byte) 0x0A && buffer[6] == (byte) 0x1A && buffer[7] == (byte) 0x0A) {
            return true;
        }
        // gif
        if (buffer[0] == (byte) 0x47 && buffer[1] == (byte) 0x49 && buffer[2] == (byte) 0x46 && buffer[3] == (byte) 0x38) {
            return true;
        }
        // bmp
        if (buffer[0] == (byte) 0x42 && buffer[1] == (byte) 0x4D) {
            return true;
        }
        // tiff
        return buffer[0] == (byte) 0x49 && buffer[1] == (byte) 0x49 && buffer[2] == (byte) 0x2A && buffer[3] == (byte) 0x00;
    }

11、树生成工具类

public class TreeUtils {

    private TreeUtils() {
    }

    /**
     * @param collection  集合
     * @param getId       子节点
     * @param getParentId 父节点
     * @param setNode     设置子集合
     * @param <E>         集合的类型
     * @param <R>         节点属性的类型
     * @return Collection
     */
    public static <E, R> Collection<E> tree(Collection<E> collection, Function<E, R> getId, Function<E, R> getParentId, BiConsumer<E, Collection<E>> setNode) {
        Collection<E> root = new LinkedList<>();
        for (E node : collection) {
            R parentId = getParentId.apply(node);
            R id = getId.apply(node);
            Collection<E> elements = new LinkedList<>();
            boolean isParent = true;
            for (E element : collection) {
                if (id.equals(getParentId.apply(element))) {
                    elements.add(element);
                }
                if (isParent && getId.apply(element).equals(parentId)) {
                    isParent = false;
                }
            }
            if (isParent) {
                root.add(node);
            }
            setNode.accept(node, elements);
        }
        return root;
    }

    /**
     * 递归构造树
     *
     * @param allList     全部数据
     * @param getId       子节点
     * @param getParentId 父节点
     * @param vertexId    顶点ID 可为空
     * @param setNode     设置子集合
     * @param <E>         集合的类型
     * @param <R>         节点属性的类型
     * @return List
     */
    private static <E, R> List<E> constructionTree(List<E> allList, Function<E, R> getId, Function<E, R> getParentId, R vertexId, BiConsumer<E, List<E>> setNode) {
        List<E> rootNodeList = new ArrayList<>();
        List<E> treeList = new ArrayList<>();
        allList.forEach(x -> {
            if (Objects.equals(vertexId, getParentId.apply(x))) {
                rootNodeList.add(x);
            }
        });
        for (E treeRootNode : rootNodeList) {
            buildChildTree(treeRootNode, allList, getId, getParentId, setNode);
            treeList.add(treeRootNode);
        }
        return treeList;
    }

    /**
     * 递归构造树(过滤)
     */
    private static <E, R> List<E> constructionTree(List<E> list, Function<E, R> getId, Function<E, R> getParentId,
                                                   R vertexId, BiConsumer<E, List<E>> setNode, Function<E, E> filter) {
        List<E> allList = new ArrayList<>();
        List<E> rootNodeList = new ArrayList<>();
        List<E> treeList = new ArrayList<>();
        list.forEach(x -> {
            E apply = filter.apply(x);
            allList.add(apply);
            if (Objects.equals(vertexId, getParentId.apply(x))) {
                rootNodeList.add(apply);
            }
        });

        for (E treeRootNode : rootNodeList) {
            buildChildTree(treeRootNode, allList, getId, getParentId, setNode);
            treeList.add(treeRootNode);
        }
        return treeList;
    }

    /**
     * 递归-----构建子树形结构
     *
     * @param pNode    根节点(顶级节点)
     * @param treeList 全部数据
     * @return 整棵树
     */
    public static <E, R> E buildChildTree(E pNode, List<E> treeList, Function<E, R> getId, Function<E, R> getParentId, BiConsumer<E, List<E>> setNode) {
        List<E> childTree = new ArrayList<>();
        for (E treeNode : treeList) {
            if (getId.apply(pNode).equals(getParentId.apply(treeNode))) {
                childTree.add(buildChildTree(treeNode, treeList, getId, getParentId, setNode));
            }
        }
        setNode.accept(pNode, childTree);
        return pNode;
    }


    /**
     * 测试
     */
    public static void main(String[] args) {
        // 普通树1
        List<MenuItem> menuList = Arrays.asList(
                new MenuItem(1, "二级菜单1", 0),
                new MenuItem(2, "二级菜单1", null),
                new MenuItem(3, "二级菜单1", 2),
                new MenuItem(4, "二级菜单2", 2),
                new MenuItem(5, "三级菜单1", 3),
                new MenuItem(6, "三级菜单2", 3)
        );
        // 普通树2
        List<MenuItem> menuList2 = Arrays.asList(
                new MenuItem(1, "二级菜单1", 0),
                new MenuItem(2, "二级菜单1", null),
                new MenuItem(3, "二级菜单1", 1),
                new MenuItem(4, "二级菜单2", 2),
                new MenuItem(5, "三级菜单1", 3),
                new MenuItem(6, "三级菜单1", 3),
                new MenuItem(7, "三级菜单2", 4)
        );
        // 过滤
        List<DiyData> diyData = Arrays.asList(
                new DiyData(1, "二级菜单1", 0),
                new DiyData(2, "二级菜单1", null),
                new DiyData(3, "二级菜单1", 1),
                new DiyData(4, "二级菜单2", 2),
                new DiyData(5, "三级菜单1", 3),
                new DiyData(6, "三级菜单1", 3),
                new DiyData(7, "三级菜单2", 4)
        );
        // 不过滤
        List<DiyData> diyData2 = Arrays.asList(
                new DiyData(1, "二级菜单1", 0),
                new DiyData(2, "二级菜单1", null),
                new DiyData(3, "二级菜单1", 1),
                new DiyData(4, "二级菜单2", 2),
                new DiyData(5, "三级菜单1", 3),
                new DiyData(6, "三级菜单1", 3),
                new DiyData(7, "三级菜单2", 4)
        );

        // 普通树1
        Collection<MenuItem> tree = TreeUtils.tree(menuList, MenuItem::getId, MenuItem::getParentId, (x, y) -> x.setChildren((List<MenuItem>) y));
        System.out.println(tree);

        // 普通树2
        List<MenuItem> tree2 = TreeUtils.constructionTree(menuList2, MenuItem::getId, MenuItem::getParentId, null, MenuItem::setChildren);
        System.out.println(tree2);

        // 过滤
        List<DiyData> tree3 = TreeUtils.constructionTree(diyData, DiyData::getId, DiyData::getParentId, null, DiyData::setChildren
                , x -> (DiyData) new DiyData()
                        .setKey(x.getId())
                        .setId(x.getId())
                        .setTitle(x.getName())
                        .setParentId(x.getParentId()));
        System.out.println(tree3);

        // 不过滤
        List<DiyData> tree4 = TreeUtils.constructionTree(diyData2, DiyData::getId, DiyData::getParentId, null, DiyData::setChildren);
        System.out.println(tree4);
    }

    /**
     * 测试普通 实体类
     */
    @Data
    @Accessors(chain = true)
    @AllArgsConstructor
    @NoArgsConstructor
    static public class MenuItem {
        private Integer id;
        private String name;
        private Integer parentId;
        private List<MenuItem> children;

        public MenuItem(Integer id, String name, Integer parentId) {
            this.id = id;
            this.name = name;
            this.parentId = parentId;
        }
    }

    /**
     * 测试过滤 实体类
     */
    @Data
    @EqualsAndHashCode(callSuper = true)
    @ToString(callSuper = true)
    @Accessors(chain = true)
    @AllArgsConstructor
    @NoArgsConstructor
    static public class DiyData extends TreeNode {
        /**
         * id 需映射 TreeNode 中的 key
         */
        private Integer id;
        /**
         * id 需映射 TreeNode 中的 title
         */
        private String name;
        private Integer parentId;
    }


    /**
     * 供前端组件使用,可自行修改
     */
    @Data
    @AllArgsConstructor
    @NoArgsConstructor
    @Accessors(chain = true)
    public static class TreeNode {

        /**
         * 前端需要对应的id
         */
        private Integer key;

        /**
         * 节点id
         */
        private Integer id;

        /**
         * 父节点id
         */
        private Integer parentId;

        /**
         * 前端需要对应公司的name
         */
        private String title;

        /**
         * 子节点
         */
        private List<? extends TreeNode> children;
    }
}

七、配置类(config包下)

1、MyBatisPlusConfig类管理乐观锁注解和填充策略

@MapperScan("com.zhao.mapper")//扫描我们的mapper文件夹
@EnableTransactionManagement //事务控制
@Configuration //配置类
public class MyBatisPlusConfig implements MetaObjectHandler {
    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor(){
        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
        // 注册乐观锁插件
        interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
        // 分页插件
        interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
        return interceptor;
    }


    //插入时的填充策略
    @Override
    public void insertFill(MetaObject metaObject) {
        this.setFieldValByName("createTime", LocalDateTime.now(),metaObject);
        this.setFieldValByName("updateTime", LocalDateTime.now(),metaObject);
    }
    //更新时的填充策略
    @Override
    public void updateFill(MetaObject metaObject) {
        this.setFieldValByName("updateTime", LocalDateTime.now(),metaObject);
    }

}

2、RedisConfig

@Configuration
public class RedisConfig {

    // 编写我们自己的 RedisTemplate,一般拿来用就行了
    @Bean
    @SuppressWarnings("all") //这tm是个坑,我现在不知道这个注释是干嘛的,但是下面这个注解和这串注解没有的话
                            // RedisConnectionFactory redisConnectionFactory 会爆红,下面这注解没有注释的话,就序列化不了
//    @ConditionalOnSingleCandidate(RedisConnectionFactory.class)
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) throws UnknownHostException {
        //我们为了自己开发方便,一般直接使用<String, Object>类型
        RedisTemplate<String, Object> template = new RedisTemplate<>();
        template.setConnectionFactory(redisConnectionFactory);

        // Json序列化配置
        Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<Object>(Object.class);
        ObjectMapper mapper = new ObjectMapper();
        mapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
//        mapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);  这个被弃用了,所以现在要用 activateDefaultTyping
        mapper.activateDefaultTyping(LaissezFaireSubTypeValidator.instance,ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.PROPERTY);
        jackson2JsonRedisSerializer.setObjectMapper(mapper);

        // String 的序列化
        RedisSerializer<String> stringRedisSerializer = new StringRedisSerializer();

        // key采用String的序列化方式
        template.setKeySerializer(stringRedisSerializer);
        // hash的key也采用String的序列化方式
        template.setHashKeySerializer(stringRedisSerializer);
        // value序列化方式采用jackson
        template.setValueSerializer(jackson2JsonRedisSerializer);
        // hash的value序列化方式采用jackson
        template.setHashValueSerializer(jackson2JsonRedisSerializer);
        template.afterPropertiesSet();

        return template;
    }
}

3、WebConfig(拦截器)

@Configuration
public class WebConfig implements WebMvcConfigurer{
    @Autowired
    VoteLoginInterceptor voteLoginInterceptor;
    @Autowired
    AdminLoginInterceptor adminLoginInterceptor;
 
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        //注册自己的拦截器,并设置拦截的请求路径
        //addPathPatterns为拦截此请求路径的请求
        //excludePathPatterns为不拦截此路径的请求
        registry.addInterceptor(voteLoginInterceptor).addPathPatterns("/vote/voting2/**","/vote/whetherVoting/**","/vote/index/voting/**").excludePathPatterns("/static/**");
        registry.addInterceptor(adminLoginInterceptor).addPathPatterns("/index/**").excludePathPatterns("/","/static/**","/index");
    }
 
}

3.5 用于token拦截的拦截器

/**
 * Token验证配置
 */
@Configuration
public class TokenValidationConfig implements WebMvcConfigurer {
    @Autowired
    AdminLoginInterceptor adminLoginInterceptor;
 
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        //注册自己的拦截器,并设置拦截的请求路径
        //addPathPatterns为拦截此请求路径的请求
        //excludePathPatterns为不拦截此路径的请求
        registry.addInterceptor(adminLoginInterceptor).addPathPatterns("/**").excludePathPatterns("/login","/login/**");
    }
 
}
@Slf4j
@Component
class AdminLoginInterceptor implements HandlerInterceptor {
    /**
     * 目标方法执行前
     * 该方法在控制器处理请求方法前执行,其返回值表示是否中断后续操作
     * 返回 true 表示继续向下执行,返回 false 表示中断后续操作
     */
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        String token = "";
        // 如果是 OPTIONS 请求,我们就让他通过,不管他
        if (request.getMethod().equals("OPTIONS")) {
            response.setStatus(HttpServletResponse.SC_OK);
            return true;
            // 如果不是,我们就把token拿到,用来做判断
        }else {
            token = request.getHeader("token");
        }
        // Auth0JwtUtils.verify(token)&&!Auth0JwtUtils.isExpired(token) 这两个是我写的验证token的方法,用的jwt,封装了一个工具类
        if (StringUtils.isNotBlank(token) && Auth0JwtUtils.verify(token) && !Auth0JwtUtils.isExpired(token)){
        	// 用于保存用户信息(可以搜索本章的ThreadLocalUtils工具类)
            ThreadLocalUtils.setUserInfo(Auth0JwtUtils.getObject(token,"userInfo",UserInfo.class));
            return true;
        }else {
            JSONObject json = EcpResponseUtil.failed("500","请重新登录");
            response.setCharacterEncoding("UTF-8");
            response.setContentType("application/json; charset=utf-8");
            PrintWriter writer = response.getWriter();
            writer.write(json.toString());
            writer.flush();
            writer.close();
            return false;
        }
    }
    /**
     * 目标方法执行后
     * 该方法在控制器处理请求方法调用之后、解析视图之前执行
     * 可以通过此方法对请求域中的模型和视图做进一步修改
     */
    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
//        log.info("postHandle执行{}", modelAndView);
    }
    /**
     * 页面渲染后
     * 该方法在视图渲染结束后执行
     * 可以通过此方法实现资源清理、记录日志信息等工作
     */
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
//        log.info("afterCompletion执行异常{}", ex);
        ThreadLocalUtils.remove();
    }
}

4、DruidConfig

@Configuration
public class DruidConfig {

    @ConfigurationProperties(prefix = "spring.datasource")
    @Bean
    public DataSource druidDataSource(){
        return new DruidDataSource();
    }

    @Bean
    //后台监控功能:相当于web.xml
    //因为Springboot 内置了 servlet 容器,所以没有web.xml , 代替方法:ServletRegistrationBean
    public ServletRegistrationBean StatViewServlet(){
        ServletRegistrationBean<StatViewServlet> bean = new ServletRegistrationBean<>(new StatViewServlet(), "/druid/*");
        //后台需要有人登录,账号密码配置
        HashMap<String, String> initParameters = new HashMap<>();
        //增加配置
        initParameters.put("loginUserName","admin");//登录的key是固定参数:loginUserName  loginPassword
        initParameters.put("loginPassword","123456");
        //允许谁可以访问
//        initParameters.put("allow","localhost");//仅允许本地登录
        initParameters.put("allow","");//允许所有人登录

        //禁止谁能访问
//        initParameters.put("kuangshen","192.168.11.123");//禁止这个ip地址访问

        bean.setInitParameters(initParameters);//设置初始化参数
        return bean;
    }
    @Bean
    //filter
    public FilterRegistrationBean webStatFilter(){
        FilterRegistrationBean<Filter> bean = new FilterRegistrationBean<>();
        bean.setFilter(new WebStatFilter());
        //可以过滤哪些请求
        HashMap<String, String> initParameters = new HashMap<>();
        //这些东西不进行统计
        initParameters.put("exclusions","*.js,*.css,/druid/*");

        bean.setInitParameters(initParameters);
        return bean;
    }


}

5、跨域问题

(1)、第一种

/**
 * @Role 跨域请求
 * @Author 赵平安
 * @GMTCreate   2022年7月21日
 * @Modifier
 * @GMTModified
 * @ModifyContent
 */
@Configuration
public class CorsConfig implements WebMvcConfigurer {
 
    @Override
    public void addCorsMappings(CorsRegistry registry) {
        registry.addMapping("/**") //拦截所有请求
                //是否发送Cookie,用于凭证请求, 默认不发送cookie。
                .allowCredentials(true)
                //出现的问题:当allowCredentials为真时,allowedorigin不能包含特殊值"*",因为不能在"访问-控制-起源“响应头中设置该值。要允许凭证到一组起源,显示地列出它们,或者考虑使用"allowedOriginPatterns”代替。
//                .allowedOrigins("*") //可跨域的域名
                .allowedOriginPatterns("*")
                .allowedMethods("GET", "POST", "PUT", "DELETE")  // 允许跨域的方法,可以单独配置
                //配置预检请求的有效时间, 单位是秒,表示:在多长时间内,不需要发出第二次预检请求。
                .maxAge(1800)
                //配置允许的自定义请求头,用于 预检请求。
                .allowedHeaders("*")
                .exposedHeaders("*");
    }
}

(1)、第二种(应对SpringBoot的版本比较低)

/**
 * 跨域请求
 */
@Configuration
public class CorsConfig implements WebMvcConfigurer {

    @Resource
    private CorsInterceptor corsInterceptor;

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        // 跨域拦截器需放在最上面
        registry.addInterceptor(corsInterceptor).addPathPatterns("/**");
    }
}
@Component
class CorsInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
        String origin = request.getHeader("origin");
        response.setHeader("Access-Control-Allow-Origin", origin);
        response.setHeader("Access-Control-Allow-Credentials", "true");
        response.setHeader("Access-Control-Allow-Methods", "GET, HEAD, POST, PUT, PATCH, DELETE, OPTIONS");
        response.setHeader("Access-Control-Max-Age", "86400");
        //Headers值区分大小写
        response.setHeader("Access-Control-Allow-Headers", "content-type,membertoken");

        // 如果是OPTIONS则结束请求
        if (HttpMethod.OPTIONS.toString().equals(request.getMethod())) {
            response.setStatus(HttpStatus.NO_CONTENT.value());
            return false;
        }
        return true;
    }
}

6、fastJson配置类

/**
 * FastJson 配置类
 * @author ZHAOPINGAN
 */
@Configuration
public class FJsonConfig {
    
    @Bean
    public HttpMessageConverter configureMessageConverters() {
        FastJsonHttpMessageConverter converter = new FastJsonHttpMessageConverter();
        FastJsonConfig config = new FastJsonConfig();
        config.setSerializerFeatures(
                // 保留map空的字段
                SerializerFeature.WriteMapNullValue,
                // 将String类型的null转成""
                SerializerFeature.WriteNullStringAsEmpty,
                // 将Number类型的null转成0
//                SerializerFeature.WriteNullNumberAsZero,
                // 将List类型的null转成[]
                SerializerFeature.WriteNullListAsEmpty,
                // 将Boolean类型的null转成false
                SerializerFeature.WriteNullBooleanAsFalse,
                // 避免循环引用
                SerializerFeature.DisableCircularReferenceDetect);

        converter.setFastJsonConfig(config);
        converter.setDefaultCharset(Charset.forName("UTF-8"));
        List<MediaType> mediaTypeList = new ArrayList<>();
        // 解决中文乱码问题,相当于在Controller上的@RequestMapping中加了个属性produces = "application/json"
        mediaTypeList.add(MediaType.APPLICATION_JSON);
        converter.setSupportedMediaTypes(mediaTypeList);
      return converter;
    }
}

八、配置拦截器(interceptor包下)

@Slf4j
@Component
public class AdminLoginInterceptor implements HandlerInterceptor {
    /**
     * 目标方法执行前
     * 该方法在控制器处理请求方法前执行,其返回值表示是否中断后续操作
     * 返回 true 表示继续向下执行,返回 false 表示中断后续操作
     */
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        Object loginUser = request.getSession().getAttribute("adminInfo");
        if (loginUser == null) {
            //未登录,返回登陆页
            request.setAttribute("msg", "您没有权限进行此操作,请先登陆!");
            request.getRequestDispatcher("/").forward(request, response);
            return false;
        } else {
            //放行
            return true;
        }
    }
    /**
     * 目标方法执行后
     * 该方法在控制器处理请求方法调用之后、解析视图之前执行
     * 可以通过此方法对请求域中的模型和视图做进一步修改
     */
    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
//        log.info("postHandle执行{}", modelAndView);
    }
    /**
     * 页面渲染后
     * 该方法在视图渲染结束后执行
     * 可以通过此方法实现资源清理、记录日志信息等工作
     */
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
//        log.info("afterCompletion执行异常{}", ex);
    }
}

九、controller、service、pojo、mapper自动生成(测试类中)

/**
 * <p>
 * 代码生成器(快速版本)
 * </p>
 *
 * @author 赵平安
 * @since 2022-4-12 0022 16:51
 */
public class FastCodeGenerator {

    // 基础信息配置
    // 数据库连接字符
    private static final String URL = "jdbc:mysql://127.0.0.1:3306/school_vote?useUnicode=true&serverTimezone=UTC&useSSL=false&characterEncoding=utf8";
    // 数据库用户名
    private static final String USERNAME = "root";
    // 数据库密码
    private static final String PASSWORD = "123456";
    // 项目根路径。生成结果如:D:\MyProject\spring-boot
    private static final String projectRootPath = System.getProperty("user.dir");
    // 项目根路径(测试用,非通用)(此句是本项目测试用的。实际项目中,包括多模块项目,请注释掉此句,使用上句)
//    private static final String projectRootPath = System.getProperty("user.dir") + "/study-mybatis-plus-fast-generator";
    // 父包名。用于生成的java文件的import。
//    private static final String parentPackageName = "com.cxhit.mybatisplus.generator";
    private static final String parentPackageName = "com.zhao.projectname";

    /**
     * 执行此处
     */
    public static void main(String[] args) {
        // 简单示例,适用于单模块项目
        simpleGenerator();
        // 完整示例,适用于多模块项目
//        completeGenerator();
    }

    /**
     * 【单模块】简单的实现方案
     */
    protected static void simpleGenerator() {

        // 包路径
        String packagePath = projectRootPath + "/src/main/java";
        // XML文件的路径
        String mapperXmlPath = projectRootPath + "/src/main/resources/Mybatis/mapper";

        // 开始执行代码生成
        FastAutoGenerator.create(URL, USERNAME, PASSWORD)
                // 1. 全局配置
                .globalConfig(builder -> builder
                        // 作者名称
                        .author("赵平安")
                        // 开启覆盖已生成的文件。注释掉则关闭覆盖。
                        // .fileOverride()
                        // 禁止打开输出目录。注释掉则生成完毕后,自动打开生成的文件目录。
                        .disableOpenDir()
                        // 指定输出目录。如果指定,Windows生成至D盘根目录下,Linux or MAC 生成至 /tmp 目录下。
                        .outputDir(packagePath)
                        // 开启swagger2.注释掉则默认关闭。
                        // .enableSwagger()
                        // 指定时间策略。
                        .dateType(DateType.TIME_PACK)
                        // 注释时间策略。
                        .commentDate("yyyy-MM-dd")
                )

                // 2. 包配置
                .packageConfig((scanner, builder) -> builder
                        // 设置父表名
                        .parent(parentPackageName)
//                        .moduleName(scanner.apply("请输入模块名:"))
                        // mapper.xml 文件的路径。单模块下,其他文件路径默认即可。
                        .pathInfo(Collections.singletonMap(OutputFile.xml, mapperXmlPath))
                        //改实体类名字为pojo
                                .entity("pojo")
                )

                // 3. 策略配置
                .strategyConfig((scanner, builder) -> builder.addInclude(getTables(scanner.apply("请输入表名,多个英文逗号分隔?生成所有表,请输入[all]:")))
                        // 阶段1:Entity实体类策略配置
                        .entityBuilder()
                        // 开启生成实体时生成字段注解。
                        // 会在实体类的属性前,添加[@TableField("nickname")]
                        .enableTableFieldAnnotation()
                        // 逻辑删除字段名(数据库)。
                        .logicDeleteColumnName("deleted")
                        // 逻辑删除属性名(实体)。
                        // 会在实体类的该字段属性前加注解[@TableLogic]
                        .logicDeletePropertyName("deleted")
                        // 乐观锁
                        .versionColumnName("version")
                        .versionPropertyName("version")
                        // 会在实体类的该字段上追加注解[@TableField(value = "create_time", fill = FieldFill.INSERT)]
                        .addTableFills(new Column("create_time", FieldFill.INSERT))
                        // 会在实体类的该字段上追加注解[@TableField(value = "update_time", fill = FieldFill.INSERT_UPDATE)]
                        .addTableFills(new Column("update_time", FieldFill.INSERT_UPDATE))
                        // 阶段2:Mapper策略配置
                        .mapperBuilder()
                        // 开启 @Mapper 注解。
                        // 会在mapper接口上添加注解[@Mapper]
                        .enableMapperAnnotation()
                        // 启用 BaseResultMap 生成。
                        // 会在mapper.xml文件生成[通用查询映射结果]配置。
                        .enableBaseResultMap()
                        // 启用 BaseColumnList。
                        // 会在mapper.xml文件生成[通用查询结果列 ]配置
                        .enableBaseColumnList()
                        // 阶段4:Controller策略配置
                        .controllerBuilder()
                        // 会在控制类中加[@RestController]注解。
                        .enableRestStyle()
                        // 开启驼峰转连字符
                        .enableHyphenStyle()
                        .build()
                )

                // 4. 模板引擎配置,默认 Velocity 可选模板引擎 Beetl 或 Freemarker
                //.templateEngine(new BeetlTemplateEngine())
                .templateEngine(new FreemarkerTemplateEngine())

                // 5. 执行
                .execute();
    }

    /**
     * 【多模块使用】完整的实现方案
     */
    protected static void completeGenerator() {
        //【1】基础信息配置
        // 指定模块名,用于生成的java文件的import。
        String moduleName = scanner("请输入模块名:");
        // 六个文件的路径。多模块项目下,一般来说每个文件的路径都是不同的(根据项目实际,可能在不同的模块下)。
        String entityPath = projectRootPath + "/project-entity/src/main/java/com/yourdomain/projectname/entity/" + moduleName;
        String mapperPath = projectRootPath + "/project-dao/src/main/java/com/yourdomain/projectname/mapper/" + moduleName;
        String mapperXmlPath = projectRootPath + "/project-dao/src/main/resources/mapper/" + moduleName;
        String servicePath = projectRootPath + "/project-service/src/main/java/com/yourdomain/projectname/service/" + moduleName;
        String serviceImplPath = projectRootPath + "/project-service/src/main/java/com/yourdomain/projectname/service/" + moduleName + "/impl";
        String controllerPath = projectRootPath + "/project-controller/src/main/java/com/yourdomain/projectname/controller/" + moduleName;
        // 关于以上写法的解释:
        // --- 假设我们的项目有四个模块:project-entity、project-dao、project-service、project-controller
        // --- project-entity 的包路径:com.yourdomain.projectname.eneity,
        //   ---则生成system模块下的sys_user表,生成的实体文件将放在:com.yourdomain.projectname.entity.system包下,SysUser.java。
        // --- project-dao 的包路径:com.yourdomain.projectname.mapper,
        //   ---则生成system模块下的sys_user表,生成的mapper接口文件将放在:com.yourdomain.projectname.mapper.system包下,类名为:SysUserMapper.java。
        // --- 其他文件以此类推,即每个模块放MVC结构中对应的类型文件。
        // --- 注意:这里最后的文件路径修改了,下文配置中的【2 包配置】中的包路径也要同步修改!否则生成的java文件,首句import会报错。原因是路径错误。

        // 【2】开始执行代码生成
        FastAutoGenerator.create(URL, USERNAME, PASSWORD)
                // 1. 全局配置
                .globalConfig(builder -> builder
                        // 作者名称
                        .author("赵平安")
                        // 开启覆盖已生成的文件。注释掉则关闭覆盖。请谨慎开启此选项!
                        // .fileOverride()
                        // 禁止打开输出目录。注释掉则生成完毕后,自动打开生成的文件目录。
                        .disableOpenDir()
                        // 指定输出目录。多模块下,每个类型的文件输出目录不一致,在包配置阶段配置。
                        // .outputDir(packagePath)
                        // 开启swagger2。注释掉则默认关闭。
                        // .enableSwagger()
                        // 开启 kotlin 模式。注释掉则关闭此模式
                        // .enableKotlin()
                        // 指定时间策略。
                        .dateType(DateType.TIME_PACK)
                        // 注释时间策略。
                        .commentDate("yyyy-MM-dd")
                )

                // 2. 包配置
                .packageConfig((scanner, builder) -> builder
                        // 阶段1:各个文件的包名设置,用来拼接每个java文件的第一句:package com.XXX.XXX.XXX.xxx;
                        // 父包名配置
                        .parent(parentPackageName)
                        // 输入模块名。此模块名会在下面的几个包名前加。多模块项目,请根据实际选择是否添加。
                        // .moduleName(moduleName)
                        .entity("entity." + moduleName)
                        .mapper("mapper." + moduleName)
                        .service("service." + moduleName)
                        .serviceImpl("service." + moduleName + ".impl")
                        .controller("controller." + moduleName)
                        .other("other")
                        // 阶段2:所有文件的生成路径配置
                        .pathInfo(
                                new HashMap<OutputFile, String>() {{
                                    // 实体类的保存路径
                                    put(OutputFile.entity, entityPath);
                                    // mapper接口的保存路径
                                    put(OutputFile.mapper, mapperPath);
                                    // mapper.xml文件的保存路径
                                    put(OutputFile.xml, mapperXmlPath);
                                    // service层接口的保存路径
                                    put(OutputFile.service, servicePath);
                                    // service层接口实现类的保存路径
                                    put(OutputFile.serviceImpl, serviceImplPath);
                                    // 控制类的保存路径
                                    put(OutputFile.controller, controllerPath);
                                }}
                        )
                )

                // 3. 策略配置【请仔细阅读每一行,根据项目实际项目需求,修改、增删!!!】
                .strategyConfig((scanner, builder) -> builder.addInclude(getTables(scanner.apply("请输入表名,多个英文逗号分隔?生成所有表,请输入[all]:")))
                        // 阶段1:Entity实体类策略配置
                        .entityBuilder()
                        // 设置父类。会在生成的实体类名后:extends BaseEntity
                        // .superClass(BaseEntity.class)
                        // 禁用生成 serialVersionUID。(不推荐禁用)
                        // .disableSerialVersionUID()
                        // 开启生成字段常量。
                        // 会在实体类末尾生成一系列 [public static final String NICKNAME = "nickname";] 的语句。(一般在写wapper时,会用到)
                        // .enableColumnConstant()
                        // 开启链式模型。
                        // 会在实体类前添加 [@Accessors(chain = true)] 注解。用法如 [User user=new User().setAge(31).setName("snzl");](这是Lombok的注解,需要添加Lombok依赖)
                        // .enableChainModel()
                        // 开启 lombok 模型。
                        // 会在实体类前添加 [@Getter] 和 [@Setter] 注解。(这是Lombok的注解,需要添加Lombok依赖)
                        // .enableLombok()
                        // 开启 Boolean 类型字段移除 is 前缀。
                        // .enableRemoveIsPrefix()
                        // 开启生成实体时生成字段注解。
                        // 会在实体类的属性前,添加[@TableField("nickname")]
                        .enableTableFieldAnnotation()
                        // 逻辑删除字段名(数据库)。
                        .logicDeleteColumnName("deleted")
                        // 逻辑删除属性名(实体)。
                        // 会在实体类的该字段属性前加注解[@TableLogic]
                        .logicDeletePropertyName("deleted")
                        // 数据库表映射到实体的命名策略(默认下划线转驼峰)。一般不用设置
                        // .naming(NamingStrategy.underline_to_camel)
                        // 数据库表字段映射到实体的命名策略(默认为 null,未指定按照 naming 执行)。一般不用设置
                        // .columnNaming(NamingStrategy.underline_to_camel)
                        // 添加父类公共字段。
                        // 这些字段不会出现在新增的实体类中。
                        .addSuperEntityColumns("id", "delete_time")
                        // 添加忽略字段。
                        // 这些字段不会出现在新增的实体类中。
                        // .addIgnoreColumns("password")
                        // 添加表字段填充
                        // 会在实体类的该字段上追加注解[@TableField(value = "create_time", fill = FieldFill.INSERT)]
                        .addTableFills(new Column("create_time", FieldFill.INSERT))
                        // 会在实体类的该字段上追加注解[@TableField(value = "update_time", fill = FieldFill.INSERT_UPDATE)]
                        .addTableFills(new Column("update_time", FieldFill.INSERT_UPDATE))
                        // 全局主键类型。如果MySQL主键设置为自增,则不需要设置此项。
                        // .idType(IdType.AUTO)
                        // 格式化文件名称。
                        // 如果不设置,如表[sys_user]的实体类名是[SysUser]。设置成下面这样,将是[SysUserEntity]
                        // .formatFileName("%sEntity")

                        // 阶段2:Mapper策略配置
                        .mapperBuilder()
                        // 设置父类
                        // 会在mapper接口方法集成[extends BaseMapper<XXXEntity>]
                        // .superClass(BaseMapper.class)
                        // 开启 @Mapper 注解。
                        // 会在mapper接口上添加注解[@Mapper]
                        .enableMapperAnnotation()
                        // 启用 BaseResultMap 生成。
                        // 会在mapper.xml文件生成[通用查询映射结果]配置。
                        .enableBaseResultMap()
                        // 启用 BaseColumnList。
                        // 会在mapper.xml文件生成[通用查询结果列 ]配置
                        .enableBaseColumnList()
                        // 设置缓存实现类
                        // .cache(MyMapperCache.class)
                        // 格式化 mapper 文件名称。
                        // 如果不设置,如表[sys_user],默认的文件名是[SysUserMapper]。写成下面这种形式后,将变成[SysUserDao]。
                        // .formatMapperFileName("%sDao")
                        // 格式化 xml 实现类文件名称。
                        // 如果不设置,如表[sys_user],默认的文件名是[SysUserMapper.xml],写成下面这种形式后,将变成[SysUserXml.xml]。
                        // .formatXmlFileName("%sXml")

                        // 阶段3:Service策略配置
                        // .serviceBuilder()
                        // 设置 service 接口父类
                        // .superServiceClass(BaseService.class)
                        // 设置 service 实现类父类
                        // .superServiceImplClass(BaseServiceImpl.class)
                        // 格式化 service 接口文件名称
                        // 如果不设置,如表[sys_user],默认的是[ISysUserService]。写成下面这种形式后,将变成[SysUserService]。
                        // .formatServiceFileName("%sService")
                        // 格式化 service 实现类文件名称
                        // 如果不设置,如表[sys_user],默认的是[SysUserServiceImpl]。
                        // .formatServiceImplFileName("%sServiceImpl")

                        // 阶段4:Controller策略配置
                        .controllerBuilder()
                        // 设置父类。
                        // 会集成此父类。
                        // .superClass(BaseController.class)
                        // 开启生成 @RestController 控制器
                        // 会在控制类中加[@RestController]注解。
                        .enableRestStyle()
                        // 开启驼峰转连字符
                        .enableHyphenStyle()

                        // 最后:构建
                        .build()
                )

                //模板引擎配置,默认 Velocity 可选模板引擎 Beetl 或 Freemarker
                //.templateEngine(new BeetlTemplateEngine())
                .templateEngine(new FreemarkerTemplateEngine())

                // 执行
                .execute();
    }

    // 处理 all 情况
    protected static List<String> getTables(String tables) {
        return "all".equals(tables) ? Collections.emptyList() : Arrays.asList(tables.split(","));
    }

    /**
     * <p>
     * 读取控制台内容
     * </p>
     */
    private static String scanner(String tip) {
        Scanner scanner = new Scanner(System.in);
        StringBuilder help = new StringBuilder();
        help.append("请输入").append(tip).append(":");
        System.out.println(help.toString());
        if (scanner.hasNext()) {
            String ipt = scanner.next();
            if (StringUtils.isNotBlank(ipt)) {
                return ipt;
            }
        }
        throw new MybatisPlusException("请输入正确的" + tip + "!");
    }
}

十、上传Git

Git使用详解全图文(基于gitee),看这一篇就够了_我认不到你的博客-CSDN博客_git的使用

.gitignore文件

# Compiled class file
*.class

# Eclipse
.project
.classpath
.settings/

# Intellij
*.ipr
*.iml
*.iws
.idea/

# Maven
target/

# Gradle
build
.gradle

# Log file
*.log
log/

# out
**/out/

# Mac
.DS_Store

# others
*.jar
*.war
*.zip
*.tar
*.tar.gz
*.pid
*.orig
temp/


在这里插入图片描述

十一、Swagger+knife4j

Swagger3+knife4j的使用_我认不到你的博客-CSDN博客

十二、MinIO

使用Docker搭建分布式文件存储系统MinIO

十三、运维:Jenkins+Docker(Docker基本没怎么讲,主要是Jenkins)

Linux安装jenkins_我认不到你的博客-CSDN博客

jenkins使用docker安装保姆级教程(面向小白教程,最新最全,全图文)2022-8-1,不会docker也没关系

使用jenkins+gitee创建docker镜像并运行

jenkins流水线(jenkinsfile)详解,保姆式教程_我认不到你的博客-CSDN博客

jenkins pipline 拉取git历史版本_我认不到你的博客-CSDN博客

十四、操作Excel

JAVA操作Excel(POI、easyPOI、easyExcel)

十五、Nginx

Windows安装Nginx并配置负载均衡_windows负载均衡软件_我认不到你的博客-CSDN博客

十六、JMeter

jmeter安装与使用,全图文讲解_我认不到你的博客-CSDN博客

十八、XXL-JOB

XXL-JOB详解(整合springboot)保姆级教程_xxljob整合_我认不到你的博客-CSDN博客

  • 1
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
根据引用和引用中的代码片段,你的SpringBoot项目启动类应该是正确的。但是根据引用中提到的问题解决方法,你可能遇到了404错误。这种错误通常是因为项目部署在外部Tomcat服务器时出现的。 为了解决这个问题,你可以尝试以下步骤: 1. 确保你的项目已经成功打包成war文件,并将它部署到外部Tomcat服务器中。根据引用中的代码片段,你需要在pom.xml文件中将打包方式修改为war,如下所示: ```xml <packaging>war</packaging> ``` 2. 确保你的外部Tomcat服务器已经正确配置并运行。检查Tomcat的日志文件是否显示任何错误信息。 如果你仍然遇到404错误,你可以尝试以下步骤: 1. 检查你的项目中的URL路径是否正确。确保你正在请求正确的URL地址。 2. 检查你的控制器类和方法是否正确映射了URL路径。确保你的@RequestMapping注解和URL路径是一致的。 3. 检查你的项目中是否存在静态资源文件(例如HTML、CSS、JavaScript等)。确保这些资源文件位于正确的位置,并且可以被外部Tomcat服务器正确访问。 如果你仍然无法解决问题,可以尝试搜索相关的错误信息或咨询Spring Boot的官方文档或社区论坛,以获得更多帮助。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *2* *3* [springboot项目Tomcat启动成功访问URL出现空白页面问题解决](https://blog.csdn.net/xun_zhao_t521/article/details/113768715)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_1"}}] [.reference_item style="max-width: 100%"] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

我认不到你

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值