Skip to content

介绍

在redis和caffenie基础上进行二次封装和扩展,更加便利的使用缓存技术。

  1. redis 序列化扩展了msgpack支持
  2. jetcache 序列化扩展msgpack和Jackson支持
  3. 扩展了幂等性和限流功能,限流功能其核心代码来源于 RuoYi-Vue-Plus 代码

依赖库

名称描述
slf4j-api
spring-boot-starter-data-redis
jackson-dataformat-msgpack
jetcache-starter-redis-lettuce
spring-boot-starter-web
spring-boot-starter-aop
fastjson
commons-pool2
zebra-common-util
caffeine

快速开始

引入

xml
<dependency>
		<groupId>io.github.zhanghongbin</groupId>
		<artifactId>zebra-spring-boot-starter-cache</artifactId>
</dependency>

序列化

提供两种序列化方式:Jackson 和 msgpack。

  1. redis序列化默认使用Jackson库进行序列化,要使用msgpack序列化配置如下:
yml
zebra:
  cache:
    serializer:
	  msgpack:
	    enabled: true
  1. jetcache 序列化配置如下:

jetcache具体使用方式参见官网文档,默认为java序列化方式,并扩展了以下两种序列化方式:
msgpack 为 bean:msgPackEncoder和bean:msgPackDecoder
Jackson 为 bean:jacksonEncoder和bean:jacksonDecoder

yml
jetcache:
  remote:
    default:
	  valueEncoder: bean:msgPackEncoder
	  valueDecoder: bean:msgPackDecoder

注: 建议采用一致的序列化方式。

接口幂等性

以注解方式提供给使用者,在controller类的方法上增加 @Idempotent 如下:

java
   /**
     * 新增客户端管理
     */
    @SaCheckPermission("system:client:add")
    @Log(title = "新增操作", module = "客户端管理", businessType = BusinessType.INSERT)
    @Idempotent
    @PostMapping()
    public boolean add(@Validated(AddGroup.class) @RequestBody SysClientQuery bo) {
        return sysClientService.insertByBo(bo);
    }

@Idempotent 参数如下

名称描述是否必填
timeout幂等的超时时间,默认为 1 秒
timeUnit时间单位,默认为 SECONDS 秒
keyResolver使用的 Key 解析器,也就是存放到redis中的key,默认为DefaultIdempotentKeyResolver.class
keyArg使用的 Key 参数, 如果keyResolver 使用ExpressionIdempotentKeyResolver.class 此参数需要填写

keyResolver解析器,框架提供 DefaultIdempotentKeyResolver和ExpressionIdempotentKeyResolver两种,如果需要自定义解析器需要实现 IdempotentKeyResolver 接口

流量控制

以注解方式提供给使用者,在controller类的方法上增加 @RateLimiter 如下:

java
  /**
     * 邮箱验证码
     *
     * @param email 邮箱
     */
    @Validated
    @RateLimiter(key = "#email", time = 60, count = 1)
    @GetMapping("/email/code")
    public void getEmailCode(@NotBlank(message = "邮箱不能为空") String email) {
        if (mailCaptchaHandler != null) {
            boolean flag = mailCaptchaHandler.build(email.trim());
            if (!flag) {
                R.failed("邮箱验证码发送异常,请稍后再试!");
            }
        } else {
            R.failed("当前系统没有开启邮箱功能!");
        }
    }

@RateLimiter 参数如下

名称描述是否必填
key限流key,支持使用Spring el表达式来动态获取方法上的参数值, 格式类似于 #code.id 或 #code
time限流时间,单位秒,默认为60秒
count限流次数,默认为100
limitType限流类型,默认为 LimitType.DEFAULT 全局 , LimitType.IP ip限制

jetcache集成

采用 jetcache 开源库,配置如下: ${spring.redis.password} 代表引用redis配置密码

yml
jetcache:
	areaInCacheName: false
	local:
	default:
		type: caffeine
		keyConvertor: fastjson2
	remote:
		default:
			type: redis.lettuce
			keyConvertor: fastjson2
			valueEncoder: bean:msgPackEncoder
			valueDecoder: bean:msgPackDecoder
			uri: redis://${spring.redis.password}@${spring.redis.host}:${spring.redis.port}/

