初始化
This commit is contained in:
389
rdms-framework/rdms-spring-boot-starter-redis/README.md
Normal file
389
rdms-framework/rdms-spring-boot-starter-redis/README.md
Normal file
@@ -0,0 +1,389 @@
|
||||
# rdms-spring-boot-starter-redis
|
||||
|
||||
## 模块定位
|
||||
`rdms-spring-boot-starter-redis` 不是单纯把 Redis 依赖引进来,而是把项目里常用的 Redis 使用约定统一收口。
|
||||
|
||||
当前模块主要承担三类职责:
|
||||
|
||||
1. 统一 `RedisTemplate` 的序列化策略
|
||||
2. 统一 Spring Cache 基于 Redis 的实现方式
|
||||
3. 引入 Redisson,为分布式锁、限流等上层模块提供基础能力
|
||||
|
||||
它的重点不是封装大量 Redis 工具类,而是先把“Redis 怎么接入、怎么序列化、缓存怎么配置”这些基础设施统一好。
|
||||
|
||||
## 设计思路
|
||||
|
||||
## 1. 先统一 RedisTemplate,再让上层模块复用
|
||||
模块通过 `RdmsRedisAutoConfiguration` 注册了统一的 `RedisTemplate<String, Object>`,核心约定是:
|
||||
|
||||
1. Key 使用字符串序列化
|
||||
2. Hash Key 使用字符串序列化
|
||||
3. Value 使用 JSON 序列化
|
||||
4. Hash Value 使用 JSON 序列化
|
||||
|
||||
这样可以避免各模块各自定义一套 `RedisTemplate`,导致同一个 Redis 里出现不同的序列化格式。
|
||||
|
||||
## 2. 优先解决“可读性和兼容性”问题
|
||||
这个模块没有采用 JDK 默认序列化,而是直接使用 JSON 序列化。这样做的直接收益是:
|
||||
|
||||
1. Redis 中的数据结构更容易观察和排查
|
||||
2. 避免 JDK 序列化带来的可读性差和兼容性问题
|
||||
3. 方便不同模块围绕同一种序列化格式工作
|
||||
|
||||
同时,它还额外处理了 `LocalDateTime` 的序列化问题,避免 Java 时间类型在 Redis 中读写异常。
|
||||
|
||||
## 3. 把 Cache 也纳入统一约定
|
||||
模块不只提供 `RedisTemplate`,还把 Spring Cache 一起接入 Redis:
|
||||
|
||||
1. 开启 `@EnableCaching`
|
||||
2. 统一 `RedisCacheConfiguration`
|
||||
3. 统一 `RedisCacheManager`
|
||||
4. 扩展支持按缓存名声明过期时间
|
||||
|
||||
这说明模块的目标不是“给你 Redis 客户端自己玩”,而是先把项目里高频使用的缓存场景标准化。
|
||||
|
||||
## 4. 通过小扩展解决 Spring Cache 默认能力不够用的问题
|
||||
模块里的 `TimeoutRedisCacheManager` 做了一个很实用的扩展:
|
||||
|
||||
1. 如果缓存名是普通格式,例如 `user`
|
||||
2. 则按全局默认 TTL 工作
|
||||
3. 如果缓存名写成 `user#10m`
|
||||
4. 则这个缓存自动按 10 分钟过期
|
||||
|
||||
这相当于把“单个缓存项的过期时间”从配置文件和自定义代码里,收口到了缓存名约定里。
|
||||
|
||||
## 5. 给上层能力模块提供 Redis 和 Redisson 基座
|
||||
这个模块本身不直接实现分布式锁、限流、幂等,但它提供了这些上层能力所依赖的基础能力:
|
||||
|
||||
1. `StringRedisTemplate`
|
||||
2. `RedisTemplate<String, Object>`
|
||||
3. `RedissonClient`
|
||||
4. 基于 Redis 的 Spring Cache
|
||||
|
||||
因此像 `rdms-spring-boot-starter-protection`、`rdms-spring-boot-starter-mq` 这样的模块,都可以把 Redis 能力建立在这个 starter 之上。
|
||||
|
||||
## 自动装配链路
|
||||
|
||||
## 1. Spring Boot 自动配置入口
|
||||
`META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports`
|
||||
|
||||
1. `com.njcn.rdms.framework.redis.config.RdmsRedisAutoConfiguration`
|
||||
2. `com.njcn.rdms.framework.redis.config.RdmsCacheAutoConfiguration`
|
||||
|
||||
## 2. 各配置类负责的事情
|
||||
|
||||
| 配置类 | 作用 |
|
||||
| --- | --- |
|
||||
| `RdmsRedisAutoConfiguration` | 注册统一序列化规则的 `RedisTemplate<String, Object>` |
|
||||
| `RdmsCacheAutoConfiguration` | 开启 Spring Cache,注册 `RedisCacheConfiguration` 和自定义 `RedisCacheManager` |
|
||||
|
||||
## 核心组件
|
||||
|
||||
| 组件 | 作用 |
|
||||
| --- | --- |
|
||||
| `RdmsRedisAutoConfiguration` | 提供统一 JSON 序列化的 RedisTemplate,并处理 `LocalDateTime` 序列化 |
|
||||
| `RdmsCacheAutoConfiguration` | 统一 Redis Cache 前缀、TTL、空值缓存策略和 CacheManager |
|
||||
| `RdmsCacheProperties` | 提供 `rdms.cache.*` 配置项,目前包含 `redis-scan-batch-size` |
|
||||
| `TimeoutRedisCacheManager` | 支持通过 `cacheName#ttl` 的形式为单个缓存声明过期时间 |
|
||||
|
||||
## 功能说明
|
||||
|
||||
## 1. 统一 RedisTemplate 序列化
|
||||
核心类:
|
||||
|
||||
1. `RdmsRedisAutoConfiguration`
|
||||
|
||||
实现方式:
|
||||
|
||||
1. `RedisTemplate<String, Object>` 统一由框架注册
|
||||
2. Key/Hash Key 使用字符串序列化
|
||||
3. Value/Hash Value 使用 JSON 序列化
|
||||
4. 通过 `JavaTimeModule` 兼容 `LocalDateTime`
|
||||
|
||||
适合场景:
|
||||
|
||||
1. 业务模块直接注入 `RedisTemplate<String, Object>` 使用
|
||||
2. 需要缓存对象、列表、Map 等结构
|
||||
3. 希望 Redis 中的数据具有一定可读性
|
||||
|
||||
示例:
|
||||
|
||||
```java
|
||||
@Resource
|
||||
private RedisTemplate<String, Object> redisTemplate;
|
||||
|
||||
public void saveUser(Long userId, UserRespVO user) {
|
||||
redisTemplate.opsForValue().set("user:" + userId, user);
|
||||
}
|
||||
```
|
||||
|
||||
## 2. 统一 Spring Cache 基于 Redis 的实现
|
||||
核心类:
|
||||
|
||||
1. `RdmsCacheAutoConfiguration`
|
||||
2. `RdmsCacheProperties`
|
||||
|
||||
实现方式:
|
||||
|
||||
1. 开启 `@EnableCaching`
|
||||
2. 创建统一的 `RedisCacheConfiguration`
|
||||
3. 按 `spring.cache.redis.*` 读取默认 TTL、前缀、空值缓存等配置
|
||||
4. 使用 JSON 序列化缓存值
|
||||
5. 使用自定义 `RedisCacheManager`
|
||||
|
||||
这个模块还额外做了一个约定:
|
||||
|
||||
1. Cache Key 前缀使用单个 `:`,而不是默认的 `::`
|
||||
2. 这样 Redis 中的 Key 更紧凑,也更方便在可视化工具中查看
|
||||
|
||||
Spring Cache 在这个模块里的作用,可以理解为:
|
||||
|
||||
1. 让“方法查询结果缓存”变成注解式能力,而不是每次都手写 `RedisTemplate`
|
||||
2. 让缓存的写入、读取、失效围绕业务方法本身声明
|
||||
3. 让“查库 -> 放缓存 -> 下次命中缓存”这类标准流程交给框架处理
|
||||
|
||||
它更适合下面这类场景:
|
||||
|
||||
1. 根据主键查询角色、菜单、模板、配置
|
||||
2. 根据某个唯一业务字段查询对象,例如 `clientId`、`code`
|
||||
3. 根据某个参数查询相对稳定的数据,例如部门子节点列表、权限对应菜单列表
|
||||
|
||||
这类场景通常有几个共同点:
|
||||
|
||||
1. 输入参数比较明确
|
||||
2. 返回结果可以直接缓存
|
||||
3. 数据更新时可以找到明确的缓存失效点
|
||||
|
||||
典型流程是:
|
||||
|
||||
1. 在查询方法上加 `@Cacheable`
|
||||
2. 第一次执行时,方法正常查数据库或远程接口
|
||||
3. 返回结果自动写入 Redis
|
||||
4. 后续相同参数调用时,直接命中缓存,不再进入方法体
|
||||
5. 在更新、删除方法上通过 `@CacheEvict` 清理对应缓存
|
||||
|
||||
和直接使用 `RedisTemplate` 相比,Spring Cache 的优势主要是:
|
||||
|
||||
1. 不需要手写 `get -> 判空 -> set -> expire` 这类模板代码
|
||||
2. 缓存逻辑和业务方法绑定更紧,读代码时更容易看出哪里有缓存
|
||||
3. 适合“查询结果缓存”这种标准模式
|
||||
|
||||
当前项目里,权限、角色、菜单、模板这类数据就大量采用这种方式;而像 Token、验证码这类更像“直接把业务对象存到 Redis”的场景,则更适合单独写 RedisDAO。
|
||||
|
||||
示例:
|
||||
|
||||
```java
|
||||
@Cacheable(cacheNames = "user", key = "#id")
|
||||
public UserRespVO getUser(Long id) {
|
||||
return userApi.getUser(id);
|
||||
}
|
||||
```
|
||||
|
||||
如果某个写操作会影响缓存,需要配套清理缓存:
|
||||
|
||||
```java
|
||||
@CacheEvict(cacheNames = "user", key = "#id")
|
||||
public void deleteUser(Long id) {
|
||||
userMapper.deleteById(id);
|
||||
}
|
||||
```
|
||||
|
||||
在当前项目语境下,更贴近实际的使用案例包括:
|
||||
|
||||
1. 根据角色 ID 查询角色信息,并在角色更新或删除时清理缓存
|
||||
2. 根据权限标识查询菜单 ID 列表,并在菜单新增、修改、删除时清理缓存
|
||||
3. 根据模板编码查询通知模板、邮件模板,并在模板变更时清理缓存
|
||||
|
||||
## 3. 支持按缓存名自定义过期时间
|
||||
核心类:
|
||||
|
||||
1. `TimeoutRedisCacheManager`
|
||||
|
||||
实现方式:
|
||||
|
||||
1. 解析 `@Cacheable(cacheNames = "...")` 中的缓存名
|
||||
2. 如果命中 `cacheName#ttl` 格式,则为该缓存单独设置 TTL
|
||||
3. 支持的单位有:
|
||||
- `d` 天
|
||||
- `h` 小时
|
||||
- `m` 分钟
|
||||
- `s` 秒
|
||||
4. 如果不带单位,默认按秒处理
|
||||
|
||||
示例:
|
||||
|
||||
```java
|
||||
@Cacheable(cacheNames = "user#10m", key = "#id")
|
||||
public UserRespVO getUser(Long id) {
|
||||
return userApi.getUser(id);
|
||||
}
|
||||
```
|
||||
|
||||
说明:
|
||||
|
||||
1. `#10m` 只用于声明这个缓存的过期时间是 10 分钟
|
||||
2. 它不会作为最终 Redis key 的一部分长期保留
|
||||
3. 如果 `id = 1`
|
||||
4. 且 `spring.cache.redis.key-prefix = rdms`
|
||||
5. 那么实际 Redis key 一般形如 `rdms:user:1`
|
||||
6. 其中 `user` 是缓存名,`1` 是 `key = "#id"` 计算出的缓存 Key
|
||||
|
||||
```java
|
||||
@Cacheable(cacheNames = "config#30s", key = "#key")
|
||||
public ConfigRespVO getConfig(String key) {
|
||||
return configService.getConfig(key);
|
||||
}
|
||||
```
|
||||
|
||||
说明:
|
||||
|
||||
1. 如果 `key = "sms"`
|
||||
2. 且 `spring.cache.redis.key-prefix = rdms`
|
||||
3. 那么实际 Redis key 一般形如 `rdms:config:sms`
|
||||
4. `#30s` 同样只影响 TTL,不直接体现在最终 key 名上
|
||||
|
||||
这种方式适合“绝大多数缓存共用默认 TTL,少数缓存按场景单独缩短或延长过期时间”的场景。
|
||||
|
||||
补充说明:
|
||||
|
||||
1. `cacheNames = "user#10m"` 中的 `user` 是缓存名,`10m` 是 TTL 描述
|
||||
2. `key = "#id"` 或 `key = "#key"` 才决定具体缓存项的业务 Key
|
||||
3. 如果配置了 `spring.cache.redis.key-prefix = rdms`,最终 Redis key 通常是 `rdms:user:1`、`rdms:config:sms` 这种形式
|
||||
4. `#10m`、`#30s` 这类 TTL 后缀用于创建缓存时解析过期时间,不应理解成最终 key 名的一部分
|
||||
|
||||
## 4. 提供 Redisson 基础能力
|
||||
从依赖上看,模块直接引入了 `redisson-spring-boot-starter`,因此除了 Spring Data Redis 以外,也会把 `RedissonClient` 这类能力带入项目。
|
||||
|
||||
这部分的直接收益是:
|
||||
|
||||
1. 分布式锁能力可被上层模块直接复用
|
||||
2. 限流等基于 Redisson 的能力可直接建立在此模块之上
|
||||
3. 项目不需要在多个模块里重复引入 Redisson
|
||||
|
||||
## 如何使用
|
||||
|
||||
## 1. 引入依赖
|
||||
业务模块通常直接依赖:
|
||||
|
||||
```xml
|
||||
<dependency>
|
||||
<groupId>com.njcn</groupId>
|
||||
<artifactId>rdms-spring-boot-starter-redis</artifactId>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
## 2. 基础配置
|
||||
最常见的配置重点包括:
|
||||
|
||||
1. `spring.data.redis.*`
|
||||
2. `spring.cache.redis.*`
|
||||
3. `rdms.cache.redis-scan-batch-size`
|
||||
|
||||
示例:
|
||||
|
||||
```yaml
|
||||
spring:
|
||||
data:
|
||||
redis:
|
||||
host: 127.0.0.1 # Redis 地址
|
||||
port: 6379 # Redis 端口
|
||||
database: 0 # 使用的 Redis 库编号
|
||||
|
||||
cache:
|
||||
type: REDIS # Spring Cache 底层使用 Redis
|
||||
redis:
|
||||
time-to-live: 1h # 全局默认缓存过期时间
|
||||
cache-null-values: false # 是否缓存空值,通常建议关闭
|
||||
use-key-prefix: true # 是否启用缓存 Key 前缀
|
||||
key-prefix: rdms # 缓存 Key 的统一前缀
|
||||
|
||||
rdms:
|
||||
cache:
|
||||
redis-scan-batch-size: 100 # Redis CacheWriter 使用 SCAN 批量处理时的单次扫描数量
|
||||
```
|
||||
|
||||
说明:
|
||||
|
||||
1. `spring.cache.redis.time-to-live` 是全局默认缓存过期时间
|
||||
2. `cacheName#ttl` 可以覆盖单个缓存的 TTL
|
||||
3. `rdms.cache.redis-scan-batch-size` 用于控制 Redis CacheWriter 使用 `SCAN` 批量处理时的单次返回数量
|
||||
|
||||
## 3. 直接使用 RedisTemplate
|
||||
如果场景不适合 Spring Cache,例如:
|
||||
|
||||
1. 需要操作复杂数据结构
|
||||
2. 需要精细控制过期时间
|
||||
3. 需要手动删除、递增、集合操作
|
||||
|
||||
可以直接注入 `RedisTemplate<String, Object>` 或 `StringRedisTemplate` 使用。
|
||||
|
||||
## 4. 优先使用 Spring Cache 的场景
|
||||
如果场景是:
|
||||
|
||||
1. 查询结果缓存
|
||||
2. 参数到结果的简单映射
|
||||
3. 希望通过注解快速声明缓存
|
||||
|
||||
则优先使用 `@Cacheable`、`@CacheEvict`、`@CachePut`,并利用统一的 Redis Cache 约定。
|
||||
|
||||
这三个注解的典型使用场景可以这样区分:
|
||||
|
||||
1. `@Cacheable`
|
||||
用于“先查缓存,缓存没有再执行方法,并把结果写入缓存”。
|
||||
适合读操作,例如“根据 ID 查询角色”“根据编码查询模板”。
|
||||
|
||||
```java
|
||||
@Cacheable(cacheNames = "role", key = "#id", unless = "#result == null")
|
||||
public RoleDO getRoleFromCache(Long id) {
|
||||
return roleMapper.selectById(id);
|
||||
}
|
||||
```
|
||||
|
||||
2. `@CacheEvict`
|
||||
用于“执行方法后删除缓存”,避免更新或删除数据后缓存还是旧值。
|
||||
适合写操作,例如“更新角色后清理角色缓存”“删除模板后清理模板缓存”。
|
||||
|
||||
```java
|
||||
@CacheEvict(cacheNames = "role", key = "#updateReqVO.id")
|
||||
public void updateRole(RoleSaveReqVO updateReqVO) {
|
||||
roleMapper.updateById(BeanUtils.toBean(updateReqVO, RoleDO.class));
|
||||
}
|
||||
```
|
||||
|
||||
3. `@CachePut`
|
||||
用于“执行方法后,把方法返回值直接写回缓存”。
|
||||
适合更新后希望立即刷新缓存,而不是简单删除缓存的场景。
|
||||
|
||||
```java
|
||||
@CachePut(cacheNames = "role", key = "#result.id")
|
||||
public RoleDO updateRoleAndReturn(RoleSaveReqVO updateReqVO) {
|
||||
RoleDO role = BeanUtils.toBean(updateReqVO, RoleDO.class);
|
||||
roleMapper.updateById(role);
|
||||
return role;
|
||||
}
|
||||
```
|
||||
|
||||
可以把它们简单记成:
|
||||
|
||||
1. `@Cacheable`:查的时候用,没有缓存才执行方法
|
||||
2. `@CacheEvict`:改或删的时候用,执行后清缓存
|
||||
3. `@CachePut`:改的时候用,执行后直接刷新缓存
|
||||
|
||||
## 注意事项
|
||||
|
||||
1. `RedisTemplate<String, Object>` 使用 JSON 序列化,适合对象缓存,但跨版本变更字段时仍应关注兼容性。
|
||||
2. `LocalDateTime` 已做序列化兼容处理,但复杂对象结构仍应关注 Jackson 序列化结果。
|
||||
3. `cacheName#ttl` 是这个模块扩展出来的约定,不是 Spring Cache 默认语法。
|
||||
4. 通过 `cacheName#ttl` 设置过期时间时,TTL 是针对这个缓存名生效的,不是针对单条 Key 动态计算。
|
||||
5. `RdmsCacheAutoConfiguration` 将 Cache Key 前缀改成了单冒号 `:` 风格,如果已有依赖默认 `::` 的脚本或习惯,需要注意。
|
||||
6. 这个模块本身主要解决“接入和约定”问题,不提供大量 Redis 业务工具方法;复杂 Redis 场景通常还是由业务模块或上层 starter 自己封装。
|
||||
|
||||
## 总结
|
||||
这个模块的核心价值,不是简单引入 Redis 依赖,而是统一项目里的 Redis 使用方式:
|
||||
|
||||
1. 统一 `RedisTemplate` 的序列化规则
|
||||
2. 统一 Spring Cache 的 Redis 落地方式
|
||||
3. 提供按缓存名声明 TTL 的扩展能力
|
||||
4. 为 Redisson 相关的上层能力提供基础环境
|
||||
|
||||
如果把它理解成一个“Redis 接入基座 + Cache 约定基座”的 starter,这个模块的设计会比较清晰。
|
||||
40
rdms-framework/rdms-spring-boot-starter-redis/pom.xml
Normal file
40
rdms-framework/rdms-spring-boot-starter-redis/pom.xml
Normal file
@@ -0,0 +1,40 @@
|
||||
<?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.njcn</groupId>
|
||||
<artifactId>rdms-framework</artifactId>
|
||||
<version>${revision}</version>
|
||||
</parent>
|
||||
<artifactId>rdms-spring-boot-starter-redis</artifactId>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<name>${project.artifactId}</name>
|
||||
<description>Redis 封装拓展</description>
|
||||
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>com.njcn</groupId>
|
||||
<artifactId>rdms-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>
|
||||
@@ -0,0 +1,82 @@
|
||||
package com.njcn.rdms.framework.redis.config;
|
||||
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import com.njcn.rdms.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 com.njcn.rdms.framework.redis.config.RdmsRedisAutoConfiguration.buildRedisSerializer;
|
||||
|
||||
/**
|
||||
* Cache 配置类,基于 Redis 实现
|
||||
*/
|
||||
@AutoConfiguration
|
||||
@EnableConfigurationProperties({CacheProperties.class, RdmsCacheProperties.class})
|
||||
@EnableCaching
|
||||
public class RdmsCacheAutoConfiguration {
|
||||
|
||||
/**
|
||||
* 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/rdms-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,
|
||||
RdmsCacheProperties rdmsCacheProperties) {
|
||||
// 创建 RedisCacheWriter 对象
|
||||
RedisConnectionFactory connectionFactory = Objects.requireNonNull(redisTemplate.getConnectionFactory());
|
||||
RedisCacheWriter cacheWriter = RedisCacheWriter.nonLockingRedisCacheWriter(connectionFactory,
|
||||
BatchStrategies.scan(rdmsCacheProperties.getRedisScanBatchSize()));
|
||||
// 创建 TenantRedisCacheManager 对象
|
||||
return new TimeoutRedisCacheManager(cacheWriter, redisCacheConfiguration);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package com.njcn.rdms.framework.redis.config;
|
||||
|
||||
import lombok.Data;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
|
||||
/**
|
||||
* Cache 配置项
|
||||
*
|
||||
* @author Wanwan
|
||||
*/
|
||||
@ConfigurationProperties("rdms.cache")
|
||||
@Data
|
||||
@Validated
|
||||
public class RdmsCacheProperties {
|
||||
|
||||
/**
|
||||
* {@link #redisScanBatchSize} 默认值
|
||||
*/
|
||||
private static final Integer REDIS_SCAN_BATCH_SIZE_DEFAULT = 30;
|
||||
|
||||
/**
|
||||
* redis scan 一次返回数量
|
||||
*/
|
||||
private Integer redisScanBatchSize = REDIS_SCAN_BATCH_SIZE_DEFAULT;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
package com.njcn.rdms.framework.redis.config;
|
||||
|
||||
import cn.hutool.core.util.ReflectUtil;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
|
||||
import org.redisson.spring.starter.RedissonAutoConfigurationV2;
|
||||
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(before = RedissonAutoConfigurationV2.class) // 目的:使用自己定义的 RedisTemplate Bean
|
||||
public class RdmsRedisAutoConfiguration {
|
||||
|
||||
/**
|
||||
* 创建 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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
package com.njcn.rdms.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 hongawen
|
||||
*/
|
||||
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) {
|
||||
// 移除 # 后面的 : 以及后面的内容,避免影响解析
|
||||
String ttlStr = StrUtil.subBefore(names[1], StrUtil.COLON, false); // 获得 ttlStr 时间部分
|
||||
names[1] = StrUtil.subAfter(names[1], ttlStr, false); // 移除掉 ttlStr 时间部分
|
||||
// 解析时间
|
||||
Duration duration = parseDuration(ttlStr);
|
||||
cacheConfig = cacheConfig.entryTtl(duration);
|
||||
}
|
||||
|
||||
// 创建 RedisCache 对象,需要忽略掉 ttlStr
|
||||
return super.createRedisCache(names[0] + names[1], 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));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
com.njcn.rdms.framework.redis.config.RdmsRedisAutoConfiguration
|
||||
com.njcn.rdms.framework.redis.config.RdmsCacheAutoConfiguration
|
||||
Reference in New Issue
Block a user