练题模块环境搭建指南

文章目录

  • 1.数据库表设计
  • 1.practice_set 套卷
  • 2.practice_set_detail 套卷细节
  • 3.practice_info 练习信息
  • 4.practice_detail 练习详情
  • 5.E-R图
  • 2.架构设计(三层架构)
  • 3.练题微服务架构搭建
  • 1.创建一个练题微服务模块
  • 1.创建一个maven项目
  • 2.把src删除,只留pom.xml
  • 2.微服务父模块的pom.xml配置
  • 1.配置packaging为pom,指定编译版本,并统一指定SpringBoot版本,统一配置阿里云仓库,使子模块继承
  • 2.type和scope解释
  • 1.type
  • 2.scope
  • 3.创建一个练题微服务的api模块
  • 1.创建一个maven项目,删除resource目录和test目录
  • 2.创建一个common包存放Result和Page相关的
  • 1.目录结构
  • 2.PageInfo.java
  • 3.PageResult.java
  • 4.Result.java
  • 5.ResultCodeEnum.java
  • 6.引入lombok的依赖
  • 3.创建一个通用的枚举包
  • 1.结构
  • 2.IsDeleteFlagEnum.java
  • 4.创建一个req和vo分别存放入参和出参实体
  • 结构
  • 4.创建一个server子模块
  • 1.创建一个maven项目
  • 2.创建一个config包,暂时先存放mybatis的东西
  • 1.结构
  • 2.SqlStatementInterceptor.java sql状态拦截器
  • 3.MybatisPlusAllSqlLog.java sql转换器
  • 4.MybatisConfiguration.java 注册两个拦截器
  • 3.引入基本依赖
  • 4.config下创建一个redis包,存放redis配置和工具类
  • 1.结构
  • 2.RedisConfig.java
  • 3.RedisUtil.java
  • 5.config下创建登录拦截器和上下文将从Header中获取logId放到ThreadLocal中
  • 1.结构
  • 2.GlobalConfig.java mvc的全局处理,空值不返回,存放自定义拦截器
  • 3.LoginContextHolder.java ThreadLocal工具类
  • 4.LoginInterceptor.java 登录拦截器,从Header中获取logId放到ThreadLocal
  • 6.创建controller包
  • 1.结构
  • 2.DemoController.java 测试
  • 7.创建其余的包
  • 1.结构
  • 2.DruidEncryptUtil.java 用于对yaml中的东西加解密
  • 8.创建启动类
  • 1.PracticeApplication.java 注意写MapperScan和ComponentScan,还有启动类注解
  • 9.创建配置文件
  • 1.resource创建一个mapper文件夹
  • 2.application.yml
  • 3.bootstrap.yml
  • 4.log4j2-spring.xml
  • 10.启动测试,一次成功!
  • 1.数据库表设计

    1.practice_set 套卷
    create table practice_set
    (
        id                  bigint auto_increment comment '主键'
            primary key,
        set_name            varchar(255)             null comment '套题名称',
        set_type            int                      null comment '套题类型 1实时生成 2预设套题',
        set_heat            int                      null comment '热度',
        set_desc            varchar(255)             null comment '套题描述',
        primary_category_id bigint                   null comment '大类id',
        created_by          varchar(32) charset utf8 null comment '创建人',
        created_time        datetime                 null comment '创建时间',
        update_by           varchar(32) charset utf8 null comment '更新人',
        update_time         datetime                 null comment '更新时间',
        is_deleted          int default 0            null comment '是否被删除 0为删除 1已删除'
    )
        comment '套题信息表' collate = utf8mb4_bin;
    
    
    2.practice_set_detail 套卷细节
    create table practice_set_detail
    (
        id           bigint auto_increment comment '主键'
            primary key,
        set_id       bigint                   not null comment '套题id',
        subject_id   bigint                   null comment '题目id',
        subject_type int                      null comment '题目类型',
        created_by   varchar(32) charset utf8 null comment '创建人',
        created_time datetime                 null comment '创建时间',
        update_by    varchar(32) charset utf8 null comment '更新人',
        update_time  datetime                 null comment '更新时间',
        is_deleted   int default 0            null comment '是否被删除 0为删除 1已删除'
    )
        comment '套题内容表' collate = utf8mb4_bin;
    
    3.practice_info 练习信息
    create table practice_info
    (
        id              bigint auto_increment comment '主键'
            primary key,
        set_id          bigint                   null comment '套题id',
        complete_status int                      null comment '是否完成 1完成 0未完成',
        time_use        varchar(32)              null comment '用时',
        submit_time     datetime                 null comment '交卷时间',
        correct_rate    decimal(10, 2)           null comment '正确率',
        created_by      varchar(32) charset utf8 null comment '创建人',
        created_time    datetime                 null comment '创建时间',
        update_by       varchar(32) charset utf8 null comment '更新人',
        update_time     datetime                 null comment '更新时间',
        is_deleted      int default 0            null comment '是否被删除 0为删除 1已删除'
    )
        comment '练习表' collate = utf8mb4_bin;
    
    4.practice_detail 练习详情
    create table practice_detail
    (
        id             bigint auto_increment comment '主键'
            primary key,
        practice_id    bigint                   null comment '练题id',
        subject_id     bigint                   null comment '题目id',
        subject_type   int                      null comment '题目类型',
        answer_status  int                      null comment '回答状态',
        answer_content varchar(64)              null comment '回答内容',
        created_by     varchar(32) charset utf8 null comment '创建人',
        created_time   datetime                 null comment '创建时间',
        update_by      varchar(32) charset utf8 null comment '更新人',
        update_time    datetime                 null comment '更新时间',
        is_deleted     int default 0            null comment '是否被删除 0为删除 1已删除'
    )
        comment '练习详情表' collate = utf8mb4_bin;
    
    5.E-R图

    image-20240625135009775

    2.架构设计(三层架构)

    img

    3.练题微服务架构搭建

    1.创建一个练题微服务模块
    1.创建一个maven项目

    image-20240625140829256

    2.把src删除,只留pom.xml
    2.微服务父模块的pom.xml配置
    1.配置packaging为pom,指定编译版本,并统一指定SpringBoot版本,统一配置阿里云仓库,使子模块继承
    <?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">
        <modelVersion>4.0.0</modelVersion>
    
        <groupId>com.sun.club</groupId>
        <artifactId>sun-club-practice</artifactId>
        <version>1.0-SNAPSHOT</version>
        <!-- 父模块需要配置这个pom -->
        <packaging>pom</packaging>
    
        <properties>
            <!-- 指定编译版本 -->
            <maven.compiler.source>8</maven.compiler.source>
            <maven.compiler.target>8</maven.compiler.target>
            <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        </properties>
    
        <!-- 父模块统一指定SpringBoot版本 -->
        <dependencyManagement>
            <dependencies>
                <dependency>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-dependencies</artifactId>
                    <version>2.4.2</version>
                    <!-- 下面两个配置表示导入spring-boot-dependencies的dependencyManagement的版本 -->
                    <type>pom</type>
                    <scope>import</scope>
                </dependency>
            </dependencies>
        </dependencyManagement>
    
    
        <!-- 阿里云仓库,在父模块中配置仓库,可以使得所有子模块自动继承这个配置,这样在多模块项目中,每个模块无需单独配置仓库信息,便于管理和维护。 -->
        <repositories>
            <repository>
                <id>central</id>
                <name>aliyun maven</name>
                <url>http://maven.aliyun.com/nexus/content/groups/public/</url>
                <layout>default</layout>
                <releases>
                    <enabled>true</enabled>
                </releases>
                <snapshots>
                    <enabled>true</enabled>
                </snapshots>
            </repository>
        </repositories>
    
    </project>
    
    2.type和scope解释
    1.type

    <type>: 在Maven中,type元素指定了依赖项的包装类型。默认情况下,如果不指定type,Maven会假定它是一个jar文件。在你提供的示例中,type被设置为pom。这意味着被引入的依赖是一个POM类型的项目,通常用于依赖管理而非包含实际的代码库。这种类型的依赖通常用于声明一组库的版本管理,而不是作为代码库直接参与构建。

    2.scope

    <scope>: scope元素定义了依赖的使用范围。不同的scope值决定了依赖在项目的不同构建阶段以及不同模块间的可见性。常见的scope包括:

  • compile:默认值,表示依赖在编译阶段和运行阶段都是必需的,且会被传递到依赖的项目。
  • runtime:表示依赖不需要在编译阶段,但在运行时需要。
  • provided:表示依赖在编译和测试时需要,但在运行时不需要,因为运行环境已提供该依赖。
  • test:表示依赖仅在测试阶段需要,用于编译和运行测试代码。
  • import(正如你的例子中所用):这是一个特殊的scope,用于只在<dependencyManagement>中有效。它表示当前POM是从其他POM中导入依赖管理信息,通常用于继承和共享一组依赖定义。通过import,可以将其他项目的依赖版本管理集成到自己的项目中,从而保持依赖版本的一致性和可管理性。
  • 3.创建一个练题微服务的api模块
    1.创建一个maven项目,删除resource目录和test目录

    image-20240625143011964

    2.创建一个common包存放Result和Page相关的
    1.目录结构

    image-20240625143640667

    2.PageInfo.java
    package com.sunxiansheng.practice.api.common;
    
    import java.util.Objects;
    
    /**
     * Description: 分页请求的入参
     * @Author sun
     * @Create 2024/5/28 16:25
     * @Version 1.1
     */
    public class PageInfo {
    
        private Integer pageNo = 1;
        private Integer pageSize = 20;
    
        public Integer getPageNo() {
            return (pageNo == null || pageNo < 1) ? 1 : pageNo;
        }
    
        public Integer getPageSize() {
            return (pageSize == null || pageSize < 1) ? 20 : pageSize;
        }
    
        public PageInfo setPageNo(Integer pageNo) {
            this.pageNo = pageNo;
            return this;
        }
    
        public PageInfo setPageSize(Integer pageSize) {
            this.pageSize = pageSize;
            return this;
        }
    
        @Override
        public boolean equals(Object o) {
            if (this == o) return true;
            if (o == null || getClass() != o.getClass()) return false;
            PageInfo pageInfo = (PageInfo) o;
            return Objects.equals(pageNo, pageInfo.pageNo) &&
                    Objects.equals(pageSize, pageInfo.pageSize);
        }
    
        @Override
        public int hashCode() {
            return Objects.hash(pageNo, pageSize);
        }
    
        @Override
        public String toString() {
            return "PageInfo{" +
                    "pageNo=" + pageNo +
                    ", pageSize=" + pageSize +
                    '}';
        }
    }
    
    3.PageResult.java
    package com.sunxiansheng.practice.api.common;
    
    import java.util.Collections;
    import java.util.List;
    import java.util.Objects;
    
    /**
     * Description: 分页返回的实体
     * @Author sun
     * @Create 2024/5/28 16:36
     * @Version 1.1
     */
    public class PageResult<T> {
    
        // 当前页码,默认为1
        private Integer pageNo = 1;
    
        // 每页显示的记录数,默认为20
        private Integer pageSize = 20;
    
        // 总记录条数
        private Integer total = 0;
    
        // 总页数
        private Integer totalPages = 0;
    
        // 当前页的记录列表
        private List<T> result = Collections.emptyList();
    
        // 表示当前页是从分页查询结果的第几条记录开始,下标从1开始
        private Integer start = 1;
    
        // 表示当前页是从分页查询结果的第几条记录结束,下标从1开始
        private Integer end = 0;
    
        // ==================== 分页查询只需要设置这几个值即可 ====================
    
        // 设置当前页码,并重新计算起始和结束位置
        public PageResult<T> setPageNo(Integer pageNo) {
            this.pageNo = Objects.requireNonNull(pageNo, "Page number cannot be null");
            calculateStartAndEnd();
            return this;
        }
    
        // 设置每页记录数,并重新计算起始和结束位置
        public PageResult<T> setPageSize(Integer pageSize) {
            this.pageSize = Objects.requireNonNull(pageSize, "Page size cannot be null");
            calculateStartAndEnd();
            return this;
        }
    
        // 设置当前页的记录列表
        public PageResult<T> setRecords(List<T> result) {
            this.result = Objects.requireNonNull(result, "Result list cannot be null");
            return this;
        }
    
        // 设置总记录条数,并重新计算总页数和起始结束位置
        public PageResult<T> setTotal(Integer total) {
            this.total = Objects.requireNonNull(total, "Total count cannot be null");
            calculateTotalPages();
            calculateStartAndEnd();
            return this;
        }
    
        // ==================== 分页查询只需要设置这几个值即可 ====================
    
        // 计算总页数
        private void calculateTotalPages() {
            if (this.pageSize > 0) {
                this.totalPages = (this.total / this.pageSize) + (this.total % this.pageSize == 0 ? 0 : 1);
            } else {
                this.totalPages = 0;
            }
        }
    
        // 计算起始和结束位置
        private void calculateStartAndEnd() {
            if (this.pageSize > 0) {
                this.start = (this.pageNo - 1) * this.pageSize + 1;
                this.end = Math.min(this.pageNo * this.pageSize, this.total);
            } else {
                this.start = 1;
                this.end = this.total;
            }
        }
    
        public Integer getStart() {
            return start;
        }
    
        // 获取每页记录数
        public Integer getPageSize() {
            return pageSize;
        }
    
        public Integer getPageNo() {
            return pageNo;
        }
    
        public Integer getTotal() {
            return total;
        }
    
        public Integer getTotalPages() {
            return totalPages;
        }
    
        public List<T> getResult() {
            return result;
        }
    
        public Integer getEnd() {
            return end;
        }
    
        @Override
        public boolean equals(Object o) {
            if (this == o) return true;
            if (o == null || getClass() != o.getClass()) return false;
            PageResult<?> that = (PageResult<?>) o;
            return Objects.equals(pageNo, that.pageNo) &&
                    Objects.equals(pageSize, that.pageSize) &&
                    Objects.equals(total, that.total) &&
                    Objects.equals(totalPages, that.totalPages) &&
                    Objects.equals(result, that.result) &&
                    Objects.equals(start, that.start) &&
                    Objects.equals(end, that.end);
        }
    
        @Override
        public int hashCode() {
            return Objects.hash(pageNo, pageSize, total, totalPages, result, start, end);
        }
    
        @Override
        public String toString() {
            return "PageResult{" +
                    "pageNo=" + pageNo +
                    ", pageSize=" + pageSize +
                    ", total=" + total +
                    ", totalPages=" + totalPages +
                    ", result=" + result +
                    ", start=" + start +
                    ", end=" + end +
                    '}';
        }
    }
    
    4.Result.java
    package com.sunxiansheng.practice.api.common;
    
    import lombok.Data;
    
    /**
     * Description:
     * @Author sun
     * @Create 2024/5/24 9:48
     * @Version 1.0
     */
    @Data
    public class Result<T> {
    
        private Boolean success;
    
        private Integer code;
    
        private String message;
    
        private T data;
    
        /**
         * 成功返回结果
         * @return
         */
        public static Result ok() {
            Result result = new Result();
            result.setSuccess(true);
            result.setCode(ResultCodeEnum.SUCCESS.getCode());
            result.setMessage(ResultCodeEnum.SUCCESS.getDesc());
            return result;
        }
    
        /**
         * 成功返回结果,携带数据
         * @param data
         * @return
         * @param <T>
         */
        public static <T> Result ok(T data) {
            Result result = new Result();
            result.setSuccess(true);
            result.setCode(ResultCodeEnum.SUCCESS.getCode());
            result.setMessage(ResultCodeEnum.SUCCESS.getDesc());
            result.setData(data);
            return result;
        }
    
        /**
         * 失败返回结果
         * @return
         */
        public static Result fail() {
            Result result = new Result();
            result.setSuccess(false);
            result.setCode(ResultCodeEnum.FAIL.getCode());
            result.setMessage(ResultCodeEnum.FAIL.getDesc());
            return result;
        }
    
        /**
         * 失败,携带数据
         * @param data
         * @return
         * @param <T>
         */
        public static <T> Result fail(T data) {
            Result result = new Result();
            result.setSuccess(false);
            result.setCode(ResultCodeEnum.FAIL.getCode());
            result.setMessage(ResultCodeEnum.FAIL.getDesc());
            result.setData(data);
            return result;
        }
    
    }
    
    5.ResultCodeEnum.java
    package com.sunxiansheng.practice.api.common;
    
    import lombok.Getter;
    
    /**
     * Description: 返回结果枚举
     * @Author sun
     * @Create 2024/5/24 9:53
     * @Version 1.0
     */
    @Getter
    public enum ResultCodeEnum {
        SUCCESS(200, "成功"),
        FAIL(500, "失败");
    
        public int code;
        public String desc;
    
        ResultCodeEnum(int code, String desc) {
            this.code = code;
            this.desc = desc;
        }
    
        /**
         * 根据code获取枚举
         * @param code
         * @return
         */
        public static ResultCodeEnum getByCode(int code) {
            for (ResultCodeEnum value : values()) {
                if (value.code == code) {
                    return value;
                }
            }
            return null;
        }
    }
    
    6.引入lombok的依赖
    <dependencies>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.16</version>
        </dependency>
    </dependencies>
    
    3.创建一个通用的枚举包
    1.结构

    image-20240625144145222

    2.IsDeleteFlagEnum.java
    package com.sunxiansheng.practice.api.enums;
    
    import lombok.Getter;
    
    /**
     * Description: 删除标识枚举
     * @Author sun
     * @Create 2024/5/24 9:53
     * @Version 1.0
     */
    @Getter
    public enum IsDeleteFlagEnum {
        DELETED(1, "已删除"),
        UN_DELETED(0, "未删除");
    
        public int code;
        public String desc;
    
        IsDeleteFlagEnum(int code, String desc) {
            this.code = code;
            this.desc = desc;
        }
    
        /**
         * 根据code获取枚举
         * @param code
         * @return
         */
        public static IsDeleteFlagEnum getByCode(int code) {
            for (IsDeleteFlagEnum value : values()) {
                if (value.code == code) {
                    return value;
                }
            }
            return null;
        }
    }
    
    4.创建一个req和vo分别存放入参和出参实体
    结构

    image-20240625144432191

    4.创建一个server子模块
    1.创建一个maven项目

    image-20240625144711871

    2.创建一个config包,暂时先存放mybatis的东西
    1.结构

    image-20240625145214132

    2.SqlStatementInterceptor.java sql状态拦截器
    package com.sunxiansheng.practice.server.config.mybatis;
    
    import org.apache.ibatis.cache.CacheKey;
    import org.apache.ibatis.executor.Executor;
    import org.apache.ibatis.mapping.BoundSql;
    import org.apache.ibatis.mapping.MappedStatement;
    import org.apache.ibatis.plugin.*;
    import org.apache.ibatis.session.ResultHandler;
    import org.apache.ibatis.session.RowBounds;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    
    import java.util.Properties;
    
    
    @Intercepts({
            @Signature(type = Executor.class, method = "update", args = {MappedStatement.class,
                    Object.class}),
            @Signature(type = Executor.class, method = "query", args = {MappedStatement.class,
                    Object.class, RowBounds.class, ResultHandler.class, CacheKey.class, BoundSql.class})})
    public class SqlStatementInterceptor implements Interceptor {
    
        public static final Logger log = LoggerFactory.getLogger("sys-sql");
    
        @Override
        public Object intercept(Invocation invocation) throws Throwable {
            long startTime = System.currentTimeMillis();
            try {
                return invocation.proceed();
            } finally {
                long timeConsuming = System.currentTimeMillis() - startTime;
                log.info("执行SQL:{}ms", timeConsuming);
                if (timeConsuming > 999 && timeConsuming < 5000) {
                    log.info("执行SQL大于1s:{}ms", timeConsuming);
                } else if (timeConsuming >= 5000 && timeConsuming < 10000) {
                    log.info("执行SQL大于5s:{}ms", timeConsuming);
                } else if (timeConsuming >= 10000) {
                    log.info("执行SQL大于10s:{}ms", timeConsuming);
                }
            }
        }
    
        @Override
        public Object plugin(Object target) {
            return Plugin.wrap(target, this);
        }
    
        @Override
        public void setProperties(Properties properties) {
    
        }
    }
    
    3.MybatisPlusAllSqlLog.java sql转换器
    package com.sunxiansheng.practice.server.config.mybatis;
    
    import com.baomidou.mybatisplus.extension.plugins.inner.InnerInterceptor;
    import org.apache.ibatis.executor.Executor;
    import org.apache.ibatis.mapping.BoundSql;
    import org.apache.ibatis.mapping.MappedStatement;
    import org.apache.ibatis.mapping.ParameterMapping;
    import org.apache.ibatis.reflection.MetaObject;
    import org.apache.ibatis.session.Configuration;
    import org.apache.ibatis.session.ResultHandler;
    import org.apache.ibatis.session.RowBounds;
    import org.apache.ibatis.type.TypeHandlerRegistry;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.util.CollectionUtils;
    
    import java.sql.SQLException;
    import java.text.DateFormat;
    import java.util.Date;
    import java.util.List;
    import java.util.Locale;
    import java.util.regex.Matcher;
    
    public class MybatisPlusAllSqlLog implements InnerInterceptor {
        public static final Logger log = LoggerFactory.getLogger("sys-sql");
    
        @Override
        public void beforeQuery(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
            logInfo(boundSql, ms, parameter);
        }
    
        @Override
        public void beforeUpdate(Executor executor, MappedStatement ms, Object parameter) throws SQLException {
            BoundSql boundSql = ms.getBoundSql(parameter);
            logInfo(boundSql, ms, parameter);
        }
    
        private static void logInfo(BoundSql boundSql, MappedStatement ms, Object parameter) {
            try {
                log.info("parameter = " + parameter);
                // 获取到节点的id,即sql语句的id
                String sqlId = ms.getId();
                log.info("sqlId = " + sqlId);
                // 获取节点的配置
                Configuration configuration = ms.getConfiguration();
                // 获取到最终的sql语句
                String sql = getSql(configuration, boundSql, sqlId);
                log.info("完整的sql:{}", sql);
            } catch (Exception e) {
                log.error("异常:{}", e.getLocalizedMessage(), e);
            }
        }
    
        // 封装了一下sql语句,使得结果返回完整xml路径下的sql语句节点id + sql语句
        public static String getSql(Configuration configuration, BoundSql boundSql, String sqlId) {
            return sqlId + ":" + showSql(configuration, boundSql);
        }
    
        // 进行?的替换
        public static String showSql(Configuration configuration, BoundSql boundSql) {
            // 获取参数
            Object parameterObject = boundSql.getParameterObject();
            List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
            // sql语句中多个空格都用一个空格代替
            String sql = boundSql.getSql().replaceAll("[\\s]+", " ");
            if (!CollectionUtils.isEmpty(parameterMappings) && parameterObject != null) {
                // 获取类型处理器注册器,类型处理器的功能是进行java类型和数据库类型的转换
                TypeHandlerRegistry typeHandlerRegistry = configuration.getTypeHandlerRegistry();
                // 如果根据parameterObject.getClass()可以找到对应的类型,则替换
                if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
                    sql = sql.replaceFirst("\\?",
                            Matcher.quoteReplacement(getParameterValue(parameterObject)));
                } else {
                    // MetaObject主要是封装了originalObject对象,提供了get和set的方法用于获取和设置originalObject的属性值,主要支持对JavaBean、Collection、Map三种类型对象的操作
                    MetaObject metaObject = configuration.newMetaObject(parameterObject);
                    for (ParameterMapping parameterMapping : parameterMappings) {
                        String propertyName = parameterMapping.getProperty();
                        if (metaObject.hasGetter(propertyName)) {
                            Object obj = metaObject.getValue(propertyName);
                            sql = sql.replaceFirst("\\?",
                                    Matcher.quoteReplacement(getParameterValue(obj)));
                        } else if (boundSql.hasAdditionalParameter(propertyName)) {
                            // 该分支是动态sql
                            Object obj = boundSql.getAdditionalParameter(propertyName);
                            sql = sql.replaceFirst("\\?",
                                    Matcher.quoteReplacement(getParameterValue(obj)));
                        } else {
                            // 打印出缺失,提醒该参数缺失并防止错位
                            sql = sql.replaceFirst("\\?", "缺失");
                        }
                    }
                }
            }
            return sql;
        }
    
        // 如果参数是String,则添加单引号, 如果是日期,则转换为时间格式器并加单引号; 对参数是null和不是null的情况作了处理
        private static String getParameterValue(Object obj) {
            String value;
            if (obj instanceof String) {
                value = "'" + obj.toString() + "'";
            } else if (obj instanceof Date) {
                DateFormat formatter = DateFormat.getDateTimeInstance(DateFormat.DEFAULT,
                        DateFormat.DEFAULT, Locale.CHINA);
                value = "'" + formatter.format(new Date()) + "'";
            } else {
                if (obj != null) {
                    value = obj.toString();
                } else {
                    value = "";
                }
            }
            return value;
        }
    
    }
    
    4.MybatisConfiguration.java 注册两个拦截器
    package com.sunxiansheng.practice.server.config.mybatis;
    
    import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    
    @Configuration
    public class MybatisConfiguration {
    
        @Bean
        public MybatisPlusInterceptor mybatisPlusInterceptor(){
            MybatisPlusInterceptor mybatisPlusInterceptor = new MybatisPlusInterceptor();
            mybatisPlusInterceptor.addInnerInterceptor(new MybatisPlusAllSqlLog());
            return mybatisPlusInterceptor;
        }
    
    }
    
    
    3.引入基本依赖
    <?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">
        <modelVersion>4.0.0</modelVersion>
        <parent>
            <groupId>com.sun.club</groupId>
            <artifactId>sun-club-practice</artifactId>
            <version>1.0-SNAPSHOT</version>
        </parent>
    
        <artifactId>sun-club-practice-server</artifactId>
    
        <!-- maven的配置 -->
        <properties>
            <!-- 解决java: -source 1.5 中不支持 diamond 运算符 问题 -->
            <java.version>1.8</java.version>
            <maven.compiler.source>1.8</maven.compiler.source>
            <maven.compiler.target>1.8</maven.compiler.target>
            <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
            <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
            <!-- 版本的配置 -->
            <spring-boot.version>2.4.2</spring-boot.version>
            <spring-cloud-alibaba.version>2021.1</spring-cloud-alibaba.version>
            <spring-cloud.version>2020.0.6</spring-cloud.version>
        </properties>
    
        <dependencies>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-web</artifactId>
                <version>2.4.2</version>
                <!-- 这里的日志跟log4j2冲突 -->
                <exclusions>
                    <exclusion>
                        <artifactId>spring-boot-starter-logging</artifactId>
                        <groupId>org.springframework.boot</groupId>
                    </exclusion>
                </exclusions>
            </dependency>
            <!-- lombok -->
            <dependency>
                <groupId>org.projectlombok</groupId>
                <artifactId>lombok</artifactId>
                <version>1.18.16</version>
            </dependency>
            <!-- mapstruct -->
            <dependency>
                <groupId>org.mapstruct</groupId>
                <artifactId>mapstruct</artifactId>
                <version>1.4.2.Final</version>
            </dependency>
            <dependency>
                <groupId>org.mapstruct</groupId>
                <artifactId>mapstruct-processor</artifactId>
                <version>1.4.2.Final</version>
            </dependency>
            <!-- log4j2打印日志 -->
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-log4j2</artifactId>
                <version>2.4.2</version>
            </dependency>
            <!-- fastjson -->
            <dependency>
                <groupId>com.alibaba</groupId>
                <artifactId>fastjson</artifactId>
                <version>1.2.24</version>
            </dependency>
            <!-- guava本地缓存 -->
            <dependency>
                <groupId>com.google.guava</groupId>
                <artifactId>guava</artifactId>
                <version>19.0</version>
            </dependency>
            <!-- commons-lang3工具包,StringUtils.isNotBlank()... -->
            <dependency>
                <groupId>org.apache.commons</groupId>
                <artifactId>commons-lang3</artifactId>
                <version>3.11</version>
            </dependency>
            <!-- gson序列化 -->
            <dependency>
                <groupId>com.google.code.gson</groupId>
                <artifactId>gson</artifactId>
                <version>2.8.6</version>
            </dependency>
            <!-- redis -->
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-data-redis</artifactId>
                <version>2.4.2</version>
            </dependency>
            <dependency>
                <groupId>org.apache.commons</groupId>
                <artifactId>commons-pool2</artifactId>
                <version>2.9.0</version>
            </dependency>
            <!-- mysql -->
            <!-- jdbc -->
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-jdbc</artifactId>
                <version>2.4.2</version>
            </dependency>
            <!-- druid连接池 -->
            <dependency>
                <groupId>com.alibaba</groupId>
                <artifactId>druid-spring-boot-starter</artifactId>
                <version>1.1.22</version>
            </dependency>
            <!-- mysql8 -->
            <dependency>
                <groupId>mysql</groupId>
                <artifactId>mysql-connector-java</artifactId>
                <version>8.0.22</version>
            </dependency>
            <!-- mybatis-plus -->
            <dependency>
                <groupId>com.baomidou</groupId>
                <artifactId>mybatis-plus-boot-starter</artifactId>
                <version>3.4.0</version>
            </dependency>
            <!-- mysql -->
            <!-- nacos配置中心 -->
            <dependency>
                <groupId>com.alibaba.cloud</groupId>
                <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
            <!--     由于上面指定了版本,会自动读取 -->
            </dependency>
            <!-- bootstrap -->
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-starter-bootstrap</artifactId>
                <!--     由于上面指定了版本,会自动读取 -->
            </dependency>
            <!-- nacos服务发现 -->
            <dependency>
                <groupId>com.alibaba.cloud</groupId>
                <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
                <!--     由于上面指定了版本,会自动读取 -->
            </dependency>
        </dependencies>
    
        <!-- 统一管理配置,以后所有的boot、cloud、alibaba都不需要指定版本了 -->
        <dependencyManagement>
            <dependencies>
                <dependency>
                    <groupId>org.springframework.cloud</groupId>
                    <artifactId>spring-cloud-dependencies</artifactId>
                    <version>${spring-cloud.version}</version>
                    <type>pom</type>
                    <scope>import</scope>
                </dependency>
                <dependency>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-dependencies</artifactId>
                    <version>${spring-boot.version}</version>
                    <type>pom</type>
                    <scope>import</scope>
                </dependency>
                <dependency>
                    <groupId>com.alibaba.cloud</groupId>
                    <artifactId>spring-cloud-alibaba-dependencies</artifactId>
                    <version>${spring-cloud-alibaba.version}</version>
                    <type>pom</type>
                    <scope>import</scope>
                </dependency>
            </dependencies>
        </dependencyManagement>
    
        <!-- maven打包常规配置 -->
        <build>
            <finalName>${project.artifactId}</finalName>
            <!--打包成jar包时的名字-->
            <plugins>
                <plugin>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-maven-plugin</artifactId>
                    <!-- 指定打包插件的版本 -->
                    <version>2.3.0.RELEASE</version>
                    <executions>
                        <execution>
                            <goals>
                                <!-- 将所有的包都打到这个模块中 -->
                                <goal>repackage</goal>
                            </goals>
                        </execution>
                    </executions>
                </plugin>
            </plugins>
        </build>
    
    </project>
    
    4.config下创建一个redis包,存放redis配置和工具类
    1.结构

    image-20240625152440231

    2.RedisConfig.java
    package com.sunxiansheng.practice.server.config.redis;
    
    import com.fasterxml.jackson.annotation.JsonAutoDetect;
    import com.fasterxml.jackson.annotation.JsonTypeInfo;
    import com.fasterxml.jackson.annotation.PropertyAccessor;
    import com.fasterxml.jackson.databind.DeserializationFeature;
    import com.fasterxml.jackson.databind.ObjectMapper;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.data.redis.connection.RedisConnectionFactory;
    import org.springframework.data.redis.core.RedisTemplate;
    import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
    import org.springframework.data.redis.serializer.RedisSerializer;
    import org.springframework.data.redis.serializer.StringRedisSerializer;
    
    /**
     * Description: 原生 redis 的 template 的序列化器会产生乱码问题,重写改为 jackson
     * @Author sun
     * @Create 2024/6/5 14:16
     * @Version 1.0
     */
    @Configuration
    public class RedisConfig {
    
        @Bean
        public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
            RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
            RedisSerializer<String> redisSerializer = new StringRedisSerializer();
            redisTemplate.setConnectionFactory(redisConnectionFactory);
            redisTemplate.setKeySerializer(redisSerializer);
            redisTemplate.setHashKeySerializer(redisSerializer);
            redisTemplate.setValueSerializer(jackson2JsonRedisSerializer());
            redisTemplate.setHashValueSerializer(jackson2JsonRedisSerializer());
            return redisTemplate;
        }
    
        private Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer() {
            Jackson2JsonRedisSerializer<Object> jsonRedisSerializer = new Jackson2JsonRedisSerializer<>(Object.class);
            ObjectMapper objectMapper = new ObjectMapper();
            objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
            objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
            objectMapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.PROPERTY);
            jsonRedisSerializer.setObjectMapper(objectMapper);
            return jsonRedisSerializer;
        }
    
    }
    
    3.RedisUtil.java
    package com.sunxiansheng.practice.server.config.redis;
    
    import lombok.extern.slf4j.Slf4j;
    import org.springframework.data.redis.core.Cursor;
    import org.springframework.data.redis.core.RedisTemplate;
    import org.springframework.data.redis.core.ScanOptions;
    import org.springframework.data.redis.core.ZSetOperations;
    import org.springframework.stereotype.Component;
    
    import javax.annotation.Resource;
    import java.util.HashMap;
    import java.util.Map;
    import java.util.Set;
    import java.util.concurrent.TimeUnit;
    import java.util.stream.Collectors;
    import java.util.stream.Stream;
    
    /**
     * Description: RedisUtil工具类
     * @Author sun
     * @Create 2024/6/5 14:17
     * @Version 1.0
     */
    @Component
    @Slf4j
    public class RedisUtil {
    
        @Resource
        private RedisTemplate redisTemplate;
    
        private static final String CACHE_KEY_SEPARATOR = ".";
    
        /**
         * 构建缓存key
         * @param strObjs
         * @return
         */
        public String buildKey(String... strObjs) {
            return Stream.of(strObjs).collect(Collectors.joining(CACHE_KEY_SEPARATOR));
        }
    
        /**
         * 是否存在key
         * @param key
         * @return
         */
        public boolean exist(String key) {
            return redisTemplate.hasKey(key);
        }
    
        /**
         * 删除key
         * @param key
         * @return
         */
        public boolean del(String key) {
            return redisTemplate.delete(key);
        }
    
        public void set(String key, String value) {
            redisTemplate.opsForValue().set(key, value);
        }
    
        public boolean setNx(String key, String value, Long time, TimeUnit timeUnit) {
            return redisTemplate.opsForValue().setIfAbsent(key, value, time, timeUnit);
        }
    
        public String get(String key) {
            return (String) redisTemplate.opsForValue().get(key);
        }
    
        public Boolean zAdd(String key, String value, Long score) {
            return redisTemplate.opsForZSet().add(key, value, Double.valueOf(String.valueOf(score)));
        }
    
        public Long countZset(String key) {
            return redisTemplate.opsForZSet().size(key);
        }
    
        public Set<String> rangeZset(String key, long start, long end) {
            return redisTemplate.opsForZSet().range(key, start, end);
        }
    
        public Long removeZset(String key, Object value) {
            return redisTemplate.opsForZSet().remove(key, value);
        }
    
        public void removeZsetList(String key, Set<String> value) {
            value.stream().forEach((val) -> redisTemplate.opsForZSet().remove(key, val));
        }
    
        public Double score(String key, Object value) {
            return redisTemplate.opsForZSet().score(key, value);
        }
    
        public Set<String> rangeByScore(String key, long start, long end) {
            return redisTemplate.opsForZSet().rangeByScore(key, Double.valueOf(String.valueOf(start)), Double.valueOf(String.valueOf(end)));
        }
    
        /**
         * 可以使用这个来实现排行榜,指定键就相当于指定了一个排行榜,再指定成员和分数,则会给排行榜中的这个成员加分数
         * @param key zset的键
         * @param obj 成员,一般为用户的唯一标识
         * @param score 分数
         * @return
         */
        public Object addScore(String key, Object obj, double score) {
            return redisTemplate.opsForZSet().incrementScore(key, obj, score);
        }
    
        public Object rank(String key, Object obj) {
            return redisTemplate.opsForZSet().rank(key, obj);
        }
    
        /**
         * 从 Redis 有序集合(Sorted Set)中按分数范围获取成员及其分数
         * @param key 排行榜的key
         * @param start 起始位置(包含)
         * @param end 结束位置(包含)
         * @return Set<ZSetOperations.TypedTuple<String>> : 每个 TypedTuple 对象包含以下内容:value: 集合中的成员,score: 成员的分数。
         */
        public Set<ZSetOperations.TypedTuple<String>> rankWithScore(String key, long start, long end) {
            Set<ZSetOperations.TypedTuple<String>> set = redisTemplate.opsForZSet().reverseRangeWithScores(key, start, end);
            return set;
        }
    
        /**
         * 向Redis中的hash结构存储数据
         * @param key 一个hash结构的key
         * @param hashKey hash中的小key
         * @param hashVal hash中的小value
         */
        public void putHash(String key, String hashKey, Object hashVal) {
            redisTemplate.opsForHash().put(key, hashKey, hashVal);
        }
    
        /**
         * Redis中的String类型,获取value时将其转换为int类型
         * @param key
         * @return
         */
        public Integer getInt(String key) {
            return (Integer) redisTemplate.opsForValue().get(key);
        }
    
        /**
         * Redis中的String类型,将value增加一
         * @param key
         * @param count
         * @return
         */
        public void increment(String key, Integer count) {
            redisTemplate.opsForValue().increment(key, count);
        }
    
        /**
         * Redis中的hash类型,根据key来将每一个hashKey和hashValue转换为Map类型
         * @param key
         * @return
         */
        public Map<Object, Object> getHashAndDelete(String key) {
            Map<Object, Object> map = new HashMap<>();
            // 扫描hash,指定每一个Entry的类型,这里返回的就是Map的游标,可以进行遍历
            Cursor<Map.Entry<Object, Object>> cursor = redisTemplate.opsForHash().scan(key, ScanOptions.NONE);
            // 遍历每一条数据,放到map中
            while (cursor.hasNext()) {
                Map.Entry<Object, Object> next = cursor.next();
                Object hashKey = next.getKey();
                Object hashValue = next.getValue();
                map.put(hashKey, hashValue);
                // 每遍历一条就删除
                redisTemplate.opsForHash().delete(key, hashKey);
            }
            return map;
        }
    }
    
    5.config下创建登录拦截器和上下文将从Header中获取logId放到ThreadLocal中
    1.结构
    2.GlobalConfig.java mvc的全局处理,空值不返回,存放自定义拦截器
    package com.sunxiansheng.practice.server.config;
    
    import com.fasterxml.jackson.annotation.JsonInclude;
    import com.fasterxml.jackson.databind.ObjectMapper;
    import com.fasterxml.jackson.databind.SerializationFeature;
    import com.sunxiansheng.practice.server.config.interceptor.LoginInterceptor;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.http.converter.HttpMessageConverter;
    import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
    import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
    import org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport;
    
    import java.util.List;
    
    /**
     * mvc的全局处理
     */
    @Configuration
    public class GlobalConfig extends WebMvcConfigurationSupport {
    
        @Override
        protected void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
            super.configureMessageConverters(converters);
            converters.add(mappingJackson2HttpMessageConverter());
        }
        /**
         * 自定义mappingJackson2HttpMessageConverter
         * 目前实现:空值忽略,空字段可返回
         */
        private MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter() {
            ObjectMapper objectMapper = new ObjectMapper();
            objectMapper.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false);
            objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
            return new MappingJackson2HttpMessageConverter(objectMapper);
        }
    
        /**
         * 将自定义拦截器放进去
         * @param registry
         */
        @Override
        protected void addInterceptors(InterceptorRegistry registry) {
            registry.addInterceptor(new LoginInterceptor());
        }
    }
    
    3.LoginContextHolder.java ThreadLocal工具类
    package com.sunxiansheng.practice.server.config.context;
    
    import java.util.Map;
    import java.util.Objects;
    import java.util.concurrent.ConcurrentHashMap;
    
    /**
     * Description: 上下文对象(ThreadLocal)
     * @Author sun
     * @Create 2024/6/15 16:27
     * @Version 1.0
     */
    public class LoginContextHolder {
    
        // 这个ThreadLocal持有一个Map
        private static final InheritableThreadLocal<Map<String, Object>> THREAD_LOCAL
                = new InheritableThreadLocal<>();
    
        /**
         * 为ThreadLocal持有的Map设值
         * @param key
         * @param val
         */
        public static void set(String key, Object val) {
            Map<String, Object> map = getThreadLocalMap();
            map.put(key, val);
        }
    
        /**
         * 从ThreadLocal持有的Map取值
         * @param key
         * @return
         */
        public static Object get(String key) {
            Map<String, Object> map = THREAD_LOCAL.get();
            return map.get(key);
        }
    
        /**
         * 清除ThreadLocal
         */
        public static void remove() {
            THREAD_LOCAL.remove();
        }
    
        /**
         * 初始化一个ThreadLocal持有的Map,要保证这个Map是单例的
         * @return
         */
        public static Map<String, Object> getThreadLocalMap() {
            // 获取到ThreadLocal的Map
            Map<String, Object> map = THREAD_LOCAL.get();
            // 如果是空的再创建一个Map,然后放进去
            if (Objects.isNull(map)) {
                map = new ConcurrentHashMap<>();
                THREAD_LOCAL.set(map);
            }
            // 放到ThreadLocal中
            return map;
        }
    
        // 以下为获取用户信息的方法
        public static String getLoginId() {
            return (String) getThreadLocalMap().get("loginId");
        }
    
    
    }
    
    4.LoginInterceptor.java 登录拦截器,从Header中获取logId放到ThreadLocal
    package com.sunxiansheng.practice.server.config.interceptor;
    
    import com.sunxiansheng.practice.server.config.context.LoginContextHolder;
    import org.apache.commons.lang3.StringUtils;
    import org.springframework.web.servlet.HandlerInterceptor;
    
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    
    /**
     * Description: 处理用户上下文的拦截器
     * @Author sun
     * @Create 2024/6/15 16:20
     * @Version 1.0
     */
    public class LoginInterceptor implements HandlerInterceptor {
    
        /**
         * 当请求到这里了,就说明,网关已经将用户的loginId放到了Header里了
         * @param request
         * @param response
         * @param handler
         * @return
         * @throws Exception
         */
        @Override
        public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
            String loginId = request.getHeader("loginId");
            if (StringUtils.isNotBlank(loginId)) {
                // 将loginId放到ThreadLocal里面
                LoginContextHolder.set("loginId", loginId);
            }
            return true;
        }
    
    
        /**
         * 在操作结束后清除ThreadLocal,因为如果线程复用并且没清除,这个ThreadLocal还会存在,造成数据污染
         * @param request
         * @param response
         * @param handler
         * @param ex
         * @throws Exception
         */
        @Override
        public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
            LoginContextHolder.remove();
        }
    }
    
    6.创建controller包
    1.结构

    image-20240625154206317

    2.DemoController.java 测试
    package com.sunxiansheng.practice.server.controller;
    
    import lombok.extern.slf4j.Slf4j;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RestController;
    
    /**
     * Description: 测试controller
     * @Author sun
     * @Create 2024/6/25 15:38
     * @Version 1.0
     */
    @RestController
    @RequestMapping("/practice/")
    @Slf4j
    public class DemoController {
    
        @RequestMapping("test")
        public String isLogin() {
            return "test";
        }
    
    }
    
    7.创建其余的包
    1.结构

    image-20240625154653794

    2.DruidEncryptUtil.java 用于对yaml中的东西加解密
    package com.sunxiansheng.practice.server.util;
    
    import com.alibaba.druid.filter.config.ConfigTools;
    
    import java.security.NoSuchAlgorithmException;
    import java.security.NoSuchProviderException;
    
    /**
     * 数据库加密util
     */
    public class DruidEncryptUtil {
    
        private static String publicKey;
    
        private static String privateKey;
    
        static {
            try {
                String[] keyPair = ConfigTools.genKeyPair(512);
                privateKey = keyPair[0];
                System.out.println("privateKey:" + privateKey);
                publicKey = keyPair[1];
                System.out.println("publicKey:" + publicKey);
            } catch (NoSuchAlgorithmException e) {
                e.printStackTrace();
            } catch (NoSuchProviderException e) {
                e.printStackTrace();
            }
        }
    
        public static String encrypt(String plainText) throws Exception {
            String encrypt = ConfigTools.encrypt(privateKey, plainText);
            System.out.println("encrypt:" + encrypt);
            return encrypt;
        }
    
        public static String decrypt(String encryptText) throws Exception {
            String decrypt = ConfigTools.decrypt(publicKey, encryptText);
            System.out.println("decrypt:" + decrypt);
            return decrypt;
        }
    
        public static void main(String[] args) throws Exception {
            String encrypt = encrypt("123456");
            System.out.println("encrypt:" + encrypt);
        }
    
    }
    
    8.创建启动类
    1.PracticeApplication.java 注意写MapperScan和ComponentScan,还有启动类注解
    package com.sunxiansheng.practice.server;
    
    import org.mybatis.spring.annotation.MapperScan;
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    import org.springframework.context.annotation.ComponentScan;
    
    /**
     * Description: 练题模块
     * @Author sun
     * @Create 2024/6/25 15:48
     * @Version 1.0
     */
    @SpringBootApplication
    // 扫描mapper接口
    @MapperScan("com.sunxiansheng.**.dao")
    // 扫描service和controller注解
    @ComponentScan("com.sunxiansheng")
    public class PracticeApplication {
        
        public static void main(String[] args) {
            SpringApplication.run(PracticeApplication.class, args);
        }
    
    }
    
    9.创建配置文件
    1.resource创建一个mapper文件夹

    image-20240625155250318

    2.application.yml
    server:
      port: 3012
      # DataSource Config
    spring:
      datasource:
        driver-class-name: com.mysql.cj.jdbc.Driver
        url: jdbc:mysql:///sun_club?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&useSSL=false
        username: 
        password: N2THnj7YlFIA4zrfxaOq1tBpLjnG3NTOM4BL6kJMMSSoTW9xE/jNW+xjtLotTXZjKw6Jk1eDbW6BjCgTMDnTbA== # 加密后的密码
        type: com.alibaba.druid.pool.DruidDataSource # druid连接池
        druid:
          connectionProperties: config.decrypt=true;config.decrypt.key=${publicKey}; # 开启配置解密,读取公匙
          initial-size: 20 # 初始化连接数
          min-idle: 20 # 最小连接数
          max-active: 100 # 最大连接数
          max-wait: 60000 # 最大等待时间,单位毫秒
          stat-view-servlet:
            enabled: true # 是否开启监控
            url-pattern: /druid/* # 监控路径
            login-username:  # 登录用户名
            login-password:  # 登录密码
          filter:
            stat:
              enabled: true # 是否开启慢sql监控
              slow-sql-millis: 2000 # 慢sql阈值,单位毫秒
              log-slow-sql: true # 是否打印慢sql
            wall:
              enabled: true # 是否开启防火墙
            config:
              enabled: true # 开启配置,可以解密
      redis:
        password:  # Redis服务器密码
        database: 0 # 默认数据库为0号
        timeout: 10000ms # 连接超时时间是10000毫秒
        lettuce:
          pool:
            max-active: 8 # 最大活跃连接数,使用负值表示没有限制,最佳配置为核数*2
            max-wait: 10000ms # 最大等待时间,单位为毫秒,使用负值表示没有限制,这里设置为10秒
            max-idle: 200 # 最大空闲连接数
            min-idle: 5 # 最小空闲连接数
        cluster:
          nodes:
    
    logging:
      config: classpath:log4j2-spring.xml # 日志配置文件
    publicKey: /hiiSS5+2angp9vKAt3Dn71mVJAp/cKcoqrtERZqcr0+/aGtsE+JOlQgquOs5cCAwEAAQ==
    mybatis-plus:
      configuration:
        log-impl: org.apache.ibatis.logging.stdout.StdOutImpl # 打印sql
    
    3.bootstrap.yml
    spring:
      application:
        name: sub-club-practice # 服务名称
      profiles:
        active: dev # 激活的环境
      cloud:
        nacos:
          config:
            server-addr: :8848 # Nacos地址
            prefix: ${spring.application.name} # 配置前缀为服务名,sub-club-practice-dev为配置文件名
            group: DEFAULT_GROUP # 配置分组
            namespace: # 命名空间,如果在public命名空间则不需要配置
            file-extension: yaml
          discovery:
            enabled: true # 启用服务发现
            server-addr: :8848 # Nacos地址
    
    4.log4j2-spring.xml
    <?xml version="1.0" encoding="UTF-8"?>
    <!--Configuration后面的status,这个用于设置log4j2自身内部的信息输出,可以不设置,当设置成trace时,你会看到log4j2内部各种详细输出 -->
    <!--monitorInterval:Log4j能够自动检测修改配置 文件和重新配置本身,设置间隔秒数 -->
    <configuration monitorInterval="5">
        <!--日志级别以及优先级排序: OFF > FATAL > ERROR > WARN > INFO > DEBUG > TRACE > ALL -->
    
    
        <!--变量配置 -->
        <Properties>
            <!-- 格式化输出:%date表示日期,%thread表示线程名,%-5level:级别从左显示5个字符宽度 %msg:日志消息,%n是换行符 -->
            <!-- %logger{36} 表示 Logger 名字最长36个字符 -->
            <property name="LOG_PATTERN"
                      value="%clr{%d{yyyy-MM-dd HH:mm:ss.SSS}}{faint} %clr{%5p} %clr{${sys:PID}}{magenta} %clr{---}{faint} %clr{[%15.15t]}{faint} %clr{%-40.40c{1.}}{cyan} %clr{:}{faint} %m%n%xwEx" />
            <!-- 定义日志存储的路径,不要配置相对路径 -->
            <property name="FILE_PATH" value="./logs" />
            <property name="FILE_NAME" value="SbTest" />
        </Properties>
    
    
        <appenders>
    
    
            <console name="Console" target="SYSTEM_OUT">
                <!--输出日志的格式 -->
                <PatternLayout pattern="${LOG_PATTERN}" />
                <!--控制台只输出level及其以上级别的信息(onMatch),其他的直接拒绝(onMismatch) -->
                <ThresholdFilter level="DEBUG" onMatch="ACCEPT"
                                 onMismatch="DENY" />
            </console>
    
    
            <!--文件会打印出所有信息,这个log每次运行程序会自动清空,由append属性决定,适合临时测试用 -->
            <File name="Filelog" fileName="${FILE_PATH}/test.log"
                  append="false">
                <PatternLayout pattern="${LOG_PATTERN}" />
            </File>
    
    
            <!-- 这个会打印出所有的info及以下级别的信息,每次大小超过size,则这size大小的日志会自动存入按年份-月份建立的文件夹下面并进行压缩,作为存档 -->
            <RollingFile name="RollingFileInfo"
                         fileName="${FILE_PATH}/info.log"
                         filePattern="${FILE_PATH}/${FILE_NAME}-INFO-%d{yyyy-MM-dd}_%i.log.gz">
                <!--控制台只输出level及以上级别的信息(onMatch),其他的直接拒绝(onMismatch) -->
                <ThresholdFilter level="info" onMatch="ACCEPT"
                                 onMismatch="DENY" />
                <PatternLayout pattern="${LOG_PATTERN}" />
                <Policies>
                    <!--interval属性用来指定多久滚动一次,默认是1 hour -->
                    <TimeBasedTriggeringPolicy interval="1" />
                    <SizeBasedTriggeringPolicy size="10MB" />
                </Policies>
                <!-- DefaultRolloverStrategy属性如不设置,则默认为最多同一文件夹下7个文件开始覆盖 -->
                <DefaultRolloverStrategy max="15" />
            </RollingFile>
    
    
            <!-- 这个会打印出所有的warn及以下级别的信息,每次大小超过size,则这size大小的日志会自动存入按年份-月份建立的文件夹下面并进行压缩,作为存档 -->
            <RollingFile name="RollingFileWarn"
                         fileName="${FILE_PATH}/warn.log"
                         filePattern="${FILE_PATH}/${FILE_NAME}-WARN-%d{yyyy-MM-dd}_%i.log.gz">
                <!--控制台只输出level及以上级别的信息(onMatch),其他的直接拒绝(onMismatch) -->
                <ThresholdFilter level="warn" onMatch="ACCEPT"
                                 onMismatch="DENY" />
                <PatternLayout pattern="${LOG_PATTERN}" />
                <Policies>
                    <!--interval属性用来指定多久滚动一次,默认是1 hour -->
                    <TimeBasedTriggeringPolicy interval="1" />
                    <SizeBasedTriggeringPolicy size="10MB" />
                </Policies>
                <!-- DefaultRolloverStrategy属性如不设置,则默认为最多同一文件夹下7个文件开始覆盖 -->
                <DefaultRolloverStrategy max="15" />
            </RollingFile>
    
    
            <!-- 这个会打印出所有的error及以下级别的信息,每次大小超过size,则这size大小的日志会自动存入按年份-月份建立的文件夹下面并进行压缩,作为存档 -->
            <RollingFile name="RollingFileError"
                         fileName="${FILE_PATH}/error.log"
                         filePattern="${FILE_PATH}/${FILE_NAME}-ERROR-%d{yyyy-MM-dd}_%i.log.gz">
                <!--控制台只输出level及以上级别的信息(onMatch),其他的直接拒绝(onMismatch) -->
                <ThresholdFilter level="error" onMatch="ACCEPT"
                                 onMismatch="DENY" />
                <PatternLayout pattern="${LOG_PATTERN}" />
                <Policies>
                    <!--interval属性用来指定多久滚动一次,默认是1 hour -->
                    <TimeBasedTriggeringPolicy interval="1" />
                    <SizeBasedTriggeringPolicy size="10MB" />
                </Policies>
                <!-- DefaultRolloverStrategy属性如不设置,则默认为最多同一文件夹下7个文件开始覆盖 -->
                <DefaultRolloverStrategy max="15" />
            </RollingFile>
    
    
        </appenders>
    
    
        <!--Logger节点用来单独指定日志的形式,比如要为指定包下的class指定不同的日志级别等。 -->
        <!--然后定义loggers,只有定义了logger并引入的appender,appender才会生效 -->
        <loggers>
    
    
            <!--过滤掉spring和mybatis的一些无用的DEBUG信息 -->
            <logger name="org.mybatis" level="info" additivity="false">
                <AppenderRef ref="Console" />
            </logger>
            <!--监控系统信息 -->
            <!--若是additivity设为false,则 子Logger 只会在自己的appender里输出,而不会在 父Logger 的appender里输出。 -->
            <Logger name="org.springframework" level="info"
                    additivity="false">
                <AppenderRef ref="Console" />
            </Logger>
    
    
            <root level="info">
                <appender-ref ref="Console" />
                <appender-ref ref="Filelog" />
                <appender-ref ref="RollingFileInfo" />
                <appender-ref ref="RollingFileWarn" />
                <appender-ref ref="RollingFileError" />
            </root>
        </loggers>
    
    
    </configuration>
    
    
    10.启动测试,一次成功!

    image-20240625162131168

    image-20240625160254293

    作者:S-X-S

    物联沃分享整理
    物联沃-IOTWORD物联网 » 练题模块环境搭建指南

    发表回复