在 application 启动类上增加 @EnableMethodCache(basePackages = "") 注解 ,其它注解使用看相关文档 https://github.com/alibaba/jetcache

实用工具

  1. CacheUtil 对 jetcache的封装,提供以下静态方法
名称参数描述
<T> Cache<String, T> getCache(String cacheName)缓存名称根据缓存名称获取缓存对象
<T> T get(String cacheName, Object key)缓存名称,key值根据缓存和key获取缓存值
<T> T get(String cacheName, Object key)缓存名称,key值根据缓存和key获取缓存值
put(String cacheName, Object key, Object value, Duration duration, CacheType cacheType)缓存名称,key值,内容,有效期,缓存类型放入缓存
put(String cacheName, Object key, Object value, CacheType cacheType)缓存名称,key值,内容,缓存类型放入缓存
remove(String cacheName, Object key)缓存名称,key值,删除缓存内容
  1. RedisUtil 对 redis的封装,提供以下静态方法
java
package org.zebra.cache.util;

import cn.hutool.extra.spring.SpringUtil;
import org.springframework.data.redis.core.RedisTemplate;
import org.zebra.cache.ext.mq.RedisMessage;

import java.time.Duration;
import java.util.*;

/**
 * redis 工具类
 *
 */
public final class RedisUtil {

    private RedisUtil() {}

    public static RedisTemplate<String, Object> getRedisTemplate() {
        return SpringUtil.getBean("redisTemplate");
    }

    public static StringRedisTemplate getStringRedisTemplate() {
        return SpringUtil.getBean(StringRedisTemplate.class);
    }

    public static RedisTemplate<String, Object> createRedisTemplate(
            RedisSerializer<?> keySerializer, RedisSerializer<?> valueSerializer) {
        RedisTemplate<String, Object> template = new RedisTemplate();
        template.setConnectionFactory(SpringUtil.getBean(RedisConnectionFactory.class));
        template.setKeySerializer(keySerializer);
        template.setHashKeySerializer(keySerializer);
        template.setValueSerializer(valueSerializer);
        template.setHashValueSerializer(valueSerializer);
        return template;
    }

    public static StringRedisTemplate createStringRedisTemplate() {
        StringRedisTemplate stringTemplate = new StringRedisTemplate();
        stringTemplate.setConnectionFactory(SpringUtil.getBean(RedisConnectionFactory.class));
        stringTemplate.afterPropertiesSet();
        return stringTemplate;
    }

    /**
     * 缓存基本的对象,Integer、String、实体类等
     *
     * @param redisTemplate redisTemplate
     * @param key           缓存的键值
     * @param <T>           value  泛型
     * @param value
     */
    public static <T> void setCacheObject(RedisTemplate redisTemplate, final String key, final T value) {
        redisTemplate.opsForValue().set(key, value);
    }

    /**
     * 缓存基本的对象,保留当前对象 TTL 有效期
     *
     * @param redisTemplate redisTemplate
     * @param key           key
     * @param <T>           value
     * @param value
     */
    public static <T> void setCacheObjectKeepTtl(RedisTemplate redisTemplate, final String key, final T value) {
        redisTemplate.opsForValue().set(key, value, 0);
    }

    /**
     * 如果不存在则设置 并返回 true 如果存在则返回 false
     *
     * @param redisTemplate redisTemplate
     * @param key           缓存的键值
     * @param <T>           value 缓存的值
     * @param value
     * @return 成功或失败
     */
    public static <T> boolean setObjectIfAbsent(RedisTemplate redisTemplate, final String key, final T value) {
        return redisTemplate.opsForValue().setIfAbsent(key, value);
    }

    /**
     * 如果存在则设置 并返回 true 如果存在则返回 false
     *
     * @param redisTemplate redisTemplate
     * @param key           缓存的键值
     * @param value         缓存的值
     * @param <T>           泛型
     * @return 成功或失败
     */
    public static <T> boolean setObjectIfPresent(RedisTemplate redisTemplate, final String key, final T value) {
        return redisTemplate.opsForValue().setIfPresent(key, value);
    }

