feat: 实现完整的 RBAC 权限管理系统与基础设施增强

在初始认证基础上,新增完整的 RBAC 权限模型(角色、权限、菜单三级管理),
  集成审计日志、接口限流、登录失败锁定、Refresh Token 机制、Redis 分布式缓存与锁、
  RocketMQ 消息队列,并引入 Flyway 数据库版本管理,同时补充项目文档与使用示例
This commit is contained in:
2026-04-10 10:58:22 +08:00
parent 3a9bf61839
commit 40c85c3c1f
97 changed files with 13434 additions and 351 deletions

View File

@@ -0,0 +1,762 @@
package com.aisi.template.utils;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import java.time.Duration;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;
/**
* Redis 缓存工具类
* 提供各种场景下的缓存操作方法
*
* 使用场景:
* 1. 对象缓存:缓存实体对象,减少数据库查询
* 2. 列表缓存:缓存列表数据
* 3. 集合缓存:缓存去重数据
* 4. 哈希缓存:缓存对象字段
* 5. 计数器:文章阅读数、点赞数等
*
* @author Claude
* @since 2024-04-09
*/
@Slf4j
@Component
@RequiredArgsConstructor
public class RedisCache {
/**
* Redis 模板
*/
private final RedisTemplate<String, Object> redisTemplate;
// ==================== 1. String 类型操作 ====================
/**
* 设置缓存(永不过期)
* 注意:生产环境建议设置过期时间,防止内存溢出
*
* @param key 键
* @param value 值
*/
public void set(String key, Object value) {
try {
redisTemplate.opsForValue().set(key, value);
log.debug("设置缓存成功 - key: {}", key);
} catch (Exception e) {
log.error("设置缓存失败 - key: {}", key, e);
}
}
/**
* 设置缓存(指定过期时间)
* 步骤:
* 1. 将值序列化后存入 Redis
* 2. 设置过期时间
*
* @param key 键
* @param value 值
* @param timeout 过期时间
* @param timeUnit 时间单位
*/
public void set(String key, Object value, long timeout, TimeUnit timeUnit) {
try {
redisTemplate.opsForValue().set(key, value, timeout, timeUnit);
log.debug("设置缓存成功 - key: {}, timeout: {} {}", key, timeout, timeUnit);
} catch (Exception e) {
log.error("设置缓存失败 - key: {}", key, e);
}
}
/**
* 设置缓存(使用 Duration
*
* @param key 键
* @param value 值
* @param duration 过期时间
*/
public void set(String key, Object value, Duration duration) {
try {
redisTemplate.opsForValue().set(key, value, duration);
log.debug("设置缓存成功 - key: {}, duration: {}", key, duration);
} catch (Exception e) {
log.error("设置缓存失败 - key: {}", key, e);
}
}
/**
* 获取缓存
* 步骤:
* 1. 从 Redis 获取值
* 2. 反序列化为对象
*
* @param key 键
* @return 值,不存在返回 null
*/
public Object get(String key) {
try {
Object value = redisTemplate.opsForValue().get(key);
log.debug("获取缓存 - key: {}, found: {}", key, value != null);
return value;
} catch (Exception e) {
log.error("获取缓存失败 - key: {}", key, e);
return null;
}
}
/**
* 获取缓存(指定类型)
*
* @param key 键
* @param type 返回值类型
* @return 值
*/
@SuppressWarnings("unchecked")
public <T> T get(String key, Class<T> type) {
try {
Object value = redisTemplate.opsForValue().get(key);
if (value != null && type.isInstance(value)) {
return (T) value;
}
return null;
} catch (Exception e) {
log.error("获取缓存失败 - key: {}", key, e);
return null;
}
}
/**
* 删除缓存
*
* @param key 键
* @return 是否删除成功
*/
public boolean delete(String key) {
try {
Boolean result = redisTemplate.delete(key);
log.debug("删除缓存 - key: {}, result: {}", key, result);
return Boolean.TRUE.equals(result);
} catch (Exception e) {
log.error("删除缓存失败 - key: {}", key, e);
return false;
}
}
/**
* 批量删除缓存
* 步骤:
* 1. 遍历所有键
* 2. 一次性删除
*
* @param keys 键集合
* @return 删除的数量
*/
public long delete(Collection<String> keys) {
try {
Long count = redisTemplate.delete(keys);
log.debug("批量删除缓存 - count: {}", count);
return count != null ? count : 0;
} catch (Exception e) {
log.error("批量删除缓存失败", e);
return 0;
}
}
/**
* 判断键是否存在
*
* @param key 键
* @return 是否存在
*/
public boolean hasKey(String key) {
try {
Boolean result = redisTemplate.hasKey(key);
return Boolean.TRUE.equals(result);
} catch (Exception e) {
log.error("判断键是否存在失败 - key: {}", key, e);
return false;
}
}
/**
* 设置过期时间
*
* @param key 键
* @param timeout 过期时间
* @param unit 时间单位
* @return 是否设置成功
*/
public boolean expire(String key, long timeout, TimeUnit unit) {
try {
Boolean result = redisTemplate.expire(key, timeout, unit);
return Boolean.TRUE.equals(result);
} catch (Exception e) {
log.error("设置过期时间失败 - key: {}", key, e);
return false;
}
}
/**
* 获取过期时间
*
* @param key 键
* @param unit 时间单位
* @return 剩余过期时间,-1 表示永不过期,-2 表示键不存在
*/
public long getExpire(String key, TimeUnit unit) {
try {
Long expire = redisTemplate.getExpire(key, unit);
return expire != null ? expire : -2;
} catch (Exception e) {
log.error("获取过期时间失败 - key: {}", key, e);
return -2;
}
}
/**
* 自增操作(原子性)
* 使用场景:计数器、点赞数、阅读数等
* 步骤:
* 1. 原子性自增
* 2. 返回自增后的值
*
* @param key 键
* @param delta 增量
* @return 自增后的值
*/
public long increment(String key, long delta) {
try {
Long result = redisTemplate.opsForValue().increment(key, delta);
log.debug("自增操作 - key: {}, delta: {}, result: {}", key, delta, result);
return result != null ? result : 0;
} catch (Exception e) {
log.error("自增操作失败 - key: {}", key, e);
return 0;
}
}
/**
* 自减操作(原子性)
*
* @param key 键
* @param delta 减量
* @return 自减后的值
*/
public long decrement(String key, long delta) {
try {
Long result = redisTemplate.opsForValue().decrement(key, delta);
log.debug("自减操作 - key: {}, delta: {}, result: {}", key, delta, result);
return result != null ? result : 0;
} catch (Exception e) {
log.error("自减操作失败 - key: {}", key, e);
return 0;
}
}
// ==================== 2. Hash 类型操作 ====================
/**
* 设置哈希字段
* 使用场景:缓存对象的单个字段
* 步骤:
* 1. 将字段和值存入哈希表
* 2. 适合部分更新对象字段
*
* @param key 键
* @param field 字段
* @param value 值
*/
public void hSet(String key, String field, Object value) {
try {
redisTemplate.opsForHash().put(key, field, value);
log.debug("设置哈希字段 - key: {}, field: {}", key, field);
} catch (Exception e) {
log.error("设置哈希字段失败 - key: {}, field: {}", key, field, e);
}
}
/**
* 获取哈希字段
*
* @param key 键
* @param field 字段
* @return 值
*/
public Object hGet(String key, String field) {
try {
return redisTemplate.opsForHash().get(key, field);
} catch (Exception e) {
log.error("获取哈希字段失败 - key: {}, field: {}", key, field, e);
return null;
}
}
/**
* 批量设置哈希字段
*
* @param key 键
* @param map 字段-值映射
*/
public void hSetAll(String key, Map<String, Object> map) {
try {
redisTemplate.opsForHash().putAll(key, map);
log.debug("批量设置哈希字段 - key: {}", key);
} catch (Exception e) {
log.error("批量设置哈希字段失败 - key: {}", key, e);
}
}
/**
* 获取所有哈希字段
*
* @param key 键
* @return 字段-值映射
*/
public Map<Object, Object> hGetAll(String key) {
try {
return redisTemplate.opsForHash().entries(key);
} catch (Exception e) {
log.error("获取所有哈希字段失败 - key: {}", key, e);
return Map.of();
}
}
/**
* 删除哈希字段
*
* @param key 键
* @param fields 字段集合
* @return 删除的数量
*/
public long hDelete(String key, Object... fields) {
try {
return redisTemplate.opsForHash().delete(key, fields);
} catch (Exception e) {
log.error("删除哈希字段失败 - key: {}", key, e);
return 0;
}
}
/**
* 判断哈希字段是否存在
*
* @param key 键
* @param field 字段
* @return 是否存在
*/
public boolean hExists(String key, String field) {
try {
return redisTemplate.opsForHash().hasKey(key, field);
} catch (Exception e) {
log.error("判断哈希字段是否存在失败 - key: {}, field: {}", key, field, e);
return false;
}
}
/**
* 哈希字段自增
*
* @param key 键
* @param field 字段
* @param delta 增量
* @return 自增后的值
*/
public long hIncrement(String key, String field, long delta) {
try {
return redisTemplate.opsForHash().increment(key, field, delta);
} catch (Exception e) {
log.error("哈希字段自增失败 - key: {}, field: {}", key, field, e);
return 0;
}
}
// ==================== 3. List 类型操作 ====================
/**
* 从左侧推入列表
* 使用场景:消息队列、最新消息列表
* 步骤:
* 1. 将元素推入列表左侧
* 2. 返回当前列表长度
*
* @param key 键
* @param value 值
* @return 推入后的列表长度
*/
public long lLeftPush(String key, Object value) {
try {
Long size = redisTemplate.opsForList().leftPush(key, value);
log.debug("从左侧推入列表 - key: {}, size: {}", key, size);
return size != null ? size : 0;
} catch (Exception e) {
log.error("从左侧推入列表失败 - key: {}", key, e);
return 0;
}
}
/**
* 从右侧推入列表
*
* @param key 键
* @param value 值
* @return 推入后的列表长度
*/
public long lRightPush(String key, Object value) {
try {
Long size = redisTemplate.opsForList().rightPush(key, value);
log.debug("从右侧推入列表 - key: {}, size: {}", key, size);
return size != null ? size : 0;
} catch (Exception e) {
log.error("从右侧推入列表失败 - key: {}", key, e);
return 0;
}
}
/**
* 从左侧弹出列表元素
*
* @param key 键
* @return 弹出的元素
*/
public Object lLeftPop(String key) {
try {
return redisTemplate.opsForList().leftPop(key);
} catch (Exception e) {
log.error("从左侧弹出列表元素失败 - key: {}", key, e);
return null;
}
}
/**
* 从右侧弹出列表元素
*
* @param key 键
* @return 弹出的元素
*/
public Object lRightPop(String key) {
try {
return redisTemplate.opsForList().rightPop(key);
} catch (Exception e) {
log.error("从右侧弹出列表元素失败 - key: {}", key, e);
return null;
}
}
/**
* 获取列表范围
* 使用场景:分页查询列表数据
* 步骤:
* 1. 获取指定范围的元素
* 2. 支持负索引(-1 表示最后一个元素)
*
* @param key 键
* @param start 开始索引
* @param end 结束索引
* @return 元素列表
*/
public List<Object> lRange(String key, long start, long end) {
try {
return redisTemplate.opsForList().range(key, start, end);
} catch (Exception e) {
log.error("获取列表范围失败 - key: {}", key, e);
return List.of();
}
}
/**
* 获取列表长度
*
* @param key 键
* @return 列表长度
*/
public long lSize(String key) {
try {
Long size = redisTemplate.opsForList().size(key);
return size != null ? size : 0;
} catch (Exception e) {
log.error("获取列表长度失败 - key: {}", key, e);
return 0;
}
}
/**
* 移除列表元素
*
* @param key 键
* @param count 移除数量(>0 从左往右,<0 从右往左,=0 全部)
* @param value 要移除的值
* @return 实际移除的数量
*/
public long lRemove(String key, long count, Object value) {
try {
Long removed = redisTemplate.opsForList().remove(key, count, value);
return removed != null ? removed : 0;
} catch (Exception e) {
log.error("移除列表元素失败 - key: {}", key, e);
return 0;
}
}
// ==================== 4. Set 类型操作 ====================
/**
* 添加到集合
* 使用场景:标签系统、共同好友、去重
* 步骤:
* 1. 添加元素到集合
* 2. 自动去重
*
* @param key 键
* @param values 值集合
* @return 添加的元素数量(不包含已存在的)
*/
public long sAdd(String key, Object... values) {
try {
Long count = redisTemplate.opsForSet().add(key, values);
log.debug("添加到集合 - key: {}, count: {}", key, count);
return count != null ? count : 0;
} catch (Exception e) {
log.error("添加到集合失败 - key: {}", key, e);
return 0;
}
}
/**
* 获取集合所有元素
*
* @param key 键
* @return 元素集合
*/
public Set<Object> sMembers(String key) {
try {
return redisTemplate.opsForSet().members(key);
} catch (Exception e) {
log.error("获取集合元素失败 - key: {}", key, e);
return Set.of();
}
}
/**
* 判断元素是否在集合中
*
* @param key 键
* @param value 值
* @return 是否存在
*/
public boolean sIsMember(String key, Object value) {
try {
Boolean result = redisTemplate.opsForSet().isMember(key, value);
return Boolean.TRUE.equals(result);
} catch (Exception e) {
log.error("判断元素是否在集合中失败 - key: {}", key, e);
return false;
}
}
/**
* 获取集合大小
*
* @param key 键
* @return 集合大小
*/
public long sSize(String key) {
try {
Long size = redisTemplate.opsForSet().size(key);
return size != null ? size : 0;
} catch (Exception e) {
log.error("获取集合大小失败 - key: {}", key, e);
return 0;
}
}
/**
* 移除集合元素
*
* @param key 键
* @param values 要移除的值
* @return 实际移除的数量
*/
public long sRemove(String key, Object... values) {
try {
Long count = redisTemplate.opsForSet().remove(key, values);
return count != null ? count : 0;
} catch (Exception e) {
log.error("移除集合元素失败 - key: {}", key, e);
return 0;
}
}
/**
* 集合交集
* 使用场景:共同好友、共同标签
* 步骤:
* 1. 获取多个集合的交集
* 2. 返回共有的元素
*
* @param key1 第一个集合键
* @param key2 第二个集合键
* @return 交集元素
*/
public Set<Object> sIntersect(String key1, String key2) {
try {
return redisTemplate.opsForSet().intersect(key1, key2);
} catch (Exception e) {
log.error("获取集合交集失败 - key1: {}, key2: {}", key1, key2, e);
return Set.of();
}
}
/**
* 集合并集
*
* @param key1 第一个集合键
* @param key2 第二个集合键
* @return 并集元素
*/
public Set<Object> sUnion(String key1, String key2) {
try {
return redisTemplate.opsForSet().union(key1, key2);
} catch (Exception e) {
log.error("获取集合并集失败 - key1: {}, key2: {}", key1, key2, e);
return Set.of();
}
}
// ==================== 5. ZSet 类型操作(有序集合)====================
/**
* 添加到有序集合
* 使用场景:排行榜、权重队列
* 步骤:
* 1. 添加元素及分数
* 2. 按分数自动排序
*
* @param key 键
* @param value 值
* @param score 分数
* @return 添加是否成功(新增返回 true更新返回 false
*/
public boolean zAdd(String key, Object value, double score) {
try {
Boolean result = redisTemplate.opsForZSet().add(key, value, score);
log.debug("添加到有序集合 - key: {}, value: {}, score: {}", key, value, score);
return Boolean.TRUE.equals(result);
} catch (Exception e) {
log.error("添加到有序集合失败 - key: {}", key, e);
return false;
}
}
/**
* 获取有序集合范围(按分数)
* 使用场景:排行榜分页查询
* 步骤:
* 1. 获取指定分数范围的元素
* 2. 按分数升序排列
*
* @param key 键
* @param min 最小分数
* @param max 最大分数
* @return 元素集合
*/
public Set<Object> zRangeByScore(String key, double min, double max) {
try {
return redisTemplate.opsForZSet().rangeByScore(key, min, max);
} catch (Exception e) {
log.error("获取有序集合范围失败 - key: {}", key, e);
return Set.of();
}
}
/**
* 获取有序集合排名(倒序)
* 使用场景:排行榜查询(分数高的排名靠前)
* 步骤:
* 1. 获取元素的排名从0开始
* 2. 按分数倒序排列
*
* @param key 键
* @param value 值
* @return 排名,不存在返回 null
*/
public Long zReverseRank(String key, Object value) {
try {
return redisTemplate.opsForZSet().reverseRank(key, value);
} catch (Exception e) {
log.error("获取有序集合排名失败 - key: {}", key, e);
return null;
}
}
/**
* 获取元素分数
*
* @param key 键
* @param value 值
* @return 分数
*/
public Double zScore(String key, Object value) {
try {
return redisTemplate.opsForZSet().score(key, value);
} catch (Exception e) {
log.error("获取元素分数失败 - key: {}", key, e);
return null;
}
}
/**
* 增加元素分数
*
* @param key 键
* @param value 值
* @param delta 增量
* @return 增加后的分数
*/
public Double zIncrementScore(String key, Object value, double delta) {
try {
return redisTemplate.opsForZSet().incrementScore(key, value, delta);
} catch (Exception e) {
log.error("增加元素分数失败 - key: {}", key, e);
return null;
}
}
/**
* 移除有序集合元素
*
* @param key 键
* @param values 要移除的值
* @return 实际移除的数量
*/
public long zRemove(String key, Object... values) {
try {
Long count = redisTemplate.opsForZSet().remove(key, values);
return count != null ? count : 0;
} catch (Exception e) {
log.error("移除有序集合元素失败 - key: {}", key, e);
return 0;
}
}
/**
* 获取有序集合大小
*
* @param key 键
* @return 集合大小
*/
public long zSize(String key) {
try {
Long size = redisTemplate.opsForZSet().size(key);
return size != null ? size : 0;
} catch (Exception e) {
log.error("获取有序集合大小失败 - key: {}", key, e);
return 0;
}
}
}