Skip to content

Redis工具类

依赖:

xml
<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

代码:

java


import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.connection.DataType;
import org.springframework.data.redis.core.*;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;

import java.util.*;
import java.util.concurrent.TimeUnit;

/**
 * Redis 工具类
 *
 * <p>覆盖 String / Hash / List / Set / ZSet 五种数据结构的常用操作,
 * 提供丰富重载,并通过 Jackson ObjectMapper 对值进行自动类型转换。</p>
 *
 * <h3>类型转换说明</h3>
 * <pre>
 *   // 存入 POJO
 *   redisUtil.set("user:1", user);
 *
 *   // 按原类型取出
 *   User user = redisUtil.get("user:1", User.class);
 *
 *   // 取出泛型复杂类型(如 List&lt;User&gt;)
 *   List&lt;User&gt; list = redisUtil.get("users", new TypeReference&lt;List&lt;User&gt;&gt;() {});
 * </pre>
 */
@Component
public class RedisUtil {

    @Autowired
    private RedisTemplate<String, Object> redisTemplate;

    /** Spring Boot 自动配置的 Jackson ObjectMapper,用于 POJO 类型转换 */
    @Autowired
    private ObjectMapper objectMapper;

    // ================================================================
    // 内部工具:类型转换
    // ================================================================

    /**
     * 将 Redis 返回的原始值转换为目标类型(支持 POJO、Map 等)。
     * 当 RedisTemplate 使用 Jackson 序列化时,返回值可能已是 LinkedHashMap,
     * objectMapper.convertValue 会将其正确地转换为目标 POJO。
     */
    @SuppressWarnings("unchecked")
    private <T> T convert(Object raw, Class<T> clazz) {
        if (raw == null) return null;
        if (clazz.isInstance(raw)) return clazz.cast(raw);
        return objectMapper.convertValue(raw, clazz);
    }

    /** 转换为泛型复杂类型,例如 {@code new TypeReference<List<User>>() {}} */
    private <T> T convert(Object raw, TypeReference<T> typeRef) {
        if (raw == null) return null;
        return objectMapper.convertValue(raw, typeRef);
    }

    // ================================================================
    // Key 相关操作
    // ================================================================

    /**
     * 设置 key 过期时间(秒)
     *
     * @return true 设置成功;false 失败
     */
    public boolean expire(String key, long seconds) {
        return expire(key, seconds, TimeUnit.SECONDS);
    }