    /**
     * 缓存基本的对象,Integer、String、实体类等
     *
     * @param redisTemplate redisTemplate
     * @param key           缓存的键值
     * @param value         缓存的值
     * @param <T>           泛型
     * @param duration      时间
     */
    public static <T> void setCacheObject(
            RedisTemplate redisTemplate, final String key, final T value, final Duration duration) {
        redisTemplate.opsForValue().set(key, value, duration);
    }

    /**
     * 如果不存在则设置 并返回 true 如果存在则返回 false
     *
     * @param redisTemplate redisTemplate
     * @param key           缓存的键值
     * @param <T>           缓存的值
     * @param value
     * @param duration      超时时间
     * @return 成功或失败
     */
    public static <T> boolean setObjectIfAbsent(
            RedisTemplate redisTemplate, final String key, final T value, final Duration duration) {
        return redisTemplate.opsForValue().setIfAbsent(key, value, duration);
    }

    /**
     * 如果存在则设置 并返回 true 如果存在则返回 false
     *
     * @param redisTemplate redisTemplate
     * @param key           缓存的键值
     * @param <T>           缓存的值
     * @param duration      超时时间
     * @param value
     * @return 成功或失败
     */
    public static <T> boolean setObjectIfPresent(
            RedisTemplate redisTemplate, final String key, final T value, final Duration duration) {
        return redisTemplate.opsForValue().setIfPresent(key, value, duration);
    }

    /**
     * 获取缓存对象
     *
     * @param redisTemplate redisTemplate
     * @param key           key
     * @param <T>           泛型
     * @return 泛型对象
     */
    public static <T> T getCacheObject(RedisTemplate redisTemplate, final String key) {
        return (T) redisTemplate.opsForValue().get(key);
    }

    /**
     * 删除单个对象
     *
     * @param redisTemplate redisTemplate
     * @param key           缓存的键值
     * @return true ,false
     */
    public static boolean delete(RedisTemplate redisTemplate, final String key) {
        return redisTemplate.delete(key);
    }

    /**
     * 删除集合对象
     *
     * @param redisTemplate redisTemplate
     * @param collection    多个对象
     */
    public static void delete(RedisTemplate redisTemplate, final Collection<String> collection) {
        redisTemplate.delete(collection);
    }

    /**
     * 检查缓存对象是否存在
     *
     * @param redisTemplate redisTemplate
     * @param key           缓存的键值
     * @return true ,false
     */
    public static boolean exist(RedisTemplate redisTemplate, final String key) {
        return redisTemplate.hasKey(key);
    }

    public static boolean exist(RedisTemplate redisTemplate, String... keys) {
        Long number = redisTemplate.countExistingKeys(Arrays.asList(keys));
        return number == null ? false : keys.length == number.intValue() ? true : false;
    }

    /**
     * 设置有效时间
     *
     * @param redisTemplate redisTemplate
     * @param key           Redis键
     * @param duration      超时时间
     * @return true=设置成功;false=设置失败
     */
    public static boolean expire(RedisTemplate redisTemplate, final String key, final Duration duration) {
        return redisTemplate.expire(key, duration);
    }

    /**
     * 设置 key 的过期时间到指定的日期
     *
     * @param redisTemplate redisTemplate
     * @param key           待修改过期时间的 key
     * @param date          过期时间
     * @return 修改成功返回 true
     */
    public static boolean expireAt(RedisTemplate redisTemplate, String key, Date date) {
        return Boolean.TRUE.equals(redisTemplate.expireAt(key, date));
    }

    /**
     * 获得key剩余存活时间
     *
     * @param redisTemplate redisTemplate
     * @param key           缓存键值
     * @return 剩余存活时间 秒
     */
    public static long getTimeToLive(RedisTemplate redisTemplate, final String key) {
        return redisTemplate.getExpire(key);
    }

    /**
     * 获取所有符合指定表达式的 key
     *
     * @param redisTemplate redisTemplate
     * @param pattern       表达式
     * @return key集合
     */
    public static Set<String> keys(RedisTemplate redisTemplate, String pattern) {
        return redisTemplate.keys(pattern);
    }

