文章目录
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图
2.架构设计(三层架构)
3.练题微服务架构搭建
1.创建一个练题微服务模块
1.创建一个maven项目
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目录
2.创建一个common包存放Result和Page相关的
1.目录结构
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.结构
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分别存放入参和出参实体
结构
4.创建一个server子模块
1.创建一个maven项目
2.创建一个config包,暂时先存放mybatis的东西
1.结构
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.结构
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.结构
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.结构
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文件夹
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>