初始化提交

This commit is contained in:
YangJ
2024-03-20 09:42:17 +08:00
commit 72f30209cf
3705 changed files with 285827 additions and 0 deletions

View File

@ -0,0 +1,41 @@
<?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">
<parent>
<groupId>cn.iocoder.boot</groupId>
<artifactId>yudao-framework</artifactId>
<version>${revision}</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>yudao-spring-boot-starter-redis</artifactId>
<packaging>jar</packaging>
<name>${project.artifactId}</name>
<description>Redis 封装拓展</description>
<url>https://github.com/YunaiV/ruoyi-vue-pro</url>
<dependencies>
<dependency>
<groupId>cn.iocoder.boot</groupId>
<artifactId>yudao-common</artifactId>
</dependency>
<!-- DB 相关 -->
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson-spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId> <!-- 实现对 Caches 的自动化配置 -->
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-jsr310</artifactId>
</dependency>
</dependencies>
</project>

View File

@ -0,0 +1,82 @@
package cn.iocoder.yudao.framework.redis.config;
import cn.hutool.core.util.StrUtil;
import cn.iocoder.yudao.framework.redis.core.TimeoutRedisCacheManager;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.cache.CacheProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Primary;
import org.springframework.data.redis.cache.BatchStrategies;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.cache.RedisCacheWriter;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.RedisSerializationContext;
import org.springframework.util.StringUtils;
import java.util.Objects;
import static cn.iocoder.yudao.framework.redis.config.YudaoRedisAutoConfiguration.buildRedisSerializer;
/**
* Cache 配置类,基于 Redis 实现
*/
@AutoConfiguration
@EnableConfigurationProperties({CacheProperties.class, YudaoCacheProperties.class})
@EnableCaching
public class YudaoCacheAutoConfiguration {
/**
* RedisCacheConfiguration Bean
* <p>
* 参考 org.springframework.boot.autoconfigure.cache.RedisCacheConfiguration 的 createConfiguration 方法
*/
@Bean
@Primary
public RedisCacheConfiguration redisCacheConfiguration(CacheProperties cacheProperties) {
RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig();
// 设置使用 : 单冒号,而不是双 :: 冒号,避免 Redis Desktop Manager 多余空格
// 详细可见 https://blog.csdn.net/chuixue24/article/details/103928965 博客
// 再次修复单冒号,而不是双 :: 冒号问题Issues 详情https://gitee.com/zhijiantianya/yudao-cloud/issues/I86VY2
config = config.computePrefixWith(cacheName -> {
String keyPrefix = cacheProperties.getRedis().getKeyPrefix();
if (StringUtils.hasText(keyPrefix)) {
keyPrefix = keyPrefix.lastIndexOf(StrUtil.COLON) == -1 ? keyPrefix + StrUtil.COLON : keyPrefix;
return keyPrefix + cacheName + StrUtil.COLON;
}
return cacheName + StrUtil.COLON;
});
// 设置使用 JSON 序列化方式
config = config.serializeValuesWith(
RedisSerializationContext.SerializationPair.fromSerializer(buildRedisSerializer()));
// 设置 CacheProperties.Redis 的属性
CacheProperties.Redis redisProperties = cacheProperties.getRedis();
if (redisProperties.getTimeToLive() != null) {
config = config.entryTtl(redisProperties.getTimeToLive());
}
if (!redisProperties.isCacheNullValues()) {
config = config.disableCachingNullValues();
}
if (!redisProperties.isUseKeyPrefix()) {
config = config.disableKeyPrefix();
}
return config;
}
@Bean
public RedisCacheManager redisCacheManager(RedisTemplate<String, Object> redisTemplate,
RedisCacheConfiguration redisCacheConfiguration,
YudaoCacheProperties yudaoCacheProperties) {
// 创建 RedisCacheWriter 对象
RedisConnectionFactory connectionFactory = Objects.requireNonNull(redisTemplate.getConnectionFactory());
RedisCacheWriter cacheWriter = RedisCacheWriter.nonLockingRedisCacheWriter(connectionFactory,
BatchStrategies.scan(yudaoCacheProperties.getRedisScanBatchSize()));
// 创建 TenantRedisCacheManager 对象
return new TimeoutRedisCacheManager(cacheWriter, redisCacheConfiguration);
}
}

View File

@ -0,0 +1,27 @@
package cn.iocoder.yudao.framework.redis.config;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.validation.annotation.Validated;
/**
* Cache 配置项
*
* @author Wanwan
*/
@ConfigurationProperties("yudao.cache")
@Data
@Validated
public class YudaoCacheProperties {
/**
* {@link #redisScanBatchSize} 默认值
*/
private static final Integer REDIS_SCAN_BATCH_SIZE_DEFAULT = 30;
/**
* redis scan 一次返回数量
*/
private Integer redisScanBatchSize = REDIS_SCAN_BATCH_SIZE_DEFAULT;
}

View File