    // -------------------------- list command start --------------------------------

    /**
     * 获取指定 list 指定索引位置的元素
     *
     * @param redisTemplate redisTemplate
     * @param key           list 的 key
     * @param index         索引位置,0 表示第一个元素,负数索引用于指定从尾部开始计数,-1 表示最后一个元素,-2 倒数第二个
     * @param <T>           泛型对象
     * @return 返回对应索引位置的元素,不存在时为 null
     */
    public static <T> T lIndex(RedisTemplate redisTemplate, String key, long index) {
        return (T) redisTemplate.opsForList().index(key, index);
    }

    /**
     * 获取指定 list 的元素个数即长度
     *
     * @param redisTemplate redisTemplate
     * @param key           list 的 key
     * @return 返回 list 的长度,当 list 不存在时返回 0
     */
    public static long lSize(RedisTemplate redisTemplate, String key) {
        return redisTemplate.opsForList().size(key);
    }

    /**
     * 以原子方式返回并删除列表的第一个元素,例如列表包含元素 "a", "b", "c" LPOP 操作将返回 ”a“ 并将其删除,list 中元素变为 ”b“, "c"
     *
     * @param redisTemplate redisTemplate
     * @param key           list 的 key
     * @param <T>           泛型对象
     * @return 返回弹出的元素
     */
    public static <T> T lPop(RedisTemplate redisTemplate, String key) {
        return (T) redisTemplate.opsForList().leftPop(key);
    }

    /**
     * 以原子方式返回并删除列表的多个元素
     *
     * @param redisTemplate redisTemplate
     * @param key           list 的 key
     * @param count         弹出的个数
     * @param <T>           泛型对象
     * @return 返回弹出的元素列表,key 不存在时为 null
     */
    public static <T> List<T> lPop(RedisTemplate redisTemplate, String key, long count) {
        return (List<T>) redisTemplate.opsForList().leftPop(key, count);
    }

    /**
     * 该命令返回 list 匹配元素的索引。它会从头到尾扫描列表,寻找 “element” 的第一个匹配项。
     *
     * @param redisTemplate redisTemplate
     * @param key           list 的 key
     * @param element       查找的元素
     * @param <T>           泛型对象
     * @return 指定元素正向第一个匹配项的索引,如果找不到,返回 null
     */
    public static <T> Long lIndexOf(RedisTemplate redisTemplate, String key, T element) {
        return redisTemplate.opsForList().indexOf(key, element);
    }

    /**
     * 将指定的元素插入 list 的头部,若 list 不存在,则先指向创建一个空的 list
     *
     * @param redisTemplate redisTemplate
     * @param key           list 的 key
     * @param elements      插入的元素
     * @param <T>           泛型对象
     * @return 插入后的 list 长度
     */
    public static <T> long lPush(RedisTemplate redisTemplate, String key, T... elements) {
        return redisTemplate.opsForList().leftPushAll(key, elements);
    }

    /**
     * 获取 list 指定 offset 间的元素。
     *
     * @param redisTemplate redisTemplate
     * @param key           list 的 key
     * @param start         begin offset, 从 0 开始,0 表示列表第一个元素,也可以为负数,表示从 list 末尾开始的偏移量, -1
     *                      是列表最后第一个元素
     * @param end           end offset,值规则 同 start
     * @param <T>           泛型对象
     * @return 元素集合
     */
    public static <T> List<T> lRange(RedisTemplate redisTemplate, String key, long start, long end) {
        return (List<T>) redisTemplate.opsForList().range(key, start, end);
    }

    /**
     * 删除 list 中的元素
     *
     * @param redisTemplate redisTemplate
     *                      <p>
     *                      count &gt; 0: 从 list 头部向尾部查找并删除 n 个和指定值相等的元素,n 为 count
     *                      count &lt; 0: 从 list 尾部向头部查找并删除 n 个和指定值相等的元素,n 为 count 的绝对值
     *                      count = 0: 删除 list 中所有和指定值相等的元素
     * @param key           list 的 key
     * @param count         删除的数量以及规则
     * @param value         待删除的元素值
     * @param <T>           泛型对象
     * @return 移除元素的数量
     */
    public static <T> long lRemove(RedisTemplate redisTemplate, String key, long count, T value) {
        return redisTemplate.opsForList().remove(key, count, value);
    }

