首页 > Python资料 博客日记
练题模块环境搭建
2024-08-22 22:00:07Python资料围观51次
文章练题模块环境搭建分享给大家,欢迎收藏Python资料网,专注分享技术知识
文章目录
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>
10.启动测试,一次成功!
版权声明:本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:jacktools123@163.com进行投诉反馈,一经查实,立即删除!
标签:
相关文章
最新发布
- 【Python】selenium安装+Microsoft Edge驱动器下载配置流程
- Python 中自动打开网页并点击[自动化脚本],Selenium
- Anaconda基础使用
- 【Python】成功解决 TypeError: ‘<‘ not supported between instances of ‘str’ and ‘int’
- manim边学边做--三维的点和线
- CPython是最常用的Python解释器之一,也是Python官方实现。它是用C语言编写的,旨在提供一个高效且易于使用的Python解释器。
- Anaconda安装配置Jupyter(2024最新版)
- Python中读取Excel最快的几种方法!
- Python某城市美食商家爬虫数据可视化分析和推荐查询系统毕业设计论文开题报告
- 如何使用 Python 批量检测和转换 JSONL 文件编码为 UTF-8
点击排行
- 版本匹配指南:Numpy版本和Python版本的对应关系
- 版本匹配指南:PyTorch版本、torchvision 版本和Python版本的对应关系
- Python 可视化 web 神器:streamlit、Gradio、dash、nicegui;低代码 Python Web 框架:PyWebIO
- 相关性分析——Pearson相关系数+热力图(附data和Python完整代码)
- Python与PyTorch的版本对应
- Anaconda版本和Python版本对应关系(持续更新...)
- Python pyinstaller打包exe最完整教程
- Could not build wheels for llama-cpp-python, which is required to install pyproject.toml-based proj