feat: 实现完整的 RBAC 权限管理系统与基础设施增强
在初始认证基础上,新增完整的 RBAC 权限模型(角色、权限、菜单三级管理), 集成审计日志、接口限流、登录失败锁定、Refresh Token 机制、Redis 分布式缓存与锁、 RocketMQ 消息队列,并引入 Flyway 数据库版本管理,同时补充项目文档与使用示例
This commit is contained in:
@@ -8,37 +8,167 @@ import org.springframework.stereotype.Component;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.Key;
|
||||
import java.util.Date;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* JWT 工具类
|
||||
* 用于生成和验证 JWT Token
|
||||
*
|
||||
* 主要功能:
|
||||
* 1. Token 生成:生成包含用户信息的 JWT Token
|
||||
* 2. Token 解析:解析 Token 并提取信息
|
||||
* 3. Token 验证:验证 Token 签名和有效期
|
||||
*
|
||||
* Token 结构:
|
||||
* - Header:算法类型和 Token 类型
|
||||
* - Payload:用户信息、签发时间、过期时间、jti(JWT ID)
|
||||
* - Signature:使用密钥签名的签名部分
|
||||
*
|
||||
* @author Claude
|
||||
* @since 2024-04-09
|
||||
*/
|
||||
@Component
|
||||
public class JwtUtil {
|
||||
|
||||
/**
|
||||
* JWT 密钥
|
||||
* 从配置文件读取
|
||||
* HS512 算法要求密钥至少 64 字符(512 位)
|
||||
*/
|
||||
@Value("${jwt.secret}")
|
||||
private String secret;
|
||||
|
||||
private final long expiration = 1000 * 60 * 60 *24; // 24小时
|
||||
/**
|
||||
* Access Token 过期时间(秒)
|
||||
* 从配置文件读取,默认 3600 秒(1 小时)
|
||||
*/
|
||||
@Value("${jwt.access-token-expiration:3600}") // Default 1 hour
|
||||
private long accessTokenExpirationSeconds;
|
||||
|
||||
/**
|
||||
* 获取签名密钥
|
||||
* 说明:
|
||||
* - HS512 算法要求密钥至少 512 位(64 字节)
|
||||
* - 如果密钥不足 64 字符,抛出异常
|
||||
*
|
||||
* @return 签名密钥
|
||||
*/
|
||||
private Key getSigningKey() {
|
||||
// HS512 要求密钥至少 512 位 = 64 字节
|
||||
if (secret.length() < 64) {
|
||||
throw new IllegalArgumentException("JWT secret must be at least 64 characters long for HS512");
|
||||
throw new IllegalArgumentException("JWT 密钥必须至少 64 个字符(HS512 算法要求)");
|
||||
}
|
||||
return Keys.hmacShaKeyFor(secret.getBytes(StandardCharsets.UTF_8));
|
||||
}
|
||||
|
||||
public String generateToken(Long userId,String username) {
|
||||
/**
|
||||
* 为用户生成 Access Token
|
||||
* 步骤:
|
||||
* 1. 生成唯一的 jti(JWT ID)
|
||||
* 2. 设置用户信息(用户名、用户ID)
|
||||
* 3. 设置签发时间和过期时间
|
||||
* 4. 使用密钥签名
|
||||
*
|
||||
* @param userId 用户ID
|
||||
* @param username 用户名
|
||||
* @return JWT Token
|
||||
*/
|
||||
public String generateToken(Long userId, String username) {
|
||||
return generateToken(userId, username, accessTokenExpirationSeconds);
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成自定义过期时间的 Token
|
||||
* 步骤:
|
||||
* 1. 生成唯一的 jti(JWT ID),用于黑名单追踪
|
||||
* 2. 计算过期时间(毫秒)
|
||||
* 3. 构建 Token
|
||||
*
|
||||
* @param userId 用户ID
|
||||
* @param username 用户名
|
||||
* @param expirationSeconds 过期时间(秒)
|
||||
* @return JWT Token
|
||||
*/
|
||||
public String generateToken(Long userId, String username, long expirationSeconds) {
|
||||
// 1. 生成唯一的 jti(JWT ID)
|
||||
String jti = UUID.randomUUID().toString();
|
||||
long expirationMillis = expirationSeconds * 1000;
|
||||
|
||||
// 2. 构建 Token
|
||||
return Jwts.builder()
|
||||
.setSubject(username)
|
||||
.claim("id", userId)
|
||||
.setIssuedAt(new Date())
|
||||
.setExpiration(new Date(System.currentTimeMillis() + expiration))
|
||||
.signWith(getSigningKey(), SignatureAlgorithm.HS512)
|
||||
.setSubject(username) // 主题:用户名
|
||||
.claim("id", userId) // 自定义声明:用户ID
|
||||
.claim("jti", jti) // 自定义声明:JWT ID(用于黑名单)
|
||||
.setIssuedAt(new Date()) // 签发时间:当前时间
|
||||
.setExpiration(new Date(System.currentTimeMillis() + expirationMillis)) // 过期时间
|
||||
.signWith(getSigningKey(), SignatureAlgorithm.HS512) // 签名算法和密钥
|
||||
.compact();
|
||||
}
|
||||
|
||||
/**
|
||||
* 从 Token 中提取用户名
|
||||
*
|
||||
* @param token JWT Token
|
||||
* @return 用户名
|
||||
*/
|
||||
public String extractUsername(String token) {
|
||||
return parseClaims(token).getBody().getSubject();
|
||||
}
|
||||
|
||||
/**
|
||||
* 从 Token 中提取用户ID
|
||||
*
|
||||
* @param token JWT Token
|
||||
* @return 用户ID
|
||||
*/
|
||||
public Long extractUserId(String token) {
|
||||
Claims claims = parseClaims(token).getBody();
|
||||
return claims.get("id", Long.class);
|
||||
}
|
||||
|
||||
/**
|
||||
* 从 Token 中提取 JWT ID(jti)
|
||||
* 说明:
|
||||
* - jti 是 JWT 的唯一标识
|
||||
* - 用于将 Token 加入黑名单(登出功能)
|
||||
*
|
||||
* @param token JWT Token
|
||||
* @return JWT ID
|
||||
*/
|
||||
public String extractJti(String token) {
|
||||
Claims claims = parseClaims(token).getBody();
|
||||
return claims.get("jti", String.class);
|
||||
}
|
||||
|
||||
/**
|
||||
* 从 Token 中提取过期时间
|
||||
*
|
||||
* @param token JWT Token
|
||||
* @return 过期时间
|
||||
*/
|
||||
public Date extractExpiration(String token) {
|
||||
return parseClaims(token).getBody().getExpiration();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 Access Token 的过期时间(秒)
|
||||
*
|
||||
* @return 过期时间(秒)
|
||||
*/
|
||||
public long getExpirationSeconds() {
|
||||
return accessTokenExpirationSeconds;
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证 Token 签名和结构
|
||||
* 说明:
|
||||
* - 验证签名是否正确
|
||||
* - 验证 Token 结构是否完整
|
||||
* - 不验证过期时间
|
||||
*
|
||||
* @param token JWT Token
|
||||
* @return true 表示 Token 有效
|
||||
*/
|
||||
public boolean validateToken(String token) {
|
||||
try {
|
||||
parseClaims(token);
|
||||
@@ -48,6 +178,31 @@ public class JwtUtil {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查 Token 是否过期
|
||||
*
|
||||
* @param token JWT Token
|
||||
* @return true 表示 Token 已过期
|
||||
*/
|
||||
public boolean isTokenExpired(String token) {
|
||||
try {
|
||||
Date expiration = extractExpiration(token);
|
||||
return expiration.before(new Date());
|
||||
} catch (JwtException e) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析并验证 JWT Token
|
||||
* 说明:
|
||||
* - 验证签名
|
||||
* - 解析 Claims
|
||||
*
|
||||
* @param token JWT Token
|
||||
* @return 解析后的 JWT 对象(包含 Claims)
|
||||
* @throws JwtException 如果 Token 无效
|
||||
*/
|
||||
private Jws<Claims> parseClaims(String token) {
|
||||
return Jwts.parserBuilder()
|
||||
.setSigningKey(getSigningKey())
|
||||
|
||||
762
src/main/java/com/aisi/template/utils/RedisCache.java
Normal file
762
src/main/java/com/aisi/template/utils/RedisCache.java
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
301
src/main/java/com/aisi/template/utils/RedisLock.java
Normal file
301
src/main/java/com/aisi/template/utils/RedisLock.java
Normal file
@@ -0,0 +1,301 @@
|
||||
package com.aisi.template.utils;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.data.redis.core.RedisTemplate;
|
||||
import org.springframework.data.redis.core.script.RedisScript;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* Redis 分布式锁工具类
|
||||
* 基于 Redis SETNX + EXPIRE 实现,支持可重入锁
|
||||
*
|
||||
* 使用场景:
|
||||
* 1. 防止重复提交(如表单重复提交)
|
||||
* 2. 库存扣减(防止超卖)
|
||||
* 3. 定时任务分布式执行(防止重复执行)
|
||||
* 4. 限流场景
|
||||
*
|
||||
* @author Claude
|
||||
* @since 2024-04-09
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
public class RedisLock {
|
||||
|
||||
/**
|
||||
* Redis 模板
|
||||
*/
|
||||
private final RedisTemplate<String, Object> redisTemplate;
|
||||
|
||||
/**
|
||||
* 锁前缀
|
||||
*/
|
||||
private static final String LOCK_PREFIX = "lock:";
|
||||
|
||||
/**
|
||||
* 锁的值前缀
|
||||
*/
|
||||
private static final String LOCK_VALUE_PREFIX = "uuid:";
|
||||
|
||||
/**
|
||||
* 默认锁过期时间(秒)
|
||||
*/
|
||||
private static final long DEFAULT_EXPIRE_TIME = 30;
|
||||
|
||||
/**
|
||||
* 获取锁的 Lua 脚本
|
||||
* SETNX + EXPIRE 原子操作
|
||||
*/
|
||||
private static final String LOCK_SCRIPT =
|
||||
"if redis.call('setnx', KEYS[1], ARGV[1]) == 1 then " +
|
||||
"redis.call('expire', KEYS[1], ARGV[2]) " +
|
||||
"return 1 " +
|
||||
"else " +
|
||||
"return 0 " +
|
||||
"end";
|
||||
|
||||
/**
|
||||
* 释放锁的 Lua 脚本
|
||||
* 确保只有锁的持有者才能释放锁
|
||||
*/
|
||||
private static final String UNLOCK_SCRIPT =
|
||||
"if redis.call('get', KEYS[1]) == ARGV[1] then " +
|
||||
"return redis.call('del', KEYS[1]) " +
|
||||
"else " +
|
||||
"return 0 " +
|
||||
"end";
|
||||
|
||||
/**
|
||||
* 尝试获取锁
|
||||
* 步骤:
|
||||
* 1. 生成唯一的锁标识(UUID)
|
||||
* 2. 执行 Lua 脚本尝试获取锁
|
||||
* 3. 如果获取成功,返回锁标识;否则返回 null
|
||||
*
|
||||
* @param lockKey 锁的键名
|
||||
* @return 锁标识,如果获取失败返回 null
|
||||
*/
|
||||
public String tryLock(String lockKey) {
|
||||
return tryLock(lockKey, DEFAULT_EXPIRE_TIME);
|
||||
}
|
||||
|
||||
/**
|
||||
* 尝试获取锁(指定过期时间)
|
||||
* 步骤:
|
||||
* 1. 生成唯一的锁标识(UUID)
|
||||
* 2. 执行 Lua 脚本尝试获取锁,并设置过期时间
|
||||
* 3. 如果获取成功,返回锁标识;否则返回 null
|
||||
*
|
||||
* @param lockKey 锁的键名
|
||||
* @param expireTime 锁的过期时间(秒)
|
||||
* @return 锁标识,如果获取失败返回 null
|
||||
*/
|
||||
public String tryLock(String lockKey, long expireTime) {
|
||||
// 1. 生成唯一的锁标识
|
||||
String lockValue = LOCK_VALUE_PREFIX + UUID.randomUUID().toString();
|
||||
String fullLockKey = LOCK_PREFIX + lockKey;
|
||||
|
||||
try {
|
||||
// 2. 执行 Lua 脚本获取锁
|
||||
// KEYS[1]: 锁的完整键名
|
||||
// ARGV[1]: 锁的值(UUID)
|
||||
// ARGV[2]: 过期时间(秒)
|
||||
Long result = redisTemplate.execute(
|
||||
RedisScript.of(LOCK_SCRIPT, Long.class),
|
||||
Collections.singletonList(fullLockKey),
|
||||
lockValue,
|
||||
String.valueOf(expireTime)
|
||||
);
|
||||
|
||||
// 3. 判断是否获取成功
|
||||
if (result != null && result == 1) {
|
||||
log.info("获取锁成功 - lockKey: {}, lockValue: {}", lockKey, lockValue);
|
||||
return lockValue;
|
||||
} else {
|
||||
log.warn("获取锁失败 - lockKey: {}", lockKey);
|
||||
return null;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("获取锁异常 - lockKey: {}", lockKey, e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 尝试获取锁(带等待时间)
|
||||
* 步骤:
|
||||
* 1. 在等待时间内循环尝试获取锁
|
||||
* 2. 每次间隔 100 毫秒重试
|
||||
* 3. 超过等待时间则放弃
|
||||
*
|
||||
* @param lockKey 锁的键名
|
||||
* @param expireTime 锁的过期时间(秒)
|
||||
* @param waitTime 等待时间(毫秒)
|
||||
* @return 锁标识,如果获取失败返回 null
|
||||
*/
|
||||
public String tryLock(String lockKey, long expireTime, long waitTime) {
|
||||
long startTime = System.currentTimeMillis();
|
||||
long timeout = startTime + waitTime;
|
||||
String lockValue = null;
|
||||
|
||||
// 1. 在等待时间内循环尝试
|
||||
while (System.currentTimeMillis() < timeout) {
|
||||
// 2. 尝试获取锁
|
||||
lockValue = tryLock(lockKey, expireTime);
|
||||
if (lockValue != null) {
|
||||
return lockValue;
|
||||
}
|
||||
|
||||
// 3. 短暂休眠后重试
|
||||
try {
|
||||
TimeUnit.MILLISECONDS.sleep(100);
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
log.warn("获取锁超时 - lockKey: {}, waitTime: {}ms", lockKey, waitTime);
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 释放锁
|
||||
* 步骤:
|
||||
* 1. 验证锁的值是否匹配(确保只有持有者才能释放)
|
||||
* 2. 如果匹配则删除锁,否则不做操作
|
||||
*
|
||||
* @param lockKey 锁的键名
|
||||
* @param lockValue 锁的标识(tryLock 返回的值)
|
||||
* @return 是否释放成功
|
||||
*/
|
||||
public boolean unlock(String lockKey, String lockValue) {
|
||||
if (lockValue == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
String fullLockKey = LOCK_PREFIX + lockKey;
|
||||
|
||||
try {
|
||||
// 1. 执行 Lua 脚本释放锁
|
||||
// KEYS[1]: 锁的完整键名
|
||||
// ARGV[1]: 锁的值(用于验证)
|
||||
Long result = redisTemplate.execute(
|
||||
RedisScript.of(UNLOCK_SCRIPT, Long.class),
|
||||
Collections.singletonList(fullLockKey),
|
||||
lockValue
|
||||
);
|
||||
|
||||
// 2. 判断是否释放成功
|
||||
if (result != null && result == 1) {
|
||||
log.info("释放锁成功 - lockKey: {}, lockValue: {}", lockKey, lockValue);
|
||||
return true;
|
||||
} else {
|
||||
log.warn("释放锁失败,锁不存在或已过期 - lockKey: {}, lockValue: {}", lockKey, lockValue);
|
||||
return false;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("释放锁异常 - lockKey: {}, lockValue: {}", lockKey, lockValue, e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 强制释放锁(不验证持有者)
|
||||
* 注意:这个方法会强制删除锁,可能导致其他持有者的锁被误删
|
||||
* 仅在特殊场景使用,如清理死锁
|
||||
*
|
||||
* @param lockKey 锁的键名
|
||||
* @return 是否删除成功
|
||||
*/
|
||||
public boolean forceUnlock(String lockKey) {
|
||||
String fullLockKey = LOCK_PREFIX + lockKey;
|
||||
try {
|
||||
Boolean result = redisTemplate.delete(fullLockKey);
|
||||
log.warn("强制释放锁 - lockKey: {}, result: {}", lockKey, result);
|
||||
return Boolean.TRUE.equals(result);
|
||||
} catch (Exception e) {
|
||||
log.error("强制释放锁异常 - lockKey: {}", lockKey, e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查锁是否存在
|
||||
*
|
||||
* @param lockKey 锁的键名
|
||||
* @return 是否存在
|
||||
*/
|
||||
public boolean isLocked(String lockKey) {
|
||||
String fullLockKey = LOCK_PREFIX + lockKey;
|
||||
try {
|
||||
Boolean result = redisTemplate.hasKey(fullLockKey);
|
||||
return Boolean.TRUE.equals(result);
|
||||
} catch (Exception e) {
|
||||
log.error("检查锁状态异常 - lockKey: {}", lockKey, e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取锁的剩余过期时间
|
||||
*
|
||||
* @param lockKey 锁的键名
|
||||
* @return 剩余过期时间(秒),-1 表示不存在或已过期
|
||||
*/
|
||||
public long getLockExpireTime(String lockKey) {
|
||||
String fullLockKey = LOCK_PREFIX + lockKey;
|
||||
try {
|
||||
Long expireTime = redisTemplate.getExpire(fullLockKey, TimeUnit.SECONDS);
|
||||
return expireTime != null ? expireTime : -1;
|
||||
} catch (Exception e) {
|
||||
log.error("获取锁过期时间异常 - lockKey: {}", lockKey, e);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 续期锁(延长锁的过期时间)
|
||||
* 注意:需要验证持有者
|
||||
*
|
||||
* @param lockKey 锁的键名
|
||||
* @param lockValue 锁的标识
|
||||
* @param addTime 增加的时间(秒)
|
||||
* @return 是否续期成功
|
||||
*/
|
||||
public boolean renewLock(String lockKey, String lockValue, long addTime) {
|
||||
if (lockValue == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
String fullLockKey = LOCK_PREFIX + lockKey;
|
||||
|
||||
try {
|
||||
// 1. 先验证锁的值
|
||||
String currentValue = (String) redisTemplate.opsForValue().get(fullLockKey);
|
||||
if (!lockValue.equals(currentValue)) {
|
||||
log.warn("续期失败,锁的值不匹配 - lockKey: {}", lockKey);
|
||||
return false;
|
||||
}
|
||||
|
||||
// 2. 续期锁
|
||||
Boolean result = redisTemplate.expire(fullLockKey, addTime, TimeUnit.SECONDS);
|
||||
if (Boolean.TRUE.equals(result)) {
|
||||
log.info("续期锁成功 - lockKey: {}, addTime: {}s", lockKey, addTime);
|
||||
return true;
|
||||
} else {
|
||||
log.warn("续期锁失败 - lockKey: {}", lockKey);
|
||||
return false;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("续期锁异常 - lockKey: {}", lockKey, e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
package com.aisi.template.utils;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.data.redis.core.StringRedisTemplate;
|
||||
import org.springframework.data.redis.core.RedisTemplate;
|
||||
import org.springframework.data.redis.core.ZSetOperations;
|
||||
import org.springframework.stereotype.Component;
|
||||
@@ -11,69 +12,187 @@ import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* Redis 工具类
|
||||
* 提供各种 Redis 数据类型的便捷操作方法
|
||||
*
|
||||
* 主要功能:
|
||||
* 1. String 类型:字符串、对象缓存
|
||||
* 2. Hash 类型:对象字段缓存
|
||||
* 3. List 类型:队列、列表
|
||||
* 4. Set 类型:去重、交集、并集
|
||||
* 5. ZSet 类型:排行榜
|
||||
* 6. Bitmap:位图操作
|
||||
* 7. HyperLogLog:基数统计
|
||||
*
|
||||
* @author Claude
|
||||
* @since 2024-04-09
|
||||
*/
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
public class RedisUtils {
|
||||
|
||||
/**
|
||||
* Redis 模板
|
||||
*/
|
||||
private final RedisTemplate<String, Object> redisTemplate;
|
||||
|
||||
// ======================== 通用 ========================
|
||||
/**
|
||||
* 字符串 Redis 模板,用于数值自增等原生命令
|
||||
*/
|
||||
private final StringRedisTemplate stringRedisTemplate;
|
||||
|
||||
// ======================== 1. 通用操作 ========================
|
||||
|
||||
/**
|
||||
* 删除键
|
||||
*
|
||||
* @param key 键
|
||||
* @return 是否删除成功
|
||||
*/
|
||||
public Boolean delete(String key) {
|
||||
return redisTemplate.delete(key);
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量删除键
|
||||
*
|
||||
* @param keys 键集合
|
||||
* @return 删除的数量
|
||||
*/
|
||||
public Long delete(Collection<String> keys) {
|
||||
return redisTemplate.delete(keys);
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断键是否存在
|
||||
*
|
||||
* @param key 键
|
||||
* @return 是否存在
|
||||
*/
|
||||
public Boolean hasKey(String key) {
|
||||
return redisTemplate.hasKey(key);
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置过期时间
|
||||
*
|
||||
* @param key 键
|
||||
* @param timeout 过期时间
|
||||
* @param unit 时间单位
|
||||
* @return 是否设置成功
|
||||
*/
|
||||
public Boolean expire(String key, long timeout, TimeUnit unit) {
|
||||
return redisTemplate.expire(key, timeout, unit);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取过期时间
|
||||
*
|
||||
* @param key 键
|
||||
* @return 剩余过期时间(秒),-1 表示永不过期,-2 表示键不存在
|
||||
*/
|
||||
public Long getExpire(String key) {
|
||||
return redisTemplate.getExpire(key);
|
||||
}
|
||||
|
||||
/**
|
||||
* 在指定时间戳过期
|
||||
*
|
||||
* @param key 键
|
||||
* @param timestamp 时间戳(毫秒)
|
||||
* @return 是否设置成功
|
||||
*/
|
||||
public Boolean expireAt(String key, long timestamp) {
|
||||
return redisTemplate.expireAt(key, new java.util.Date(timestamp));
|
||||
}
|
||||
|
||||
/**
|
||||
* 查找匹配模式的键
|
||||
*
|
||||
* @param pattern 模式(如:user:*)
|
||||
* @return 匹配的键集合
|
||||
*/
|
||||
public Set<String> keys(String pattern) {
|
||||
return redisTemplate.keys(pattern);
|
||||
}
|
||||
|
||||
/**
|
||||
* 重命名键
|
||||
*
|
||||
* @param oldKey 旧键名
|
||||
* @param newKey 新键名
|
||||
* @return 是否重命名成功
|
||||
*/
|
||||
public Boolean rename(String oldKey, String newKey) {
|
||||
redisTemplate.rename(oldKey, newKey);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取键的剩余生存时间(秒)
|
||||
*
|
||||
* @param key 键
|
||||
* @return 剩余秒数
|
||||
*/
|
||||
public Long ttl(String key) {
|
||||
return redisTemplate.getExpire(key, TimeUnit.SECONDS);
|
||||
}
|
||||
|
||||
// ======================== String ========================
|
||||
// ======================== 2. String 类型操作 ========================
|
||||
|
||||
/**
|
||||
* 设置键值
|
||||
*
|
||||
* @param key 键
|
||||
* @param value 值
|
||||
*/
|
||||
public void set(String key, Object value) {
|
||||
redisTemplate.opsForValue().set(key, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置键值(带过期时间)
|
||||
*
|
||||
* @param key 键
|
||||
* @param value 值
|
||||
* @param timeout 过期时间
|
||||
* @param unit 时间单位
|
||||
*/
|
||||
public void set(String key, Object value, long timeout, TimeUnit unit) {
|
||||
redisTemplate.opsForValue().set(key, value, timeout, unit);
|
||||
}
|
||||
|
||||
/**
|
||||
* 如果键不存在则设置(原子操作)
|
||||
* 使用场景:分布式锁
|
||||
*
|
||||
* @param key 键
|
||||
* @param value 值
|
||||
* @param timeout 过期时间
|
||||
* @param unit 时间单位
|
||||
*/
|
||||
public void setIfAbsent(String key, Object value, long timeout, TimeUnit unit) {
|
||||
redisTemplate.opsForValue().setIfAbsent(key, value, timeout, unit);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取键值
|
||||
*
|
||||
* @param key 键
|
||||
* @return 值
|
||||
*/
|
||||
public Object get(String key) {
|
||||
return redisTemplate.opsForValue().get(key);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取键值(指定类型)
|
||||
*
|
||||
* @param key 键
|
||||
* @param clazz 类型
|
||||
* @return 值
|
||||
*/
|
||||
public <T> T get(String key, Class<T> clazz) {
|
||||
Object value = redisTemplate.opsForValue().get(key);
|
||||
if (value == null) {
|
||||
@@ -82,255 +201,680 @@ public class RedisUtils {
|
||||
return clazz.cast(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取并设置新值
|
||||
*
|
||||
* @param key 键
|
||||
* @param value 新值
|
||||
* @return 旧值
|
||||
*/
|
||||
public String getAndSet(String key, Object value) {
|
||||
Object old = redisTemplate.opsForValue().getAndSet(key, value);
|
||||
return old != null ? old.toString() : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 自增(原子操作)
|
||||
*
|
||||
* @param key 键
|
||||
* @return 自增后的值
|
||||
*/
|
||||
public Long increment(String key) {
|
||||
return redisTemplate.opsForValue().increment(key);
|
||||
return stringRedisTemplate.opsForValue().increment(key);
|
||||
}
|
||||
|
||||
/**
|
||||
* 自增指定增量(原子操作)
|
||||
*
|
||||
* @param key 键
|
||||
* @param delta 增量
|
||||
* @return 自增后的值
|
||||
*/
|
||||
public Long increment(String key, long delta) {
|
||||
return redisTemplate.opsForValue().increment(key, delta);
|
||||
return stringRedisTemplate.opsForValue().increment(key, delta);
|
||||
}
|
||||
|
||||
/**
|
||||
* 自减(原子操作)
|
||||
*
|
||||
* @param key 键
|
||||
* @return 自减后的值
|
||||
*/
|
||||
public Long decrement(String key) {
|
||||
return redisTemplate.opsForValue().decrement(key);
|
||||
return stringRedisTemplate.opsForValue().decrement(key);
|
||||
}
|
||||
|
||||
/**
|
||||
* 自减指定减量(原子操作)
|
||||
*
|
||||
* @param key 键
|
||||
* @param delta 减量
|
||||
* @return 自减后的值
|
||||
*/
|
||||
public Long decrement(String key, long delta) {
|
||||
return redisTemplate.opsForValue().decrement(key, delta);
|
||||
return stringRedisTemplate.opsForValue().decrement(key, delta);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取字符串长度
|
||||
*
|
||||
* @param key 键
|
||||
* @return 字符串长度
|
||||
*/
|
||||
public Long strLength(String key) {
|
||||
return redisTemplate.opsForValue().size(key);
|
||||
}
|
||||
|
||||
// ======================== Hash ========================
|
||||
// ======================== 3. Hash 类型操作 ========================
|
||||
|
||||
/**
|
||||
* 设置哈希字段
|
||||
*
|
||||
* @param key 键
|
||||
* @param hashKey 哈希键
|
||||
* @param value 值
|
||||
*/
|
||||
public void hSet(String key, String hashKey, Object value) {
|
||||
redisTemplate.opsForHash().put(key, hashKey, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量设置哈希字段
|
||||
*
|
||||
* @param key 键
|
||||
* @param map 哈希键值映射
|
||||
*/
|
||||
public void hSetAll(String key, Map<String, Object> map) {
|
||||
redisTemplate.opsForHash().putAll(key, map);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取哈希字段
|
||||
*
|
||||
* @param key 键
|
||||
* @param hashKey 哈希键
|
||||
* @return 值
|
||||
*/
|
||||
public Object hGet(String key, String hashKey) {
|
||||
return redisTemplate.opsForHash().get(key, hashKey);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取所有哈希字段
|
||||
*
|
||||
* @param key 键
|
||||
* @return 哈希键值映射
|
||||
*/
|
||||
public Map<Object, Object> hGetAll(String key) {
|
||||
return redisTemplate.opsForHash().entries(key);
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除哈希字段
|
||||
*
|
||||
* @param key 键
|
||||
* @param hashKeys 哈希键集合
|
||||
*/
|
||||
public void hDelete(String key, Object... hashKeys) {
|
||||
redisTemplate.opsForHash().delete(key, hashKeys);
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断哈希字段是否存在
|
||||
*
|
||||
* @param key 键
|
||||
* @param hashKey 哈希键
|
||||
* @return 是否存在
|
||||
*/
|
||||
public Boolean hHasKey(String key, String hashKey) {
|
||||
return redisTemplate.opsForHash().hasKey(key, hashKey);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取哈希大小
|
||||
*
|
||||
* @param key 键
|
||||
* @return 哈希大小
|
||||
*/
|
||||
public Long hSize(String key) {
|
||||
return redisTemplate.opsForHash().size(key);
|
||||
}
|
||||
|
||||
/**
|
||||
* 哈希字段自增
|
||||
*
|
||||
* @param key 键
|
||||
* @param hashKey 哈希键
|
||||
* @param delta 增量
|
||||
* @return 自增后的值
|
||||
*/
|
||||
public Long hIncrement(String key, String hashKey, long delta) {
|
||||
return redisTemplate.opsForHash().increment(key, hashKey, delta);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取所有哈希键
|
||||
*
|
||||
* @param key 键
|
||||
* @return 哈希键集合
|
||||
*/
|
||||
public Set<Object> hKeys(String key) {
|
||||
return redisTemplate.opsForHash().keys(key);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取所有哈希值
|
||||
*
|
||||
* @param key 键
|
||||
* @return 哈希值列表
|
||||
*/
|
||||
public List<Object> hValues(String key) {
|
||||
return redisTemplate.opsForHash().values(key);
|
||||
}
|
||||
|
||||
// ======================== List ========================
|
||||
// ======================== 4. List 类型操作 ========================
|
||||
|
||||
/**
|
||||
* 从左侧推入列表
|
||||
*
|
||||
* @param key 键
|
||||
* @param value 值
|
||||
* @return 列表长度
|
||||
*/
|
||||
public Long lPush(String key, Object value) {
|
||||
return redisTemplate.opsForList().leftPush(key, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* 从左侧批量推入列表
|
||||
*
|
||||
* @param key 键
|
||||
* @param values 值数组
|
||||
* @return 列表长度
|
||||
*/
|
||||
public Long lPushAll(String key, Object... values) {
|
||||
return redisTemplate.opsForList().leftPushAll(key, values);
|
||||
}
|
||||
|
||||
/**
|
||||
* 从右侧推入列表
|
||||
*
|
||||
* @param key 键
|
||||
* @param value 值
|
||||
* @return 列表长度
|
||||
*/
|
||||
public Long rPush(String key, Object value) {
|
||||
return redisTemplate.opsForList().rightPush(key, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* 从右侧批量推入列表
|
||||
*
|
||||
* @param key 键
|
||||
* @param values 值数组
|
||||
* @return 列表长度
|
||||
*/
|
||||
public Long rPushAll(String key, Object... values) {
|
||||
return redisTemplate.opsForList().rightPushAll(key, values);
|
||||
}
|
||||
|
||||
/**
|
||||
* 从左侧弹出元素
|
||||
*
|
||||
* @param key 键
|
||||
* @return 弹出的元素
|
||||
*/
|
||||
public Object lPop(String key) {
|
||||
return redisTemplate.opsForList().leftPop(key);
|
||||
}
|
||||
|
||||
/**
|
||||
* 从左侧批量弹出元素
|
||||
*
|
||||
* @param key 键
|
||||
* @param count 数量
|
||||
* @return 弹出的元素列表
|
||||
*/
|
||||
public List<Object> lPop(String key, long count) {
|
||||
return redisTemplate.opsForList().leftPop(key, count);
|
||||
}
|
||||
|
||||
/**
|
||||
* 从右侧弹出元素
|
||||
*
|
||||
* @param key 键
|
||||
* @return 弹出的元素
|
||||
*/
|
||||
public Object rPop(String key) {
|
||||
return redisTemplate.opsForList().rightPop(key);
|
||||
}
|
||||
|
||||
/**
|
||||
* 从右侧批量弹出元素
|
||||
*
|
||||
* @param key 键
|
||||
* @param count 数量
|
||||
* @return 弹出的元素列表
|
||||
*/
|
||||
public List<Object> rPop(String key, long count) {
|
||||
return redisTemplate.opsForList().rightPop(key, count);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取列表指定索引的元素
|
||||
*
|
||||
* @param key 键
|
||||
* @param index 索引(0 表示第一个)
|
||||
* @return 元素
|
||||
*/
|
||||
public Object lIndex(String key, long index) {
|
||||
return redisTemplate.opsForList().index(key, index);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取列表长度
|
||||
*
|
||||
* @param key 键
|
||||
* @return 列表长度
|
||||
*/
|
||||
public Long lSize(String key) {
|
||||
return redisTemplate.opsForList().size(key);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取列表范围
|
||||
*
|
||||
* @param key 键
|
||||
* @param start 开始索引
|
||||
* @param end 结束索引
|
||||
* @return 元素列表
|
||||
*/
|
||||
public List<Object> lRange(String key, long start, long end) {
|
||||
return redisTemplate.opsForList().range(key, start, end);
|
||||
}
|
||||
|
||||
/**
|
||||
* 裁剪列表(只保留指定范围的元素)
|
||||
*
|
||||
* @param key 键
|
||||
* @param start 开始索引
|
||||
* @param end 结束索引
|
||||
*/
|
||||
public void lTrim(String key, long start, long end) {
|
||||
redisTemplate.opsForList().trim(key, start, end);
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置列表指定索引的元素
|
||||
*
|
||||
* @param key 键
|
||||
* @param index 索引
|
||||
* @param value 值
|
||||
*/
|
||||
public void lSet(String key, long index, Object value) {
|
||||
redisTemplate.opsForList().set(key, index, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* 移除列表元素
|
||||
*
|
||||
* @param key 键
|
||||
* @param count 移除数量(>0 从左往右,<0 从右往左,=0 全部)
|
||||
* @param value 要移除的值
|
||||
* @return 实际移除的数量
|
||||
*/
|
||||
public Long lRemove(String key, long count, Object value) {
|
||||
return redisTemplate.opsForList().remove(key, count, value);
|
||||
}
|
||||
|
||||
// ======================== Set ========================
|
||||
// ======================== 5. Set 类型操作 ========================
|
||||
|
||||
/**
|
||||
* 添加到集合
|
||||
*
|
||||
* @param key 键
|
||||
* @param values 值数组
|
||||
* @return 添加的元素数量(不包含已存在的)
|
||||
*/
|
||||
public Long sAdd(String key, Object... values) {
|
||||
return redisTemplate.opsForSet().add(key, values);
|
||||
}
|
||||
|
||||
/**
|
||||
* 从集合移除元素
|
||||
*
|
||||
* @param key 键
|
||||
* @param values 值数组
|
||||
* @return 实际移除的数量
|
||||
*/
|
||||
public Long sRemove(String key, Object... values) {
|
||||
return redisTemplate.opsForSet().remove(key, values);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取集合所有元素
|
||||
*
|
||||
* @param key 键
|
||||
* @return 元素集合
|
||||
*/
|
||||
public Set<Object> sMembers(String key) {
|
||||
return redisTemplate.opsForSet().members(key);
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断元素是否在集合中
|
||||
*
|
||||
* @param key 键
|
||||
* @param value 值
|
||||
* @return 是否存在
|
||||
*/
|
||||
public Boolean sIsMember(String key, Object value) {
|
||||
return redisTemplate.opsForSet().isMember(key, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取集合大小
|
||||
*
|
||||
* @param key 键
|
||||
* @return 集合大小
|
||||
*/
|
||||
public Long sSize(String key) {
|
||||
return redisTemplate.opsForSet().size(key);
|
||||
}
|
||||
|
||||
/**
|
||||
* 随机获取集合中的一个元素
|
||||
*
|
||||
* @param key 键
|
||||
* @return 随机元素
|
||||
*/
|
||||
public Object sRandomMember(String key) {
|
||||
return redisTemplate.opsForSet().randomMember(key);
|
||||
}
|
||||
|
||||
/**
|
||||
* 随机获取集合中的多个元素
|
||||
*
|
||||
* @param key 键
|
||||
* @param count 数量
|
||||
* @return 随机元素集合
|
||||
*/
|
||||
public Set<Object> sRandomMembers(String key, long count) {
|
||||
return redisTemplate.opsForSet().distinctRandomMembers(key, count);
|
||||
}
|
||||
|
||||
/**
|
||||
* 集合交集
|
||||
*
|
||||
* @param key1 第一个集合键
|
||||
* @param key2 第二个集合键
|
||||
* @return 交集元素
|
||||
*/
|
||||
public Set<Object> sIntersect(String key1, String key2) {
|
||||
return redisTemplate.opsForSet().intersect(key1, key2);
|
||||
}
|
||||
|
||||
/**
|
||||
* 集合并集
|
||||
*
|
||||
* @param key1 第一个集合键
|
||||
* @param key2 第二个集合键
|
||||
* @return 并集元素
|
||||
*/
|
||||
public Set<Object> sUnion(String key1, String key2) {
|
||||
return redisTemplate.opsForSet().union(key1, key2);
|
||||
}
|
||||
|
||||
/**
|
||||
* 集合差集(key1 - key2)
|
||||
*
|
||||
* @param key1 第一个集合键
|
||||
* @param key2 第二个集合键
|
||||
* @return 差集元素
|
||||
*/
|
||||
public Set<Object> sDifference(String key1, String key2) {
|
||||
return redisTemplate.opsForSet().difference(key1, key2);
|
||||
}
|
||||
|
||||
// ======================== ZSet(有序集合)========================
|
||||
// ======================== 6. ZSet 类型操作(有序集合)====================
|
||||
|
||||
/**
|
||||
* 添加到有序集合
|
||||
*
|
||||
* @param key 键
|
||||
* @param value 值
|
||||
* @param score 分数
|
||||
* @return 是否添加成功
|
||||
*/
|
||||
public Boolean zAdd(String key, Object value, double score) {
|
||||
return redisTemplate.opsForZSet().add(key, value, score);
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量添加到有序集合
|
||||
*
|
||||
* @param key 键
|
||||
* @param tuples 分数-值对集合
|
||||
* @return 添加的数量
|
||||
*/
|
||||
public Long zAdd(String key, Set<ZSetOperations.TypedTuple<Object>> tuples) {
|
||||
return redisTemplate.opsForZSet().add(key, tuples);
|
||||
}
|
||||
|
||||
/**
|
||||
* 从有序集合移除元素
|
||||
*
|
||||
* @param key 键
|
||||
* @param values 要移除的值
|
||||
* @return 移除的数量
|
||||
*/
|
||||
public Long zRemove(String key, Object... values) {
|
||||
return redisTemplate.opsForZSet().remove(key, values);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取元素排名(升序)
|
||||
*
|
||||
* @param key 键
|
||||
* @param value 值
|
||||
* @return 排名(从 0 开始),不存在返回 null
|
||||
*/
|
||||
public Long zRank(String key, Object value) {
|
||||
return redisTemplate.opsForZSet().rank(key, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取元素排名(降序)
|
||||
*
|
||||
* @param key 键
|
||||
* @param value 值
|
||||
* @return 排名(从 0 开始),不存在返回 null
|
||||
*/
|
||||
public Long zReverseRank(String key, Object value) {
|
||||
return redisTemplate.opsForZSet().reverseRank(key, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取有序集合范围(升序)
|
||||
*
|
||||
* @param key 键
|
||||
* @param start 开始索引
|
||||
* @param end 结束索引
|
||||
* @return 元素集合
|
||||
*/
|
||||
public Set<Object> zRange(String key, long start, long end) {
|
||||
return redisTemplate.opsForZSet().range(key, start, end);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取有序集合范围(降序)
|
||||
*
|
||||
* @param key 键
|
||||
* @param start 开始索引
|
||||
* @param end 结束索引
|
||||
* @return 元素集合
|
||||
*/
|
||||
public Set<Object> zReverseRange(String key, long start, long end) {
|
||||
return redisTemplate.opsForZSet().reverseRange(key, start, end);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取有序集合范围(带分数,升序)
|
||||
*
|
||||
* @param key 键
|
||||
* @param start 开始索引
|
||||
* @param end 结束索引
|
||||
* @return 分数-值对集合
|
||||
*/
|
||||
public Set<ZSetOperations.TypedTuple<Object>> zRangeWithScores(String key, long start, long end) {
|
||||
return redisTemplate.opsForZSet().rangeWithScores(key, start, end);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取指定分数范围的元素
|
||||
*
|
||||
* @param key 键
|
||||
* @param min 最小分数
|
||||
* @param max 最大分数
|
||||
* @return 元素集合
|
||||
*/
|
||||
public Set<Object> zRangeByScore(String key, double min, double max) {
|
||||
return redisTemplate.opsForZSet().rangeByScore(key, min, max);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取指定分数范围的元素(分页)
|
||||
*
|
||||
* @param key 键
|
||||
* @param min 最小分数
|
||||
* @param max 最大分数
|
||||
* @param offset 偏移量
|
||||
* @param count 数量
|
||||
* @return 元素集合
|
||||
*/
|
||||
public Set<Object> zRangeByScore(String key, double min, double max, long offset, long count) {
|
||||
return redisTemplate.opsForZSet().rangeByScore(key, min, max, offset, count);
|
||||
}
|
||||
|
||||
/**
|
||||
* 统计指定分数范围内的元素数量
|
||||
*
|
||||
* @param key 键
|
||||
* @param min 最小分数
|
||||
* @param max 最大分数
|
||||
* @return 元素数量
|
||||
*/
|
||||
public Long zCount(String key, double min, double max) {
|
||||
return redisTemplate.opsForZSet().count(key, min, max);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取有序集合大小
|
||||
*
|
||||
* @param key 键
|
||||
* @return 集合大小
|
||||
*/
|
||||
public Long zSize(String key) {
|
||||
return redisTemplate.opsForZSet().size(key);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取元素分数
|
||||
*
|
||||
* @param key 键
|
||||
* @param value 值
|
||||
* @return 分数
|
||||
*/
|
||||
public Double zScore(String key, Object value) {
|
||||
return redisTemplate.opsForZSet().score(key, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* 移除指定排名范围的元素
|
||||
*
|
||||
* @param key 键
|
||||
* @param start 开始排名
|
||||
* @param end 结束排名
|
||||
* @return 移除的数量
|
||||
*/
|
||||
public Long zRemoveRange(String key, long start, long end) {
|
||||
return redisTemplate.opsForZSet().removeRange(key, start, end);
|
||||
}
|
||||
|
||||
/**
|
||||
* 移除指定分数范围的元素
|
||||
*
|
||||
* @param key 键
|
||||
* @param min 最小分数
|
||||
* @param max 最大分数
|
||||
* @return 移除的数量
|
||||
*/
|
||||
public Long zRemoveRangeByScore(String key, double min, double max) {
|
||||
return redisTemplate.opsForZSet().removeRangeByScore(key, min, max);
|
||||
}
|
||||
|
||||
// ======================== Bitmap ========================
|
||||
// ======================== 7. Bitmap 类型操作 ========================
|
||||
|
||||
/**
|
||||
* 设置位图的指定位
|
||||
* 使用场景:
|
||||
* - 用户签到统计
|
||||
* - 在线状态统计
|
||||
* - 布隆过滤器
|
||||
*
|
||||
* @param key 键
|
||||
* @param offset 偏移量(位位置)
|
||||
* @param value 值(true=1,false=0)
|
||||
* @return 设置前的值
|
||||
*/
|
||||
public Boolean setBit(String key, long offset, boolean value) {
|
||||
return redisTemplate.opsForValue().setBit(key, offset, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取位图的指定位
|
||||
*
|
||||
* @param key 键
|
||||
* @param offset 偏移量(位位置)
|
||||
* @return 位值(true=1,false=0)
|
||||
*/
|
||||
public Boolean getBit(String key, long offset) {
|
||||
return redisTemplate.opsForValue().getBit(key, offset);
|
||||
}
|
||||
|
||||
// ======================== HyperLogLog ========================
|
||||
// ======================== 8. HyperLogLog 类型操作 ========================
|
||||
|
||||
/**
|
||||
* 添加元素到 HyperLogLog
|
||||
* 使用场景:
|
||||
* - 统计独立访客数(UV)
|
||||
* - 统计独立元素数(基数统计)
|
||||
* - 优点:内存占用小,适合大数据量
|
||||
*
|
||||
* @param key 键
|
||||
* @param values 值数组
|
||||
* @return 添加后的基数
|
||||
*/
|
||||
public Long pfAdd(String key, Object... values) {
|
||||
return redisTemplate.opsForHyperLogLog().add(key, values);
|
||||
}
|
||||
|
||||
/**
|
||||
* 统计基数
|
||||
*
|
||||
* @param keys 键数组(可统计多个键的并集基数)
|
||||
* @return 基数
|
||||
*/
|
||||
public Long pfCount(String... keys) {
|
||||
return redisTemplate.opsForHyperLogLog().size(keys);
|
||||
}
|
||||
|
||||
/**
|
||||
* 合并多个 HyperLogLog
|
||||
*
|
||||
* @param destination 目标键
|
||||
* @param sourceKeys 源键数组
|
||||
*/
|
||||
public void pfMerge(String destination, String... sourceKeys) {
|
||||
redisTemplate.opsForHyperLogLog().union(destination, sourceKeys);
|
||||
}
|
||||
|
||||
@@ -6,26 +6,56 @@ import org.springframework.security.core.context.SecurityContext;
|
||||
import org.springframework.security.core.context.SecurityContextHolder;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* Security 工具类
|
||||
* 提供获取当前登录用户信息的便捷方法
|
||||
*
|
||||
* 主要功能:
|
||||
* 1. 获取当前认证对象
|
||||
* 2. 获取当前用户名
|
||||
* 3. 获取当前用户ID
|
||||
*
|
||||
* 使用场景:
|
||||
* - 在 Service 层获取当前用户信息
|
||||
* - 在拦截器中获取当前用户信息
|
||||
* - 在日志中记录操作用户
|
||||
*
|
||||
* @author Claude
|
||||
* @since 2024-04-09
|
||||
*/
|
||||
@Component
|
||||
public class SecurityUtils {
|
||||
|
||||
/**
|
||||
* 获取当前 Authentication
|
||||
* 获取当前 Authentication 对象
|
||||
* 说明:
|
||||
* - Authentication 包含认证信息和用户详情
|
||||
* - 如果用户未认证,返回 null
|
||||
*
|
||||
* @return 当前认证对象
|
||||
*/
|
||||
public static Authentication getAuthentication(){
|
||||
public static Authentication getAuthentication() {
|
||||
return SecurityContextHolder.getContext().getAuthentication();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取登录名
|
||||
* 获取当前登录用户的用户名
|
||||
* 步骤:
|
||||
* 1. 获取当前认证对象
|
||||
* 2. 检查用户是否已认证
|
||||
* 3. 从 Principal 中提取用户名
|
||||
*
|
||||
* @return 用户名,未登录返回 null
|
||||
*/
|
||||
public static String getUsername(){
|
||||
public static String getUsername() {
|
||||
// 1. 获取当前认证对象
|
||||
Authentication auth = getAuthentication();
|
||||
if (auth != null && auth.isAuthenticated()) {
|
||||
Object principal = auth.getPrincipal();
|
||||
// 2. 判断 Principal 类型并提取用户名
|
||||
if (principal instanceof CustomUserDetails user) {
|
||||
return user.getUsername();
|
||||
}else if (principal instanceof String username) {
|
||||
return user.getUsername();
|
||||
} else if (principal instanceof String username) {
|
||||
return username;
|
||||
}
|
||||
}
|
||||
@@ -33,14 +63,21 @@ public class SecurityUtils {
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取用户Id
|
||||
* 获取当前登录用户的用户ID
|
||||
* 步骤:
|
||||
* 1. 获取当前认证对象
|
||||
* 2. 检查用户是否已认证
|
||||
* 3. 从 CustomUserDetails 中提取用户ID
|
||||
*
|
||||
* @return 用户ID,未登录返回 null
|
||||
*/
|
||||
public static Long getUserId() {
|
||||
// 1. 获取当前认证对象
|
||||
Authentication auth = getAuthentication();
|
||||
// 2. 检查是否已认证且 Principal 类型为 CustomUserDetails
|
||||
if (auth != null && auth.isAuthenticated() && auth.getPrincipal() instanceof CustomUserDetails user) {
|
||||
return user.getId();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user