    /**
     * 将 list 指定 index 位置的元素设置为当前值
     *
     * @param redisTemplate redisTemplate
     * @param key           list 的 key
     * @param index         索引位置,0 表示第一个元素,负数索引用于指定从尾部开始计数,-1 表示最后一个元素,-2 倒数第二个
     * @param value
     * @param <T>           泛型对象
     */
    public static <T> void lSet(RedisTemplate redisTemplate, String key, long index, T value) {
        redisTemplate.opsForList().set(key, index, value);
    }

    /**
     * 裁剪 list,只保留 start 到 end 之间的元素值,包含 start 和 end
     *
     * @param redisTemplate redisTemplate
     * @param key           list 的 key
     * @param start         开始索引位置,0 表示第一个元素,负数索引用于指定从尾部开始计数,-1 表示最后一个元素,-2 倒数第二个
     * @param end           结束的索引位置
     */
    public static void lTrim(RedisTemplate redisTemplate, String key, long start, long end) {
        redisTemplate.opsForList().trim(key, start, end);
    }

    /**
     * 以原子方式返回并删除列表的最后一个元素。
     *
     * @param redisTemplate redisTemplate
     *                      <p>
     *                      例如 list 包含元素 "a"、"b"、"c", RPOP 操作将返回 ”c“ 并将其删除,list 中元素变为 ”a“, "b"
     * @param key           list 的 key
     * @param <T>           泛型对象
     * @return 弹出的元素
     */
    public static <T> T lRightPop(RedisTemplate redisTemplate, String key) {
        return (T) redisTemplate.opsForList().rightPop(key);
    }

    /**
     * 从 list 尾部,以原子方式返回并删除列表中指定数量的元素。
     *
     * @param redisTemplate redisTemplate
     * @param key           list 的 key
     * @param count         待弹出的元素数量
     * @param <T>           泛型对象
     * @return 弹出的元素集合
     */
    public static <T> List<T> lRightPop(RedisTemplate redisTemplate, String key, long count) {
        return (List<T>) redisTemplate.opsForList().rightPop(key, count);
    }

    /**
     * 将指定的值插入 list 的尾部,若 list 不存在,则先指向创建一个空的 list
     *
     * @param redisTemplate redisTemplate
     * @param key           list 的 key
     * @param values        插入的元素
     * @param <T>           泛型对象
     * @return 插入后的 list 长度
     */
    public static <T> long LRightPush(RedisTemplate redisTemplate, String key, T... values) {
        return redisTemplate.opsForList().rightPushAll(key, values);
    }

    // ---------------------- hash command start ---------------

    /**
     * 删除指定 hash 的 fields
     *
     * @param redisTemplate redisTemplate
     * @param key           hash 的 key
     * @param fields        hash 元素的 field 集合
     * @return 删除的 field 数量
     */
    public static long hDelete(RedisTemplate redisTemplate, String key, String... fields) {
        return redisTemplate.opsForHash().delete(key, fields);
    }

    /**
     * 判断指定 hash 的 指定 field 是否存在
     *
     * @param redisTemplate redisTemplate
     * @param key           hash 的 key
     * @param field         元素的 field
     * @return 存在返回 {@code true}, 否则返回 {@code false}
     */
    public static boolean hExists(RedisTemplate redisTemplate, String key, String field) {
        return redisTemplate.opsForHash().hasKey(key, field);
    }

    /**
     * 获取 hash 中的指定 field 对应的 value 值
     *
     * @param redisTemplate redisTemplate
     * @param key           hash 的 key
     * @param field         元素的 field
     * @param <T>           泛型对象
     * @return 对象
     */
    public static <T> T hGet(RedisTemplate redisTemplate, String key, String field) {
        return (T) redisTemplate.opsForHash().get(key, field);
    }

