文章目录
- 1.数据库表设计
- 1.practice_set 套卷
- 2.practice_set_detail 套卷细节
- 3.practice_info 练习信息
- 4.practice_detail 练习详情
- 5.E-R图
- 2.架构设计(三层架构)
- 3.练题微服务架构搭建
- 1.创建一个练题微服务模块
- 1.创建一个maven项目
- 2.把src删除,只留pom.xml
- 2.微服务父模块的pom.xml配置
- 1.配置packaging为pom,指定编译版本,并统一指定SpringBoot版本,统一配置阿里云仓库,使子模块继承
- 2.type和scope解释
- 1.type
- 2.scope
- 3.创建一个练题微服务的api模块
- 1.创建一个maven项目,删除resource目录和test目录
- 2.创建一个common包存放Result和Page相关的
- 1.目录结构
- 2.PageInfo.java
- 3.PageResult.java
- 4.Result.java
- 5.ResultCodeEnum.java
- 6.引入lombok的依赖
- 3.创建一个通用的枚举包
- 1.结构
- 2.IsDeleteFlagEnum.java
- 4.创建一个req和vo分别存放入参和出参实体
- 结构
- 4.创建一个server子模块
- 1.创建一个maven项目
- 2.创建一个config包,暂时先存放mybatis的东西
- 1.结构
- 2.SqlStatementInterceptor.java sql状态拦截器
- 3.MybatisPlusAllSqlLog.java sql转换器
- 4.MybatisConfiguration.java 注册两个拦截器
- 3.引入基本依赖
- 4.config下创建一个redis包,存放redis配置和工具类
- 1.结构
- 2.RedisConfig.java
- 3.RedisUtil.java
- 5.config下创建登录拦截器和上下文将从Header中获取logId放到ThreadLocal中
- 1.结构
- 2.GlobalConfig.java mvc的全局处理,空值不返回,存放自定义拦截器
- 3.LoginContextHolder.java ThreadLocal工具类
- 4.LoginInterceptor.java 登录拦截器,从Header中获取logId放到ThreadLocal
- 6.创建controller包
- 1.结构
- 2.DemoController.java 测试
- 7.创建其余的包
- 1.结构
- 2.DruidEncryptUtil.java 用于对yaml中的东西加解密
- 8.创建启动类
- 1.PracticeApplication.java 注意写MapperScan和ComponentScan,还有启动类注解
- 9.创建配置文件
- 1.resource创建一个mapper文件夹
- 2.application.yml
- 3.bootstrap.yml
- 4.log4j2-spring.xml
- 10.启动测试,一次成功!
1.数据库表设计
1.practice_set 套卷
create table practice_set
(id bigint auto_increment comment '主键'primary key,set_name varchar(255) null comment '套题名称',set_type int null comment '套题类型 1实时生成 2预设套题',set_heat int null comment '热度',set_desc varchar(255) null comment '套题描述',primary_category_id bigint null comment '大类id',created_by varchar(32) charset utf8 null comment '创建人',created_time datetime null comment '创建时间',update_by varchar(32) charset utf8 null comment '更新人',update_time datetime null comment '更新时间',is_deleted int default 0 null comment '是否被删除 0为删除 1已删除'
)comment '套题信息表' collate = utf8mb4_bin;
2.practice_set_detail 套卷细节
create table practice_set_detail
(id bigint auto_increment comment '主键'primary key,set_id bigint not null comment '套题id',subject_id bigint null comment '题目id',subject_type int null comment '题目类型',created_by varchar(32) charset utf8 null comment '创建人',created_time datetime null comment '创建时间',update_by varchar(32) charset utf8 null comment '更新人',update_time datetime null comment '更新时间',is_deleted int default 0 null comment '是否被删除 0为删除 1已删除'
)comment '套题内容表' collate = utf8mb4_bin;
3.practice_info 练习信息
create table practice_info
(id bigint auto_increment comment '主键'primary key,set_id bigint null comment '套题id',complete_status int null comment '是否完成 1完成 0未完成',time_use varchar(32) null comment '用时',submit_time datetime null comment '交卷时间',correct_rate decimal(10, 2) null comment '正确率',created_by varchar(32) charset utf8 null comment '创建人',created_time datetime null comment '创建时间',update_by varchar(32) charset utf8 null comment '更新人',update_time datetime null comment '更新时间',is_deleted int default 0 null comment '是否被删除 0为删除 1已删除'
)comment '练习表' collate = utf8mb4_bin;
4.practice_detail 练习详情
create table practice_detail
(id bigint auto_increment comment '主键'primary key,practice_id bigint null comment '练题id',subject_id bigint null comment '题目id',subject_type int null comment '题目类型',answer_status int null comment '回答状态',answer_content varchar(64) null comment '回答内容',created_by varchar(32) charset utf8 null comment '创建人',created_time datetime null comment '创建时间',update_by varchar(32) charset utf8 null comment '更新人',update_time datetime null comment '更新时间',is_deleted int default 0 null comment '是否被删除 0为删除 1已删除'
)comment '练习详情表' collate = utf8mb4_bin;
5.E-R图
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;}@Overridepublic 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);}@Overridepublic int hashCode() {return Objects.hash(pageNo, pageSize);}@Overridepublic 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> {// 当前页码,默认为1private Integer pageNo = 1;// 每页显示的记录数,默认为20private 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;}@Overridepublic 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);}@Overridepublic int hashCode() {return Objects.hash(pageNo, pageSize, total, totalPages, result, start, end);}@Overridepublic 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");@Overridepublic 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);}}}@Overridepublic Object plugin(Object target) {return Plugin.wrap(target, this);}@Overridepublic 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");@Overridepublic void beforeQuery(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {logInfo(boundSql, ms, parameter);}@Overridepublic 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语句的idString 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)) {// 该分支是动态sqlObject 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 {@Beanpublic 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 {@Beanpublic 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 {@Resourceprivate 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 {@Overrideprotected 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*/@Overrideprotected 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持有一个Mapprivate 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的MapMap<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*/@Overridepublic 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*/@Overridepublic 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.Driverurl: jdbc:mysql:///sun_club?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&useSSL=falseusername: rootpassword: 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 # 是否打印慢sqlwall:enabled: true # 是否开启防火墙config:enabled: true # 开启配置,可以解密redis:password: # Redis服务器密码database: 0 # 默认数据库为0号timeout: 10000ms # 连接超时时间是10000毫秒lettuce:pool:max-active: 8 # 最大活跃连接数,使用负值表示没有限制,最佳配置为核数*2max-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: yamldiscovery: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>