feat: 优化Redis缓存配置,集成Redisson,增强日志配置

pull/872/head
Jie Zheng 2025-01-15 16:36:42 +08:00
parent 51d9f42273
commit 5963acb664
20 changed files with 265 additions and 149 deletions

View File

@ -15,47 +15,39 @@
*/
package me.zhengjie.config;
import cn.hutool.core.lang.Assert;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.parser.ParserConfig;
import com.alibaba.fastjson.serializer.SerializerFeature;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.codec.digest.DigestUtils;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.data.redis.RedisProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.cache.Cache;
import org.springframework.cache.annotation.CachingConfigurerSupport;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.cache.interceptor.CacheErrorHandler;
import org.springframework.cache.interceptor.KeyGenerator;
import org.springframework.cache.interceptor.SimpleCacheErrorHandler;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.RedisSerializationContext;
import org.springframework.data.redis.serializer.RedisSerializer;
import reactor.util.annotation.Nullable;
import java.nio.charset.Charset;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import java.nio.charset.StandardCharsets;
import java.time.Duration;
import java.util.HashMap;
import java.util.Map;
/**
* @author Zheng Jie
* @date 2018-11-24
* @date 2025-01-13
*/
@Slf4j
@Configuration
@EnableCaching
@ConditionalOnClass(RedisOperations.class)
@EnableConfigurationProperties(RedisProperties.class)
public class RedisConfig extends CachingConfigurerSupport {
public class RedisConfiguration {
/**
* redis 2
@ -70,9 +62,7 @@ public class RedisConfig extends CachingConfigurerSupport {
return configuration;
}
@SuppressWarnings("all")
@Bean(name = "redisTemplate")
@ConditionalOnMissingBean(name = "redisTemplate")
public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
RedisTemplate<Object, Object> template = new RedisTemplate<>();
//序列化
@ -92,6 +82,8 @@ public class RedisConfig extends CachingConfigurerSupport {
ParserConfig.getGlobalInstance().addAccept("me.zhengjie.modules.quartz.service.dto");
ParserConfig.getGlobalInstance().addAccept("me.zhengjie.modules.security.service.dto");
ParserConfig.getGlobalInstance().addAccept("me.zhengjie.modules.system.service.dto");
// 分页返回数据
ParserConfig.getGlobalInstance().addAccept("me.zhengjie.utils.PageResult");
// key的序列化采用StringRedisSerializer
template.setKeySerializer(new StringRedisSerializer());
template.setHashKeySerializer(new StringRedisSerializer());
@ -100,10 +92,24 @@ public class RedisConfig extends CachingConfigurerSupport {
}
/**
* key使
* 使
* @param redisConnectionFactory /
* @return
*/
@Bean
public RedisCacheManager cacheManager(RedisConnectionFactory redisConnectionFactory) {
RedisCacheConfiguration config = redisCacheConfiguration();
return RedisCacheManager.builder(redisConnectionFactory)
.cacheDefaults(config)
.build();
}
/**
* key使keyGenerator
* 使SimpleKeyGeneratorkey
* CachingConfigurerSupport PR
*/
@Bean
@Override
public KeyGenerator keyGenerator() {
return (target, method, params) -> {
Map<String,Object> container = new HashMap<>(8);
@ -126,100 +132,63 @@ public class RedisConfig extends CachingConfigurerSupport {
}
@Bean
@Override
@SuppressWarnings({"all"})
@SuppressWarnings("all")
public CacheErrorHandler errorHandler() {
// 异常处理当Redis发生异常时打印日志但是程序正常走
log.info("初始化 -> [{}]", "Redis CacheErrorHandler");
return new CacheErrorHandler() {
return new SimpleCacheErrorHandler() {
@Override
public void handleCacheGetError(RuntimeException e, Cache cache, Object key) {
log.error("Redis occur handleCacheGetErrorkey -> [{}]", key, e);
public void handleCacheGetError(RuntimeException exception, Cache cache, Object key) {
// 处理缓存读取错误
log.error("Cache Get Error: {}",exception.getMessage());
}
@Override
public void handleCachePutError(RuntimeException e, Cache cache, Object key, Object value) {
log.error("Redis occur handleCachePutErrorkey -> [{}]value -> [{}]", key, value, e);
public void handleCachePutError(RuntimeException exception, Cache cache, Object key, Object value) {
// 处理缓存写入错误
log.error("Cache Put Error: {}",exception.getMessage());
}
@Override
public void handleCacheEvictError(RuntimeException e, Cache cache, Object key) {
log.error("Redis occur handleCacheEvictErrorkey -> [{}]", key, e);
public void handleCacheEvictError(RuntimeException exception, Cache cache, Object key) {
// 处理缓存删除错误
log.error("Cache Evict Error: {}",exception.getMessage());
}
@Override
public void handleCacheClearError(RuntimeException e, Cache cache) {
log.error("Redis occur handleCacheClearError", e);
public void handleCacheClearError(RuntimeException exception, Cache cache) {
// 处理缓存清除错误
log.error("Cache Clear Error: {}",exception.getMessage());
}
};
}
}
/**
* Value
*
* @author /
* @param <T>
*/
class FastJsonRedisSerializer<T> implements RedisSerializer<T> {
/**
* Value
*
* @param <T>
* @author /
*/
static class FastJsonRedisSerializer<T> implements RedisSerializer<T> {
private final Class<T> clazz;
private final Class<T> clazz;
FastJsonRedisSerializer(Class<T> clazz) {
super();
this.clazz = clazz;
}
@Override
public byte[] serialize(T t) {
if (t == null) {
return new byte[0];
FastJsonRedisSerializer(Class<T> clazz) {
super();
this.clazz = clazz;
}
return JSON.toJSONString(t, SerializerFeature.WriteClassName).getBytes(StandardCharsets.UTF_8);
}
@Override
public T deserialize(byte[] bytes) {
if (bytes == null || bytes.length == 0) {
return null;
@Override
public byte[] serialize(T t) {
if (t == null) {
return new byte[0];
}
return JSON.toJSONString(t, SerializerFeature.WriteClassName).getBytes(StandardCharsets.UTF_8);
}
String str = new String(bytes, StandardCharsets.UTF_8);
return JSON.parseObject(str, clazz);
@Override
public T deserialize(byte[] bytes) {
if (bytes == null || bytes.length == 0) {
return null;
}
String str = new String(bytes, StandardCharsets.UTF_8);
return JSON.parseObject(str, clazz);
}
}
}
/**
*
*
* @author /
*/
class StringRedisSerializer implements RedisSerializer<Object> {
private final Charset charset;
StringRedisSerializer() {
this(StandardCharsets.UTF_8);
}
private StringRedisSerializer(Charset charset) {
Assert.notNull(charset, "Charset must not be null!");
this.charset = charset;
}
@Override
public String deserialize(byte[] bytes) {
return (bytes == null ? null : new String(bytes, charset));
}
@Override
public @Nullable byte[] serialize(Object object) {
String string = JSON.toJSONString(object);
if (org.apache.commons.lang3.StringUtils.isBlank(string)) {
return null;
}
string = string.replace("\"", "");
return string.getBytes(charset);
}
}
}

View File

@ -0,0 +1,68 @@
/*
* Copyright 2019-2020 Zheng Jie
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package me.zhengjie.config;
import cn.hutool.core.util.StrUtil;
import lombok.Data;
import org.redisson.Redisson;
import org.redisson.api.RedissonClient;
import org.redisson.config.Config;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Data
@Configuration
public class RedissonConfiguration {
@Value("${spring.redis.host}")
private String redisHost;
@Value("${spring.redis.port}")
private int redisPort;
@Value("${spring.redis.database}")
private int redisDatabase;
@Value("${spring.redis.password:}")
private String redisPassword;
@Value("${spring.redis.timeout:5000}")
private int timeout;
@Value("${spring.redis.lettuce.pool.max-active:64}")
private int connectionPoolSize;
@Value("${spring.redis.lettuce.pool.min-idle:16}")
private int connectionMinimumIdleSize;
@Bean
public RedissonClient redissonClient() {
Config config = new Config();
config.useSingleServer()
.setAddress("redis://" + redisHost + ":" + redisPort)
.setDatabase(redisDatabase)
.setTimeout(timeout)
.setConnectionPoolSize(connectionPoolSize)
.setConnectionMinimumIdleSize(connectionMinimumIdleSize);
if(StrUtil.isNotBlank(redisPassword)){
config.useSingleServer().setPassword(redisPassword);
}
return Redisson.create(config);
}
}

View File

@ -15,23 +15,18 @@
*/
package me.zhengjie.config.webConfig;
import cn.hutool.core.collection.CollUtil;
import com.fasterxml.classmate.TypeResolver;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.RequiredArgsConstructor;
import me.zhengjie.utils.AnonTagUtils;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.Ordered;
import org.springframework.data.domain.Pageable;
import org.springframework.util.ReflectionUtils;
import org.springframework.web.servlet.mvc.method.RequestMappingInfoHandlerMapping;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.schema.AlternateTypeRule;
import springfox.documentation.schema.AlternateTypeRuleConvention;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.service.ApiKey;
import springfox.documentation.service.AuthorizationScope;
@ -40,15 +35,16 @@ import springfox.documentation.service.SecurityScheme;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spi.service.contexts.SecurityContext;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.spring.web.plugins.WebFluxRequestHandlerProvider;
import springfox.documentation.spring.web.plugins.WebMvcRequestHandlerProvider;
import springfox.documentation.swagger2.annotations.EnableSwagger2;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
import static springfox.documentation.schema.AlternateTypeRules.newRule;
/**
* api /doc.html
* @author Zheng Jie
@ -129,39 +125,45 @@ public class SwaggerConfig {
securityReferences.add(new SecurityReference(tokenHeader, authorizationScopes));
return securityReferences;
}
}
/**
* Pageableswagger
*/
@Configuration
class SwaggerDataConfig {
/**
* SpringfoxSpringBootWebMvcRequestHandlerProviderWebFluxRequestHandlerProvider
* @return /
*/
@Bean
public AlternateTypeRuleConvention pageableConvention(final TypeResolver resolver) {
return new AlternateTypeRuleConvention() {
@SuppressWarnings({"all"})
public static BeanPostProcessor springfoxHandlerProviderBeanPostProcessor() {
return new BeanPostProcessor() {
@Override
public int getOrder() {
return Ordered.HIGHEST_PRECEDENCE;
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
if (bean instanceof WebMvcRequestHandlerProvider || bean instanceof WebFluxRequestHandlerProvider) {
customizeSpringfoxHandlerMappings(getHandlerMappings(bean));
}
return bean;
}
@Override
public List<AlternateTypeRule> rules() {
return CollUtil.newArrayList(newRule(resolver.resolve(Pageable.class), resolver.resolve(Page.class)));
private <T extends RequestMappingInfoHandlerMapping> void customizeSpringfoxHandlerMappings(List<T> mappings) {
List<T> filteredMappings = mappings.stream()
.filter(mapping -> mapping.getPatternParser() == null)
.collect(Collectors.toList());
mappings.clear();
mappings.addAll(filteredMappings);
}
private List<RequestMappingInfoHandlerMapping> getHandlerMappings(Object bean) {
Field field = ReflectionUtils.findField(bean.getClass(), "handlerMappings");
if (field != null) {
field.setAccessible(true);
try {
return (List<RequestMappingInfoHandlerMapping>) field.get(bean);
} catch (IllegalAccessException e) {
throw new IllegalStateException("Failed to access handlerMappings field", e);
}
}
return Collections.emptyList();
}
};
}
@ApiModel
@Data
private static class Page {
@ApiModelProperty("页码 (0..N)")
private Integer page;
@ApiModelProperty("每页显示的数目")
private Integer size;
@ApiModelProperty("以下列格式排序标准property[,asc | desc]。 默认排序顺序为升序。 支持多种排序条件id,asc")
private List<String> sort;
}
}

View File

@ -0,0 +1,52 @@
package me.zhengjie.config.webConfig;
import cn.hutool.core.collection.CollUtil;
import com.fasterxml.classmate.TypeResolver;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.Ordered;
import org.springframework.data.domain.Pageable;
import springfox.documentation.schema.AlternateTypeRule;
import springfox.documentation.schema.AlternateTypeRuleConvention;
import java.util.List;
import static springfox.documentation.schema.AlternateTypeRules.newRule;
/**
* Pageableswagger
*/
@Configuration
public class SwaggerDataConfig {
@Bean
public AlternateTypeRuleConvention pageableConvention(final TypeResolver resolver) {
return new AlternateTypeRuleConvention() {
@Override
public int getOrder() {
return Ordered.HIGHEST_PRECEDENCE;
}
@Override
public List<AlternateTypeRule> rules() {
return CollUtil.newArrayList(newRule(resolver.resolve(Pageable.class), resolver.resolve(Page.class)));
}
};
}
@ApiModel
@Data
private static class Page {
@ApiModelProperty("页码 (0..N)")
private Integer page;
@ApiModelProperty("每页显示的数目")
private Integer size;
@ApiModelProperty("以下列格式排序标准property[,asc | desc]。 默认排序顺序为升序。 支持多种排序条件id,asc")
private List<String> sort;
}
}

View File

@ -36,7 +36,7 @@ import java.util.*;
**/
@Service
@RequiredArgsConstructor
@CacheConfig(cacheNames = "data")
@CacheConfig(cacheNames = "data", keyGenerator = "keyGenerator")
public class DataServiceImpl implements DataService {
private final RoleService roleService;

View File

@ -47,7 +47,7 @@ import java.util.stream.Collectors;
*/
@Service
@RequiredArgsConstructor
@CacheConfig(cacheNames = "dept")
@CacheConfig(cacheNames = "dept", keyGenerator = "keyGenerator")
public class DeptServiceImpl implements DeptService {
private final DeptRepository deptRepository;

View File

@ -40,7 +40,7 @@ import java.util.List;
*/
@Service
@RequiredArgsConstructor
@CacheConfig(cacheNames = "dict")
@CacheConfig(cacheNames = "dict", keyGenerator = "keyGenerator")
public class DictDetailServiceImpl implements DictDetailService {
private final DictRepository dictRepository;

View File

@ -41,7 +41,7 @@ import java.util.*;
*/
@Service
@RequiredArgsConstructor
@CacheConfig(cacheNames = "dict")
@CacheConfig(cacheNames = "dict", keyGenerator = "keyGenerator")
public class DictServiceImpl implements DictService {
private final DictRepository dictRepository;

View File

@ -44,7 +44,7 @@ import java.util.*;
*/
@Service
@RequiredArgsConstructor
@CacheConfig(cacheNames = "job")
@CacheConfig(cacheNames = "job", keyGenerator = "keyGenerator")
public class JobServiceImpl implements JobService {
private final JobRepository jobRepository;

View File

@ -50,7 +50,7 @@ import java.util.stream.Collectors;
*/
@Service
@RequiredArgsConstructor
@CacheConfig(cacheNames = "menu")
@CacheConfig(cacheNames = "menu", keyGenerator = "keyGenerator")
public class MenuServiceImpl implements MenuService {
private final MenuRepository menuRepository;

View File

@ -52,7 +52,7 @@ import java.util.stream.Collectors;
*/
@Service
@RequiredArgsConstructor
@CacheConfig(cacheNames = "role")
@CacheConfig(cacheNames = "role", keyGenerator = "keyGenerator")
public class RoleServiceImpl implements RoleService {
private final RoleRepository roleRepository;

View File

@ -50,7 +50,7 @@ import java.util.stream.Collectors;
*/
@Service
@RequiredArgsConstructor
@CacheConfig(cacheNames = "user")
@CacheConfig(cacheNames = "user", keyGenerator = "keyGenerator")
public class UserServiceImpl implements UserService {
private final UserRepository userRepository;

View File

@ -45,7 +45,7 @@ spring:
enabled: true
# 记录慢SQL
log-slow-sql: true
slow-sql-millis: 1000
slow-sql-millis: 2000
merge-sql: true
wall:
config:

View File

@ -49,7 +49,7 @@ spring:
enabled: true
# 记录慢SQL
log-slow-sql: true
slow-sql-millis: 1000
slow-sql-millis: 2000
merge-sql: true
wall:
config:

View File

@ -40,6 +40,17 @@ spring:
password: ${REDIS_PWD:}
#连接超时时间
timeout: 5000
# 连接池配置
lettuce:
pool:
# 连接池最大连接数
max-active: 30
# 连接池最大阻塞等待时间(毫秒),负值表示没有限制
max-wait: -1
# 连接池中的最大空闲连接数
max-idle: 20
# 连接池中的最小空闲连接数
min-idle: 1
task:
pool:

View File

@ -16,4 +16,11 @@
<root level="info">
<appender-ref ref="console" />
</root>
<!-- DnsServerAddressStreamProviders调整为ERROR -->
<logger name="io.netty.resolver.dns.DnsServerAddressStreamProviders" level="ERROR"/>
<!-- 设置其他类的日志级别为 ERROR -->
<logger name="org.apache.catalina.core.ContainerBase.[Tomcat].[localhost].[/]" level="ERROR"/>
<logger name="org.springframework.web.servlet.DispatcherServlet" level="ERROR"/>
</configuration>

View File

@ -38,7 +38,7 @@ import java.util.Optional;
*/
@Service
@RequiredArgsConstructor
@CacheConfig(cacheNames = "aliPay")
@CacheConfig(cacheNames = "aliPay", keyGenerator = "keyGenerator")
public class AliPayServiceImpl implements AliPayService {
private final AliPayRepository alipayRepository;

View File

@ -37,7 +37,7 @@ import java.util.Optional;
*/
@Service
@RequiredArgsConstructor
@CacheConfig(cacheNames = "email")
@CacheConfig(cacheNames = "email", keyGenerator = "keyGenerator")
public class EmailServiceImpl implements EmailService {
private final EmailRepository emailRepository;

View File

@ -51,7 +51,7 @@ import java.util.*;
*/
@Service
@RequiredArgsConstructor
@CacheConfig(cacheNames = "qiNiu")
@CacheConfig(cacheNames = "qiNiu", keyGenerator = "keyGenerator")
public class QiNiuServiceImpl implements QiNiuService {
private final QiNiuConfigRepository qiNiuConfigRepository;

View File

@ -81,6 +81,13 @@
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!--Spring boot redisson-->
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson-spring-boot-starter</artifactId>
<version>3.17.1</version>
</dependency>
<!--spring boot 集成redis所需common-pool2-->
<!-- https://mvnrepository.com/artifact/org.apache.commons/commons-pool2 -->
<dependency>