    /**
     * 设置 key 过期时间(指定单位)
     *
     * @return true 设置成功;false 失败
     */
    public boolean expire(String key, long time, TimeUnit timeUnit) {
        try {
            if (time > 0) {
                return Boolean.TRUE.equals(redisTemplate.expire(key, time, timeUnit));
            }
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 获取 key 的剩余过期时间(秒)
     *
     * @return 剩余秒数;-1 表示永久;-2 表示 key 不存在
     */
    public long getExpire(String key) {
        return getExpire(key, TimeUnit.SECONDS);
    }

    /**
     * 获取 key 的剩余过期时间(指定单位)
     *
     * @return 剩余时间;-1 表示永久;-2 表示 key 不存在
     */
    public long getExpire(String key, TimeUnit timeUnit) {
        Long ttl = redisTemplate.getExpire(key, timeUnit);
        return ttl == null ? -2L : ttl;
    }

    /**
     * 移除 key 的过期时间,使其永久存在(PERSIST)
     *
     * @return true 成功;false key 不存在或已永久
     */
    public boolean persist(String key) {
        return Boolean.TRUE.equals(redisTemplate.persist(key));
    }

    /**
     * 判断 key 是否存在
     */
    public boolean hasKey(String key) {
        try {
            return Boolean.TRUE.equals(redisTemplate.hasKey(key));
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 获取 key 存储的数据类型(string / hash / list / set / zset / none)
     */
    public String type(String key) {
        DataType dataType = redisTemplate.type(key);
        return dataType == null ? "none" : dataType.code();
    }

    /**
     * 删除单个 key
     *
     * @return true 删除成功
     */
    public boolean del(String key) {
        return Boolean.TRUE.equals(redisTemplate.delete(key));
    }

    /**
     * 批量删除 key(varargs)
     *
     * @return 实际删除数量
     */
    public long del(String... keys) {
        if (keys == null || keys.length == 0) return 0L;
        @SuppressWarnings("unchecked")
        Collection<String> keyList = (Collection<String>) CollectionUtils.arrayToList(keys);
        Long count = redisTemplate.delete(keyList);
        return count == null ? 0L : count;
    }

    /**
     * 批量删除 key(Collection)
     *
     * @return 实际删除数量
     */
    public long del(Collection<String> keys) {
        if (CollectionUtils.isEmpty(keys)) return 0L;
        Long count = redisTemplate.delete(keys);
        return count == null ? 0L : count;
    }

    /**
     * 模糊匹配获取所有符合条件的 key(KEYS pattern)
     *
     * <p><strong>警告:</strong>生产环境数据量大时请使用 {@link #scan} 代替。</p>
     */
    public Set<String> keys(String pattern) {
        return redisTemplate.keys(pattern);
    }

    /**
     * 使用 SCAN 游标安全地遍历匹配 key(推荐替代 keys)
     *
     * @param pattern 匹配模式,例如 {@code "user:*"}
     * @param count   每次扫描的建议数量(仅提示 Redis,实际数量不保证)
     */
    public List<String> scan(String pattern, long count) {
        List<String> result = new ArrayList<>();
        try (Cursor<String> cursor = redisTemplate.scan(
                ScanOptions.scanOptions().match(pattern).count(count).build())) {
            while (cursor.hasNext()) {
                result.add(cursor.next());
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return result;
    }

    /**
     * 重命名 key
     */
    public boolean rename(String oldKey, String newKey) {
        try {
            redisTemplate.rename(oldKey, newKey);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 仅当 newKey 不存在时重命名(RENAMENX)
     *
     * @return true 重命名成功;false newKey 已存在
     */
    public boolean renameIfAbsent(String oldKey, String newKey) {
        return Boolean.TRUE.equals(redisTemplate.renameIfAbsent(oldKey, newKey));
    }

    // ================================================================
    // String 操作
    // ================================================================

    /**
     * 获取值(原始 Object)
     */
    public Object get(String key) {
        return key == null ? null : redisTemplate.opsForValue().get(key);
    }

    /**
     * 获取值并自动转换为目标类型
     *
     * <pre>User user = redisUtil.get("user:1", User.class);</pre>
     */
    public <T> T get(String key, Class<T> clazz) {
        return convert(get(key), clazz);
    }

    /**
     * 获取值并转换为泛型复杂类型
     *
     * <pre>List&lt;User&gt; users = redisUtil.get("users", new TypeReference&lt;List&lt;User&gt;&gt;() {});</pre>
     */
    public <T> T get(String key, TypeReference<T> typeRef) {
        return convert(get(key), typeRef);
    }

    /** 获取字符串值 */
    public String getString(String key) {
        Object val = get(key);
        return val == null ? null : val.toString();
    }

    /** 获取 Integer 值 */
    public Integer getInteger(String key) {
        return get(key, Integer.class);
    }

    /** 获取 Long 值(兼容存入时为 Integer 的情况) */
    public Long getLong(String key) {
        Object val = get(key);
        if (val == null) return null;
        if (val instanceof Long) return (Long) val;
        if (val instanceof Number) return ((Number) val).longValue();
        return Long.parseLong(val.toString());
    }

    /** 获取 Double 值 */
    public Double getDouble(String key) {
        Object val = get(key);
        if (val == null) return null;
        if (val instanceof Double) return (Double) val;
        if (val instanceof Number) return ((Number) val).doubleValue();
        return Double.parseDouble(val.toString());
    }

    /**
     * 批量获取(MGET),返回顺序与 keys 一致,缺失项为 null
     */
    public List<Object> mGet(Collection<String> keys) {
        return redisTemplate.opsForValue().multiGet(keys);
    }

    /**
     * 批量获取并将每个值转换为指定类型
     */
    public <T> List<T> mGet(Collection<String> keys, Class<T> clazz) {
        List<Object> raw = mGet(keys);
        if (raw == null) return Collections.emptyList();
        List<T> result = new ArrayList<>(raw.size());
        raw.forEach(obj -> result.add(convert(obj, clazz)));
        return result;
    }

    /**
     * 设置值(永久)
     */
    public boolean set(String key, Object value) {
        try {
            redisTemplate.opsForValue().set(key, value);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 设置值(秒级过期)
     */
    public boolean set(String key, Object value, long seconds) {
        return set(key, value, seconds, TimeUnit.SECONDS);
    }

    /**
     * 设置值(指定单位过期)
     */
    public boolean set(String key, Object value, long time, TimeUnit timeUnit) {
        try {
            if (time > 0) {
                redisTemplate.opsForValue().set(key, value, time, timeUnit);
            } else {
                redisTemplate.opsForValue().set(key, value);
            }
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 仅当 key 不存在时设置,永久(SET NX)
     *
     * @return true 设置成功;false key 已存在
     */
    public boolean setIfAbsent(String key, Object value) {
        return Boolean.TRUE.equals(redisTemplate.opsForValue().setIfAbsent(key, value));
    }

    /**
     * 仅当 key 不存在时设置,秒级过期(SET NX EX)
     *
     * @return true 设置成功;false key 已存在
     */
    public boolean setIfAbsent(String key, Object value, long seconds) {
        return setIfAbsent(key, value, seconds, TimeUnit.SECONDS);
    }

    /**
     * 仅当 key 不存在时设置,指定单位过期
     *
     * @return true 设置成功;false key 已存在
     */
    public boolean setIfAbsent(String key, Object value, long time, TimeUnit timeUnit) {
        return Boolean.TRUE.equals(redisTemplate.opsForValue().setIfAbsent(key, value, time, timeUnit));
    }

    /**
     * 仅当 key 存在时更新值(SET XX),永久
     *
     * @return true 更新成功;false key 不存在
     */
    public boolean setIfPresent(String key, Object value) {
        return Boolean.TRUE.equals(redisTemplate.opsForValue().setIfPresent(key, value));
    }

    /**
     * 仅当 key 存在时更新值,秒级过期
     */
    public boolean setIfPresent(String key, Object value, long seconds) {
        return setIfPresent(key, value, seconds, TimeUnit.SECONDS);
    }

    /**
     * 仅当 key 存在时更新值,指定单位过期
     */
    public boolean setIfPresent(String key, Object value, long time, TimeUnit timeUnit) {
        return Boolean.TRUE.equals(redisTemplate.opsForValue().setIfPresent(key, value, time, timeUnit));
    }

    /**
     * 设置值并返回旧值(GETSET)
     */
    public Object getAndSet(String key, Object value) {
        return redisTemplate.opsForValue().getAndSet(key, value);
    }

    /**
     * 设置值并返回旧值,转换为指定类型
     */
    public <T> T getAndSet(String key, Object value, Class<T> clazz) {
        return convert(getAndSet(key, value), clazz);
    }

    /**
     * 批量设置(MSET),永久
     */
    public boolean mSet(Map<String, Object> map) {
        try {
            redisTemplate.opsForValue().multiSet(map);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 批量设置,仅当所有 key 都不存在时生效(MSETNX)
     *
     * @return true 全部设置成功;false 至少有一个 key 已存在
     */
    public boolean mSetIfAbsent(Map<String, Object> map) {
        return Boolean.TRUE.equals(redisTemplate.opsForValue().multiSetIfAbsent(map));
    }

    /**
     * 追加字符串到已有值末尾(APPEND)
     *
     * @return 追加后字符串的字节长度
     */
    public int append(String key, String value) {
        Integer len = redisTemplate.opsForValue().append(key, value);
        return len == null ? 0 : len;
    }

    /**
     * 获取字符串字节长度(STRLEN)
     */
    public long strlen(String key) {
        Long len = redisTemplate.opsForValue().size(key);
        return len == null ? 0L : len;
    }

    /**
     * 自增 1(INCR)
     *
     * @return 自增后的值
     */
    public long incr(String key) {
        return incr(key, 1L);
    }

    /**
     * 自增指定步长(INCRBY),delta 必须 &gt; 0
     *
     * @return 自增后的值
     */
    public long incr(String key, long delta) {
        if (delta <= 0) throw new IllegalArgumentException("递增因子必须大于 0");
        Long val = redisTemplate.opsForValue().increment(key, delta);
        return val == null ? 0L : val;
    }

    /**
     * 自减 1(DECR)
     *
     * @return 自减后的值
     */
    public long decr(String key) {
        return decr(key, 1L);
    }

    /**
     * 自减指定步长,delta 必须 &gt; 0
     *
     * @return 自减后的值
     */
    public long decr(String key, long delta) {
        if (delta <= 0) throw new IllegalArgumentException("递减因子必须大于 0");
        Long val = redisTemplate.opsForValue().increment(key, -delta);
        return val == null ? 0L : val;
    }

    // ================================================================
    // Hash 操作(对应 Java Map)
    // ================================================================

    /**
     * 获取 Hash 中指定字段的值(原始 Object)
     */
    public Object hGet(String key, String field) {
        return redisTemplate.opsForHash().get(key, field);
    }

    /**
     * 获取 Hash 中指定字段的值并自动转换类型
     *
     * <pre>User user = redisUtil.hGet("users", "user001", User.class);</pre>
     */
    public <T> T hGet(String key, String field, Class<T> clazz) {
        return convert(hGet(key, field), clazz);
    }

    /**
     * 获取 Hash 中指定字段的值并转换为泛型复杂类型
     */
    public <T> T hGet(String key, String field, TypeReference<T> typeRef) {
        return convert(hGet(key, field), typeRef);
    }

    /**
     * 获取 Hash 所有字段及值(HGETALL)
     */
    public Map<Object, Object> hGetAll(String key) {
        return redisTemplate.opsForHash().entries(key);
    }

    /**
     * 获取 Hash 所有字段及值,将 value 转换为指定类型(Map&lt;String, T&gt;)
     *
     * <pre>Map&lt;String, User&gt; map = redisUtil.hGetAll("users", User.class);</pre>
     */
    public <T> Map<String, T> hGetAll(String key, Class<T> clazz) {
        Map<Object, Object> raw = hGetAll(key);
        if (raw == null || raw.isEmpty()) return Collections.emptyMap();
        Map<String, T> result = new LinkedHashMap<>(raw.size());
        raw.forEach((k, v) -> result.put(k.toString(), convert(v, clazz)));
        return result;
    }

    /**
     * 批量获取 Hash 中指定字段的值(HMGET)
     */
    public List<Object> hMultiGet(String key, Collection<String> fields) {
        return redisTemplate.opsForHash().multiGet(key, new ArrayList<>(fields));
    }

    /**
     * 批量获取 Hash 中指定字段的值并转换类型
     */
    public <T> List<T> hMultiGet(String key, Collection<String> fields, Class<T> clazz) {
        List<Object> raw = hMultiGet(key, fields);
        if (raw == null) return Collections.emptyList();
        List<T> result = new ArrayList<>(raw.size());
        raw.forEach(obj -> result.add(convert(obj, clazz)));
        return result;
    }

    /**
     * 设置 Hash 中单个字段(HSET)
     */
    public boolean hSet(String key, String field, Object value) {
        try {
            redisTemplate.opsForHash().put(key, field, value);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 设置 Hash 中单个字段,并刷新 key 的过期时间(秒)
     */
    public boolean hSet(String key, String field, Object value, long seconds) {
        return hSet(key, field, value, seconds, TimeUnit.SECONDS);
    }

    /**
     * 设置 Hash 中单个字段,并刷新 key 的过期时间(指定单位)
     */
    public boolean hSet(String key, String field, Object value, long time, TimeUnit timeUnit) {
        try {
            redisTemplate.opsForHash().put(key, field, value);
            if (time > 0) expire(key, time, timeUnit);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 仅当字段不存在时设置(HSETNX)
     *
     * @return true 设置成功;false 字段已存在
     */
    public boolean hSetIfAbsent(String key, String field, Object value) {
        return Boolean.TRUE.equals(redisTemplate.opsForHash().putIfAbsent(key, field, value));
    }

    /**
     * 批量设置 Hash 字段(HMSET)
     */
    public boolean hSetAll(String key, Map<String, Object> map) {
        try {
            redisTemplate.opsForHash().putAll(key, map);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 批量设置 Hash 字段,并设置过期时间(秒)
     */
    public boolean hSetAll(String key, Map<String, Object> map, long seconds) {
        return hSetAll(key, map, seconds, TimeUnit.SECONDS);
    }

    /**
     * 批量设置 Hash 字段,并设置过期时间(指定单位)
     */
    public boolean hSetAll(String key, Map<String, Object> map, long time, TimeUnit timeUnit) {
        try {
            redisTemplate.opsForHash().putAll(key, map);
            if (time > 0) expire(key, time, timeUnit);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 删除 Hash 中的字段(HDEL),支持多个
     *
     * @return 实际删除数量
     */
    public long hDel(String key, String... fields) {
        return redisTemplate.opsForHash().delete(key, (Object[]) fields);
    }

    /**
     * 判断 Hash 中字段是否存在(HEXISTS)
     */
    public boolean hHasKey(String key, String field) {
        return redisTemplate.opsForHash().hasKey(key, field);
    }

    /**
     * 获取 Hash 所有字段名(HKEYS)
     */
    public Set<Object> hKeys(String key) {
        return redisTemplate.opsForHash().keys(key);
    }

    /**
     * 获取 Hash 所有字段值列表(HVALS)
     */
    public List<Object> hValues(String key) {
        return redisTemplate.opsForHash().values(key);
    }

    /**
     * 获取 Hash 所有字段值列表并转换类型
     */
    public <T> List<T> hValues(String key, Class<T> clazz) {
        List<Object> raw = hValues(key);
        if (raw == null) return Collections.emptyList();
        List<T> result = new ArrayList<>(raw.size());
        raw.forEach(obj -> result.add(convert(obj, clazz)));
        return result;
    }

    /**
     * 获取 Hash 字段数量(HLEN)
     */
    public long hSize(String key) {
        return redisTemplate.opsForHash().size(key);
    }

    /**
     * Hash 字段数值自增 1(long)
     */
    public long hIncr(String key, String field) {
        return hIncr(key, field, 1L);
    }

    /**
     * Hash 字段数值自增(long)
     */
    public long hIncr(String key, String field, long delta) {
        return redisTemplate.opsForHash().increment(key, field, delta);
    }

    /**
     * Hash 字段数值自增(double,支持浮点步长)
     */
    public double hIncr(String key, String field, double delta) {
        return redisTemplate.opsForHash().increment(key, field, delta);
    }

    /**
     * Hash 字段数值自减 1(long)
     */
    public long hDecr(String key, String field) {
        return hDecr(key, field, 1L);
    }

    /**
     * Hash 字段数值自减(long)
     */
    public long hDecr(String key, String field, long delta) {
        return redisTemplate.opsForHash().increment(key, field, -delta);
    }

    /**
     * Hash 字段数值自减(double)
     */
    public double hDecr(String key, String field, double delta) {
        return redisTemplate.opsForHash().increment(key, field, -delta);
    }

    // ================================================================
    // List 操作
    // ================================================================

    /**
     * 获取 List 全部元素
     */
    public List<Object> lRange(String key) {
        return lRange(key, 0, -1);
    }

    /**
     * 获取 List 全部元素并转换类型
     *
     * <pre>List&lt;User&gt; users = redisUtil.lRange("users", User.class);</pre>
     */
    public <T> List<T> lRange(String key, Class<T> clazz) {
        return lRange(key, 0, -1, clazz);
    }

    /**
     * 获取 List 指定范围元素(LRANGE),0 到 -1 表示全部
     */
    public List<Object> lRange(String key, long start, long end) {
        try {
            return redisTemplate.opsForList().range(key, start, end);
        } catch (Exception e) {
            e.printStackTrace();
            return Collections.emptyList();
        }
    }

    /**
     * 获取 List 指定范围元素并转换类型
     */
    public <T> List<T> lRange(String key, long start, long end, Class<T> clazz) {
        List<Object> raw = lRange(key, start, end);
        if (raw == null) return Collections.emptyList();
        List<T> result = new ArrayList<>(raw.size());
        raw.forEach(obj -> result.add(convert(obj, clazz)));
        return result;
    }

    /**
     * 获取 List 长度(LLEN)
     */
    public long lSize(String key) {
        try {
            Long size = redisTemplate.opsForList().size(key);
            return size == null ? 0L : size;
        } catch (Exception e) {
            e.printStackTrace();
            return 0L;
        }
    }

    /**
     * 按索引获取元素(LINDEX);index 为负数时从尾部计数(-1 为末尾)
     */
    public Object lIndex(String key, long index) {
        try {
            return redisTemplate.opsForList().index(key, index);
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

    /**
     * 按索引获取元素并转换类型
     */
    public <T> T lIndex(String key, long index, Class<T> clazz) {
        return convert(lIndex(key, index), clazz);
    }

    /**
     * 右侧压入单个元素(RPUSH)
     */
    public boolean rPush(String key, Object value) {
        try {
            redisTemplate.opsForList().rightPush(key, value);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 右侧压入单个元素,并设置过期时间(秒)
     */
    public boolean rPush(String key, Object value, long seconds) {
        return rPush(key, value, seconds, TimeUnit.SECONDS);
    }

    /**
     * 右侧压入单个元素,并设置过期时间(指定单位)
     */
    public boolean rPush(String key, Object value, long time, TimeUnit timeUnit) {
        try {
            redisTemplate.opsForList().rightPush(key, value);
            if (time > 0) expire(key, time, timeUnit);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 右侧批量压入(RPUSH 多个值)
     */
    public boolean rPushAll(String key, List<Object> values) {
        try {
            redisTemplate.opsForList().rightPushAll(key, values);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 右侧批量压入,并设置过期时间(秒)
     */
    public boolean rPushAll(String key, List<Object> values, long seconds) {
        boolean ok = rPushAll(key, values);
        if (ok && seconds > 0) expire(key, seconds);
        return ok;
    }

    /**
     * 左侧压入单个元素(LPUSH)
     */
    public boolean lPush(String key, Object value) {
        try {
            redisTemplate.opsForList().leftPush(key, value);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 左侧批量压入(LPUSH 多个值)
     */
    public boolean lPushAll(String key, List<Object> values) {
        try {
            redisTemplate.opsForList().leftPushAll(key, values);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 按索引更新 List 中指定位置的元素(LSET)
     */
    public boolean lSet(String key, long index, Object value) {
        try {
            redisTemplate.opsForList().set(key, index, value);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 左侧弹出一个元素(LPOP)
     */
    public Object lPop(String key) {
        return redisTemplate.opsForList().leftPop(key);
    }

    /**
     * 左侧弹出一个元素并转换类型
     */
    public <T> T lPop(String key, Class<T> clazz) {
        return convert(lPop(key), clazz);
    }

    /**
     * 右侧弹出一个元素(RPOP)
     */
    public Object rPop(String key) {
        return redisTemplate.opsForList().rightPop(key);
    }

    /**
     * 右侧弹出一个元素并转换类型
     */
    public <T> T rPop(String key, Class<T> clazz) {
        return convert(rPop(key), clazz);
    }

    /**
     * 阻塞式左侧弹出(BLPOP),超时后返回 null
     */
    public Object blPop(String key, long timeout, TimeUnit timeUnit) {
        return redisTemplate.opsForList().leftPop(key, timeout, timeUnit);
    }

    /**
     * 阻塞式右侧弹出(BRPOP),超时后返回 null
     */
    public Object brPop(String key, long timeout, TimeUnit timeUnit) {
        return redisTemplate.opsForList().rightPop(key, timeout, timeUnit);
    }

    /**
     * 移除 List 中值等于 value 的元素(LREM)
     * <ul>
     *   <li>count &gt; 0:从头向尾移除 count 个</li>
     *   <li>count &lt; 0:从尾向头移除 |count| 个</li>
     *   <li>count = 0:移除全部匹配</li>
     * </ul>
     *
     * @return 实际移除数量
     */
    public long lRem(String key, long count, Object value) {
        Long removed = redisTemplate.opsForList().remove(key, count, value);
        return removed == null ? 0L : removed;
    }

    /**
     * 裁剪 List,只保留 [start, end] 范围内的元素(LTRIM)
     */
    public boolean lTrim(String key, long start, long end) {
        try {
            redisTemplate.opsForList().trim(key, start, end);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    // ================================================================
    // Set 操作(无序集合,元素唯一)
    // ================================================================

    /**
     * 获取 Set 所有成员(SMEMBERS)
     */
    public Set<Object> sMembers(String key) {
        try {
            return redisTemplate.opsForSet().members(key);
        } catch (Exception e) {
            e.printStackTrace();
            return Collections.emptySet();
        }
    }

    /**
     * 获取 Set 所有成员并转换类型
     *
     * <pre>Set&lt;User&gt; users = redisUtil.sMembers("onlineUsers", User.class);</pre>
     */
    public <T> Set<T> sMembers(String key, Class<T> clazz) {
        Set<Object> raw = sMembers(key);
        if (raw == null) return Collections.emptySet();
        Set<T> result = new LinkedHashSet<>(raw.size());
        raw.forEach(obj -> result.add(convert(obj, clazz)));
        return result;
    }

    /**
     * 判断 value 是否是 Set 的成员(SISMEMBER)
     */
    public boolean sIsMember(String key, Object value) {
        return Boolean.TRUE.equals(redisTemplate.opsForSet().isMember(key, value));
    }

    /**
     * 向 Set 添加元素(SADD),支持多个
     *
     * @return 成功添加的元素数量
     */
    public long sAdd(String key, Object... values) {
        try {
            Long count = redisTemplate.opsForSet().add(key, values);
            return count == null ? 0L : count;
        } catch (Exception e) {
            e.printStackTrace();
            return 0L;
        }
    }

    /**
     * 向 Set 添加元素,并设置过期时间(秒)
     *
     * @return 成功添加的元素数量
     */
    public long sAdd(String key, long seconds, Object... values) {
        long count = sAdd(key, values);
        if (seconds > 0) expire(key, seconds);
        return count;
    }

    /**
     * 从 Set 中移除元素(SREM),支持多个
     *
     * @return 实际移除数量
     */
    public long sRem(String key, Object... values) {
        Long count = redisTemplate.opsForSet().remove(key, values);
        return count == null ? 0L : count;
    }

    /**
     * 随机弹出一个成员(SPOP,同时移除)
     */
    public Object sPop(String key) {
        return redisTemplate.opsForSet().pop(key);
    }

    /**
     * 随机弹出一个成员并转换类型
     */
    public <T> T sPop(String key, Class<T> clazz) {
        return convert(sPop(key), clazz);
    }

    /**
     * 随机弹出多个成员(SPOP count)
     */
    public List<Object> sPop(String key, long count) {
        return redisTemplate.opsForSet().pop(key, count);
    }

    /**
     * 随机获取一个成员(不移除,SRANDMEMBER)
     */
    public Object sRandMember(String key) {
        return redisTemplate.opsForSet().randomMember(key);
    }

    /**
     * 随机获取多个成员(不移除,可重复)
     */
    public List<Object> sRandMembers(String key, long count) {
        return redisTemplate.opsForSet().randomMembers(key, count);
    }

    /**
     * 随机获取多个不重复成员(不移除)
     */
    public Set<Object> sDistinctRandMembers(String key, long count) {
        return redisTemplate.opsForSet().distinctRandomMembers(key, count);
    }

    /**
     * 获取 Set 成员数量(SCARD)
     */
    public long sSize(String key) {
        Long size = redisTemplate.opsForSet().size(key);
        return size == null ? 0L : size;
    }

    /**
     * 获取两个 Set 的交集(SINTER)
     */
    public Set<Object> sIntersect(String key, String otherKey) {
        return redisTemplate.opsForSet().intersect(key, otherKey);
    }

    /**
     * 获取多个 Set 的交集
     */
    public Set<Object> sIntersect(String key, Collection<String> otherKeys) {
        return redisTemplate.opsForSet().intersect(key, otherKeys);
    }

    /**
     * 获取两个 Set 的并集(SUNION)
     */
    public Set<Object> sUnion(String key, String otherKey) {
        return redisTemplate.opsForSet().union(key, otherKey);
    }

    /**
     * 获取多个 Set 的并集
     */
    public Set<Object> sUnion(String key, Collection<String> otherKeys) {
        return redisTemplate.opsForSet().union(key, otherKeys);
    }

    /**
     * 获取两个 Set 的差集(SDIFF,key 有而 otherKey 无的元素)
     */
    public Set<Object> sDiff(String key, String otherKey) {
        return redisTemplate.opsForSet().difference(key, otherKey);
    }

    /**
     * 获取多个 Set 的差集
     */
    public Set<Object> sDiff(String key, Collection<String> otherKeys) {
        return redisTemplate.opsForSet().difference(key, otherKeys);
    }

    // ================================================================
    // ZSet 操作(有序集合,按 score 排序)
    // ================================================================

    /**
     * 添加元素并指定 score(若已存在则更新 score)(ZADD)
     *
     * @return true 为新增;false 为更新
     */
    public boolean zAdd(String key, Object value, double score) {
        return Boolean.TRUE.equals(redisTemplate.opsForZSet().add(key, value, score));
    }

    /**
     * 批量添加元素(ZADD 多个)
     *
     * @return 实际新增数量
     */
    public long zAddAll(String key, Set<ZSetOperations.TypedTuple<Object>> tuples) {
        Long count = redisTemplate.opsForZSet().add(key, tuples);
        return count == null ? 0L : count;
    }

    /**
     * 获取元素的 score(ZSCORE)
     */
    public Double zScore(String key, Object value) {
        return redisTemplate.opsForZSet().score(key, value);
    }

    /**
     * 获取元素的正序排名(ZRANK,0 为最小 score)
     */
    public Long zRank(String key, Object value) {
        return redisTemplate.opsForZSet().rank(key, value);
    }

    /**
     * 获取元素的逆序排名(ZREVRANK,0 为最大 score)
     */
    public Long zReverseRank(String key, Object value) {
        return redisTemplate.opsForZSet().reverseRank(key, value);
    }

    /**
     * 按排名范围获取元素,正序(ZRANGE),0 到 -1 为全部
     */
    public Set<Object> zRange(String key, long start, long end) {
        return redisTemplate.opsForZSet().range(key, start, end);
    }

    /**
     * 获取全部元素,正序
     */
    public Set<Object> zRange(String key) {
        return zRange(key, 0, -1);
    }

    /**
     * 按排名范围获取元素并转换类型,正序
     */
    public <T> List<T> zRange(String key, long start, long end, Class<T> clazz) {
        Set<Object> raw = zRange(key, start, end);
        if (raw == null) return Collections.emptyList();
        List<T> result = new ArrayList<>(raw.size());
        raw.forEach(obj -> result.add(convert(obj, clazz)));
        return result;
    }

    /**
     * 按排名范围获取元素,逆序(ZREVRANGE)
     */
    public Set<Object> zReverseRange(String key, long start, long end) {
        return redisTemplate.opsForZSet().reverseRange(key, start, end);
    }

    /**
     * 按排名范围获取带 score 的元素(ZRANGE WITHSCORES),正序
     */
    public Set<ZSetOperations.TypedTuple<Object>> zRangeWithScores(String key, long start, long end) {
        return redisTemplate.opsForZSet().rangeWithScores(key, start, end);
    }

    /**
     * 按排名范围获取带 score 的元素,逆序
     */
    public Set<ZSetOperations.TypedTuple<Object>> zReverseRangeWithScores(String key, long start, long end) {
        return redisTemplate.opsForZSet().reverseRangeWithScores(key, start, end);
    }

    /**
     * 按 score 范围获取元素(ZRANGEBYSCORE),正序
     */
    public Set<Object> zRangeByScore(String key, double min, double max) {
        return redisTemplate.opsForZSet().rangeByScore(key, min, max);
    }

    /**
     * 按 score 范围获取元素(带分页偏移),正序
     */
    public Set<Object> zRangeByScore(String key, double min, double max, long offset, long count) {
        return redisTemplate.opsForZSet().rangeByScore(key, min, max, offset, count);
    }

    /**
     * 按 score 范围获取元素并转换类型,正序
     */
    public <T> List<T> zRangeByScore(String key, double min, double max, Class<T> clazz) {
        Set<Object> raw = zRangeByScore(key, min, max);
        if (raw == null) return Collections.emptyList();
        List<T> result = new ArrayList<>(raw.size());
        raw.forEach(obj -> result.add(convert(obj, clazz)));
        return result;
    }

    /**
     * 按 score 范围获取元素(ZREVRANGEBYSCORE),逆序
     */
    public Set<Object> zReverseRangeByScore(String key, double min, double max) {
        return redisTemplate.opsForZSet().reverseRangeByScore(key, min, max);
    }

    /**
     * 按 score 范围获取带 score 的元素,正序
     */
    public Set<ZSetOperations.TypedTuple<Object>> zRangeByScoreWithScores(String key, double min, double max) {
        return redisTemplate.opsForZSet().rangeByScoreWithScores(key, min, max);
    }

    /**
     * 元素 score 自增(ZINCRBY)
     *
     * @return 自增后的 score
     */
    public double zIncr(String key, Object value, double delta) {
        Double score = redisTemplate.opsForZSet().incrementScore(key, value, delta);
        return score == null ? 0.0 : score;
    }

    /**
     * 删除指定元素(ZREM),支持多个
     *
     * @return 实际删除数量
     */
    public long zRem(String key, Object... values) {
        Long count = redisTemplate.opsForZSet().remove(key, values);
        return count == null ? 0L : count;
    }

    /**
     * 按 score 范围删除元素(ZREMRANGEBYSCORE)
     *
     * @return 实际删除数量
     */
    public long zRemByScore(String key, double min, double max) {
        Long count = redisTemplate.opsForZSet().removeRangeByScore(key, min, max);
        return count == null ? 0L : count;
    }

    /**
     * 按排名范围删除元素(ZREMRANGEBYRANK)
     *
     * @return 实际删除数量
     */
    public long zRemByRank(String key, long start, long end) {
        Long count = redisTemplate.opsForZSet().removeRange(key, start, end);
        return count == null ? 0L : count;
    }

    /**
     * 获取有序集合成员数量(ZCARD)
     */
    public long zCard(String key) {
        Long size = redisTemplate.opsForZSet().size(key);
        return size == null ? 0L : size;
    }

    /**
     * 统计 score 在 [min, max] 范围内的成员数量(ZCOUNT)
     */
    public long zCount(String key, double min, double max) {
        Long count = redisTemplate.opsForZSet().count(key, min, max);
        return count == null ? 0L : count;
    }
}
最近更新