    /**
     * 获取 hash 中所有的 fields 和 values, 并已键值对的方式返回
     *
     * @param redisTemplate redisTemplate
     * @param <T>           泛型对象
     * @param key           hash 的 key
     * @return map 对象
     */
    public static <T> Map<String, T> hGetAll(RedisTemplate redisTemplate, String key) {
        return (Map) redisTemplate.opsForHash().entries(key);
    }

    /**
     * 对 hash 中指定的 field 进行自增若 field 不存在则,先设置为 0 再进行自增,若 hash 不存在则先创建 hash 再进行上述步骤
     *
     * @param redisTemplate redisTemplate
     * @param key           key
     * @param field         field
     * @param delta         自增步长
     * @return 自增后的 value 值
     */
    public static long hIncrBy(RedisTemplate redisTemplate, String key, String field, long delta) {
        return redisTemplate.opsForHash().increment(key, field, delta);
    }

    public static Long hIncrBy(RedisTemplate redisTemplate, String key, String field) {
        return hIncrBy(redisTemplate, key, field, 1);
    }

    /**
     * 对 hash 中指定的 field 进行自增若 field 不存在则,先设置为 0 再进行自增,若 hash 不存在则先创建 hash 再进行上述步骤
     *
     * @param redisTemplate redisTemplate
     * @param key           key
     * @param field         field
     * @param delta         自增步长
     * @return 自增后的 value 值
     */
    public static double hIncrByFloat(RedisTemplate redisTemplate, String key, String field, double delta) {
        return redisTemplate.opsForHash().increment(key, field, delta);
    }

    /**
     * 返回 hash 中的所有 fields
     *
     * @param redisTemplate redisTemplate
     * @param key           hash 的 key
     * @return Set of fields in hash
     */
    public static Set<String> hKeys(RedisTemplate redisTemplate, String key) {
        return (Set) redisTemplate.opsForHash().keys(key);
    }

    /**
     * 返回 hash 中 fields 的数量
     *
     * @param redisTemplate redisTemplate
     * @param key           hash 的 key
     * @return fields size
     */
    public static long hSize(RedisTemplate redisTemplate, String key) {
        return redisTemplate.opsForHash().size(key);
    }

    /**
     * 返回 hash 中指定 fields 的值集合
     *
     * @param redisTemplate redisTemplate
     * @param key           hash 的 key
     * @param <T>           泛型对象
     * @return fields value list, 按传入的 fields 顺序排列
     */
    public static <T> List<T> hMGet(RedisTemplate redisTemplate, String key, String... fields) {
        return (List<T>) redisTemplate.opsForHash().multiGet(key, Arrays.asList(fields));
    }

    /**
     * 修改 hash 中的 field 的值,有则覆盖,无则添加
     *
     * @param redisTemplate redisTemplate
     * @param key           hash 的 key
     * @param field         field
     * @param value         value
     * @param <T>           泛型对象
     */
    public static <T> void hSet(RedisTemplate redisTemplate, String key, String field, T value) {
        redisTemplate.opsForHash().put(key, field, value);
    }

    /**
     * 修改 hash 中的 field 的值,有则不进行操作,无则添加
     *
     * @param redisTemplate redisTemplate
     * @param key           hash 的 key
     * @param field         field
     * @param value         value
     * @param <T>           泛型对象
     */
    public static <T> void hSetNx(RedisTemplate redisTemplate, String key, String field, T value) {
        redisTemplate.opsForHash().putIfAbsent(key, field, value);
    }

    /**
     * 返回 hash 中的所有 values
     *
     * @param redisTemplate redisTemplate
     * @param key           hash 的 key
     * @return List of fields in hash
     */
    public static <T> List<T> hValues(RedisTemplate redisTemplate, String key) {
        return (List<T>) redisTemplate.opsForHash().values(key);
    }

    // -------------------------- hash command end --------------------------------

    // -------------------------- Set command start --------------------------------