@ -0,0 +1,44 @@
package cn.iocoder.yudao.framework.redis.config;
import cn.hutool.core.util.ReflectUtil;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.RedisSerializer;
/**
* Redis 配置类
*/
@AutoConfiguration
public class YudaoRedisAutoConfiguration {
/**
* 创建 RedisTemplate Bean使用 JSON 序列化方式
*/
@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
// 创建 RedisTemplate 对象
RedisTemplate<String, Object> template = new RedisTemplate<>();
// 设置 RedisConnection 工厂。😈 它就是实现多种 Java Redis 客户端接入的秘密工厂。感兴趣的胖友,可以自己去撸下。
template.setConnectionFactory(factory);
// 使用 String 序列化方式,序列化 KEY 。
template.setKeySerializer(RedisSerializer.string());
template.setHashKeySerializer(RedisSerializer.string());
// 使用 JSON 序列化方式(库是 Jackson ),序列化 VALUE 。
template.setValueSerializer(buildRedisSerializer());
template.setHashValueSerializer(buildRedisSerializer());
return template;
}
public static RedisSerializer<?> buildRedisSerializer() {
RedisSerializer<Object> json = RedisSerializer.json();
// 解决 LocalDateTime 的序列化
ObjectMapper objectMapper = (ObjectMapper) ReflectUtil.getFieldValue(json, "mapper");
objectMapper.registerModules(new JavaTimeModule());
return json;
}
}

View File

@ -0,0 +1,83 @@
package cn.iocoder.yudao.framework.redis.core;
import cn.hutool.core.util.NumberUtil;
import cn.hutool.core.util.StrUtil;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.data.redis.cache.RedisCache;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.cache.RedisCacheWriter;
import java.time.Duration;
/**
* 支持自定义过期时间的 {@link RedisCacheManager} 实现类
*
* 在 {@link Cacheable#cacheNames()} 格式为 "key#ttl" 时,# 后面的 ttl 为过期时间。
* 单位为最后一个字母支持的单位有d 天h 小时m 分钟s 秒),默认单位为 s 秒
*
* @author 芋道源码
*/
public class TimeoutRedisCacheManager extends RedisCacheManager {
private static final String SPLIT = "#";
public TimeoutRedisCacheManager(RedisCacheWriter cacheWriter, RedisCacheConfiguration defaultCacheConfiguration) {
super(cacheWriter, defaultCacheConfiguration);
}
@Override
protected RedisCache createRedisCache(String name, RedisCacheConfiguration cacheConfig) {
if (StrUtil.isEmpty(name)) {
return super.createRedisCache(name, cacheConfig);
}
// 如果使用 # 分隔,大小不为 2则说明不使用自定义过期时间
String[] names = StrUtil.splitToArray(name, SPLIT);
if (names.length != 2) {
return super.createRedisCache(name, cacheConfig);
}
// 核心:通过修改 cacheConfig 的过期时间,实现自定义过期时间
if (cacheConfig != null) {
// 移除 # 后面的 : 以及后面的内容,避免影响解析
names[1] = StrUtil.subBefore(names[1], StrUtil.COLON, false);
// 解析时间
Duration duration = parseDuration(names[1]);
cacheConfig = cacheConfig.entryTtl(duration);
}
return super.createRedisCache(name, cacheConfig);
}
/**
* 解析过期时间 Duration
*
* @param ttlStr 过期时间字符串
* @return 过期时间 Duration
*/
private Duration parseDuration(String ttlStr) {
String timeUnit = StrUtil.subSuf(ttlStr, -1);
switch (timeUnit) {
case "d":
return Duration.ofDays(removeDurationSuffix(ttlStr));
case "h":
return Duration.ofHours(removeDurationSuffix(ttlStr));
case "m":
return Duration.ofMinutes(removeDurationSuffix(ttlStr));
case "s":
return Duration.ofSeconds(removeDurationSuffix(ttlStr));
default:
return Duration.ofSeconds(Long.parseLong(ttlStr));
}
}
/**
* 移除多余的后缀,返回具体的时间
*
* @param ttlStr 过期时间字符串
* @return 时间
*/
private Long removeDurationSuffix(String ttlStr) {
return NumberUtil.parseLong(StrUtil.sub(ttlStr, 0, ttlStr.length() - 1));
}
}

View File

@ -0,0 +1,4 @@
/**
* 采用 Spring Data Redis 操作 Redis底层使用 Redisson 作为客户端
*/
package cn.iocoder.yudao.framework.redis;

View File

@ -0,0 +1,2 @@
cn.iocoder.yudao.framework.redis.config.YudaoRedisAutoConfiguration
cn.iocoder.yudao.framework.redis.config.YudaoCacheAutoConfiguration

View File

@ -0,0 +1 @@
<http://www.iocoder.cn/Spring-Boot/Cache/?yudao>

View File

@ -0,0 +1 @@
<http://www.iocoder.cn/Spring-Boot/Redis/?yudao>