练题模块环境搭建

文章目录

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-20240625140734223

image-20240625140829256

2.把src删除,只留pom.xml

image-20240625141000578

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.结构

image-20240625153251290

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: root
    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: 
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:  # Nacos地址
        prefix: ${spring.application.name} # 配置前缀为服务名,sub-club-practice-dev为配置文件名
        group: DEFAULT_GROUP # 配置分组
        namespace: # 命名空间,如果在public命名空间则不需要配置
        file-extension: yaml
      discovery:
        enabled: true # 启用服务发现
        server-addr:  # 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

  • 15
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

S-X-S

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

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

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

打赏作者

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

抵扣说明:

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

余额充值