    /**
     * 将指定的 member 添加到 Set 中,如果 Set 中已有该 member 则忽略。如果 Set 不存在,则先创建一个新的 Set,再进行添加
     * @param redisTemplate redisTemplate
     * <p>
     * Time complexity O(1)
     *
     * @param key     Set 的 key
     * @param members 添加的成员
     * @return 添加到集合中的元素数量,不包括集合中已经存在的所有元素
     */
    public static <T> long sAdd(RedisTemplate redisTemplate, String key, T... members) {
        return redisTemplate.opsForSet().add(key, members);
    }

    /**
     * 返回 Set 中的元素数,如果 set 不存在则返回 0
     * @param redisTemplate redisTemplate
     * @param key Set 的 key
     * @return The cardinality (number of elements) of the set
     * @see <a href="https://redis.io/commands/scard/">SCard Command</a>
     */
    public static long sSize(RedisTemplate redisTemplate, String key) {
        return redisTemplate.opsForSet().size(key);
    }

    /**
     * 判断指定的值是否是 Set 中的元素
     * @param redisTemplate redisTemplate
     * <p>
     * Time complexity O(1)
     *
     * @param key   Set 的 key
     * @param value 待判断的值
     * @return 如果是 Set 中的元素返回{@code true}, 否则返回{@code false}
     */
    public static <T> boolean sIsMember(RedisTemplate redisTemplate, String key, T value) {
        return redisTemplate.opsForSet().isMember(key, value);
    }

    /**
     * 获取 Set 中的所有元素
     * @param redisTemplate redisTemplate
     * <p>
     * Time complexity O(N)
     *
     * @param key Set 的 key
     * @return Set 中的所有元素
     */
    public static <T> Set<T> sMembers(RedisTemplate redisTemplate, String key) {
        return (Set<T>) redisTemplate.opsForSet().members(key);
    }

    /**
     * 判断指定的值是否是 Set 中的元素
     * @param redisTemplate redisTemplate
     * <p>
     * Time complexity O(N)
     *
     * @param key    Set 的 key
     * @param values 待判断的值集合
     * @return 一个 Map, key 为待判断的值,value 为结果
     */
    public static <T> Map<T, Boolean> sIsMember(RedisTemplate redisTemplate, String key, T... values) {
        return (Map<T, Boolean>) redisTemplate.opsForSet().isMember(key, values);
    }

    /**
     * 随机从 Set 中删除一个元素,并返回它,如果 Set 为空,则返回 null
     * @param redisTemplate redisTemplate
     * <p>
     * Time complexity O(1)
     *
     * @param key Set 的 key
     * @return 弹出的元素,或者 null
     */
    public static <T> T sPop(RedisTemplate redisTemplate, String key) {
        return (T) redisTemplate.opsForSet().pop(key);
    }

    /**
     * 随机从 Set 中返回一个元素,但不删除,如果 Set 为空,则返回 null
     * @param redisTemplate redisTemplate
     * <p>
     * Time complexity O(1)
     *
     * @param key Set 的 key
     * @return 随机选中的元素或者 null
     */
    public static <T> T sRandMember(RedisTemplate redisTemplate, String key) {
        return (T) redisTemplate.opsForSet().randomMember(key);
    }

    /**
     * 随机从 Set 中返回 count 个元素,但不删除,如果 Set 为空,则返回 null
     * @param redisTemplate redisTemplate
     * <p>
     * Time complexity O(1)
     *
     * @param key   Set 的 key
     * @param count 随机返回的元素数量
     * @return 随机选中的元素或者 null
     */
    public static <T> Set<T> sRandMember(RedisTemplate redisTemplate, String key, long count) {
        return (Set<T>) redisTemplate.opsForSet().distinctRandomMembers(key, count);
    }

    /**
     * 从 Set 中删除指定的 member,如果给的值不是 Set 的 member 则不进行操作
     * @param redisTemplate redisTemplate
     * <p>
     * Time complexity O(1)
     *
     * @param key     Set 的 key
     * @param members 待删除的成员
     * @return The number of members that were removed from the set, not including
     * non-existing members
     */
    public static <T> long sRemove(RedisTemplate redisTemplate, String key, T... members) {
        StringRedisTemplate stringTemplate = new StringRedisTemplate();
        return redisTemplate.opsForSet().remove(key, members);
    }
}