springboot整合redis多数据源(附带RedisUtil)

单数据源RedisUtil(静态)

单数据源RedisUtil,我这里implements ApplicationContextAware在setApplicationContext注入redisTemplate,工具类可以直接类RedisUtil.StringOps.get()使用

package com.vehicle.manager.core.util;import com.alibaba.fastjson.JSON;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.data.redis.RedisSystemException;
import org.springframework.data.redis.connection.DataType;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.connection.RedisStringCommands;
import org.springframework.data.redis.connection.ReturnType;
import org.springframework.data.redis.connection.jedis.JedisConnection;
import org.springframework.data.redis.connection.lettuce.LettuceConnection;
import org.springframework.data.redis.core.*;
import org.springframework.data.redis.core.types.Expiration;
import org.springframework.stereotype.Component;import java.nio.charset.StandardCharsets;
import java.time.Instant;
import java.util.*;
import java.util.Map.Entry;
import java.util.concurrent.TimeUnit;/***  统一说明一: 方法中的key、 value都不能为null。*  统一说明二: 不能跨数据类型进行操作, 否者会操作失败/操作报错。*             如: 向一个String类型的做Hash操作,会失败/报错......等等* @author zr 2024/3/4*/@Slf4j
@Component
@SuppressWarnings("unused")
public class RedisUtil implements ApplicationContextAware {/*** 使用StringRedisTemplate(,其是RedisTemplate的定制化升级)*/private static StringRedisTemplate redisTemplate;@Overridepublic void setApplicationContext(ApplicationContext applicationContext) throws BeansException {RedisUtil.redisTemplate = applicationContext.getBean(StringRedisTemplate.class);}/*** key相关操作** @date 2020/3/7 16:54:25*/public static class KeyOps {/*** 根据key, 删除redis中的对应key-value* <p>* 注: 若删除失败, 则返回false。* <p>* 若redis中,不存在该key, 那么返回的也是false。* 所以,不能因为返回了false,就认为redis中一定还存* 在该key对应的key-value。** @param key 要删除的key* @return 删除是否成功* @date 2020/3/7 17:15:02*/public static boolean delete(String key) {log.info("delete(...) => key -> {}", key);// 返回值只可能为true/false, 不可能为nullBoolean result = redisTemplate.delete(key);log.info("delete(...) => result -> {}", result);if (result == null) {throw new RedisOpsResultIsNullException();}return result;}/*** 根据keys, 批量删除key-value* <p>* 注: 若redis中,不存在对应的key, 那么计数不会加1, 即:* redis中存在的key-value里,有名为a1、a2的key,* 删除时,传的集合是a1、a2、a3,那么返回结果为2。** @param keys 要删除的key集合* @return 删除了的key-value个数* @date 2020/3/7 17:48:04*/public static long delete(Collection<String> keys) {log.info("delete(...) => keys -> {}", keys);Long count = redisTemplate.delete(keys);log.info("delete(...) => count -> {}", count);if (count == null) {throw new RedisOpsResultIsNullException();}return count;}/*** 将key对应的value值进行序列化,并返回序列化后的value值。* <p>* 注: 若不存在对应的key, 则返回null。* 注: dump时,并不会删除redis中的对应key-value。* 注: dump功能与restore相反。** @param key 要序列化的value的key* @return 序列化后的value值* @date 2020/3/8 11:34:13*/public static byte[] dump(String key) {log.info("dump(...) =>key -> {}", key);byte[] result = redisTemplate.dump(key);log.info("dump(...) => result -> {}", result);return result;}/*** 将给定的value值,反序列化到redis中, 形成新的key-value。** @param key        value对应的key* @param value      要反序列的value值。*                   注: 这个值可以由{@link this#dump(String)}获得* @param timeToLive 反序列化后的key-value的存活时长* @param unit       timeToLive的单位* @throws RedisSystemException 如果redis中已存在同样的key时,抛出此异常* @date 2020/3/8 11:36:45*/public static void restore(String key, byte[] value, long timeToLive, TimeUnit unit) {restore(key, value, timeToLive, unit, false);}/*** 将给定的value值,反序列化到redis中, 形成新的key-value。** @param key     value对应的key* @param value   要反序列的value值。*                注: 这个值可以由{@link this#dump(String)}获得* @param timeout 反序列化后的key-value的存活时长* @param unit    timeout的单位* @param replace 若redis中已经存在了相同的key, 是否替代原来的key-value* @throws RedisSystemException 如果redis中已存在同样的key, 且replace为false时,抛出此异常* @date 2020/3/8 11:36:45*/public static void restore(String key, byte[] value, long timeout, TimeUnit unit, boolean replace) {log.info("restore(...) => key -> {}, value -> {}, timeout -> {}, unit -> {}, replace -> {}",key, value, timeout, unit, replace);redisTemplate.restore(key, value, timeout, unit, replace);}/*** redis中是否存在,指定key的key-value** @param key 指定的key* @return 是否存在对应的key-value* @date 2020/3/8 12:16:46*/public static boolean hasKey(String key) {log.info("hasKey(...) => key -> {}", key);Boolean result = redisTemplate.hasKey(key);log.info("hasKey(...) => result -> {}", result);if (result == null) {throw new RedisOpsResultIsNullException();}return result;}/*** 给指定的key对应的key-value设置: 多久过时* <p>* 注:过时后,redis会自动删除对应的key-value。* 注:若key不存在,那么也会返回false。** @param key     指定的key* @param timeout 过时时间* @param unit    timeout的单位* @return 操作是否成功* @date 2020/3/8 12:18:58*/public static boolean expire(String key, long timeout, TimeUnit unit) {log.info("expire(...) => key -> {}, timeout -> {}, unit -> {}", key, timeout, unit);Boolean result = redisTemplate.expire(key, timeout, unit);log.info("expire(...) => result is -> {}", result);if (result == null) {throw new RedisOpsResultIsNullException();}return result;}/*** 给指定的key对应的key-value设置: 什么时候过时* <p>* 注:过时后,redis会自动删除对应的key-value。* 注:若key不存在,那么也会返回false。** @param key  指定的key* @param date 啥时候过时* @return 操作是否成功* @date 2020/3/8 12:19:29*/public static boolean expireAt(String key, Date date) {log.info("expireAt(...) => key -> {}, date -> {}", key, date);Boolean result = redisTemplate.expireAt(key, date);log.info("expireAt(...) => result is -> {}", result);if (result == null) {throw new RedisOpsResultIsNullException();}return result;}/*** 找到所有匹配pattern的key,并返回该key的结合.* <p>* 提示:若redis中键值对较多,此方法耗时相对较长,慎用!慎用!慎用!** @param pattern 匹配模板。*                注: 常用的通配符有:*                ?    有且只有一个;*                *     >=0哥;* @return 匹配pattern的key的集合。 可能为null。* @date 2020/3/8 12:38:38*/public static Set<String> keys(String pattern) {log.info("keys(...) => pattern -> {}", pattern);Set<String> keys = redisTemplate.keys(pattern);log.info("keys(...) => keys -> {}", keys);return keys;}/*** 将当前数据库中的key对应的key-value,移动到对应位置的数据库中。* <p>* 注:单机版的redis,默认将存储分为16个db, index为0 到 15。* 注:同一个db下,key唯一; 但是在不同db中,key可以相同。* 注:若目标db下,已存在相同的key, 那么move会失败,返回false。** @param key     定位要移动的key-value的key* @param dbIndex 要移动到哪个db* @return 移动是否成功。* 注: 若目标db下,已存在相同的key, 那么move会失败,返回false。* @date 2020/3/8 13:01:00*/public static boolean move(String key, int dbIndex) {log.info("move(...) => key  -> {}, dbIndex -> {}", key, dbIndex);Boolean result = redisTemplate.move(key, dbIndex);log.info("move(...) =>result -> {}", result);if (result == null) {throw new RedisOpsResultIsNullException();}return result;}/*** 移除key对应的key-value的过期时间, 使该key-value一直存在* <p>* 注: 若key对应的key-value,本身就是一直存在(无过期时间的), 那么persist方法会返回false;* 若没有key对应的key-value存在,本那么persist方法会返回false;** @param key 定位key-value的key* @return 操作是否成功* @date 2020/3/8 13:10:02*/public static boolean persist(String key) {log.info("persist(...) => key -> {}", key);Boolean result = redisTemplate.persist(key);log.info("persist(...) => result -> {}", result);if (result == null) {throw new RedisOpsResultIsNullException();}return result;}/*** 获取key对应的key-value的过期时间* <p>* 注: 若key-value永不过期, 那么返回的为-1。* 注: 若不存在key对应的key-value, 那么返回的为-2* 注:若存在零碎时间不足1 SECONDS,则(大体上)四舍五入到SECONDS级别。** @param key 定位key-value的key* @return 过期时间(单位s)* @date 2020/3/8 13:17:35*/public static long getExpire(String key) {Long result = getExpire(key, TimeUnit.SECONDS);return result;}/*** 获取key对应的key-value的过期时间* <p>* 注: 若key-value永不过期, 那么返回的为-1。* 注: 若不存在key对应的key-value, 那么返回的为-2* 注:若存在零碎时间不足1 unit,则(大体上)四舍五入到unit别。** @param key 定位key-value的key* @return 过期时间(单位unit)* @date 2020/3/8 13:17:35*/public static long getExpire(String key, TimeUnit unit) {log.info("getExpire(...) =>key -> {}, unit is -> {}", key, unit);Long result = redisTemplate.getExpire(key, unit);log.info("getExpire(...) => result ->  {}", result);if (result == null) {throw new RedisOpsResultIsNullException();}return result;}/*** 从redis的所有key中,随机获取一个key* <p>* 注: 若redis中不存在任何key-value, 那么这里返回null** @return 随机获取到的一个key* @date 2020/3/8 14:11:43*/public static String randomKey() {String result = redisTemplate.randomKey();log.info("randomKey(...) => result is -> {}", result);return result;}/*** 重命名对应的oldKey为新的newKey* <p>* 注: 若oldKey不存在, 则会抛出异常.* 注: 若redis中已存在与newKey一样的key,* 那么原key-value会被丢弃,* 只留下新的key,以及原来的value* 示例说明: 假设redis中已有 (keyAlpha, valueAlpha) 和 (keyBeta, valueBeat),* 在使用rename(keyAlpha, keyBeta)替换后, redis中只会剩下(keyBeta, valueAlpha)** @param oldKey 旧的key* @param newKey 新的key* @throws RedisSystemException 若oldKey不存在时, 抛出此异常* @date 2020/3/8 14:14:17*/public static void rename(String oldKey, String newKey) {log.info("rename(...) => oldKey -> {}, newKey -> {}", oldKey, newKey);redisTemplate.rename(oldKey, newKey);}/*** 当redis中不存在newKey时, 重命名对应的oldKey为新的newKey。* 否者不进行重命名操作。* <p>* 注: 若oldKey不存在, 则会抛出异常.** @param oldKey 旧的key* @param newKey 新的key* @throws RedisSystemException 若oldKey不存在时, 抛出此异常* @date 2020/3/8 14:14:17*/public static boolean renameIfAbsent(String oldKey, String newKey) {log.info("renameIfAbsent(...) => oldKey -> {}, newKey -> {}", oldKey, newKey);Boolean result = redisTemplate.renameIfAbsent(oldKey, newKey);log.info("renameIfAbsent(...) => result -> {}", result);if (result == null) {throw new RedisOpsResultIsNullException();}return result;}/*** 获取key对应的value的数据类型* <p>* 注: 若redis中不存在该key对应的key-value, 那么这里返回NONE。** @param key 用于定位的key* @return key对应的value的数据类型* @date 2020/3/8 14:40:16*/public static DataType type(String key) {log.info("type(...) => key -> {}", key);DataType result = redisTemplate.type(key);log.info("type(...) => result -> {}", result);return result;}}/*** string相关操作* <p>* 提示: redis中String的数据结构可参考resources/data-structure/String(字符串)的数据结构(示例一).png* redis中String的数据结构可参考resources/data-structure/String(字符串)的数据结构(示例二).png** @date 2020/3/7 16:54:25*/public static class StringOps {/*** 设置key-value* <p>* 注: 若已存在相同的key, 那么原来的key-value会被丢弃。** @param key   key* @param value key对应的value* @date 2020/3/8 15:40:59*/public static void set(String key, String value) {log.info("set(...) => key -> {}, value -> {}", key, value);redisTemplate.opsForValue().set(key, value);}/*** 处理redis中key对应的value值, 将第offset位的值, 设置为1或0。* <p>* 说明: 在redis中,存储的字符串都是以二级制的进行存在的; 如存储的key-value里,值为abc,实际上,* 在redis里面存储的是011000010110001001100011,前8为对应a,中间8为对应b,后面8位对应c。* 示例:这里如果setBit(key, 6, true)的话,就是将索引位置6的那个数,设置值为1,值就变成* 了011000110110001001100011* 追注:offset即index,从0开始。* <p>* 注: 参数value为true, 则设置为1;参数value为false, 则设置为0。* <p>* 注: 若redis中不存在对应的key,那么会自动创建新的。* 注: offset可以超过value在二进制下的索引长度。** @param key    定位value的key* @param offset 要改变的bit的索引* @param value  改为1或0, true - 改为1, false - 改为0* @return set是否成功* @date 2020/3/8 16:30:37*/public static boolean setBit(String key, long offset, boolean value) {log.info("setBit(...) => key -> {}, offset -> {}, value -> {}", key, offset, value);Boolean result = redisTemplate.opsForValue().setBit(key, offset, value);log.info("setBit(...) => result -> {}", result);if (result == null) {throw new RedisOpsResultIsNullException();}return result;}/*** 设置key-value* <p>* 注: 若已存在相同的key, 那么原来的key-value会被丢弃** @param key     key* @param value   key对应的value* @param timeout 过时时长* @param unit    timeout的单位* @date 2020/3/8 15:40:59*/public static void setEx(String key, String value, long timeout, TimeUnit unit) {log.info("setEx(...) => key -> {}, value -> {}, timeout -> {}, unit -> {}",key, value, timeout, unit);redisTemplate.opsForValue().set(key, value, timeout, unit);}/*** 若不存在key时, 向redis中添加key-value, 返回成功/失败。* 若存在,则不作任何操作, 返回false。** @param key   key* @param value key对应的value* @return set是否成功* @date 2020/3/8 16:51:36*/public static boolean setIfAbsent(String key, String value) {log.info("setIfAbsent(...) => key -> {}, value -> {}", key, value);Boolean result = redisTemplate.opsForValue().setIfAbsent(key, value);log.info("setIfAbsent(...) => result -> {}", result);if (result == null) {throw new RedisOpsResultIsNullException();}return result;}/*** 若不存在key时, 向redis中添加一个(具有超时时长的)key-value, 返回成功/失败。* 若存在,则不作任何操作, 返回false。** @param key     key* @param value   key对应的value* @param timeout 超时时长* @param unit    timeout的单位* @return set是否成功* @date 2020/3/8 16:51:36*/public static boolean setIfAbsent(String key, String value, long timeout, TimeUnit unit) {log.info("setIfAbsent(...) => key -> {}, value -> {}, key -> {}, value -> {}", key, value, timeout, unit);Boolean result = redisTemplate.opsForValue().setIfAbsent(key, value, timeout, unit);log.info("setIfAbsent(...) => result -> {}", result);if (result == null) {throw new RedisOpsResultIsNullException();}return result;}/*** 从(redis中key对应的)value的offset位置起(包含该位置),用replaceValue替换对应长度的值。* <p>* 举例说明:* 1.假设redis中存在key-value ("ds", "0123456789"); 调* 用setRange("ds", "abcdefghijk", 3)后, redis中该value值就变为了[012abcdefghijk]* <p>* 2.假设redis中存在key-value ("jd", "0123456789");调* 用setRange("jd", "xyz", 3)后, redis中该value值就变为了[012xyz6789]* <p>* 3.假设redis中存在key-value ("ey", "0123456789");调* 用setRange("ey", "qwer", 15)后, redis中该value值就变为了[0123456789     qwer]* 注:case3比较特殊,offset超过了原value的长度了, 中间就会有一些空格来填充,但是如果在程序* 中直接输出的话,中间那部分空格可能会出现乱码。** @param key          定位key-value的key* @param replaceValue 要替换的值* @param offset       起始位置* @date 2020/3/8 17:04:31*/public static void setRange(String key, String replaceValue, long offset) {log.info("setRange(...) => key -> {}, replaceValue -> {}, offset -> {}", key, replaceValue, offset);redisTemplate.opsForValue().set(key, replaceValue, offset);}/*** 获取到key对应的value的长度。* <p>* 注: 长度等于{@link String#length}。* 注: 若redis中不存在对应的key-value, 则返回值为0.** @param key 定位value的key* @return value的长度* @date 2020/3/8 17:14:30*/public static long size(String key) {log.info("size(...) => key -> {}", key);Long result = redisTemplate.opsForValue().size(key);log.info("size(...) => result -> {}", result);if (result == null) {throw new RedisOpsResultIsNullException();}return result;}/*** 批量设置 key-value* <p>* 注: 若存在相同的key, 则原来的key-value会被丢弃。** @param maps key-value 集* @date 2020/3/8 17:21:19*/public static void multiSet(Map<String, String> maps) {log.info("multiSet(...) => maps -> {}", maps);redisTemplate.opsForValue().multiSet(maps);}/*** 当redis中,不存在任何一个keys时, 才批量设置 key-value, 并返回成功/失败.* 否者,不进行任何操作, 并返回false。* <p>* 即: 假设调用此方法时传入的参数map是这样的: {k1=v1, k2=v2, k3=v3}* 那么redis中, k1、k2、k3都不存在时,才会批量设置key-value;* 否则不会设置任何key-value。* <p>* 注: 若存在相同的key, 则原来的key-value会被丢弃。* <p>* 注:** @param maps key-value 集* @return 操作是否成功* @date 2020/3/8 17:21:19*/public static boolean multiSetIfAbsent(Map<String, String> maps) {log.info("multiSetIfAbsent(...) => maps -> {}", maps);Boolean result = redisTemplate.opsForValue().multiSetIfAbsent(maps);log.info("multiSetIfAbsent(...) => result -> {}", result);if (result == null) {throw new RedisOpsResultIsNullException();}return result;}/*** 增/减 整数* <p>* 注: 负数则为减。* 注: 若key对应的value值不支持增/减操作(即: value不是数字), 那么会* 抛出org.springframework.data.redis.RedisSystemException** @param key       用于定位value的key* @param increment 增加多少* @return 增加后的总值。* @throws RedisSystemException key对应的value值不支持增/减操作时* @date 2020/3/8 17:45:51*/public static long incrBy(String key, long increment) {log.info("incrBy(...) => key -> {}, increment -> {}", key, increment);Long result = redisTemplate.opsForValue().increment(key, increment);log.info("incrBy(...) => result -> {}", result);if (result == null) {throw new RedisOpsResultIsNullException();}return result;}/*** 增/减 浮点数* <p>* 注: 慎用浮点数,会有精度问题。* 如: 先 RedisUtil.StringOps.set("ds", "123");* 然后再RedisUtil.StringOps.incrByFloat("ds", 100.6);* 就会看到精度问题。* 注: 负数则为减。* 注: 若key对应的value值不支持增/减操作(即: value不是数字), 那么会* 抛出org.springframework.data.redis.RedisSystemException** @param key       用于定位value的key* @param increment 增加多少* @return 增加后的总值。* @throws RedisSystemException key对应的value值不支持增/减操作时* @date 2020/3/8 17:45:51*/public static double incrByFloat(String key, double increment) {log.info("incrByFloat(...) => key -> {}, increment -> {}", key, increment);Double result = redisTemplate.opsForValue().increment(key, increment);log.info("incrByFloat(...) => result -> {}", result);if (result == null) {throw new RedisOpsResultIsNullException();}return result;}/*** 追加值到末尾* <p>* 注: 当redis中原本不存在key时,那么(从效果上来看)此方法就等价于{@link this#set(String, String)}** @param key   定位value的key* @param value 要追加的value值* @return 追加后, 整个value的长度* @date 2020/3/8 17:59:21*/public static int append(String key, String value) {log.info("append(...) => key -> {}, value -> {}", key, value);Integer result = redisTemplate.opsForValue().append(key, value);log.info("append(...) => result -> {}", result);if (result == null) {throw new RedisOpsResultIsNullException();}return result;}/*** 根据key,获取到对应的value值** @param key key-value对应的key* @return 该key对应的值。* 注: 若key不存在, 则返回null。* @date 2020/3/8 16:27:41*/public static String get(String key) {log.info("get(...) => key -> {}", key);String result = redisTemplate.opsForValue().get(key);log.info("get(...) => result -> {} ", result);return result;}/*** 对(key对应的)value进行截取, 截取范围为[start, end]* <p>* 注: 若[start, end]的范围不在value的范围中,那么返回的是空字符串 ""* 注: 若value只有一部分在[start, end]的范围中,那么返回的是value对应部分的内容(即:不足的地方,并不会以空来填充)** @param key   定位value的key* @param start 起始位置 (从0开始)* @param end   结尾位置 (从0开始)* @return 截取后的字符串* @date 2020/3/8 18:08:45*/public static String getRange(String key, long start, long end) {log.info("getRange(...) => kry -> {}", key);String result = redisTemplate.opsForValue().get(key, start, end);log.info("getRange(...) => result -> {} ", result);return result;}/*** 给指定key设置新的value, 并返回旧的value* <p>* 注: 若redis中不存在key, 那么此操作仍然可以成功, 不过返回的旧值是null** @param key      定位value的key* @param newValue 要为该key设置的新的value值* @return 旧的value值* @date 2020/3/8 18:14:24*/public static String getAndSet(String key, String newValue) {log.info("getAndSet(...) => key -> {}, value -> {}", key, newValue);String oldValue = redisTemplate.opsForValue().getAndSet(key, newValue);log.info("getAndSet(...) => oldValue -> {}", oldValue);return oldValue;}/*** 获取(key对应的)value在二进制下,offset位置的bit值。* <p>* 注: 当offset的值在(二进制下的value的)索引范围外时, 返回的也是false。* <p>* 示例:* RedisUtil.StringOps.set("akey", "a");* 字符串a, 转换为二进制为01100001* 那么getBit("akey", 6)获取到的结果为false。** @param key    定位value的key* @param offset 定位bit的索引* @return offset位置对应的bit的值(true - 1, false - 0)* @date 2020/3/8 18:21:10*/public static boolean getBit(String key, long offset) {log.info("getBit(...) => key -> {}, offset -> {}", key, offset);Boolean result = redisTemplate.opsForValue().getBit(key, offset);log.info("getBit(...) => result -> {}", result);if (result == null) {throw new RedisOpsResultIsNullException();}return result;}/*** 批量获取value值* <p>* 注: 若redis中,对应的key不存在,那么该key对应的返回的value值为null** @param keys key集* @return value值集合* @date 2020/3/8 18:26:33*/public static List<String> multiGet(Collection<String> keys) {log.info("multiGet(...) => keys -> {}", keys);List<String> result = redisTemplate.opsForValue().multiGet(keys);log.info("multiGet(...) => result -> {}", result);return result;}}/*** hash相关操作* <p>* 提示: 简单的,可以将redis中hash的数据结构看作是 Map<String, Map<HK, HV>>* 提示: redis中String的数据结构可参考resources/data-structure/Hash(散列)的数据结构(示例一).png* redis中String的数据结构可参考resources/data-structure/Hash(散列)的数据结构(示例二).png** @date 2020/3/8 23:39:26*/public static class HashOps {/*** 向key对应的hash中,增加一个键值对entryKey-entryValue* <p>* 注: 同一个hash里面,若已存在相同的entryKey, 那么此操作将丢弃原来的entryKey-entryValue,* 而使用新的entryKey-entryValue。** @param key        定位hash的key* @param entryKey   要向hash中增加的键值对里的 键* @param entryValue 要向hash中增加的键值对里的 值* @date 2020/3/8 23:49:52*/public static void hPut(String key, String entryKey, String entryValue) {log.info("hPut(...) => key -> {}, entryKey -> {}, entryValue -> {}", key, entryKey, entryValue);redisTemplate.opsForHash().put(key, entryKey, entryValue);}/*** 向key对应的hash中,增加maps(即: 批量增加entry集)* <p>* 注: 同一个hash里面,若已存在相同的entryKey, 那么此操作将丢弃原来的entryKey-entryValue,* 而使用新的entryKey-entryValue** @param key  定位hash的key* @param maps 要向hash中增加的键值对集* @date 2020/3/8 23:49:52*/public static void hPutAll(String key, Map<String, String> maps) {log.info("hPutAll(...) => key -> {}, maps -> {}", key, maps);redisTemplate.opsForHash().putAll(key, maps);}/*** 当key对应的hash中,不存在entryKey时,才(向key对应的hash中,)增加entryKey-entryValue* 否者,不进行任何操作** @param key        定位hash的key* @param entryKey   要向hash中增加的键值对里的 键* @param entryValue 要向hash中增加的键值对里的 值* @return 操作是否成功。* @date 2020/3/8 23:49:52*/public static boolean hPutIfAbsent(String key, String entryKey, String entryValue) {log.info("hPutIfAbsent(...) => key -> {}, entryKey -> {}, entryValue -> {}",key, entryKey, entryValue);Boolean result = redisTemplate.opsForHash().putIfAbsent(key, entryKey, entryValue);log.info("hPutIfAbsent(...) => result -> {}", result);if (result == null) {throw new RedisOpsResultIsNullException();}return result;}/*** 获取到key对应的hash里面的对应字段的值* <p>* 注: 若redis中不存在对应的key, 则返回null。* 若key对应的hash中不存在对应的entryKey, 也会返回null。** @param key      定位hash的key* @param entryKey 定位hash里面的entryValue的entryKey* @return key对应的hash里的entryKey对应的entryValue值* @date 2020/3/9 9:09:30*/public static Object hGet(String key, String entryKey) {log.info("hGet(...) => key -> {}, entryKey -> {}", key, entryKey);Object entryValue = redisTemplate.opsForHash().get(key, entryKey);log.info("hGet(...) => entryValue -> {}", entryValue);return entryValue;}/*** 获取到key对应的hash(即: 获取到key对应的Map<HK, HV>)* <p>* 注: 若redis中不存在对应的key, 则返回一个没有任何entry的空的Map(,而不是返回null)。** @param key 定位hash的key* @return key对应的hash。* @date 2020/3/9 9:09:30*/public static Map<Object, Object> hGetAll(String key) {log.info("hGetAll(...) => key -> {}", key);Map<Object, Object> result = redisTemplate.opsForHash().entries(key);log.info("hGetAll(...) => result -> {}", result);return result;}/*** 批量获取(key对应的)hash中的entryKey的entryValue* <p>* 注: 若hash中对应的entryKey不存在,那么返回的对应的entryValue值为null* 注: redis中key不存在,那么返回的List中,每个元素都为null。* 追注: 这个List本身不为null, size也不为0, 只是每个list中的每个元素为null而已。** @param key       定位hash的key* @param entryKeys 需要获取的hash中的字段集* @return hash中对应entryKeys的对应entryValue集* @date 2020/3/9 9:25:38*/public static List<Object> hMultiGet(String key, Collection<Object> entryKeys) {log.info("hMultiGet(...) => key -> {}, entryKeys -> {}", key, entryKeys);List<Object> entryValues = redisTemplate.opsForHash().multiGet(key, entryKeys);log.info("hMultiGet(...) => entryValues -> {}", entryValues);return entryValues;}/*** (批量)删除(key对应的)hash中的对应entryKey-entryValue* <p>* 注: 1、若redis中不存在对应的key, 则返回0;* 2、若要删除的entryKey,在key对应的hash中不存在,在count不会+1, 如:* RedisUtil.HashOps.hPut("ds", "name", "邓沙利文");* RedisUtil.HashOps.hPut("ds", "birthday", "1994-02-05");* RedisUtil.HashOps.hPut("ds", "hobby", "女");* 则调用RedisUtil.HashOps.hDelete("ds", "name", "birthday", "hobby", "non-exist-entryKey")* 的返回结果为3* 注: 若(key对应的)hash中的所有entry都被删除了,那么该key也会被删除** @param key       定位hash的key* @param entryKeys 定位要删除的entryKey-entryValue的entryKey* @return 删除了对应hash中多少个entry* @date 2020/3/9 9:37:47*/public static long hDelete(String key, Object... entryKeys) {log.info("hDelete(...) => key -> {}, entryKeys -> {}", key, entryKeys);Long count = redisTemplate.opsForHash().delete(key, entryKeys);log.info("hDelete(...) => count -> {}", count);if (count == null) {throw new RedisOpsResultIsNullException();}return count;}/*** 查看(key对应的)hash中,是否存在entryKey对应的entry* <p>* 注: 若redis中不存在key,则返回false。* 注: 若key对应的hash中不存在对应的entryKey, 也会返回false。** @param key      定位hash的key* @param entryKey 定位hash中entry的entryKey* @return hash中是否存在entryKey对应的entry.* @date 2020/3/9 9:51:55*/public static boolean hExists(String key, String entryKey) {log.info("hDelete(...) => key -> {}, entryKeys -> {}", key, entryKey);Boolean exist = redisTemplate.opsForHash().hasKey(key, entryKey);log.info("hDelete(...) => exist -> {}", exist);return exist;}/*** 增/减(hash中的某个entryValue值) 整数* <p>* 注: 负数则为减。* 注: 若key不存在,那么会自动创建对应的hash,并创建对应的entryKey、entryValue,entryValue的初始值为increment。* 注: 若entryKey不存在,那么会自动创建对应的entryValue,entryValue的初始值为increment。* 注: 若key对应的value值不支持增/减操作(即: value不是数字), 那么会* 抛出org.springframework.data.redis.RedisSystemException** @param key       用于定位hash的key* @param entryKey  用于定位entryValue的entryKey* @param increment 增加多少* @return 增加后的总值。* @throws RedisSystemException key对应的value值不支持增/减操作时* @date 2020/3/9 10:09:28*/public static long hIncrBy(String key, Object entryKey, long increment) {log.info("hIncrBy(...) => key -> {}, entryKey -> {}, increment -> {}",key, entryKey, increment);Long result = redisTemplate.opsForHash().increment(key, entryKey, increment);log.info("hIncrBy(...) => result -> {}", result);if (result == null) {throw new RedisOpsResultIsNullException();}return result;}/*** 增/减(hash中的某个entryValue值) 浮点数* <p>* 注: 负数则为减。* 注: 若key不存在,那么会自动创建对应的hash,并创建对应的entryKey、entryValue,entryValue的初始值为increment。* 注: 若entryKey不存在,那么会自动创建对应的entryValue,entryValue的初始值为increment。* 注: 若key对应的value值不支持增/减操作(即: value不是数字), 那么会* 抛出org.springframework.data.redis.RedisSystemException* 注: 因为是浮点数, 所以可能会和{@link StringOps#incrByFloat(String, double)}一样, 出现精度问题。* 追注: 本人简单测试了几组数据,暂未出现精度问题。** @param key       用于定位hash的key* @param entryKey  用于定位entryValue的entryKey* @param increment 增加多少* @return 增加后的总值。* @throws RedisSystemException key对应的value值不支持增/减操作时* @date 2020/3/9 10:09:28*/public static double hIncrByFloat(String key, Object entryKey, double increment) {log.info("hIncrByFloat(...) => key -> {}, entryKey -> {}, increment -> {}",key, entryKey, increment);Double result = redisTemplate.opsForHash().increment(key, entryKey, increment);log.info("hIncrByFloat(...) => result -> {}", result);if (result == null) {throw new RedisOpsResultIsNullException();}return result;}/*** 获取(key对应的)hash中的所有entryKey* <p>* 注: 若key不存在,则返回的是一个空的Set(,而不是返回null)** @param key 定位hash的key* @return hash中的所有entryKey* @date 2020/3/9 10:30:13*/public static Set<Object> hKeys(String key) {log.info("hKeys(...) => key -> {}", key);Set<Object> entryKeys = redisTemplate.opsForHash().keys(key);log.info("hKeys(...) => entryKeys -> {}", entryKeys);return entryKeys;}/*** 获取(key对应的)hash中的所有entryValue* <p>* 注: 若key不存在,则返回的是一个空的List(,而不是返回null)** @param key 定位hash的key* @return hash中的所有entryValue* @date 2020/3/9 10:30:13*/public static List<Object> hValues(String key) {log.info("hValues(...) => key -> {}", key);List<Object> entryValues = redisTemplate.opsForHash().values(key);log.info("hValues(...) => entryValues -> {}", entryValues);return entryValues;}/*** 获取(key对应的)hash中的所有entry的数量* <p>* 注: 若redis中不存在对应的key, 则返回值为0** @param key 定位hash的key* @return (key对应的)hash中, entry的个数* @date 2020/3/9 10:41:01*/public static long hSize(String key) {log.info("hSize(...) => key -> {}", key);Long count = redisTemplate.opsForHash().size(key);log.info("hSize(...) => count -> {}", count);if (count == null) {throw new RedisOpsResultIsNullException();}return count;}/*** 根据options匹配到(key对应的)hash中的对应的entryKey, 并返回对应的entry集* <p>* <p>* 注: ScanOptions实例的创建方式举例:* 1、ScanOptions.NONE* 2、ScanOptions.scanOptions().match("n??e").build()** @param key     定位hash的key* @param options 匹配entryKey的条件*                注: ScanOptions.NONE表示全部匹配。*                注: ScanOptions.scanOptions().match(pattern).build()表示按照pattern匹配,*                其中pattern中可以使用通配符 * ? 等,*                * 表示>=0个字符*                ? 表示有且只有一个字符*                此处的匹配规则与{@link KeyOps#keys(String)}处的一样。* @return 匹配到的(key对应的)hash中的entry* @date 2020/3/9 10:49:27*/public static Cursor<Entry<Object, Object>> hScan(String key, ScanOptions options) {log.info("hScan(...) => key -> {}, options -> {}", key, JSON.toJSONString(options));Cursor<Entry<Object, Object>> cursor = redisTemplate.opsForHash().scan(key, options);log.info("hScan(...) => cursor -> {}", JSON.toJSONString(cursor));return cursor;}}/*** list相关操作* <p>* 提示: 列表中的元素,可以重复。* <p>* 提示: list是有序的。* <p>* 提示: redis中的list中的索引,可分为两类,这两类都可以用来定位list中元素:* 类别一: 从left到right, 是从0开始依次增大:   0,  1,  2,  3...* 类别二: 从right到left, 是从-1开始依次减小: -1, -2, -3, -4...* <p>* 提示: redis中String的数据结构可参考resources/data-structure/List(列表)的数据结构(示例一).png* redis中String的数据结构可参考resources/data-structure/List(列表)的数据结构(示例二).png** @date 2020/3/9 11:30:48*/public static class ListOps {/*** 从左端推入元素进列表* <p>* 注: 若redis中不存在对应的key, 那么会自动创建** @param key  定位list的key* @param item 要推入list的元素* @return 推入后,(key对应的)list的size* @date 2020/3/9 11:56:05*/public static long lLeftPush(String key, String item) {log.info("lLeftPush(...) => key -> {}, item -> {}", key, item);Long size = redisTemplate.opsForList().leftPush(key, item);log.info("lLeftPush(...) => size -> {}", size);if (size == null) {throw new RedisOpsResultIsNullException();}return size;}/*** 从左端批量推入元素进列表* <p>* 注: 若redis中不存在对应的key, 那么会自动创建* 注: 这一批item中,先push左侧的, 后push右侧的** @param key   定位list的key* @param items 要批量推入list的元素集* @return 推入后,(key对应的)list的size* @date 2020/3/9 11:56:05*/public static long lLeftPushAll(String key, String... items) {log.info("lLeftPushAll(...) => key -> {}, items -> {}", key, items);Long size = redisTemplate.opsForList().leftPushAll(key, items);log.info("lLeftPushAll(...) => size -> {}", size);if (size == null) {throw new RedisOpsResultIsNullException();}return size;}/*** 从左端批量推入元素进列表* <p>* 注: 若redis中不存在对应的key, 那么会自动创建* 注: 这一批item中,那个item先从Collection取出来,就先push哪个** @param key   定位list的key* @param items 要批量推入list的元素集* @return 推入后,(key对应的)list的size* @date 2020/3/9 11:56:05*/public static long lLeftPushAll(String key, Collection<String> items) {log.info("lLeftPushAll(...) => key -> {}, items -> {}", key, items);Long size = redisTemplate.opsForList().leftPushAll(key, items);log.info("lLeftPushAll(...) => size -> {}", size);if (size == null) {throw new RedisOpsResultIsNullException();}return size;}/*** 如果redis中存在key, 则从左端批量推入元素进列表;* 否则,不进行任何操作** @param key  定位list的key* @param item 要推入list的项* @return 推入后,(key对应的)list的size* @date 2020/3/9 13:40:08*/public static long lLeftPushIfPresent(String key, String item) {log.info("lLeftPushIfPresent(...) => key -> {}, item -> {}", key, item);Long size = redisTemplate.opsForList().leftPushIfPresent(key, item);log.info("lLeftPushIfPresent(...) => size -> {}", size);if (size == null) {throw new RedisOpsResultIsNullException();}return size;}/*** 若key对应的list中存在pivot项, 那么将item放入第一个pivot项前(即:放在第一个pivot项左边);* 若key对应的list中不存在pivot项, 那么不做任何操作, 直接返回-1。* <p>* 注: 若redis中不存在对应的key, 那么会自动创建** @param key  定位list的key* @param item 要推入list的元素* @return 推入后,(key对应的)list的size* @date 2020/3/9 11:56:05*/public static long lLeftPush(String key, String pivot, String item) {log.info("lLeftPush(...) => key -> {}, pivot -> {}, item -> {}", key, pivot, item);Long size = redisTemplate.opsForList().leftPush(key, pivot, item);log.info("lLeftPush(...) => size -> {}", size);if (size == null) {throw new RedisOpsResultIsNullException();}return size;}/*** 与{@link ListOps#lLeftPush(String, String)}类比即可, 不过是从list右侧推入元素*/public static long lRightPush(String key, String item) {log.info("lRightPush(...) => key -> {}, item -> {}", key, item);Long size = redisTemplate.opsForList().rightPush(key, item);log.info("lRightPush(...) => size -> {}", size);if (size == null) {throw new RedisOpsResultIsNullException();}return size;}/*** 与{@link ListOps#lLeftPushAll(String, String...)}类比即可, 不过是从list右侧推入元素*/public static long lRightPushAll(String key, String... items) {log.info("lRightPushAll(...) => key -> {}, items -> {}", key, items);Long size = redisTemplate.opsForList().rightPushAll(key, items);log.info("lRightPushAll(...) => size -> {}", size);if (size == null) {throw new RedisOpsResultIsNullException();}return size;}/*** 与{@link ListOps#lLeftPushAll(String, Collection<String>)}类比即可, 不过是从list右侧推入元素*/public static long lRightPushAll(String key, Collection<String> items) {log.info("lRightPushAll(...) => key -> {}, items -> {}", key, items);Long size = redisTemplate.opsForList().rightPushAll(key, items);log.info("lRightPushAll(...) => size -> {}", size);if (size == null) {throw new RedisOpsResultIsNullException();}return size;}/*** 与{@link ListOps#lLeftPushIfPresent(String, String)}类比即可, 不过是从list右侧推入元素*/public static long lRightPushIfPresent(String key, String item) {log.info("lRightPushIfPresent(...) => key -> {}, item -> {}", key, item);Long size = redisTemplate.opsForList().rightPushIfPresent(key, item);log.info("lRightPushIfPresent(...) => size -> {}", size);if (size == null) {throw new RedisOpsResultIsNullException();}return size;}/*** 与{@link ListOps#lLeftPush(String, String, String)}类比即可, 不过是从list右侧推入元素*/public static long lRightPush(String key, String pivot, String item) {log.info("lLeftPush(...) => key -> {}, pivot -> {}, item -> {}", key, pivot, item);Long size = redisTemplate.opsForList().rightPush(key, pivot, item);log.info("lLeftPush(...) => size -> {}", size);if (size == null) {throw new RedisOpsResultIsNullException();}return size;}/*** 【非阻塞队列】 从左侧移出(key对应的)list中的第一个元素, 并将该元素返回* <p>* 注: 此方法是非阻塞的, 即: 若(key对应的)list中的所有元素都被pop移出了,此时,再进行pop的话,会立即返回null* 注: 此方法是非阻塞的, 即: 若redis中不存在对应的key,那么会立即返回null* 注: 若将(key对应的)list中的所有元素都pop完了,那么该key会被删除** @param key 定位list的key* @return 移出的那个元素* @date 2020/3/9 14:33:56*/public static String lLeftPop(String key) {log.info("lLeftPop(...) => key -> {}", key);String item = redisTemplate.opsForList().leftPop(key);log.info("lLeftPop(...) => item -> {}", item);return item;}/*** 【阻塞队列】 从左侧移出(key对应的)list中的第一个元素, 并将该元素返回* <p>* 注: 此方法是阻塞的, 即: 若(key对应的)list中的所有元素都被pop移出了,此时,再进行pop的话,* 会阻塞timeout这么久,然后返回null* 注: 此方法是阻塞的, 即: 若redis中不存在对应的key,那么会阻塞timeout这么久,然后返回null* 注: 若将(key对应的)list中的所有元素都pop完了,那么该key会被删除* <p>* 提示: 若阻塞过程中, 目标key-list出现了,且里面有item了,那么会立马停止阻塞, 进行元素移出并返回** @param key     定位list的key* @param timeout 超时时间* @param unit    timeout的单位* @return 移出的那个元素* @date 2020/3/9 14:33:56*/public static String lLeftPop(String key, long timeout, TimeUnit unit) {log.info("lLeftPop(...) => key -> {}, timeout -> {}, unit -> {}", key, timeout, unit);String item = redisTemplate.opsForList().leftPop(key, timeout, unit);log.info("lLeftPop(...) => item -> {}", item);return item;}/*** 与{@link ListOps#lLeftPop(String)}类比即可, 不过是从list右侧移出元素*/public static String lRightPop(String key) {log.info("lRightPop(...) => key -> {}", key);String item = redisTemplate.opsForList().rightPop(key);log.info("lRightPop(...) => item -> {}", item);return item;}/*** 与{@link ListOps#lLeftPop(String, long, TimeUnit)}类比即可, 不过是从list右侧移出元素*/public static String lRightPop(String key, long timeout, TimeUnit unit) {log.info("lRightPop(...) => key -> {}, timeout -> {}, unit -> {}", key, timeout, unit);String item = redisTemplate.opsForList().rightPop(key, timeout, unit);log.info("lRightPop(...) => item -> {}", item);return item;}/*** 【非阻塞队列】 从sourceKey对应的sourceList右侧移出一个item, 并将这个item推* 入(destinationKey对应的)destinationList的左侧* <p>* 注: 若sourceKey对应的list中没有item了,则立马认为(从sourceKey对应的list中pop出来的)item为null,* null并不会往destinationKey对应的list中push。* 追注: 此时,此方法的返回值是null。* <p>* 注: 若将(sourceKey对应的)list中的所有元素都pop完了,那么该sourceKey会被删除。** @param sourceKey      定位sourceList的key* @param destinationKey 定位destinationList的key* @return 移动的这个元素* @date 2020/3/9 15:06:59*/public static String lRightPopAndLeftPush(String sourceKey, String destinationKey) {log.info("lRightPopAndLeftPush(...) => sourceKey -> {}, destinationKey -> {}",sourceKey, destinationKey);String item = redisTemplate.opsForList().rightPopAndLeftPush(sourceKey, destinationKey);log.info("lRightPopAndLeftPush(...) => item -> {}", item);return item;}/*** 【阻塞队列】 从sourceKey对应的sourceList右侧移出一个item, 并将这个item推* 入(destinationKey对应的)destinationList的左侧* <p>* 注: 若sourceKey对应的list中没有item了,则阻塞等待, 直到能从sourceList中移出一个非null的item(或等待时长超时);* case1: 等到了一个非null的item, 那么继续下面的push操作,并返回这个item。* case2: 超时了,还没等到非null的item, 那么pop出的结果就未null,此时并不会往destinationList进行push。* 此时,此方法的返回值是null。* <p>* 注: 若将(sourceKey对应的)list中的所有元素都pop完了,那么该sourceKey会被删除。** @param sourceKey      定位sourceList的key* @param destinationKey 定位destinationList的key* @param timeout        超时时间* @param unit           timeout的单位* @return 移动的这个元素* @date 2020/3/9 15:06:59*/public static String lRightPopAndLeftPush(String sourceKey, String destinationKey, long timeout,TimeUnit unit) {log.info("lRightPopAndLeftPush(...) => sourceKey -> {}, destinationKey -> {}, timeout -> {},"+ " unit -> {}", sourceKey, destinationKey, timeout, unit);String item = redisTemplate.opsForList().rightPopAndLeftPush(sourceKey, destinationKey, timeout, unit);log.info("lRightPopAndLeftPush(...) => item -> {}", item);return item;}/*** 设置(key对应的)list中对应索引位置index处的元素为item* <p>* 注: 若key不存在,则会抛出org.springframework.data.redis.RedisSystemException* 注: 若索引越界,也会抛出org.springframework.data.redis.RedisSystemException** @param key   定位list的key* @param index 定位list中的元素的索引* @param item  要替换成的值* @date 2020/3/9 15:39:50*/public static void lSet(String key, long index, String item) {log.info("lSet(...) => key -> {}, index -> {}, item -> {}", key, index, item);redisTemplate.opsForList().set(key, index, item);}/*** 通过索引index, 获取(key对应的)list中的元素* <p>* 注: 若key不存在 或 index超出(key对应的)list的索引范围,那么返回null** @param key   定位list的key* @param index 定位list中的item的索引* @return list中索引index对应的item* @date 2020/3/10 0:27:23*/public static String lIndex(String key, long index) {log.info("lIndex(...) => key -> {}, index -> {}", key, index);String item = redisTemplate.opsForList().index(key, index);log.info("lIndex(...) => item -> {}", item);return item;}/*** 获取(key对应的)list中索引在[start, end]之间的item集* <p>* 注: 含start、含end。* 注: 当key不存在时,获取到的是空的集合。* 注: 当获取的范围比list的范围还要大时,获取到的是这两个范围的交集。* <p>* 提示: 可通过RedisUtil.ListOps.lRange(key, 0, -1)来获取到该key对应的整个list** @param key   定位list的key* @param start 起始元素的index* @param end   结尾元素的index* @return 对应的元素集合* @date 2020/3/10 0:34:59*/public static List<String> lRange(String key, long start, long end) {log.info("lRange(...) => key -> {}, start -> {}, end -> {}", key, start, end);List<String> result = redisTemplate.opsForList().range(key, start, end);log.info("lRange(...) => result -> {}", result);return result;}/*** 获取(key对应的)list** @param key 定位list的key* @return (key对应的)list* @date 2020/3/10 0:46:50* @see ListOps#lRange(String, long, long)*/public static List<String> lWholeList(String key) {log.info("lWholeList(...) => key -> {}", key);List<String> result = redisTemplate.opsForList().range(key, 0, -1);log.info("lWholeList(...) => result -> {}", result);return result;}/*** 获取(key对应的)list的size* <p>* 注: 当key不存在时,获取到的size为0.** @param key 定位list的key* @return list的size。* @date 2020/3/10 0:48:40*/public static long lSize(String key) {log.info("lSize(...) => key -> {}", key);Long size = redisTemplate.opsForList().size(key);log.info("lSize(...) => size -> {}", size);if (size == null) {throw new RedisOpsResultIsNullException();}return size;}/*** 删除(key对应的)list中,前expectCount个值等于item的项* <p>* 注: 若expectCount == 0, 则表示删除list中所有的值等于item的项.* 注: 若expectCount > 0,  则表示删除从左往右进行* 注: 若expectCount < 0,  则表示删除从右往左进行* <p>* 注: 若list中,值等于item的项的个数少于expectCount时,那么会删除list中所有的值等于item的项。* 注: 当key不存在时, 返回0。* 注: 若lRemove后, 将(key对应的)list中没有任何元素了,那么该key会被删除。** @param key         定位list的key* @param expectCount 要删除的item的个数* @param item        要删除的item* @return 实际删除了的item的个数* @date 2020/3/10 0:52:57*/public static long lRemove(String key, long expectCount, String item) {log.info("lRemove(...) => key -> {}, expectCount -> {}, item -> {}", key, expectCount, item);Long actualCount = redisTemplate.opsForList().remove(key, expectCount, item);log.info("lRemove(...) => actualCount -> {}", actualCount);if (actualCount == null) {throw new RedisOpsResultIsNullException();}return actualCount;}/*** 裁剪(即: 对list中的元素取交集。)* <p>* 举例说明: list中的元素索引范围是[0, 8], 而这个方法传入的[start, end]为 [3, 10],* 那么裁剪就是对[0, 8]和[3, 10]进行取交集, 得到[3, 8], 那么裁剪后* 的list中,只剩下(原来裁剪前)索引在[3, 8]之间的元素了。* <p>* 注: 若裁剪后的(key对应的)list就是空的,那么该key会被删除。** @param key   定位list的key* @param start 要删除的item集的起始项的索引* @param end   要删除的item集的结尾项的索引* @date 2020/3/10 1:16:58*/public static void lTrim(String key, long start, long end) {log.info("lTrim(...) => key -> {}, start -> {}, end -> {}", key, start, end);redisTemplate.opsForList().trim(key, start, end);}}/*** set相关操作* <p>* 提示: set中的元素,不可以重复。* 提示: set是无序的。* 提示: redis中String的数据结构可参考resources/data-structure/Set(集合)的数据结构(示例一).png* redis中String的数据结构可参考resources/data-structure/Set(集合)的数据结构(示例二).png** @date 2020/3/9 11:30:48*/public static class SetOps {/*** 向(key对应的)set中添加items* <p>* 注: 若key不存在,则会自动创建。* 注: set中的元素会去重。** @param key   定位set的key* @param items 要向(key对应的)set中添加的items* @return 此次添加操作, 添加到set中的元素的个数* @date 2020/3/11 8:16:00*/public static long sAdd(String key, String... items) {log.info("sAdd(...) => key -> {}, items -> {}", key, items);Long count = redisTemplate.opsForSet().add(key, items);log.info("sAdd(...) => count -> {}", count);if (count == null) {throw new RedisOpsResultIsNullException();}return count;}/*** 从(key对应的)set中删除items* <p>* 注: 若key不存在, 则返回0。* 注: 若已经将(key对应的)set中的项删除完了,那么对应的key也会被删除。** @param key   定位set的key* @param items 要移除的items* @return 实际删除了的个数* @date 2020/3/11 8:26:43*/public static long sRemove(String key, Object... items) {log.info("sRemove(...) => key -> {}, items -> {}", key, items);Long count = redisTemplate.opsForSet().remove(key, items);log.info("sRemove(...) => count -> {}", count);if (count == null) {throw new RedisOpsResultIsNullException();}return count;}/*** 从(key对应的)set中随机移出一个item, 并返回这个item* <p>* 注: 因为set是无序的,所以移出的这个item,是随机的; 并且,哪怕* 是数据一样的set,多次测试移出操作,移除的元素也是随机的。* <p>* 注: 若已经将(key对应的)set中的项pop完了,那么对应的key会被删除。** @param key 定位set的key* @return 移出的项* @date 2020/3/11 8:32:40*/public static String sPop(String key) {log.info("sPop(...) => key -> {}", key);String popItem = redisTemplate.opsForSet().pop(key);log.info("sPop(...) => popItem -> {}", popItem);return popItem;}/*** 将(sourceKey对应的)sourceSet中的元素item, 移动到(destinationKey对应的)destinationSet中* <p>* 注: 当sourceKey不存在时, 返回false* 注: 当item不存在时, 返回false* 注: 若destinationKey不存在, 那么在移动时会自动创建* 注: 若已经将(sourceKey对应的)set中的项move出去完了,那么对应的sourceKey会被删除。** @param sourceKey      定位sourceSet的key* @param item           要移动的项目* @param destinationKey 定位destinationSet的key* @return 移动成功与否* @date 2020/3/11 8:43:32*/public static boolean sMove(String sourceKey, String item, String destinationKey) {Boolean result = redisTemplate.opsForSet().move(sourceKey, item, destinationKey);log.info("sMove(...) => sourceKey -> {}, destinationKey -> {}, item -> {}",sourceKey, destinationKey, item);log.info("sMove(...) =>  result -> {}", result);if (result == null) {throw new RedisOpsResultIsNullException();}return result;}/*** 获取(key对应的)set中的元素个数* <p>* 注: 若key不存在,则返回0** @param key 定位set的key* @return (key对应的)set中的元素个数* @date 2020/3/11 8:57:19*/public static long sSize(String key) {log.info("sSize(...) => key -> {}", key);Long size = redisTemplate.opsForSet().size(key);log.info("sSize(...) => size -> {}", size);if (size == null) {throw new RedisOpsResultIsNullException();}return size;}/*** 判断(key对应的)set中是否含有item* <p>* 注: 若key不存在,则返回false。** @param key  定位set的key* @param item 被查找的项* @return (key对应的)set中是否含有item* @date 2020/3/11 9:03:29*/public static boolean sIsMember(String key, Object item) {log.info("sSize(...) => key -> {}, size -> {}", key, item);Boolean result = redisTemplate.opsForSet().isMember(key, item);log.info("sSize(...) => result -> {}", result);if (result == null) {throw new RedisOpsResultIsNullException();}return result;}/*** 获取两个(key对应的)Set的交集* <p>* 注: 若不存在任何交集,那么返回空的集合(, 而不是null)* 注: 若其中一个key不存在(或两个key都不存在),那么返回空的集合(, 而不是null)** @param key      定位其中一个set的键* @param otherKey 定位其中另一个set的键* @return item交集* @date 2020/3/11 9:31:25*/public static Set<String> sIntersect(String key, String otherKey) {log.info("sIntersect(...) => key -> {}, otherKey -> {}", key, otherKey);Set<String> intersectResult = redisTemplate.opsForSet().intersect(key, otherKey);log.info("sIntersect(...) => intersectResult -> {}", intersectResult);return intersectResult;}/*** 获取多个(key对应的)Set的交集* <p>* 注: 若不存在任何交集,那么返回空的集合(, 而不是null)* 注: 若>=1个key不存在,那么返回空的集合(, 而不是null)** @param key       定位其中一个set的键* @param otherKeys 定位其它set的键集* @return item交集* @date 2020/3/11 9:39:23*/public static Set<String> sIntersect(String key, Collection<String> otherKeys) {log.info("sIntersect(...) => key -> {}, otherKeys -> {}", key, otherKeys);Set<String> intersectResult = redisTemplate.opsForSet().intersect(key, otherKeys);log.info("sIntersect(...) => intersectResult -> {}", intersectResult);return intersectResult;}/*** 获取两个(key对应的)Set的交集, 并将结果add到storeKey对应的Set中。* <p>* case1: 交集不为空, storeKey不存在, 则 会创建对应的storeKey,并将交集添加到(storeKey对应的)set中* case2: 交集不为空, storeKey已存在, 则 会清除原(storeKey对应的)set中所有的项,然后将交集添加到(storeKey对应的)set中* case3: 交集为空, 则不进行下面的操作, 直接返回0* <p>* 注: 求交集的部分,详见{@link SetOps#sIntersect(String, String)}** @param key      定位其中一个set的键* @param otherKey 定位其中另一个set的键* @param storeKey 定位(要把交集添加到哪个)set的key* @return add到(storeKey对应的)Set后, 该set对应的size* @date 2020/3/11 9:46:46*/public static long sIntersectAndStore(String key, String otherKey, String storeKey) {log.info("sIntersectAndStore(...) => key -> {}, otherKey -> {}, storeKey -> {}",key, otherKey, storeKey);Long size = redisTemplate.opsForSet().intersectAndStore(key, otherKey, storeKey);log.info("sIntersectAndStore(...) => size -> {}", size);if (size == null) {throw new RedisOpsResultIsNullException();}return size;}/*** 获取多个(key对应的)Set的交集, 并将结果add到storeKey对应的Set中。* <p>* case1: 交集不为空, storeKey不存在, 则 会创建对应的storeKey,并将交集添加到(storeKey对应的)set中* case2: 交集不为空, storeKey已存在, 则 会清除原(storeKey对应的)set中所有的项,然后将交集添加到(storeKey对应的)set中* case3: 交集为空, 则不进行下面的操作, 直接返回0* <p>* 注: 求交集的部分,详见{@link SetOps#sIntersect(String, Collection)}** @date 2020/3/11 11:04:29*/public static long sIntersectAndStore(String key, Collection<String> otherKeys, String storeKey) {log.info("sIntersectAndStore(...) => key -> {}, otherKeys -> {}, storeKey -> {}", key, otherKeys, storeKey);Long size = redisTemplate.opsForSet().intersectAndStore(key, otherKeys, storeKey);log.info("sIntersectAndStore(...) => size -> {}", size);if (size == null) {throw new RedisOpsResultIsNullException();}return size;}/*** 获取两个(key对应的)Set的并集* <p>* 注: 并集中的元素也是唯一的,这是Set保证的。** @param key      定位其中一个set的键* @param otherKey 定位其中另一个set的键* @return item并集* @date 2020/3/11 11:18:35*/public static Set<String> sUnion(String key, String otherKey) {log.info("sUnion(...) => key -> {}, otherKey -> {}", key, otherKey);Set<String> unionResult = redisTemplate.opsForSet().union(key, otherKey);log.info("sUnion(...) => unionResult -> {}", unionResult);return unionResult;}/*** 获取两个(key对应的)Set的并集* <p>* 注: 并集中的元素也是唯一的,这是Set保证的。** @param key       定位其中一个set的键* @param otherKeys 定位其它set的键集* @return item并集* @date 2020/3/11 11:18:35*/public static Set<String> sUnion(String key, Collection<String> otherKeys) {log.info("sUnion(...) => key -> {}, otherKeys -> {}", key, otherKeys);Set<String> unionResult = redisTemplate.opsForSet().union(key, otherKeys);log.info("sUnion(...) => unionResult -> {}", unionResult);return unionResult;}/*** 获取两个(key对应的)Set的并集, 并将结果add到storeKey对应的Set中。* <p>* case1: 并集不为空, storeKey不存在, 则 会创建对应的storeKey,并将并集添加到(storeKey对应的)set中* case2: 并集不为空, storeKey已存在, 则 会清除原(storeKey对应的)set中所有的项,然后将并集添加到(storeKey对应的)set中* case3: 并集为空, 则不进行下面的操作, 直接返回0* <p>* 注: 求并集的部分,详见{@link SetOps#sUnion(String, String)}** @param key      定位其中一个set的键* @param otherKey 定位其中另一个set的键* @param storeKey 定位(要把并集添加到哪个)set的key* @return add到(storeKey对应的)Set后, 该set对应的size* @date 2020/3/11 12:26:24*/public static long sUnionAndStore(String key, String otherKey, String storeKey) {log.info("sUnionAndStore(...) => key -> {}, otherKey -> {}, storeKey -> {}",key, otherKey, storeKey);Long size = redisTemplate.opsForSet().unionAndStore(key, otherKey, storeKey);log.info("sUnionAndStore(...) => size -> {}", size);if (size == null) {throw new RedisOpsResultIsNullException();}return size;}/*** 获取两个(key对应的)Set的并集, 并将结果add到storeKey对应的Set中。* <p>* case1: 并集不为空, storeKey不存在, 则 会创建对应的storeKey,并将并集添加到(storeKey对应的)set中* case2: 并集不为空, storeKey已存在, 则 会清除原(storeKey对应的)set中所有的项,然后将并集添加到(storeKey对应的)set中* case3: 并集为空, 则不进行下面的操作, 直接返回0* <p>* 注: 求并集的部分,详见{@link SetOps#sUnion(String, Collection)}** @param key       定位其中一个set的键* @param otherKeys 定位其它set的键集* @param storeKey  定位(要把并集添加到哪个)set的key* @return add到(storeKey对应的)Set后, 该set对应的size* @date 2020/3/11 12:26:24*/public static long sUnionAndStore(String key, Collection<String> otherKeys, String storeKey) {log.info("sUnionAndStore(...) => key -> {}, otherKeys -> {}, storeKey -> {}",key, otherKeys, storeKey);Long size = redisTemplate.opsForSet().unionAndStore(key, otherKeys, storeKey);log.info("sUnionAndStore(...) => size -> {}", size);if (size == null) {throw new RedisOpsResultIsNullException();}return size;}/*** 获取 (key对应的)Set 减去 (otherKey对应的)Set 的差集* <p>* 注: 如果被减数key不存在, 那么结果为空的集合(,而不是null)* 注: 如果被减数key存在,但减数key不存在, 那么结果即为(被减数key对应的)Set** @param key      定位"被减数set"的键* @param otherKey 定位"减数set"的键* @return item差集* @date 2020/3/11 14:03:57*/public static Set<String> sDifference(String key, String otherKey) {log.info("sDifference(...) => key -> {}, otherKey -> {}",key, otherKey);Set<String> differenceResult = redisTemplate.opsForSet().difference(key, otherKey);log.info("sDifference(...) => differenceResult -> {}", differenceResult);return differenceResult;}/*** 获取 (key对应的)Set 减去 (otherKeys对应的)Sets 的差集* <p>* 注: 如果被减数key不存在, 那么结果为空的集合(,而不是null)* 注: 如果被减数key存在,但减数key不存在, 那么结果即为(被减数key对应的)Set* <p>* 提示: 当有多个减数时, 被减数先减去哪一个减数,后减去哪一个减数,是无所谓的,是不影响最终结果的。** @param key       定位"被减数set"的键* @param otherKeys 定位"减数集sets"的键集* @return item差集* @date 2020/3/11 14:03:57*/public static Set<String> sDifference(String key, Collection<String> otherKeys) {log.info("sDifference(...) => key -> {}, otherKeys -> {}", key, otherKeys);Set<String> differenceResult = redisTemplate.opsForSet().difference(key, otherKeys);log.info("sDifference(...) => differenceResult -> {}", differenceResult);return differenceResult;}/*** 获取 (key对应的)Set 减去 (otherKey对应的)Set 的差集, 并将结果add到storeKey对应的Set中。* <p>* case1: 差集不为空, storeKey不存在, 则 会创建对应的storeKey,并将差集添加到(storeKey对应的)set中* case2: 差集不为空, storeKey已存在, 则 会清除原(storeKey对应的)set中所有的项,然后将差集添加到(storeKey对应的)set中* case3: 差集为空, 则不进行下面的操作, 直接返回0* <p>* 注: 求并集的部分,详见{@link SetOps#sDifference(String, String)}** @param key      定位"被减数set"的键* @param otherKey 定位"减数set"的键* @param storeKey 定位(要把差集添加到哪个)set的key* @return add到(storeKey对应的)Set后, 该set对应的size* @date 2020/3/11 14:33:36*/public static long sDifferenceAndStore(String key, String otherKey, String storeKey) {log.info("sDifferenceAndStore(...) => key -> {}, otherKey -> {}, storeKey -> {}",key, otherKey, storeKey);Long size = redisTemplate.opsForSet().differenceAndStore(key, otherKey, storeKey);log.info("sDifferenceAndStore(...) => size -> {}", size);if (size == null) {throw new RedisOpsResultIsNullException();}return size;}/*** 获取 (key对应的)Set 减去 (otherKey对应的)Set 的差集, 并将结果add到storeKey对应的Set中。* <p>* case1: 差集不为空, storeKey不存在, 则 会创建对应的storeKey,并将差集添加到(storeKey对应的)set中* case2: 差集不为空, storeKey已存在, 则 会清除原(storeKey对应的)set中所有的项,然后将差集添加到(storeKey对应的)set中* case3: 差集为空, 则不进行下面的操作, 直接返回0* <p>* 注: 求并集的部分,详见{@link SetOps#sDifference(String, String)}** @param key       定位"被减数set"的键* @param otherKeys 定位"减数集sets"的键集* @param storeKey  定位(要把差集添加到哪个)set的key* @return add到(storeKey对应的)Set后, 该set对应的size* @date 2020/3/11 14:33:36*/public static long sDifferenceAndStore(String key, Collection<String> otherKeys, String storeKey) {log.info("sDifferenceAndStore(...) => key -> {}, otherKeys -> {}, storeKey -> {}",key, otherKeys, storeKey);Long size = redisTemplate.opsForSet().differenceAndStore(key, otherKeys, storeKey);log.info("sDifferenceAndStore(...) => size -> {}", size);if (size == null) {throw new RedisOpsResultIsNullException();}return size;}/*** 获取key对应的set* <p>* 注: 若key不存在, 则返回的是空的set(, 而不是null)** @param key 定位set的key* @return (key对应的)set* @date 2020/3/11 14:49:39*/public static Set<String> sMembers(String key) {log.info("sMembers(...) => key -> {}", key);Set<String> members = redisTemplate.opsForSet().members(key);log.info("sMembers(...) => members -> {}", members);return members;}/*** 从key对应的set中随机获取一项** @param key 定位set的key* @return 随机获取到的项* @date 2020/3/11 14:54:58*/public static String sRandomMember(String key) {log.info("sRandomMember(...) => key -> {}", key);String randomItem = redisTemplate.opsForSet().randomMember(key);log.info("sRandomMember(...) => randomItem -> {}", randomItem);return randomItem;}/*** 从key对应的set中获取count次随机项(, set中的同一个项可能被多次获取)* <p>* 注: count可大于set的size。* 注: 取出来的结果里可能存在相同的值。** @param key   定位set的key* @param count 要取多少项* @return 随机获取到的项集* @date 2020/3/11 14:54:58*/public static List<String> sRandomMembers(String key, long count) {log.info("sRandomMembers(...) => key -> {}, count -> {}", key, count);List<String> randomItems = redisTemplate.opsForSet().randomMembers(key, count);log.info("sRandomMembers(...) => randomItems -> {}", randomItems);return randomItems;}/*** 从key对应的set中随机获取count个项* <p>* 注: 若count >= set的size, 那么返回的即为这个key对应的set。* 注: 取出来的结果里没有重复的项。** @param key   定位set的key* @param count 要取多少项* @return 随机获取到的项集* @date 2020/3/11 14:54:58*/public static Set<String> sDistinctRandomMembers(String key, long count) {log.info("sDistinctRandomMembers(...) => key -> {}, count -> {}", key, count);Set<String> distinctRandomItems = redisTemplate.opsForSet().distinctRandomMembers(key, count);log.info("sDistinctRandomMembers(...) => distinctRandomItems -> {}", distinctRandomItems);return distinctRandomItems;}/*** 根据options匹配到(key对应的)set中的对应的item, 并返回对应的item集* <p>* <p>* 注: ScanOptions实例的创建方式举例:* 1、ScanOptions.NONE* 2、ScanOptions.scanOptions().match("n??e").build()** @param key     定位set的key* @param options 匹配set中的item的条件*                注: ScanOptions.NONE表示全部匹配。*                注: ScanOptions.scanOptions().match(pattern).build()表示按照pattern匹配,*                其中pattern中可以使用通配符 * ? 等,*                * 表示>=0个字符*                ? 表示有且只有一个字符*                此处的匹配规则与{@link KeyOps#keys(String)}处的一样。* @return 匹配到的(key对应的)set中的项* @date 2020/3/9 10:49:27*/public static Cursor<String> sScan(String key, ScanOptions options) {log.info("sScan(...) => key -> {}, options -> {}", key, JSON.toJSONString(options));Cursor<String> cursor = redisTemplate.opsForSet().scan(key, options);log.info("sScan(...) => cursor -> {}", JSON.toJSONString(cursor));return cursor;}}/*** ZSet相关操作* <p>* 特别说明: ZSet是有序的,* 不仅体现在: redis中的存储上有序。* 还体现在:   此工具类ZSetOps中返回值类型为Set<?>的方法, 实际返回类型是LinkedHashSet<?>* <p>* 提示: redis中的ZSet, 一定程度等于redis中的Set + redis中的Hash的结合体。* 提示: redis中String的数据结构可参考resources/data-structure/ZSet(有序集合)的数据结构(示例一).png* redis中String的数据结构可参考resources/data-structure/ZSet(有序集合)的数据结构(示例二).png* 提示: ZSet中的entryKey即为成员项, entryValue即为这个成员项的分值, ZSet根据成员的分值,来堆成员进行排序。** @date 2020/3/11 15:28:48*/public static class ZSetOps {/*** 向(key对应的)zset中添加(item, score)* <p>* 注: item为entryKey成员项, score为entryValue分数值。* <p>* 注: 若(key对应的)zset中已存在(与此次要添加的项)相同的item项,那么此次添加操作会失败,返回false;* 但是!!! zset中原item的score会被更新为此次add的相同item项的score。* 所以, 也可以通过zAdd达到更新item对应score的目的。* <p>* 注: score可为正、可为负、可为0; 总之, double范围内都可以。* <p>* 注: 若score的值一样,则按照item排序。** @param key   定位set的key* @param item  要往(key对应的)zset中添加的成员项* @param score item的分值* @return 是否添加成功* @date 2020/3/11 15:35:30*/public static boolean zAdd(String key, String item, double score) {log.info("zAdd(...) => key -> {}, item -> {}, score -> {}", key, item, score);Boolean result = redisTemplate.opsForZSet().add(key, item, score);log.info("zAdd(...) => result -> {}", result);if (result == null) {throw new RedisOpsResultIsNullException();}return result;}/*** 批量添加entry<item, score>* <p>* 注: 若entry<item, score>集中存在item相同的项(, score不一样),那么redis在执行真正的批量add操作前,会* 将其中一个item过滤掉。* 注: 同样的,若(key对应的)zset中已存在(与此次要添加的项)相同的item项,那么此次批量添加操作中,* 对该item项的添加会失败,会失败,成功计数器不会加1;但是!!! zset中原item的score会被更新为此* 次add的相同item项的score。所以, 也可以通过zAdd达到更新item对应score的目的。** @param key     定位set的key* @param entries 要添加的entry<item, score>集* @return 本次添加进(key对应的)zset中的entry的个数* @date 2020/3/11 16:45:45*/public static long zAdd(String key, Set<ZSetOperations.TypedTuple<String>> entries) {log.info("zAdd(...) => key -> {}, entries -> {}", key, JSON.toJSONString(entries));Long count = redisTemplate.opsForZSet().add(key, entries);log.info("zAdd(...) => count -> {}", count);if (count == null) {throw new RedisOpsResultIsNullException();}return count;}/*** 从(key对应的)zset中移除项* <p>* 注:若key不存在,则返回0** @param key   定位set的key* @param items 要移除的项集* @return 实际移除了的项的个数* @date 2020/3/11 17:20:12*/public static long zRemove(String key, Object... items) {log.info("zRemove(...) => key -> {}, items -> {}", key, items);Long count = redisTemplate.opsForZSet().remove(key, items);log.info("zRemove(...) => count -> {}", count);if (count == null) {throw new RedisOpsResultIsNullException();}return count;}/*** 移除(key对应的)zset中, 排名范围在[startIndex, endIndex]内的item* <p>* 注:默认的,按score.item升序排名, 排名从0开始* <p>* 注: 类似于List中的索引, 排名可以分为多个方式:* 从前到后(正向)的排名: 0、1、2...* 从后到前(反向)的排名: -1、-2、-3...* <p>* 注: 不论是使用正向排名,还是使用反向排名, 使用此方法时, 应保证 startRange代表的元素的位置* 在endRange代表的元素的位置的前面, 如:* 示例一: RedisUtil.ZSetOps.zRemoveRange("name", 0, 2);* 示例二: RedisUtil.ZSetOps.zRemoveRange("site", -2, -1);* 示例三: RedisUtil.ZSetOps.zRemoveRange("foo", 0, -1);* <p>* 注:若key不存在,则返回0** @param key        定位set的key* @param startRange 开始项的排名* @param endRange   结尾项的排名* @return 实际移除了的项的个数* @date 2020/3/11 17:20:12*/public static long zRemoveRange(String key, long startRange, long endRange) {log.info("zRemoveRange(...) => key -> {}, startRange -> {}, endRange -> {}",key, startRange, endRange);Long count = redisTemplate.opsForZSet().removeRange(key, startRange, endRange);log.info("zRemoveRange(...) => count -> {}", count);if (count == null) {throw new RedisOpsResultIsNullException();}return count;}/*** 移除(key对应的)zset中, score范围在[minScore, maxScore]内的item* <p>* 提示: 虽然删除范围包含两侧的端点(即:包含minScore和maxScore), 但是由于double存在精度问题,所以建议:* 设置值时,minScore应该设置得比要删除的项里,最小的score还小一点* maxScore应该设置得比要删除的项里,最大的score还大一点* 追注: 本人简单测试了几组数据,暂未出现精度问题。* <p>* 注:若key不存在,则返回0** @param key      定位set的key* @param minScore score下限(含这个值)* @param maxScore score上限(含这个值)* @return 实际移除了的项的个数* @date 2020/3/11 17:20:12*/public static long zRemoveRangeByScore(String key, double minScore, double maxScore) {log.info("zRemoveRangeByScore(...) => key -> {}, startIndex -> {}, startIndex -> {}",key, minScore, maxScore);Long count = redisTemplate.opsForZSet().removeRangeByScore(key, minScore, maxScore);log.info("zRemoveRangeByScore(...) => count -> {}", count);if (count == null) {throw new RedisOpsResultIsNullException();}return count;}/*** 增/减 (key对应的zset中,)item的分数值** @param key   定位zset的key* @param item  项* @param delta 变化量(正 - 增, 负 - 减)* @return 修改后的score值* @date 2020/3/12 8:55:38*/public static double zIncrementScore(String key, String item, double delta) {log.info("zIncrementScore(...) => key -> {}, item -> {}, delta -> {}", key, item, delta);Double scoreValue = redisTemplate.opsForZSet().incrementScore(key, item, delta);log.info("zIncrementScore(...) => scoreValue -> {}", scoreValue);if (scoreValue == null) {throw new RedisOpsResultIsNullException();}return scoreValue;}/*** 返回item在(key对应的)zset中的(按score从小到大的)排名* <p>* 注: 排名从0开始。 即意味着,此方法等价于: 返回item在(key对应的)zset中的位置索引。* 注: 若key或item不存在, 返回null。* 注: 排序规则是score,item, 即:优先以score排序,若score相同,则再按item排序。** @param key  定位zset的key* @param item 项* @return 排名(等价于 : 索引)* @date 2020/3/12 9:14:09*/public static long zRank(String key, Object item) {log.info("zRank(...) => key -> {}, item -> {}", key, item);Long rank = redisTemplate.opsForZSet().rank(key, item);log.info("zRank(...) => rank -> {}", rank);if (rank == null) {throw new RedisOpsResultIsNullException();}return rank;}/*** 返回item在(key对应的)zset中的(按score从大到小的)排名* <p>* 注: 排名从0开始。补充: 因为是按score从大到小排序的, 所以最大score对应的item的排名为0。* 注: 若key或item不存在, 返回null。* 注: 排序规则是score,item, 即:优先以score排序,若score相同,则再按item排序。** @param key  定位zset的key* @param item 项* @return 排名(等价于 : 索引)* @date 2020/3/12 9:14:09*/public static long zReverseRank(String key, Object item) {log.info("zReverseRank(...) => key -> {}, item -> {}", key, item);Long reverseRank = redisTemplate.opsForZSet().reverseRank(key, item);log.info("zReverseRank(...) => reverseRank -> {}", reverseRank);if (reverseRank == null) {throw new RedisOpsResultIsNullException();}return reverseRank;}/*** 根据索引位置, 获取(key对应的)zset中排名处于[start, end]中的item项集* <p>* 注: 不论是使用正向排名,还是使用反向排名, 使用此方法时, 应保证 startIndex代表的元素的* 位置在endIndex代表的元素的位置的前面, 如:* 示例一: RedisUtil.ZSetOps.zRange("name", 0, 2);* 示例二: RedisUtil.ZSetOps.zRange("site", -2, -1);* 示例三: RedisUtil.ZSetOps.zRange("foo", 0, -1);* <p>* 注: 若key不存在, 则返回空的集合。* <p>* 注: 当[start, end]的范围比实际zset的范围大时, 返回范围上"交集"对应的项集合。** @param key   定位zset的key* @param start 排名开始位置* @param end   排名结束位置* @return 对应的item项集* @date 2020/3/12 9:50:40*/public static Set<String> zRange(String key, long start, long end) {log.info("zRange(...) => key -> {}, start -> {}, end -> {}", key, start, end);Set<String> result = redisTemplate.opsForZSet().range(key, start, end);log.info("zRange(...) => result -> {}", result);return result;}/*** 获取(key对应的)zset中的所有item项** @param key 定位zset的键* @return (key对应的)zset中的所有item项* @date 2020/3/12 10:02:07* @see ZSetOps#zRange(String, long, long)*/public static Set<String> zWholeZSetItem(String key) {log.info("zWholeZSetItem(...) => key -> {}", key);Set<String> result = redisTemplate.opsForZSet().range(key, 0, -1);log.info("zWholeZSetItem(...) =>result -> {}", result);return result;}/*** 根据索引位置, 获取(key对应的)zset中排名处于[start, end]中的entry集* <p>* 注: 不论是使用正向排名,还是使用反向排名, 使用此方法时, 应保证 startIndex代表的元素的* 位置在endIndex代表的元素的位置的前面, 如:* 示例一: RedisUtil.ZSetOps.zRange("name", 0, 2);* 示例二: RedisUtil.ZSetOps.zRange("site", -2, -1);* 示例三: RedisUtil.ZSetOps.zRange("foo", 0, -1);* <p>* 注: 若key不存在, 则返回空的集合。* <p>* 注: 当[start, end]的范围比实际zset的范围大时, 返回范围上"交集"对应的项集合。* <p>* 注: 此方法和{@link ZSetOps#zRange(String, long, long)}类似,不过此方法返回的不是item集, 而是entry集** @param key   定位zset的key* @param start 排名开始位置* @param end   排名结束位置* @return 对应的entry集* @date 2020/3/12 9:50:40*/public static Set<ZSetOperations.TypedTuple<String>> zRangeWithScores(String key, long start, long end) {log.info("zRangeWithScores(...) => key -> {}, start -> {}, end -> {}", key, start, end);Set<ZSetOperations.TypedTuple<String>> entries = redisTemplate.opsForZSet().rangeWithScores(key, start, end);log.info("zRangeWithScores(...) => entries -> {}", JSON.toJSONString(entries));return entries;}/*** 获取(key对应的)zset中的所有entry** @param key 定位zset的键* @return (key对应的)zset中的所有entry* @date 2020/3/12 10:02:07* @see ZSetOps#zRangeWithScores(String, long, long)*/public static Set<ZSetOperations.TypedTuple<String>> zWholeZSetEntry(String key) {log.info("zWholeZSetEntry(...) => key -> {}", key);Set<ZSetOperations.TypedTuple<String>> entries = redisTemplate.opsForZSet().rangeWithScores(key, 0, -1);log.info("zWholeZSetEntry(...) => entries -> {}", key, JSON.toJSONString(entries));return entries;}/*** 根据score, 获取(key对应的)zset中分数值处于[minScore, maxScore]中的item项集* <p>* 注: 若key不存在, 则返回空的集合。* 注: 当[minScore, maxScore]的范围比实际zset中score的范围大时, 返回范围上"交集"对应的项集合。* <p>* 提示: 虽然删除范围包含两侧的端点(即:包含minScore和maxScore), 但是由于double存在精度问题,所以建议:* 设置值时,minScore应该设置得比要删除的项里,最小的score还小一点* maxScore应该设置得比要删除的项里,最大的score还大一点* 追注: 本人简单测试了几组数据,暂未出现精度问题。** @param key      定位zset的key* @param minScore score下限* @param maxScore score上限* @return 对应的item项集* @date 2020/3/12 9:50:40*/public static Set<String> zRangeByScore(String key, double minScore, double maxScore) {log.info("zRangeByScore(...) => key -> {}, minScore -> {}, maxScore -> {}", key, minScore, maxScore);Set<String> items = redisTemplate.opsForZSet().rangeByScore(key, minScore, maxScore);log.info("zRangeByScore(...) => items -> {}", items);return items;}/*** 根据score, 获取(key对应的)zset中分数值处于[minScore, maxScore]中的, score处于[minScore,* 排名大于等于offset的count个item项* <p>* 特别注意: 对于不是特别熟悉redis的人来说, offset 和 count最好都使用正数, 避免引起理解上的歧义。* <p>* 注: 若key不存在, 则返回空的集合。* <p>* 提示: 虽然删除范围包含两侧的端点(即:包含minScore和maxScore), 但是由于double存在精度问题,所以建议:* 设置值时,minScore应该设置得比要删除的项里,最小的score还小一点* maxScore应该设置得比要删除的项里,最大的score还大一点* 追注: 本人简单测试了几组数据,暂未出现精度问题。** @param key      定位zset的key* @param minScore score下限* @param maxScore score上限* @param offset   偏移量(即:排名下限)* @param count    期望获取到的元素个数* @return 对应的item项集* @date 2020/3/12 9:50:40*/public static Set<String> zRangeByScore(String key, double minScore, double maxScore,long offset, long count) {log.info("zRangeByScore(...) => key -> {}, minScore -> {}, maxScore -> {}, offset -> {}, "+ "count -> {}", key, minScore, maxScore, offset, count);Set<String> items = redisTemplate.opsForZSet().rangeByScore(key, minScore, maxScore, offset, count);log.info("zRangeByScore(...) => items -> {}", items);return items;}/*** 获取(key对应的)zset中的所有score处于[minScore, maxScore]中的entry** @param key      定位zset的键* @param minScore score下限* @param maxScore score上限* @return (key对应的)zset中的所有score处于[minScore, maxScore]中的entry* @date 2020/3/12 10:02:07* @see ZSetOps#zRangeByScore(String, double, double)* <p>* 注: 若key不存在, 则返回空的集合。* 注: 当[minScore, maxScore]的范围比实际zset中score的范围大时, 返回范围上"交集"对应的项集合。*/public static Set<ZSetOperations.TypedTuple<String>> zRangeByScoreWithScores(String key, double minScore, double maxScore) {log.info("zRangeByScoreWithScores(...) => key -> {}, minScore -> {}, maxScore -> {}",key, minScore, maxScore);Set<ZSetOperations.TypedTuple<String>> entries = redisTemplate.opsForZSet().rangeByScoreWithScores(key, minScore, maxScore);log.info("zRangeByScoreWithScores(...) => entries -> {}", JSON.toJSONString(entries));return entries;}/*** 获取(key对应的)zset中, score处于[minScore, maxScore]里的、排名大于等于offset的count个entry* <p>* 特别注意: 对于不是特别熟悉redis的人来说, offset 和 count最好都使用正数, 避免引起理解上的歧义。** @param key      定位zset的键* @param minScore score下限* @param maxScore score上限* @param offset   偏移量(即:排名下限)* @param count    期望获取到的元素个数* @return [startIndex, endIndex] & [minScore, maxScore]里的entry* @date 2020/3/12 11:09:06*/public static Set<ZSetOperations.TypedTuple<String>> zRangeByScoreWithScores(String key, double minScore,double maxScore, long offset,long count) {log.info("zRangeByScoreWithScores(...) => key -> {}, minScore -> {}, maxScore -> {},"+ " offset -> {}, count -> {}",key, minScore, maxScore, offset, count);Set<ZSetOperations.TypedTuple<String>> entries = redisTemplate.opsForZSet().rangeByScoreWithScores(key, minScore,maxScore, offset, count);log.info("zRangeByScoreWithScores(...) => entries -> {}", JSON.toJSONString(entries));return entries;}/*** 获取时, 先按score倒序, 然后根据索引位置, 获取(key对应的)zset中排名处于[start, end]中的item项集** @see ZSetOps#zRange(String, long, long)。 只是zReverseRange这里会提前多一个倒序。*/public static Set<String> zReverseRange(String key, long start, long end) {log.info("zReverseRange(...) => key -> {}, start -> {}, end -> {}", key, start, end);Set<String> entries = redisTemplate.opsForZSet().reverseRange(key, start, end);log.info("zReverseRange(...) => entries -> {}", entries);return entries;}/*** 获取时, 先按score倒序, 然后根据索引位置, 获取(key对应的)zset中排名处于[start, end]中的entry集** @see ZSetOps#zRangeWithScores(String, long, long)。 只是zReverseRangeWithScores这里会提前多一个倒序。*/public static Set<ZSetOperations.TypedTuple<String>> zReverseRangeWithScores(String key, long start, long end) {log.info("zReverseRangeWithScores(...) => key -> {}, start -> {}, end -> {}", key, start, end);Set<ZSetOperations.TypedTuple<String>> entries = redisTemplate.opsForZSet().reverseRangeWithScores(key, start, end);log.info("zReverseRangeWithScores(...) => entries -> {}", JSON.toJSONString(entries));return entries;}/*** 获取时, 先按score倒序, 然后根据score, 获取(key对应的)zset中分数值处于[minScore, maxScore]中的item项集** @see ZSetOps#zRangeByScore(String, double, double)。 只是zReverseRangeByScore这里会提前多一个倒序。*/public static Set<String> zReverseRangeByScore(String key, double minScore, double maxScore) {log.info("zReverseRangeByScore(...) => key -> {}, minScore -> {}, maxScore -> {}",key, minScore, maxScore);Set<String> items = redisTemplate.opsForZSet().reverseRangeByScore(key, minScore, maxScore);log.info("zReverseRangeByScore(...) => items -> {}", items);return items;}/*** 获取时, 先按score倒序, 然后获取(key对应的)zset中的所有score处于[minScore, maxScore]中的entry** @see ZSetOps#zRangeByScoreWithScores(String, double, double)。 只是zReverseRangeByScoreWithScores这里会提前多一个倒序。*/public static Set<ZSetOperations.TypedTuple<String>> zReverseRangeByScoreWithScores(String key, double minScore, double maxScore) {log.info("zReverseRangeByScoreWithScores(...) => key -> {}, minScore -> {}, maxScore -> {}",key, minScore, maxScore);Set<ZSetOperations.TypedTuple<String>> entries = redisTemplate.opsForZSet().reverseRangeByScoreWithScores(key,minScore, maxScore);log.info("zReverseRangeByScoreWithScores(...) => entries -> {}", JSON.toJSONString(entries));return entries;}/*** 获取时, 先按score倒序, 然后根据score, 获取(key对应的)zset中分数值处于[minScore, maxScore]中的,* score处于[minScore,排名大于等于offset的count个item项** @see ZSetOps#zRangeByScore(String, double, double, long, long)。 只是zReverseRangeByScore这里会提前多一个倒序。*/public static Set<String> zReverseRangeByScore(String key, double minScore, double maxScore, long offset, long count) {log.info("zReverseRangeByScore(...) => key -> {}, minScore -> {}, maxScore -> {}, offset -> {}, "+ "count -> {}", key, minScore, maxScore, offset, count);Set<String> items = redisTemplate.opsForZSet().reverseRangeByScore(key, minScore, maxScore, offset, count);log.info("items -> {}", items);return items;}/*** 统计(key对应的zset中)score处于[minScore, maxScore]中的item的个数** @param key      定位zset的key* @param minScore score下限* @param maxScore score上限* @return [minScore, maxScore]中item的个数* @date 2020/3/13 12:20:43*/public static long zCount(String key, double minScore, double maxScore) {log.info("zCount(...) => key -> {}, minScore -> {}, maxScore -> {}", key, minScore, maxScore);Long count = redisTemplate.opsForZSet().count(key, minScore, maxScore);log.info("zCount(...) => count -> {}", count);if (count == null) {throw new RedisOpsResultIsNullException();}return count;}/*** 统计(key对应的)zset中item的个数* <p>* 注: 此方法等价于{@link ZSetOps#zZCard(String)}** @param key 定位zset的key* @return zset中item的个数* @date 2020/3/13 12:20:43*/public static long zSize(String key) {log.info("zSize(...) => key -> {}", key);Long size = redisTemplate.opsForZSet().size(key);log.info("zSize(...) => size -> {}", size);if (size == null) {throw new RedisOpsResultIsNullException();}return size;}/*** 统计(key对应的)zset中item的个数* <p>* 注: 此方法等价于{@link ZSetOps#zSize(String)}** @param key 定位zset的key* @return zset中item的个数* @date 2020/3/13 12:20:43*/public static long zZCard(String key) {log.info("zZCard(...) => key -> {}", key);Long size = redisTemplate.opsForZSet().zCard(key);log.info("zZCard(...) => size -> {}", size);if (size == null) {throw new RedisOpsResultIsNullException();}return size;}/*** 统计(key对应的)zset中指定item的score** @param key  定位zset的key* @param item zset中的item* @return item的score* @date 2020/3/13 14:51:43*/public static double zScore(String key, Object item) {log.info("zScore(...) => key -> {}, item -> {}", key, item);Double score = redisTemplate.opsForZSet().score(key, item);log.info("zScore(...) => score -> {}", score);if (score == null) {throw new RedisOpsResultIsNullException();}return score;}/*** 获取两个(key对应的)ZSet的并集, 并将结果add到storeKey对应的ZSet中。* <p>* 注: 和set一样,zset中item是唯一的, 在多个zset进行Union时, 处理相同的item时, score的值会变为对应的score之和,如:* RedisUtil.ZSetOps.zAdd("name1", "a", 1);和RedisUtil.ZSetOps.zAdd("name2", "a", 2);* 对(name1和name2对应的)zset进行zUnionAndStore之后,新的zset中的项a,对应的score值为3* <p>* case1: 交集不为空, storeKey不存在, 则 会创建对应的storeKey,并将并集添加到(storeKey对应的)ZSet中* case2: 交集不为空, storeKey已存在, 则 会清除原(storeKey对应的)ZSet中所有的项,然后将并集添加到(storeKey对应的)ZSet中* case3: 交集为空, 则不进行下面的操作, 直接返回0** @param key      定位其中一个zset的键* @param otherKey 定位另外的zset的键* @param storeKey 定位(要把交集添加到哪个)set的key* @return add到(storeKey对应的)ZSet后, 该ZSet对应的size* @date 2020/3/11 12:26:24*/public static long zUnionAndStore(String key, String otherKey, String storeKey) {log.info("zUnionAndStore(...) => key -> {}, otherKey -> {}, storeKey -> {}", key, otherKey, storeKey);Long size = redisTemplate.opsForZSet().unionAndStore(key, otherKey, storeKey);log.info("zUnionAndStore(...) => size -> {}", size);if (size == null) {throw new RedisOpsResultIsNullException();}return size;}/*** 获取两个(key对应的)ZSet的并集, 并将结果add到storeKey对应的ZSet中。* <p>* 注: 和set一样,zset中item是唯一的, 在多个zset进行Union时, 处理相同的item时, score的值会变为对应的score之和,如:* RedisUtil.ZSetOps.zAdd("name1", "a", 1);和RedisUtil.ZSetOps.zAdd("name2", "a", 2);* 对(name1和name2对应的)zset进行zUnionAndStore之后,新的zset中的项a,对应的score值为3* <p>* case1: 并集不为空, storeKey不存在, 则 会创建对应的storeKey,并将并集添加到(storeKey对应的)ZSet中* case2: 并集不为空, storeKey已存在, 则 会清除原(storeKey对应的)ZSet中所有的项,然后将并集添加到(storeKey对应的)ZSet中* case3: 并集为空, 则不进行下面的操作, 直接返回0** @param key       定位其中一个set的键* @param otherKeys 定位其它set的键集* @param storeKey  定位(要把并集添加到哪个)set的key* @return add到(storeKey对应的)ZSet后, 该ZSet对应的size* @date 2020/3/11 12:26:24*/public static long zUnionAndStore(String key, Collection<String> otherKeys, String storeKey) {log.info("zUnionAndStore(...) => key -> {}, otherKeys -> {}, storeKey -> {}", key, otherKeys, storeKey);Long size = redisTemplate.opsForZSet().unionAndStore(key, otherKeys, storeKey);log.info("zUnionAndStore(...) => size -> {}", size);if (size == null) {throw new RedisOpsResultIsNullException();}return size;}/*** 获取两个(key对应的)ZSet的交集, 并将结果add到storeKey对应的ZSet中。* <p>* 注: 和set一样,zset中item是唯一的, 在多个zset进行Intersect时, 处理相同的item时, score的值会变为对应的score之和,如:* RedisUtil.ZSetOps.zAdd("name1", "a", 1);* RedisUtil.ZSetOps.zAdd("name1", "b", 100);* 和R* edisUtil.ZSetOps.zAdd("name2", "a", 2);* edisUtil.ZSetOps.zAdd("name2", "c", 200);* 对(name1和name2对应的)zset进行zIntersectAndStore之后,新的zset中的项a,对应的score值为3* <p>* case1: 交集不为空, storeKey不存在, 则 会创建对应的storeKey,并将交集添加到(storeKey对应的)ZSet中* case2: 交集不为空, storeKey已存在, 则 会清除原(storeKey对应的)ZSet中所有的项,然后将交集添加到(storeKey对应的)ZSet中* case3: 交集为空, 则不进行下面的操作, 直接返回0** @param key      定位其中一个ZSet的键* @param otherKey 定位其中另一个ZSet的键* @param storeKey 定位(要把交集添加到哪个)ZSet的key* @return add到(storeKey对应的)ZSet后, 该ZSet对应的size* @date 2020/3/11 9:46:46*/public static long zIntersectAndStore(String key, String otherKey, String storeKey) {log.info("zIntersectAndStore(...) => key -> {}, otherKey -> {}, storeKey -> {}", key, otherKey, storeKey);Long size = redisTemplate.opsForZSet().intersectAndStore(key, otherKey, storeKey);log.info("zIntersectAndStore(...) => size -> {}", size);if (size == null) {throw new RedisOpsResultIsNullException();}return size;}/*** 获取多个(key对应的)ZSet的交集, 并将结果add到storeKey对应的ZSet中。* <p>* case1: 交集不为空, storeKey不存在, 则 会创建对应的storeKey,并将交集添加到(storeKey对应的)ZSet中* case2: 交集不为空, storeKey已存在, 则 会清除原(storeKey对应的)ZSet中所有的项,然后将交集添加到(storeKey对应的)ZSet中* case3: 交集为空, 则不进行下面的操作, 直接返回0** @param key       定位其中一个set的键* @param otherKeys 定位其它set的键集* @param storeKey  定位(要把并集添加到哪个)set的key* @return add到(storeKey对应的)ZSet后, 该ZSet对应的size* @date 2020/3/11 11:04:29*/public static long zIntersectAndStore(String key, Collection<String> otherKeys, String storeKey) {log.info("zIntersectAndStore(...) => key -> {}, otherKeys -> {}, storeKey -> {}",key, otherKeys, storeKey);Long size = redisTemplate.opsForZSet().intersectAndStore(key, otherKeys, storeKey);log.info("zIntersectAndStore(...) => size -> {}", size);if (size == null) {throw new RedisOpsResultIsNullException();}return size;}}/*** redis分布式锁(单机版).* <p>* 使用方式(示例):* boolean flag = false;* String lockName = "sichuan:mianyang:fucheng:ds";* String lockValue = UUID.randomUUID().toString();* try {* //	非阻塞获取(锁的最大存活时间采用默认值)* flag = RedisUtil.LockOps.getLock(lockName, lockValue);* //	非阻塞获取e.g.* flag = RedisUtil.LockOps.getLock(lockName, lockValue, 3, TimeUnit.SECONDS);* // 阻塞获取(锁的最大存活时间采用默认值)* flag = RedisUtil.LockOps.getLockUntilTimeout(lockName, lockValue, 2000);* // 阻塞获取e.g.* flag = RedisUtil.LockOps.getLockUntilTimeout(lockName, lockValue, 2, TimeUnit.SECONDS, 2000);* if (!flag) {* throw new RuntimeException(" obtain redis-lock[" + lockName + "] fail");* }* // your logic* //	...* } finally {* if (flag) {* RedisUtil.LockOps.releaseLock(lockName, lockValue);* }* }* <p>* |--------------------------------------------------------------------------------------------------------------------|* |单机版分布式锁、集群版分布式锁,特别说明:                                                                                 |* |   - 此锁是针对单机Redis的分布式锁;                                                                                    |* |   - 对于Redis集群而言, 此锁可能存在失效的情况。考虑如下情况:                                                              |* |         首先,当客户端A通过key-value(假设为key名为key123)在Master上获取到一个锁。                                        |* |         然后,Master试着把这个数据同步到Slave的时候突然挂了(此时Slave上没有该分布式锁的key123)。                            |* |         接着,Slave变成了Master。                                                                                    |* |         不巧的是,客户端B此时也一以相同的key去获取分布式锁;                                                              |* |                 因为现在的Master上没有key123代表的分布式锁,                                                            |* |                 所以客户端B此时再通过key123去获取分布式锁时,                                                            |* |                 就能获取成功。                                                                                       |* |         那么此时,客户端A和客户端B同时获取到了同一把分布式锁,分布式锁失效。                                                 |* |   - 在Redis集群模式下,如果需要严格的分布式锁的话,可使用Redlock算法来实现。Redlock算法原理简述:                              |* |     - 获取分布式锁:                                                                                                 |* |           1. 客户端获取服务器当前的的时间t0。                                                                           |* |           2. 使用相同的key和value依次向5个实例获取锁。                                                                  |* |              注:为了避免在某个redis节点耗时太久而影响到对后面的Redis节点的锁的获取;                                         |* |                 客户端在获取每一个Redis节点的锁的时候,自身需要设置一个较小的等待获取锁超时的时间,                             |* |                 一旦都在某个节点获取分布式锁的时间超过了超时时间,那么就认为在这个节点获取分布式锁失败,                        |* |                 (不把时间浪费在这一个节点上),继续获取下一个节点的分布式锁。                                              |* |           3. 客户端通过当前时间(t1)减去t0,计算(从所有redis节点)获取锁所消耗的总时间t2(注:t2=t1-t0)。                      |* |              只有t2小于锁本身的锁定时长(注:若锁的锁定时长是1小时, 假设下午一点开始上锁,那么锁会在下午两点                     |* |              的时候失效, 而你却在两点后才获取到锁,这个时候已经没意义了),并且,客户端在至少在多半Redis                        |* |              节点上获取到锁, 我们才认为分布式锁获取成功。                                                                |* |           5. 如果锁已经获取,那么  锁的实际有效时长 = 锁的总有效时长 - 获取分布式锁所消耗的时长; 锁的实际有效时长 应保证 > 0。    |* |              注: 也就是说, 如果获取锁失败,那么                                                                        |* |                  A. 可能是   获取到的锁的个数,不满足大多数原则。                                                         |* |                  B. 也可能是 锁的实际有效时长不大于0。                                                                  |* |      - 释放分布式锁: 在每个redis节点上试着删除锁(, 不论有没有在该节点上获取到锁)。                                          |* |   - 集群下的分布式锁,可直接使用现有类库<a href="https://github.com/redisson/redisson"/>                                |* |                                                                                                                    |* |   注: 如果Redis集群项目能够容忍master宕机导致单机版分布式锁失效的情况的话,那么是直接使用单机版分布式锁在Redis集群的项目中的;     |* |       如果Redis集群项目不能容忍单机版分布式锁失效的情况的话,那么请使用基于RedLock算法的集群版分布式锁;                        |* |--------------------------------------------------------------------------------------------------------------------|** @date 2020/3/14 19:23:26*/public static class LockOps {/*** lua脚本, 保证 释放锁脚本 的原子性(以避免, 并发场景下, 释放了别人的锁)*/private static final String RELEASE_LOCK_LUA;/*** 分布式锁默认(最大)存活时长*/public static final long DEFAULT_LOCK_TIMEOUT = 3;/*** DEFAULT_LOCK_TIMEOUT的单位*/public static final TimeUnit DEFAULT_TIMEOUT_UNIT = TimeUnit.SECONDS;static {// 不论lua中0是否代表失败; 对于java的Boolean而言, 返回0, 则会被解析为falseRELEASE_LOCK_LUA = "if redis.call('get',KEYS[1]) == ARGV[1] "+ "then "+ "    return redis.call('del',KEYS[1]) "+ "else "+ "    return 0 "+ "end ";}/*** 获取(分布式)锁.* <p>* 注: 获取结果是即时返回的、是非阻塞的。** @see RedisUtil.LockOps#getLock(String, String, long, TimeUnit)*/public static boolean getLock(final String key, final String value) {return getLock(key, value, DEFAULT_LOCK_TIMEOUT, DEFAULT_TIMEOUT_UNIT);}/*** 获取(分布式)锁。* 若成功, 则直接返回;* 若失败, 则进行重试, 直到成功 或 超时为止。* <p>* 注: 获取结果是阻塞的, 要么成功, 要么超时, 才返回。** @param retryTimeoutLimit 重试的超时时长(ms)*                          其它参数可详见:* @return 是否成功* @see RedisUtil.LockOps#getLock(String, String, long, TimeUnit)*/public static boolean getLockUntilTimeout(final String key, final String value,final long retryTimeoutLimit) {return getLockUntilTimeout(key, value, DEFAULT_LOCK_TIMEOUT, DEFAULT_TIMEOUT_UNIT, retryTimeoutLimit);}/*** 获取(分布式)锁。* 若成功, 则直接返回;* 若失败, 则进行重试, 直到成功 或 超时为止。* <p>* 注: 获取结果是阻塞的, 要么成功, 要么超时, 才返回。** @param retryTimeoutLimit 重试的超时时长(ms)*                          其它参数可详见:* @return 是否成功* @see RedisUtil.LockOps#getLock(String, String, long, TimeUnit, boolean)*/public static boolean getLockUntilTimeout(final String key, final String value,final long timeout, final TimeUnit unit,final long retryTimeoutLimit) {log.info("getLockUntilTimeout(...) => key -> {}, value -> {}, timeout -> {}, unit -> {}, "+ "retryTimeoutLimit -> {}ms", key, value, timeout, unit, retryTimeoutLimit);long startTime = Instant.now().toEpochMilli();long now = startTime;do {try {boolean alreadyGotLock = getLock(key, value, timeout, unit, false);if (alreadyGotLock) {log.info("getLockUntilTimeout(...) => consume time -> {}ms, result -> true", now - startTime);return true;}} catch (Exception e) {log.warn("getLockUntilTimeout(...) => try to get lock failure! e.getMessage -> {}",e.getMessage());}now = Instant.now().toEpochMilli();} while (now < startTime + retryTimeoutLimit);log.info("getLockUntilTimeout(...) => consume time -> {}ms, result -> false", now - startTime);return false;}/*** 获取(分布式)锁* <p>* 注: 获取结果是即时返回的、是非阻塞的。** @see RedisUtil.LockOps#getLock(String, String, long, TimeUnit, boolean)*/public static boolean getLock(final String key, final String value,final long timeout, final TimeUnit unit) {return getLock(key, value, timeout, unit, true);}/*** 获取(分布式)锁* <p>* 注: 获取结果是即时返回的、是非阻塞的。** @param key       锁名* @param value     锁名对应的value*                  注: value一般采用全局唯一的值, 如: requestId、uuid等。*                  这样, 释放锁的时候, 可以再次验证value值,*                  保证自己上的锁只能被自己释放, 而不会被别人释放。*                  当然, 如果锁超时时, 会被redis自动删除释放。* @param timeout   锁的(最大)存活时长*                  注: 一般的, 获取锁与释放锁 都是成对使用的, 在锁在达到(最大)存活时长之前,都会被主动释放。*                  但是在某些情况下(如:程序获取锁后,释放锁前,崩了),锁得不到释放, 这时就需要等锁过*                  了(最大)存活时长后,被redis自动删除清理了。这样就能保证redis中不会留下死数据。* @param unit      timeout的单位* @param recordLog 是否记录日志* @return 是否成功*/public static boolean getLock(final String key, final String value,final long timeout, final TimeUnit unit,boolean recordLog) {if (recordLog) {log.info("getLock(...) => key -> {}, value -> {}, timeout -> {}, unit -> {}, recordLog -> {}",key, value, timeout, unit, recordLog);}Boolean result = redisTemplate.execute((RedisConnection connection) ->connection.set(key.getBytes(StandardCharsets.UTF_8),value.getBytes(StandardCharsets.UTF_8),Expiration.seconds(unit.toSeconds(timeout)),RedisStringCommands.SetOption.SET_IF_ABSENT));if (recordLog) {log.info("getLock(...) => result -> {}", result);}if (result == null) {throw new RedisOpsResultIsNullException();}return result;}/*** 释放(分布式)锁* <p>* 注: 此方式能(通过value的唯一性)保证: 自己加的锁, 只能被自己释放。* 注: 锁超时时, 也会被redis自动删除释放。** @param key   锁名* @param value 锁名对应的value* @return 释放锁是否成功* @date 2020/3/15 17:00:45*/public static boolean releaseLock(final String key, final String value) {log.info("releaseLock(...) => key -> {}, lockValue -> {}", key, value);Boolean result = redisTemplate.execute((RedisConnection connection) ->connection.eval(RELEASE_LOCK_LUA.getBytes(),ReturnType.BOOLEAN, 1,key.getBytes(StandardCharsets.UTF_8), value.getBytes(StandardCharsets.UTF_8)));log.info("releaseLock(...) => result -> {}", result);if (result == null) {throw new RedisOpsResultIsNullException();}return result;}/*** 释放锁, 不校验该key对应的value值* <p>* 注: 此方式释放锁,可能导致: 自己加的锁, 结果被别人释放了。* 所以不建议使用此方式释放锁。** @param key 锁名* @date 2020/3/15 18:56:59*/@Deprecatedpublic static void releaseLock(final String key) {RedisUtil.KeyOps.delete(key);}}/*** 当使用Pipeline 或 Transaction操作redis时, (不论redis中实际操作是否成功, 这里)结果(都)会返回null。* 此时,如果试着将null转换为基本类型的数据时,会抛出此异常。* <p>* 即: 此工具类中的某些方法, 希望不要使用Pipeline或Transaction操作redis。* <p>* 注: Pipeline 或 Transaction默认是不启用的, 可详见源码:** @date 2020/3/14 21:22:39* @see LettuceConnection#isPipelined()* @see LettuceConnection#isQueueing()* @see JedisConnection#isPipelined()* @see JedisConnection#isQueueing()*/public static class RedisOpsResultIsNullException extends NullPointerException {public RedisOpsResultIsNullException() {super();}public RedisOpsResultIsNullException(String message) {super(message);}}/*** 提供一些基础功能支持** @date 2020/3/16 0:48:14*/public static class Helper {/*** 默认拼接符*/public static final String DEFAULT_SYMBOL = ":";/*** 拼接args** @see Helper#joinBySymbol(String, String...)*/public static String join(String... args) {return Helper.joinBySymbol(DEFAULT_SYMBOL, args);}/*** 使用symbol拼接args** @param symbol 分隔符, 如: 【:】* @param args   要拼接的元素数组, 如: 【a b c】* @return 拼接后的字符串, 如  【a:b:c】* @date 2019/9/8 16:11*/public static String joinBySymbol(String symbol, String... args) {if (symbol == null || symbol.trim().length() == 0) {throw new RuntimeException(" symbol must not be empty!");}if (args == null || args.length == 0) {throw new RuntimeException(" args must not be empty!");}StringBuilder sb = new StringBuilder(16);for (String arg : args) {sb.append(arg).append(symbol);}sb.replace(sb.length() - symbol.length(), sb.length(), "");return sb.toString();}}
}

多数据源配置

单数据源RedisUtil(静态)的基础上修改

  1. 导入依赖
        <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId></dependency><dependency><groupId>redis.clients</groupId><artifactId>jedis</artifactId><type>jar</type></dependency>
  1. 配置多数据源
spring:redis:# 这个是我的主redishost: 8.137.112.xxxpassword: xxxxxport: 6379#记得改这个database: 1#这个是我的第二个redis,用的阿里云的,需要配置username,不需要的可以去掉second:host: r-xxxxxx.redis.rds.aliyuncs.comusername: xxxxpassword: xxxxport: 6379#记得改这个database: 11
  1. 配置多数据源RedisConnectionFactory
package com.applets.manager.core.config;import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.connection.RedisPassword;
import org.springframework.data.redis.connection.RedisStandaloneConfiguration;
import org.springframework.data.redis.connection.jedis.JedisClientConfiguration;
import org.springframework.data.redis.connection.jedis.JedisConnectionFactory;
import redis.clients.jedis.JedisPoolConfig;/*** @author zr 2024/5/10*/
@Configuration
public class RedisConfig {@Value("${spring.redis.host}")private String host;@Value("${spring.redis.port}")private int port;@Value("${spring.redis.password}")private String password;@Value("${spring.redis.database}")private String database;@Value("${spring.redis.second.host}")private String secondHost;@Value("${spring.redis.second.port}")private int secondPort;@Value("${spring.redis.second.username}")private String secondUsername;@Value("${spring.redis.second.password}")private String secondPassword;@Value("${spring.redis.second.database}")private String secondDatabase;@Bean(name = "redisConnectionFactory")@Primary  //默认选择这个数据源进行执行@Qualifier("redisConnectionFactory")public RedisConnectionFactory redisConnectionFactory() {RedisStandaloneConfiguration config = new RedisStandaloneConfiguration();config.setHostName(host);config.setPort(port);config.setPassword(RedisPassword.of(password));config.setDatabase(Integer.parseInt(database));JedisPoolConfig poolConfig = new JedisPoolConfig();poolConfig.setMaxTotal(100);poolConfig.setMaxIdle(50);poolConfig.setMinIdle(10);poolConfig.setMaxWaitMillis(10000);JedisClientConfiguration jedisClientConfiguration = JedisClientConfiguration.builder().usePooling().poolConfig(poolConfig).build();return new JedisConnectionFactory(config,jedisClientConfiguration);}@Bean(name = "secondRedisConnectionFactory")@Qualifier("secondRedisConnectionFactory")public RedisConnectionFactory secondRedisConnectionFactory() {RedisStandaloneConfiguration config = new RedisStandaloneConfiguration();config.setHostName(secondHost);config.setPort(secondPort);config.setPassword(RedisPassword.of(secondPassword));config.setDatabase(secondPort);config.setUsername(secondUsername);config.setDatabase(Integer.parseInt(secondDatabase));JedisPoolConfig poolConfig = new JedisPoolConfig();poolConfig.setMaxTotal(100);poolConfig.setMaxIdle(50);poolConfig.setMinIdle(10);poolConfig.setMaxWaitMillis(10000);JedisClientConfiguration jedisClientConfiguration = JedisClientConfiguration.builder().usePooling().poolConfig(poolConfig).build();return new JedisConnectionFactory(config,jedisClientConfiguration);}
}
  1. 单数据源RedisUtil(静态)

因为第二个数据源只需要操作hash,所以第二个数据源只写了LyHashOps,有需要其他类型的操作可以复制数据源一的其他操作方法,把redisTemplate改为redisTemplate2即可
也可以通过getRedisTemplate或getYlRedisTemplate拿到对应数据源的RedisTemplate进行操作

package com.applets.manager.core.util;import com.alibaba.fastjson.JSON;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.data.redis.RedisSystemException;
import org.springframework.data.redis.connection.*;
import org.springframework.data.redis.connection.jedis.JedisConnection;
import org.springframework.data.redis.connection.lettuce.LettuceConnection;
import org.springframework.data.redis.core.*;
import org.springframework.data.redis.core.types.Expiration;
import org.springframework.stereotype.Component;import java.nio.charset.StandardCharsets;
import java.time.Instant;
import java.util.*;
import java.util.Map.Entry;
import java.util.concurrent.TimeUnit;/***  统一说明一: 方法中的key、 value都不能为null。*  统一说明二: 不能跨数据类型进行操作, 否者会操作失败/操作报错。*             如: 向一个String类型的做Hash操作,会失败/报错......等等* @author zr 2024/3/4*/@Slf4j
@Component
@SuppressWarnings("unused")
public class RedisUtil implements ApplicationContextAware {/*** 使用StringRedisTemplate(,其是RedisTemplate的定制化升级)*/private static StringRedisTemplate redisTemplate;private static StringRedisTemplate redisTemplate2;@Autowired@Qualifier("redisConnectionFactory")private RedisConnectionFactory redisConnectionFactory1;@Autowired@Qualifier("secondRedisConnectionFactory")private RedisConnectionFactory redisConnectionFactory2;@Overridepublic void setApplicationContext(ApplicationContext applicationContext) throws BeansException {redisTemplate = createStringRedisTemplate(redisConnectionFactory1);redisTemplate2 = createStringRedisTemplate(redisConnectionFactory2);}private StringRedisTemplate createStringRedisTemplate(RedisConnectionFactory redisConnectionFactory) {return new StringRedisTemplate(redisConnectionFactory);}public static StringRedisTemplate getRedisTemplate() {return redisTemplate;}public static StringRedisTemplate getYlRedisTemplate() {return redisTemplate2;}/*** key相关操作** @date 2020/3/7 16:54:25*/public static class KeyOps {/*** 根据key, 删除redis中的对应key-value* <p>* 注: 若删除失败, 则返回false。* <p>* 若redis中,不存在该key, 那么返回的也是false。* 所以,不能因为返回了false,就认为redis中一定还存* 在该key对应的key-value。** @param key 要删除的key* @return 删除是否成功* @date 2020/3/7 17:15:02*/public static boolean delete(String key) {log.info("delete(...) => key -> {}", key);// 返回值只可能为true/false, 不可能为nullBoolean result = redisTemplate.delete(key);log.info("delete(...) => result -> {}", result);if (result == null) {throw new RedisOpsResultIsNullException();}return result;}/*** 根据keys, 批量删除key-value* <p>* 注: 若redis中,不存在对应的key, 那么计数不会加1, 即:* redis中存在的key-value里,有名为a1、a2的key,* 删除时,传的集合是a1、a2、a3,那么返回结果为2。** @param keys 要删除的key集合* @return 删除了的key-value个数* @date 2020/3/7 17:48:04*/public static long delete(Collection<String> keys) {log.info("delete(...) => keys -> {}", keys);Long count = redisTemplate.delete(keys);log.info("delete(...) => count -> {}", count);if (count == null) {throw new RedisOpsResultIsNullException();}return count;}/*** 将key对应的value值进行序列化,并返回序列化后的value值。* <p>* 注: 若不存在对应的key, 则返回null。* 注: dump时,并不会删除redis中的对应key-value。* 注: dump功能与restore相反。** @param key 要序列化的value的key* @return 序列化后的value值* @date 2020/3/8 11:34:13*/public static byte[] dump(String key) {log.info("dump(...) =>key -> {}", key);byte[] result = redisTemplate.dump(key);log.info("dump(...) => result -> {}", result);return result;}/*** 将给定的value值,反序列化到redis中, 形成新的key-value。** @param key        value对应的key* @param value      要反序列的value值。*                   注: 这个值可以由{@link this#dump(String)}获得* @param timeToLive 反序列化后的key-value的存活时长* @param unit       timeToLive的单位* @throws RedisSystemException 如果redis中已存在同样的key时,抛出此异常* @date 2020/3/8 11:36:45*/public static void restore(String key, byte[] value, long timeToLive, TimeUnit unit) {restore(key, value, timeToLive, unit, false);}/*** 将给定的value值,反序列化到redis中, 形成新的key-value。** @param key     value对应的key* @param value   要反序列的value值。*                注: 这个值可以由{@link this#dump(String)}获得* @param timeout 反序列化后的key-value的存活时长* @param unit    timeout的单位* @param replace 若redis中已经存在了相同的key, 是否替代原来的key-value* @throws RedisSystemException 如果redis中已存在同样的key, 且replace为false时,抛出此异常* @date 2020/3/8 11:36:45*/public static void restore(String key, byte[] value, long timeout, TimeUnit unit, boolean replace) {log.info("restore(...) => key -> {}, value -> {}, timeout -> {}, unit -> {}, replace -> {}",key, value, timeout, unit, replace);redisTemplate.restore(key, value, timeout, unit, replace);}/*** redis中是否存在,指定key的key-value** @param key 指定的key* @return 是否存在对应的key-value* @date 2020/3/8 12:16:46*/public static boolean hasKey(String key) {log.info("hasKey(...) => key -> {}", key);Boolean result = redisTemplate.hasKey(key);log.info("hasKey(...) => result -> {}", result);if (result == null) {throw new RedisOpsResultIsNullException();}return result;}/*** 给指定的key对应的key-value设置: 多久过时* <p>* 注:过时后,redis会自动删除对应的key-value。* 注:若key不存在,那么也会返回false。** @param key     指定的key* @param timeout 过时时间* @param unit    timeout的单位* @return 操作是否成功* @date 2020/3/8 12:18:58*/public static boolean expire(String key, long timeout, TimeUnit unit) {log.info("expire(...) => key -> {}, timeout -> {}, unit -> {}", key, timeout, unit);Boolean result = redisTemplate.expire(key, timeout, unit);log.info("expire(...) => result is -> {}", result);if (result == null) {throw new RedisOpsResultIsNullException();}return result;}/*** 给指定的key对应的key-value设置: 什么时候过时* <p>* 注:过时后,redis会自动删除对应的key-value。* 注:若key不存在,那么也会返回false。** @param key  指定的key* @param date 啥时候过时* @return 操作是否成功* @date 2020/3/8 12:19:29*/public static boolean expireAt(String key, Date date) {log.info("expireAt(...) => key -> {}, date -> {}", key, date);Boolean result = redisTemplate.expireAt(key, date);log.info("expireAt(...) => result is -> {}", result);if (result == null) {throw new RedisOpsResultIsNullException();}return result;}/*** 找到所有匹配pattern的key,并返回该key的结合.* <p>* 提示:若redis中键值对较多,此方法耗时相对较长,慎用!慎用!慎用!** @param pattern 匹配模板。*                注: 常用的通配符有:*                ?    有且只有一个;*                *     >=0哥;* @return 匹配pattern的key的集合。 可能为null。* @date 2020/3/8 12:38:38*/public static Set<String> keys(String pattern) {log.info("keys(...) => pattern -> {}", pattern);Set<String> keys = redisTemplate.keys(pattern);log.info("keys(...) => keys -> {}", keys);return keys;}/*** 将当前数据库中的key对应的key-value,移动到对应位置的数据库中。* <p>* 注:单机版的redis,默认将存储分为16个db, index为0 到 15。* 注:同一个db下,key唯一; 但是在不同db中,key可以相同。* 注:若目标db下,已存在相同的key, 那么move会失败,返回false。** @param key     定位要移动的key-value的key* @param dbIndex 要移动到哪个db* @return 移动是否成功。* 注: 若目标db下,已存在相同的key, 那么move会失败,返回false。* @date 2020/3/8 13:01:00*/public static boolean move(String key, int dbIndex) {log.info("move(...) => key  -> {}, dbIndex -> {}", key, dbIndex);Boolean result = redisTemplate.move(key, dbIndex);log.info("move(...) =>result -> {}", result);if (result == null) {throw new RedisOpsResultIsNullException();}return result;}/*** 移除key对应的key-value的过期时间, 使该key-value一直存在* <p>* 注: 若key对应的key-value,本身就是一直存在(无过期时间的), 那么persist方法会返回false;* 若没有key对应的key-value存在,本那么persist方法会返回false;** @param key 定位key-value的key* @return 操作是否成功* @date 2020/3/8 13:10:02*/public static boolean persist(String key) {log.info("persist(...) => key -> {}", key);Boolean result = redisTemplate.persist(key);log.info("persist(...) => result -> {}", result);if (result == null) {throw new RedisOpsResultIsNullException();}return result;}/*** 获取key对应的key-value的过期时间* <p>* 注: 若key-value永不过期, 那么返回的为-1。* 注: 若不存在key对应的key-value, 那么返回的为-2* 注:若存在零碎时间不足1 SECONDS,则(大体上)四舍五入到SECONDS级别。** @param key 定位key-value的key* @return 过期时间(单位s)* @date 2020/3/8 13:17:35*/public static long getExpire(String key) {Long result = getExpire(key, TimeUnit.SECONDS);return result;}/*** 获取key对应的key-value的过期时间* <p>* 注: 若key-value永不过期, 那么返回的为-1。* 注: 若不存在key对应的key-value, 那么返回的为-2* 注:若存在零碎时间不足1 unit,则(大体上)四舍五入到unit别。** @param key 定位key-value的key* @return 过期时间(单位unit)* @date 2020/3/8 13:17:35*/public static long getExpire(String key, TimeUnit unit) {log.info("getExpire(...) =>key -> {}, unit is -> {}", key, unit);Long result = redisTemplate.getExpire(key, unit);log.info("getExpire(...) => result ->  {}", result);if (result == null) {throw new RedisOpsResultIsNullException();}return result;}/*** 从redis的所有key中,随机获取一个key* <p>* 注: 若redis中不存在任何key-value, 那么这里返回null** @return 随机获取到的一个key* @date 2020/3/8 14:11:43*/public static String randomKey() {String result = redisTemplate.randomKey();log.info("randomKey(...) => result is -> {}", result);return result;}/*** 重命名对应的oldKey为新的newKey* <p>* 注: 若oldKey不存在, 则会抛出异常.* 注: 若redis中已存在与newKey一样的key,* 那么原key-value会被丢弃,* 只留下新的key,以及原来的value* 示例说明: 假设redis中已有 (keyAlpha, valueAlpha) 和 (keyBeta, valueBeat),* 在使用rename(keyAlpha, keyBeta)替换后, redis中只会剩下(keyBeta, valueAlpha)** @param oldKey 旧的key* @param newKey 新的key* @throws RedisSystemException 若oldKey不存在时, 抛出此异常* @date 2020/3/8 14:14:17*/public static void rename(String oldKey, String newKey) {log.info("rename(...) => oldKey -> {}, newKey -> {}", oldKey, newKey);redisTemplate.rename(oldKey, newKey);}/*** 当redis中不存在newKey时, 重命名对应的oldKey为新的newKey。* 否者不进行重命名操作。* <p>* 注: 若oldKey不存在, 则会抛出异常.** @param oldKey 旧的key* @param newKey 新的key* @throws RedisSystemException 若oldKey不存在时, 抛出此异常* @date 2020/3/8 14:14:17*/public static boolean renameIfAbsent(String oldKey, String newKey) {log.info("renameIfAbsent(...) => oldKey -> {}, newKey -> {}", oldKey, newKey);Boolean result = redisTemplate.renameIfAbsent(oldKey, newKey);log.info("renameIfAbsent(...) => result -> {}", result);if (result == null) {throw new RedisOpsResultIsNullException();}return result;}/*** 获取key对应的value的数据类型* <p>* 注: 若redis中不存在该key对应的key-value, 那么这里返回NONE。** @param key 用于定位的key* @return key对应的value的数据类型* @date 2020/3/8 14:40:16*/public static DataType type(String key) {log.info("type(...) => key -> {}", key);DataType result = redisTemplate.type(key);log.info("type(...) => result -> {}", result);return result;}}/*** string相关操作* <p>* 提示: redis中String的数据结构可参考resources/data-structure/String(字符串)的数据结构(示例一).png* redis中String的数据结构可参考resources/data-structure/String(字符串)的数据结构(示例二).png** @date 2020/3/7 16:54:25*/public static class StringOps {/*** 设置key-value* <p>* 注: 若已存在相同的key, 那么原来的key-value会被丢弃。** @param key   key* @param value key对应的value* @date 2020/3/8 15:40:59*/public static void set(String key, String value) {log.info("set(...) => key -> {}, value -> {}", key, value);redisTemplate.opsForValue().set(key, value);}/*** 处理redis中key对应的value值, 将第offset位的值, 设置为1或0。* <p>* 说明: 在redis中,存储的字符串都是以二级制的进行存在的; 如存储的key-value里,值为abc,实际上,* 在redis里面存储的是011000010110001001100011,前8为对应a,中间8为对应b,后面8位对应c。* 示例:这里如果setBit(key, 6, true)的话,就是将索引位置6的那个数,设置值为1,值就变成* 了011000110110001001100011* 追注:offset即index,从0开始。* <p>* 注: 参数value为true, 则设置为1;参数value为false, 则设置为0。* <p>* 注: 若redis中不存在对应的key,那么会自动创建新的。* 注: offset可以超过value在二进制下的索引长度。** @param key    定位value的key* @param offset 要改变的bit的索引* @param value  改为1或0, true - 改为1, false - 改为0* @return set是否成功* @date 2020/3/8 16:30:37*/public static boolean setBit(String key, long offset, boolean value) {log.info("setBit(...) => key -> {}, offset -> {}, value -> {}", key, offset, value);Boolean result = redisTemplate.opsForValue().setBit(key, offset, value);log.info("setBit(...) => result -> {}", result);if (result == null) {throw new RedisOpsResultIsNullException();}return result;}/*** 设置key-value* <p>* 注: 若已存在相同的key, 那么原来的key-value会被丢弃** @param key     key* @param value   key对应的value* @param timeout 过时时长* @param unit    timeout的单位* @date 2020/3/8 15:40:59*/public static void setEx(String key, String value, long timeout, TimeUnit unit) {log.info("setEx(...) => key -> {}, value -> {}, timeout -> {}, unit -> {}",key, value, timeout, unit);redisTemplate.opsForValue().set(key, value, timeout, unit);}/*** 若不存在key时, 向redis中添加key-value, 返回成功/失败。* 若存在,则不作任何操作, 返回false。** @param key   key* @param value key对应的value* @return set是否成功* @date 2020/3/8 16:51:36*/public static boolean setIfAbsent(String key, String value) {log.info("setIfAbsent(...) => key -> {}, value -> {}", key, value);Boolean result = redisTemplate.opsForValue().setIfAbsent(key, value);log.info("setIfAbsent(...) => result -> {}", result);if (result == null) {throw new RedisOpsResultIsNullException();}return result;}/*** 若不存在key时, 向redis中添加一个(具有超时时长的)key-value, 返回成功/失败。* 若存在,则不作任何操作, 返回false。** @param key     key* @param value   key对应的value* @param timeout 超时时长* @param unit    timeout的单位* @return set是否成功* @date 2020/3/8 16:51:36*/public static boolean setIfAbsent(String key, String value, long timeout, TimeUnit unit) {log.info("setIfAbsent(...) => key -> {}, value -> {}, key -> {}, value -> {}", key, value, timeout, unit);Boolean result = redisTemplate.opsForValue().setIfAbsent(key, value, timeout, unit);log.info("setIfAbsent(...) => result -> {}", result);if (result == null) {throw new RedisOpsResultIsNullException();}return result;}/*** 从(redis中key对应的)value的offset位置起(包含该位置),用replaceValue替换对应长度的值。* <p>* 举例说明:* 1.假设redis中存在key-value ("ds", "0123456789"); 调* 用setRange("ds", "abcdefghijk", 3)后, redis中该value值就变为了[012abcdefghijk]* <p>* 2.假设redis中存在key-value ("jd", "0123456789");调* 用setRange("jd", "xyz", 3)后, redis中该value值就变为了[012xyz6789]* <p>* 3.假设redis中存在key-value ("ey", "0123456789");调* 用setRange("ey", "qwer", 15)后, redis中该value值就变为了[0123456789     qwer]* 注:case3比较特殊,offset超过了原value的长度了, 中间就会有一些空格来填充,但是如果在程序* 中直接输出的话,中间那部分空格可能会出现乱码。** @param key          定位key-value的key* @param replaceValue 要替换的值* @param offset       起始位置* @date 2020/3/8 17:04:31*/public static void setRange(String key, String replaceValue, long offset) {log.info("setRange(...) => key -> {}, replaceValue -> {}, offset -> {}", key, replaceValue, offset);redisTemplate.opsForValue().set(key, replaceValue, offset);}/*** 获取到key对应的value的长度。* <p>* 注: 长度等于{@link String#length}。* 注: 若redis中不存在对应的key-value, 则返回值为0.** @param key 定位value的key* @return value的长度* @date 2020/3/8 17:14:30*/public static long size(String key) {log.info("size(...) => key -> {}", key);Long result = redisTemplate.opsForValue().size(key);log.info("size(...) => result -> {}", result);if (result == null) {throw new RedisOpsResultIsNullException();}return result;}/*** 批量设置 key-value* <p>* 注: 若存在相同的key, 则原来的key-value会被丢弃。** @param maps key-value 集* @date 2020/3/8 17:21:19*/public static void multiSet(Map<String, String> maps) {log.info("multiSet(...) => maps -> {}", maps);redisTemplate.opsForValue().multiSet(maps);}/*** 当redis中,不存在任何一个keys时, 才批量设置 key-value, 并返回成功/失败.* 否者,不进行任何操作, 并返回false。* <p>* 即: 假设调用此方法时传入的参数map是这样的: {k1=v1, k2=v2, k3=v3}* 那么redis中, k1、k2、k3都不存在时,才会批量设置key-value;* 否则不会设置任何key-value。* <p>* 注: 若存在相同的key, 则原来的key-value会被丢弃。* <p>* 注:** @param maps key-value 集* @return 操作是否成功* @date 2020/3/8 17:21:19*/public static boolean multiSetIfAbsent(Map<String, String> maps) {log.info("multiSetIfAbsent(...) => maps -> {}", maps);Boolean result = redisTemplate.opsForValue().multiSetIfAbsent(maps);log.info("multiSetIfAbsent(...) => result -> {}", result);if (result == null) {throw new RedisOpsResultIsNullException();}return result;}/*** 增/减 整数* <p>* 注: 负数则为减。* 注: 若key对应的value值不支持增/减操作(即: value不是数字), 那么会* 抛出org.springframework.data.redis.RedisSystemException** @param key       用于定位value的key* @param increment 增加多少* @return 增加后的总值。* @throws RedisSystemException key对应的value值不支持增/减操作时* @date 2020/3/8 17:45:51*/public static long incrBy(String key, long increment) {log.info("incrBy(...) => key -> {}, increment -> {}", key, increment);Long result = redisTemplate.opsForValue().increment(key, increment);log.info("incrBy(...) => result -> {}", result);if (result == null) {throw new RedisOpsResultIsNullException();}return result;}/*** 增/减 浮点数* <p>* 注: 慎用浮点数,会有精度问题。* 如: 先 RedisUtil.StringOps.set("ds", "123");* 然后再RedisUtil.StringOps.incrByFloat("ds", 100.6);* 就会看到精度问题。* 注: 负数则为减。* 注: 若key对应的value值不支持增/减操作(即: value不是数字), 那么会* 抛出org.springframework.data.redis.RedisSystemException** @param key       用于定位value的key* @param increment 增加多少* @return 增加后的总值。* @throws RedisSystemException key对应的value值不支持增/减操作时* @date 2020/3/8 17:45:51*/public static double incrByFloat(String key, double increment) {log.info("incrByFloat(...) => key -> {}, increment -> {}", key, increment);Double result = redisTemplate.opsForValue().increment(key, increment);log.info("incrByFloat(...) => result -> {}", result);if (result == null) {throw new RedisOpsResultIsNullException();}return result;}/*** 追加值到末尾* <p>* 注: 当redis中原本不存在key时,那么(从效果上来看)此方法就等价于{@link this#set(String, String)}** @param key   定位value的key* @param value 要追加的value值* @return 追加后, 整个value的长度* @date 2020/3/8 17:59:21*/public static int append(String key, String value) {log.info("append(...) => key -> {}, value -> {}", key, value);Integer result = redisTemplate.opsForValue().append(key, value);log.info("append(...) => result -> {}", result);if (result == null) {throw new RedisOpsResultIsNullException();}return result;}/*** 根据key,获取到对应的value值** @param key key-value对应的key* @return 该key对应的值。* 注: 若key不存在, 则返回null。* @date 2020/3/8 16:27:41*/public static String get(String key) {log.info("get(...) => key -> {}", key);String result = redisTemplate.opsForValue().get(key);log.info("get(...) => result -> {} ", result);return result;}/*** 对(key对应的)value进行截取, 截取范围为[start, end]* <p>* 注: 若[start, end]的范围不在value的范围中,那么返回的是空字符串 ""* 注: 若value只有一部分在[start, end]的范围中,那么返回的是value对应部分的内容(即:不足的地方,并不会以空来填充)** @param key   定位value的key* @param start 起始位置 (从0开始)* @param end   结尾位置 (从0开始)* @return 截取后的字符串* @date 2020/3/8 18:08:45*/public static String getRange(String key, long start, long end) {log.info("getRange(...) => kry -> {}", key);String result = redisTemplate.opsForValue().get(key, start, end);log.info("getRange(...) => result -> {} ", result);return result;}/*** 给指定key设置新的value, 并返回旧的value* <p>* 注: 若redis中不存在key, 那么此操作仍然可以成功, 不过返回的旧值是null** @param key      定位value的key* @param newValue 要为该key设置的新的value值* @return 旧的value值* @date 2020/3/8 18:14:24*/public static String getAndSet(String key, String newValue) {log.info("getAndSet(...) => key -> {}, value -> {}", key, newValue);String oldValue = redisTemplate.opsForValue().getAndSet(key, newValue);log.info("getAndSet(...) => oldValue -> {}", oldValue);return oldValue;}/*** 获取(key对应的)value在二进制下,offset位置的bit值。* <p>* 注: 当offset的值在(二进制下的value的)索引范围外时, 返回的也是false。* <p>* 示例:* RedisUtil.StringOps.set("akey", "a");* 字符串a, 转换为二进制为01100001* 那么getBit("akey", 6)获取到的结果为false。** @param key    定位value的key* @param offset 定位bit的索引* @return offset位置对应的bit的值(true - 1, false - 0)* @date 2020/3/8 18:21:10*/public static boolean getBit(String key, long offset) {log.info("getBit(...) => key -> {}, offset -> {}", key, offset);Boolean result = redisTemplate.opsForValue().getBit(key, offset);log.info("getBit(...) => result -> {}", result);if (result == null) {throw new RedisOpsResultIsNullException();}return result;}/*** 批量获取value值* <p>* 注: 若redis中,对应的key不存在,那么该key对应的返回的value值为null** @param keys key集* @return value值集合* @date 2020/3/8 18:26:33*/public static List<String> multiGet(Collection<String> keys) {log.info("multiGet(...) => keys -> {}", keys);List<String> result = redisTemplate.opsForValue().multiGet(keys);log.info("multiGet(...) => result -> {}", result);return result;}}/*** ylhash相关操作* <p>* 提示: 简单的,可以将redis中hash的数据结构看作是 Map<String, Map<HK, HV>>* 提示: redis中String的数据结构可参考resources/data-structure/Hash(散列)的数据结构(示例一).png* redis中String的数据结构可参考resources/data-structure/Hash(散列)的数据结构(示例二).png** @date 2020/3/8 23:39:26*/public static class LyHashOps {/*** 向key对应的hash中,增加一个键值对entryKey-entryValue* <p>* 注: 同一个hash里面,若已存在相同的entryKey, 那么此操作将丢弃原来的entryKey-entryValue,* 而使用新的entryKey-entryValue。** @param key        定位hash的key* @param entryKey   要向hash中增加的键值对里的 键* @param entryValue 要向hash中增加的键值对里的 值* @date 2020/3/8 23:49:52*/public static void hPut(String key, String entryKey, String entryValue) {log.info("hPut(...) => key -> {}, entryKey -> {}, entryValue -> {}", key, entryKey, entryValue);redisTemplate2.opsForHash().put(key, entryKey, entryValue);}/*** 向key对应的hash中,增加maps(即: 批量增加entry集)* <p>* 注: 同一个hash里面,若已存在相同的entryKey, 那么此操作将丢弃原来的entryKey-entryValue,* 而使用新的entryKey-entryValue** @param key  定位hash的key* @param maps 要向hash中增加的键值对集* @date 2020/3/8 23:49:52*/public static void hPutAll(String key, Map<String, String> maps) {log.info("hPutAll(...) => key -> {}, maps -> {}", key, maps);redisTemplate2.opsForHash().putAll(key, maps);}/*** 当key对应的hash中,不存在entryKey时,才(向key对应的hash中,)增加entryKey-entryValue* 否者,不进行任何操作** @param key        定位hash的key* @param entryKey   要向hash中增加的键值对里的 键* @param entryValue 要向hash中增加的键值对里的 值* @return 操作是否成功。* @date 2020/3/8 23:49:52*/public static boolean hPutIfAbsent(String key, String entryKey, String entryValue) {log.info("hPutIfAbsent(...) => key -> {}, entryKey -> {}, entryValue -> {}",key, entryKey, entryValue);Boolean result = redisTemplate2.opsForHash().putIfAbsent(key, entryKey, entryValue);log.info("hPutIfAbsent(...) => result -> {}", result);if (result == null) {throw new RedisOpsResultIsNullException();}return result;}/*** 获取到key对应的hash里面的对应字段的值* <p>* 注: 若redis中不存在对应的key, 则返回null。* 若key对应的hash中不存在对应的entryKey, 也会返回null。** @param key      定位hash的key* @param entryKey 定位hash里面的entryValue的entryKey* @return key对应的hash里的entryKey对应的entryValue值* @date 2020/3/9 9:09:30*/public static Object hGet(String key, String entryKey) {log.info("hGet(...) => key -> {}, entryKey -> {}", key, entryKey);Object entryValue = redisTemplate2.opsForHash().get(key, entryKey);log.info("hGet(...) => entryValue -> {}", entryValue);return entryValue;}/*** 获取到key对应的hash(即: 获取到key对应的Map<HK, HV>)* <p>* 注: 若redis中不存在对应的key, 则返回一个没有任何entry的空的Map(,而不是返回null)。** @param key 定位hash的key* @return key对应的hash。* @date 2020/3/9 9:09:30*/public static Map<Object, Object> hGetAll(String key) {log.info("hGetAll(...) => key -> {}", key);Map<Object, Object> result = redisTemplate2.opsForHash().entries(key);log.info("hGetAll(...) => result -> {}", result);return result;}/*** 批量获取(key对应的)hash中的entryKey的entryValue* <p>* 注: 若hash中对应的entryKey不存在,那么返回的对应的entryValue值为null* 注: redis中key不存在,那么返回的List中,每个元素都为null。* 追注: 这个List本身不为null, size也不为0, 只是每个list中的每个元素为null而已。** @param key       定位hash的key* @param entryKeys 需要获取的hash中的字段集* @return hash中对应entryKeys的对应entryValue集* @date 2020/3/9 9:25:38*/public static List<Object> hMultiGet(String key, Collection<Object> entryKeys) {log.info("hMultiGet(...) => key -> {}, entryKeys -> {}", key, entryKeys);List<Object> entryValues = redisTemplate2.opsForHash().multiGet(key, entryKeys);log.info("hMultiGet(...) => entryValues -> {}", entryValues);return entryValues;}/*** (批量)删除(key对应的)hash中的对应entryKey-entryValue* <p>* 注: 1、若redis中不存在对应的key, 则返回0;* 2、若要删除的entryKey,在key对应的hash中不存在,在count不会+1, 如:* RedisUtil.HashOps.hPut("ds", "name", "邓沙利文");* RedisUtil.HashOps.hPut("ds", "birthday", "1994-02-05");* RedisUtil.HashOps.hPut("ds", "hobby", "女");* 则调用RedisUtil.HashOps.hDelete("ds", "name", "birthday", "hobby", "non-exist-entryKey")* 的返回结果为3* 注: 若(key对应的)hash中的所有entry都被删除了,那么该key也会被删除** @param key       定位hash的key* @param entryKeys 定位要删除的entryKey-entryValue的entryKey* @return 删除了对应hash中多少个entry* @date 2020/3/9 9:37:47*/public static long hDelete(String key, Object... entryKeys) {log.info("hDelete(...) => key -> {}, entryKeys -> {}", key, entryKeys);Long count = redisTemplate2.opsForHash().delete(key, entryKeys);log.info("hDelete(...) => count -> {}", count);if (count == null) {throw new RedisOpsResultIsNullException();}return count;}/*** 查看(key对应的)hash中,是否存在entryKey对应的entry* <p>* 注: 若redis中不存在key,则返回false。* 注: 若key对应的hash中不存在对应的entryKey, 也会返回false。** @param key      定位hash的key* @param entryKey 定位hash中entry的entryKey* @return hash中是否存在entryKey对应的entry.* @date 2020/3/9 9:51:55*/public static boolean hExists(String key, String entryKey) {log.info("hDelete(...) => key -> {}, entryKeys -> {}", key, entryKey);Boolean exist = redisTemplate2.opsForHash().hasKey(key, entryKey);log.info("hDelete(...) => exist -> {}", exist);return exist;}/*** 增/减(hash中的某个entryValue值) 整数* <p>* 注: 负数则为减。* 注: 若key不存在,那么会自动创建对应的hash,并创建对应的entryKey、entryValue,entryValue的初始值为increment。* 注: 若entryKey不存在,那么会自动创建对应的entryValue,entryValue的初始值为increment。* 注: 若key对应的value值不支持增/减操作(即: value不是数字), 那么会* 抛出org.springframework.data.redis.RedisSystemException** @param key       用于定位hash的key* @param entryKey  用于定位entryValue的entryKey* @param increment 增加多少* @return 增加后的总值。* @throws RedisSystemException key对应的value值不支持增/减操作时* @date 2020/3/9 10:09:28*/public static long hIncrBy(String key, Object entryKey, long increment) {log.info("hIncrBy(...) => key -> {}, entryKey -> {}, increment -> {}",key, entryKey, increment);Long result = redisTemplate2.opsForHash().increment(key, entryKey, increment);log.info("hIncrBy(...) => result -> {}", result);if (result == null) {throw new RedisOpsResultIsNullException();}return result;}/*** 增/减(hash中的某个entryValue值) 浮点数* <p>* 注: 负数则为减。* 注: 若key不存在,那么会自动创建对应的hash,并创建对应的entryKey、entryValue,entryValue的初始值为increment。* 注: 若entryKey不存在,那么会自动创建对应的entryValue,entryValue的初始值为increment。* 注: 若key对应的value值不支持增/减操作(即: value不是数字), 那么会* 抛出org.springframework.data.redis.RedisSystemException* 注: 因为是浮点数, 所以可能会和{@link StringOps#incrByFloat(String, double)}一样, 出现精度问题。* 追注: 本人简单测试了几组数据,暂未出现精度问题。** @param key       用于定位hash的key* @param entryKey  用于定位entryValue的entryKey* @param increment 增加多少* @return 增加后的总值。* @throws RedisSystemException key对应的value值不支持增/减操作时* @date 2020/3/9 10:09:28*/public static double hIncrByFloat(String key, Object entryKey, double increment) {log.info("hIncrByFloat(...) => key -> {}, entryKey -> {}, increment -> {}",key, entryKey, increment);Double result = redisTemplate2.opsForHash().increment(key, entryKey, increment);log.info("hIncrByFloat(...) => result -> {}", result);if (result == null) {throw new RedisOpsResultIsNullException();}return result;}/*** 获取(key对应的)hash中的所有entryKey* <p>* 注: 若key不存在,则返回的是一个空的Set(,而不是返回null)** @param key 定位hash的key* @return hash中的所有entryKey* @date 2020/3/9 10:30:13*/public static Set<Object> hKeys(String key) {log.info("hKeys(...) => key -> {}", key);Set<Object> entryKeys = redisTemplate2.opsForHash().keys(key);log.info("hKeys(...) => entryKeys -> {}", entryKeys);return entryKeys;}/*** 获取(key对应的)hash中的所有entryValue* <p>* 注: 若key不存在,则返回的是一个空的List(,而不是返回null)** @param key 定位hash的key* @return hash中的所有entryValue* @date 2020/3/9 10:30:13*/public static List<Object> hValues(String key) {log.info("hValues(...) => key -> {}", key);List<Object> entryValues = redisTemplate2.opsForHash().values(key);log.info("hValues(...) => entryValues -> {}", entryValues);return entryValues;}/*** 获取(key对应的)hash中的所有entry的数量* <p>* 注: 若redis中不存在对应的key, 则返回值为0** @param key 定位hash的key* @return (key对应的)hash中, entry的个数* @date 2020/3/9 10:41:01*/public static long hSize(String key) {log.info("hSize(...) => key -> {}", key);Long count = redisTemplate2.opsForHash().size(key);log.info("hSize(...) => count -> {}", count);if (count == null) {throw new RedisOpsResultIsNullException();}return count;}/*** 根据options匹配到(key对应的)hash中的对应的entryKey, 并返回对应的entry集* <p>* <p>* 注: ScanOptions实例的创建方式举例:* 1、ScanOptions.NONE* 2、ScanOptions.scanOptions().match("n??e").build()** @param key     定位hash的key* @param options 匹配entryKey的条件*                注: ScanOptions.NONE表示全部匹配。*                注: ScanOptions.scanOptions().match(pattern).build()表示按照pattern匹配,*                其中pattern中可以使用通配符 * ? 等,*                * 表示>=0个字符*                ? 表示有且只有一个字符*                此处的匹配规则与{@link KeyOps#keys(String)}处的一样。* @return 匹配到的(key对应的)hash中的entry* @date 2020/3/9 10:49:27*/public static Cursor<Entry<Object, Object>> hScan(String key, ScanOptions options) {log.info("hScan(...) => key -> {}, options -> {}", key, JSON.toJSONString(options));Cursor<Entry<Object, Object>> cursor = redisTemplate2.opsForHash().scan(key, options);log.info("hScan(...) => cursor -> {}", JSON.toJSONString(cursor));return cursor;}}/*** hash相关操作* <p>* 提示: 简单的,可以将redis中hash的数据结构看作是 Map<String, Map<HK, HV>>* 提示: redis中String的数据结构可参考resources/data-structure/Hash(散列)的数据结构(示例一).png* redis中String的数据结构可参考resources/data-structure/Hash(散列)的数据结构(示例二).png** @date 2020/3/8 23:39:26*/public static class HashOps {/*** 向key对应的hash中,增加一个键值对entryKey-entryValue* <p>* 注: 同一个hash里面,若已存在相同的entryKey, 那么此操作将丢弃原来的entryKey-entryValue,* 而使用新的entryKey-entryValue。** @param key        定位hash的key* @param entryKey   要向hash中增加的键值对里的 键* @param entryValue 要向hash中增加的键值对里的 值* @date 2020/3/8 23:49:52*/public static void hPut(String key, String entryKey, String entryValue) {log.info("hPut(...) => key -> {}, entryKey -> {}, entryValue -> {}", key, entryKey, entryValue);redisTemplate.opsForHash().put(key, entryKey, entryValue);}/*** 向key对应的hash中,增加maps(即: 批量增加entry集)* <p>* 注: 同一个hash里面,若已存在相同的entryKey, 那么此操作将丢弃原来的entryKey-entryValue,* 而使用新的entryKey-entryValue** @param key  定位hash的key* @param maps 要向hash中增加的键值对集* @date 2020/3/8 23:49:52*/public static void hPutAll(String key, Map<String, String> maps) {log.info("hPutAll(...) => key -> {}, maps -> {}", key, maps);redisTemplate.opsForHash().putAll(key, maps);}/*** 当key对应的hash中,不存在entryKey时,才(向key对应的hash中,)增加entryKey-entryValue* 否者,不进行任何操作** @param key        定位hash的key* @param entryKey   要向hash中增加的键值对里的 键* @param entryValue 要向hash中增加的键值对里的 值* @return 操作是否成功。* @date 2020/3/8 23:49:52*/public static boolean hPutIfAbsent(String key, String entryKey, String entryValue) {log.info("hPutIfAbsent(...) => key -> {}, entryKey -> {}, entryValue -> {}",key, entryKey, entryValue);Boolean result = redisTemplate.opsForHash().putIfAbsent(key, entryKey, entryValue);log.info("hPutIfAbsent(...) => result -> {}", result);if (result == null) {throw new RedisOpsResultIsNullException();}return result;}/*** 获取到key对应的hash里面的对应字段的值* <p>* 注: 若redis中不存在对应的key, 则返回null。* 若key对应的hash中不存在对应的entryKey, 也会返回null。** @param key      定位hash的key* @param entryKey 定位hash里面的entryValue的entryKey* @return key对应的hash里的entryKey对应的entryValue值* @date 2020/3/9 9:09:30*/public static Object hGet(String key, String entryKey) {log.info("hGet(...) => key -> {}, entryKey -> {}", key, entryKey);Object entryValue = redisTemplate.opsForHash().get(key, entryKey);log.info("hGet(...) => entryValue -> {}", entryValue);return entryValue;}/*** 获取到key对应的hash(即: 获取到key对应的Map<HK, HV>)* <p>* 注: 若redis中不存在对应的key, 则返回一个没有任何entry的空的Map(,而不是返回null)。** @param key 定位hash的key* @return key对应的hash。* @date 2020/3/9 9:09:30*/public static Map<Object, Object> hGetAll(String key) {log.info("hGetAll(...) => key -> {}", key);Map<Object, Object> result = redisTemplate.opsForHash().entries(key);log.info("hGetAll(...) => result -> {}", result);return result;}/*** 批量获取(key对应的)hash中的entryKey的entryValue* <p>* 注: 若hash中对应的entryKey不存在,那么返回的对应的entryValue值为null* 注: redis中key不存在,那么返回的List中,每个元素都为null。* 追注: 这个List本身不为null, size也不为0, 只是每个list中的每个元素为null而已。** @param key       定位hash的key* @param entryKeys 需要获取的hash中的字段集* @return hash中对应entryKeys的对应entryValue集* @date 2020/3/9 9:25:38*/public static List<Object> hMultiGet(String key, Collection<Object> entryKeys) {log.info("hMultiGet(...) => key -> {}, entryKeys -> {}", key, entryKeys);List<Object> entryValues = redisTemplate.opsForHash().multiGet(key, entryKeys);log.info("hMultiGet(...) => entryValues -> {}", entryValues);return entryValues;}/*** (批量)删除(key对应的)hash中的对应entryKey-entryValue* <p>* 注: 1、若redis中不存在对应的key, 则返回0;* 2、若要删除的entryKey,在key对应的hash中不存在,在count不会+1, 如:* RedisUtil.HashOps.hPut("ds", "name", "邓沙利文");* RedisUtil.HashOps.hPut("ds", "birthday", "1994-02-05");* RedisUtil.HashOps.hPut("ds", "hobby", "女");* 则调用RedisUtil.HashOps.hDelete("ds", "name", "birthday", "hobby", "non-exist-entryKey")* 的返回结果为3* 注: 若(key对应的)hash中的所有entry都被删除了,那么该key也会被删除** @param key       定位hash的key* @param entryKeys 定位要删除的entryKey-entryValue的entryKey* @return 删除了对应hash中多少个entry* @date 2020/3/9 9:37:47*/public static long hDelete(String key, Object... entryKeys) {log.info("hDelete(...) => key -> {}, entryKeys -> {}", key, entryKeys);Long count = redisTemplate.opsForHash().delete(key, entryKeys);log.info("hDelete(...) => count -> {}", count);if (count == null) {throw new RedisOpsResultIsNullException();}return count;}/*** 查看(key对应的)hash中,是否存在entryKey对应的entry* <p>* 注: 若redis中不存在key,则返回false。* 注: 若key对应的hash中不存在对应的entryKey, 也会返回false。** @param key      定位hash的key* @param entryKey 定位hash中entry的entryKey* @return hash中是否存在entryKey对应的entry.* @date 2020/3/9 9:51:55*/public static boolean hExists(String key, String entryKey) {log.info("hDelete(...) => key -> {}, entryKeys -> {}", key, entryKey);Boolean exist = redisTemplate.opsForHash().hasKey(key, entryKey);log.info("hDelete(...) => exist -> {}", exist);return exist;}/*** 增/减(hash中的某个entryValue值) 整数* <p>* 注: 负数则为减。* 注: 若key不存在,那么会自动创建对应的hash,并创建对应的entryKey、entryValue,entryValue的初始值为increment。* 注: 若entryKey不存在,那么会自动创建对应的entryValue,entryValue的初始值为increment。* 注: 若key对应的value值不支持增/减操作(即: value不是数字), 那么会* 抛出org.springframework.data.redis.RedisSystemException** @param key       用于定位hash的key* @param entryKey  用于定位entryValue的entryKey* @param increment 增加多少* @return 增加后的总值。* @throws RedisSystemException key对应的value值不支持增/减操作时* @date 2020/3/9 10:09:28*/public static long hIncrBy(String key, Object entryKey, long increment) {log.info("hIncrBy(...) => key -> {}, entryKey -> {}, increment -> {}",key, entryKey, increment);Long result = redisTemplate.opsForHash().increment(key, entryKey, increment);log.info("hIncrBy(...) => result -> {}", result);if (result == null) {throw new RedisOpsResultIsNullException();}return result;}/*** 增/减(hash中的某个entryValue值) 浮点数* <p>* 注: 负数则为减。* 注: 若key不存在,那么会自动创建对应的hash,并创建对应的entryKey、entryValue,entryValue的初始值为increment。* 注: 若entryKey不存在,那么会自动创建对应的entryValue,entryValue的初始值为increment。* 注: 若key对应的value值不支持增/减操作(即: value不是数字), 那么会* 抛出org.springframework.data.redis.RedisSystemException* 注: 因为是浮点数, 所以可能会和{@link StringOps#incrByFloat(String, double)}一样, 出现精度问题。* 追注: 本人简单测试了几组数据,暂未出现精度问题。** @param key       用于定位hash的key* @param entryKey  用于定位entryValue的entryKey* @param increment 增加多少* @return 增加后的总值。* @throws RedisSystemException key对应的value值不支持增/减操作时* @date 2020/3/9 10:09:28*/public static double hIncrByFloat(String key, Object entryKey, double increment) {log.info("hIncrByFloat(...) => key -> {}, entryKey -> {}, increment -> {}",key, entryKey, increment);Double result = redisTemplate.opsForHash().increment(key, entryKey, increment);log.info("hIncrByFloat(...) => result -> {}", result);if (result == null) {throw new RedisOpsResultIsNullException();}return result;}/*** 获取(key对应的)hash中的所有entryKey* <p>* 注: 若key不存在,则返回的是一个空的Set(,而不是返回null)** @param key 定位hash的key* @return hash中的所有entryKey* @date 2020/3/9 10:30:13*/public static Set<Object> hKeys(String key) {log.info("hKeys(...) => key -> {}", key);Set<Object> entryKeys = redisTemplate.opsForHash().keys(key);log.info("hKeys(...) => entryKeys -> {}", entryKeys);return entryKeys;}/*** 获取(key对应的)hash中的所有entryValue* <p>* 注: 若key不存在,则返回的是一个空的List(,而不是返回null)** @param key 定位hash的key* @return hash中的所有entryValue* @date 2020/3/9 10:30:13*/public static List<Object> hValues(String key) {log.info("hValues(...) => key -> {}", key);List<Object> entryValues = redisTemplate.opsForHash().values(key);log.info("hValues(...) => entryValues -> {}", entryValues);return entryValues;}/*** 获取(key对应的)hash中的所有entry的数量* <p>* 注: 若redis中不存在对应的key, 则返回值为0** @param key 定位hash的key* @return (key对应的)hash中, entry的个数* @date 2020/3/9 10:41:01*/public static long hSize(String key) {log.info("hSize(...) => key -> {}", key);Long count = redisTemplate.opsForHash().size(key);log.info("hSize(...) => count -> {}", count);if (count == null) {throw new RedisOpsResultIsNullException();}return count;}/*** 根据options匹配到(key对应的)hash中的对应的entryKey, 并返回对应的entry集* <p>* <p>* 注: ScanOptions实例的创建方式举例:* 1、ScanOptions.NONE* 2、ScanOptions.scanOptions().match("n??e").build()** @param key     定位hash的key* @param options 匹配entryKey的条件*                注: ScanOptions.NONE表示全部匹配。*                注: ScanOptions.scanOptions().match(pattern).build()表示按照pattern匹配,*                其中pattern中可以使用通配符 * ? 等,*                * 表示>=0个字符*                ? 表示有且只有一个字符*                此处的匹配规则与{@link KeyOps#keys(String)}处的一样。* @return 匹配到的(key对应的)hash中的entry* @date 2020/3/9 10:49:27*/public static Cursor<Entry<Object, Object>> hScan(String key, ScanOptions options) {log.info("hScan(...) => key -> {}, options -> {}", key, JSON.toJSONString(options));Cursor<Entry<Object, Object>> cursor = redisTemplate.opsForHash().scan(key, options);log.info("hScan(...) => cursor -> {}", JSON.toJSONString(cursor));return cursor;}}/*** list相关操作* <p>* 提示: 列表中的元素,可以重复。* <p>* 提示: list是有序的。* <p>* 提示: redis中的list中的索引,可分为两类,这两类都可以用来定位list中元素:* 类别一: 从left到right, 是从0开始依次增大:   0,  1,  2,  3...* 类别二: 从right到left, 是从-1开始依次减小: -1, -2, -3, -4...* <p>* 提示: redis中String的数据结构可参考resources/data-structure/List(列表)的数据结构(示例一).png* redis中String的数据结构可参考resources/data-structure/List(列表)的数据结构(示例二).png** @date 2020/3/9 11:30:48*/public static class ListOps {/*** 从左端推入元素进列表* <p>* 注: 若redis中不存在对应的key, 那么会自动创建** @param key  定位list的key* @param item 要推入list的元素* @return 推入后,(key对应的)list的size* @date 2020/3/9 11:56:05*/public static long lLeftPush(String key, String item) {log.info("lLeftPush(...) => key -> {}, item -> {}", key, item);Long size = redisTemplate.opsForList().leftPush(key, item);log.info("lLeftPush(...) => size -> {}", size);if (size == null) {throw new RedisOpsResultIsNullException();}return size;}/*** 从左端批量推入元素进列表* <p>* 注: 若redis中不存在对应的key, 那么会自动创建* 注: 这一批item中,先push左侧的, 后push右侧的** @param key   定位list的key* @param items 要批量推入list的元素集* @return 推入后,(key对应的)list的size* @date 2020/3/9 11:56:05*/public static long lLeftPushAll(String key, String... items) {log.info("lLeftPushAll(...) => key -> {}, items -> {}", key, items);Long size = redisTemplate.opsForList().leftPushAll(key, items);log.info("lLeftPushAll(...) => size -> {}", size);if (size == null) {throw new RedisOpsResultIsNullException();}return size;}/*** 从左端批量推入元素进列表* <p>* 注: 若redis中不存在对应的key, 那么会自动创建* 注: 这一批item中,那个item先从Collection取出来,就先push哪个** @param key   定位list的key* @param items 要批量推入list的元素集* @return 推入后,(key对应的)list的size* @date 2020/3/9 11:56:05*/public static long lLeftPushAll(String key, Collection<String> items) {log.info("lLeftPushAll(...) => key -> {}, items -> {}", key, items);Long size = redisTemplate.opsForList().leftPushAll(key, items);log.info("lLeftPushAll(...) => size -> {}", size);if (size == null) {throw new RedisOpsResultIsNullException();}return size;}/*** 如果redis中存在key, 则从左端批量推入元素进列表;* 否则,不进行任何操作** @param key  定位list的key* @param item 要推入list的项* @return 推入后,(key对应的)list的size* @date 2020/3/9 13:40:08*/public static long lLeftPushIfPresent(String key, String item) {log.info("lLeftPushIfPresent(...) => key -> {}, item -> {}", key, item);Long size = redisTemplate.opsForList().leftPushIfPresent(key, item);log.info("lLeftPushIfPresent(...) => size -> {}", size);if (size == null) {throw new RedisOpsResultIsNullException();}return size;}/*** 若key对应的list中存在pivot项, 那么将item放入第一个pivot项前(即:放在第一个pivot项左边);* 若key对应的list中不存在pivot项, 那么不做任何操作, 直接返回-1。* <p>* 注: 若redis中不存在对应的key, 那么会自动创建** @param key  定位list的key* @param item 要推入list的元素* @return 推入后,(key对应的)list的size* @date 2020/3/9 11:56:05*/public static long lLeftPush(String key, String pivot, String item) {log.info("lLeftPush(...) => key -> {}, pivot -> {}, item -> {}", key, pivot, item);Long size = redisTemplate.opsForList().leftPush(key, pivot, item);log.info("lLeftPush(...) => size -> {}", size);if (size == null) {throw new RedisOpsResultIsNullException();}return size;}/*** 与{@link ListOps#lLeftPush(String, String)}类比即可, 不过是从list右侧推入元素*/public static long lRightPush(String key, String item) {log.info("lRightPush(...) => key -> {}, item -> {}", key, item);Long size = redisTemplate.opsForList().rightPush(key, item);log.info("lRightPush(...) => size -> {}", size);if (size == null) {throw new RedisOpsResultIsNullException();}return size;}/*** 与{@link ListOps#lLeftPushAll(String, String...)}类比即可, 不过是从list右侧推入元素*/public static long lRightPushAll(String key, String... items) {log.info("lRightPushAll(...) => key -> {}, items -> {}", key, items);Long size = redisTemplate.opsForList().rightPushAll(key, items);log.info("lRightPushAll(...) => size -> {}", size);if (size == null) {throw new RedisOpsResultIsNullException();}return size;}/*** 与{@link ListOps#lLeftPushAll(String, Collection<String>)}类比即可, 不过是从list右侧推入元素*/public static long lRightPushAll(String key, Collection<String> items) {log.info("lRightPushAll(...) => key -> {}, items -> {}", key, items);Long size = redisTemplate.opsForList().rightPushAll(key, items);log.info("lRightPushAll(...) => size -> {}", size);if (size == null) {throw new RedisOpsResultIsNullException();}return size;}/*** 与{@link ListOps#lLeftPushIfPresent(String, String)}类比即可, 不过是从list右侧推入元素*/public static long lRightPushIfPresent(String key, String item) {log.info("lRightPushIfPresent(...) => key -> {}, item -> {}", key, item);Long size = redisTemplate.opsForList().rightPushIfPresent(key, item);log.info("lRightPushIfPresent(...) => size -> {}", size);if (size == null) {throw new RedisOpsResultIsNullException();}return size;}/*** 与{@link ListOps#lLeftPush(String, String, String)}类比即可, 不过是从list右侧推入元素*/public static long lRightPush(String key, String pivot, String item) {log.info("lLeftPush(...) => key -> {}, pivot -> {}, item -> {}", key, pivot, item);Long size = redisTemplate.opsForList().rightPush(key, pivot, item);log.info("lLeftPush(...) => size -> {}", size);if (size == null) {throw new RedisOpsResultIsNullException();}return size;}/*** 【非阻塞队列】 从左侧移出(key对应的)list中的第一个元素, 并将该元素返回* <p>* 注: 此方法是非阻塞的, 即: 若(key对应的)list中的所有元素都被pop移出了,此时,再进行pop的话,会立即返回null* 注: 此方法是非阻塞的, 即: 若redis中不存在对应的key,那么会立即返回null* 注: 若将(key对应的)list中的所有元素都pop完了,那么该key会被删除** @param key 定位list的key* @return 移出的那个元素* @date 2020/3/9 14:33:56*/public static String lLeftPop(String key) {log.info("lLeftPop(...) => key -> {}", key);String item = redisTemplate.opsForList().leftPop(key);log.info("lLeftPop(...) => item -> {}", item);return item;}/*** 【阻塞队列】 从左侧移出(key对应的)list中的第一个元素, 并将该元素返回* <p>* 注: 此方法是阻塞的, 即: 若(key对应的)list中的所有元素都被pop移出了,此时,再进行pop的话,* 会阻塞timeout这么久,然后返回null* 注: 此方法是阻塞的, 即: 若redis中不存在对应的key,那么会阻塞timeout这么久,然后返回null* 注: 若将(key对应的)list中的所有元素都pop完了,那么该key会被删除* <p>* 提示: 若阻塞过程中, 目标key-list出现了,且里面有item了,那么会立马停止阻塞, 进行元素移出并返回** @param key     定位list的key* @param timeout 超时时间* @param unit    timeout的单位* @return 移出的那个元素* @date 2020/3/9 14:33:56*/public static String lLeftPop(String key, long timeout, TimeUnit unit) {log.info("lLeftPop(...) => key -> {}, timeout -> {}, unit -> {}", key, timeout, unit);String item = redisTemplate.opsForList().leftPop(key, timeout, unit);log.info("lLeftPop(...) => item -> {}", item);return item;}/*** 与{@link ListOps#lLeftPop(String)}类比即可, 不过是从list右侧移出元素*/public static String lRightPop(String key) {log.info("lRightPop(...) => key -> {}", key);String item = redisTemplate.opsForList().rightPop(key);log.info("lRightPop(...) => item -> {}", item);return item;}/*** 与{@link ListOps#lLeftPop(String, long, TimeUnit)}类比即可, 不过是从list右侧移出元素*/public static String lRightPop(String key, long timeout, TimeUnit unit) {log.info("lRightPop(...) => key -> {}, timeout -> {}, unit -> {}", key, timeout, unit);String item = redisTemplate.opsForList().rightPop(key, timeout, unit);log.info("lRightPop(...) => item -> {}", item);return item;}/*** 【非阻塞队列】 从sourceKey对应的sourceList右侧移出一个item, 并将这个item推* 入(destinationKey对应的)destinationList的左侧* <p>* 注: 若sourceKey对应的list中没有item了,则立马认为(从sourceKey对应的list中pop出来的)item为null,* null并不会往destinationKey对应的list中push。* 追注: 此时,此方法的返回值是null。* <p>* 注: 若将(sourceKey对应的)list中的所有元素都pop完了,那么该sourceKey会被删除。** @param sourceKey      定位sourceList的key* @param destinationKey 定位destinationList的key* @return 移动的这个元素* @date 2020/3/9 15:06:59*/public static String lRightPopAndLeftPush(String sourceKey, String destinationKey) {log.info("lRightPopAndLeftPush(...) => sourceKey -> {}, destinationKey -> {}",sourceKey, destinationKey);String item = redisTemplate.opsForList().rightPopAndLeftPush(sourceKey, destinationKey);log.info("lRightPopAndLeftPush(...) => item -> {}", item);return item;}/*** 【阻塞队列】 从sourceKey对应的sourceList右侧移出一个item, 并将这个item推* 入(destinationKey对应的)destinationList的左侧* <p>* 注: 若sourceKey对应的list中没有item了,则阻塞等待, 直到能从sourceList中移出一个非null的item(或等待时长超时);* case1: 等到了一个非null的item, 那么继续下面的push操作,并返回这个item。* case2: 超时了,还没等到非null的item, 那么pop出的结果就未null,此时并不会往destinationList进行push。* 此时,此方法的返回值是null。* <p>* 注: 若将(sourceKey对应的)list中的所有元素都pop完了,那么该sourceKey会被删除。** @param sourceKey      定位sourceList的key* @param destinationKey 定位destinationList的key* @param timeout        超时时间* @param unit           timeout的单位* @return 移动的这个元素* @date 2020/3/9 15:06:59*/public static String lRightPopAndLeftPush(String sourceKey, String destinationKey, long timeout,TimeUnit unit) {log.info("lRightPopAndLeftPush(...) => sourceKey -> {}, destinationKey -> {}, timeout -> {},"+ " unit -> {}", sourceKey, destinationKey, timeout, unit);String item = redisTemplate.opsForList().rightPopAndLeftPush(sourceKey, destinationKey, timeout, unit);log.info("lRightPopAndLeftPush(...) => item -> {}", item);return item;}/*** 设置(key对应的)list中对应索引位置index处的元素为item* <p>* 注: 若key不存在,则会抛出org.springframework.data.redis.RedisSystemException* 注: 若索引越界,也会抛出org.springframework.data.redis.RedisSystemException** @param key   定位list的key* @param index 定位list中的元素的索引* @param item  要替换成的值* @date 2020/3/9 15:39:50*/public static void lSet(String key, long index, String item) {log.info("lSet(...) => key -> {}, index -> {}, item -> {}", key, index, item);redisTemplate.opsForList().set(key, index, item);}/*** 通过索引index, 获取(key对应的)list中的元素* <p>* 注: 若key不存在 或 index超出(key对应的)list的索引范围,那么返回null** @param key   定位list的key* @param index 定位list中的item的索引* @return list中索引index对应的item* @date 2020/3/10 0:27:23*/public static String lIndex(String key, long index) {log.info("lIndex(...) => key -> {}, index -> {}", key, index);String item = redisTemplate.opsForList().index(key, index);log.info("lIndex(...) => item -> {}", item);return item;}/*** 获取(key对应的)list中索引在[start, end]之间的item集* <p>* 注: 含start、含end。* 注: 当key不存在时,获取到的是空的集合。* 注: 当获取的范围比list的范围还要大时,获取到的是这两个范围的交集。* <p>* 提示: 可通过RedisUtil.ListOps.lRange(key, 0, -1)来获取到该key对应的整个list** @param key   定位list的key* @param start 起始元素的index* @param end   结尾元素的index* @return 对应的元素集合* @date 2020/3/10 0:34:59*/public static List<String> lRange(String key, long start, long end) {log.info("lRange(...) => key -> {}, start -> {}, end -> {}", key, start, end);List<String> result = redisTemplate.opsForList().range(key, start, end);log.info("lRange(...) => result -> {}", result);return result;}/*** 获取(key对应的)list** @param key 定位list的key* @return (key对应的)list* @date 2020/3/10 0:46:50* @see ListOps#lRange(String, long, long)*/public static List<String> lWholeList(String key) {log.info("lWholeList(...) => key -> {}", key);List<String> result = redisTemplate.opsForList().range(key, 0, -1);log.info("lWholeList(...) => result -> {}", result);return result;}/*** 获取(key对应的)list的size* <p>* 注: 当key不存在时,获取到的size为0.** @param key 定位list的key* @return list的size。* @date 2020/3/10 0:48:40*/public static long lSize(String key) {log.info("lSize(...) => key -> {}", key);Long size = redisTemplate.opsForList().size(key);log.info("lSize(...) => size -> {}", size);if (size == null) {throw new RedisOpsResultIsNullException();}return size;}/*** 删除(key对应的)list中,前expectCount个值等于item的项* <p>* 注: 若expectCount == 0, 则表示删除list中所有的值等于item的项.* 注: 若expectCount > 0,  则表示删除从左往右进行* 注: 若expectCount < 0,  则表示删除从右往左进行* <p>* 注: 若list中,值等于item的项的个数少于expectCount时,那么会删除list中所有的值等于item的项。* 注: 当key不存在时, 返回0。* 注: 若lRemove后, 将(key对应的)list中没有任何元素了,那么该key会被删除。** @param key         定位list的key* @param expectCount 要删除的item的个数* @param item        要删除的item* @return 实际删除了的item的个数* @date 2020/3/10 0:52:57*/public static long lRemove(String key, long expectCount, String item) {log.info("lRemove(...) => key -> {}, expectCount -> {}, item -> {}", key, expectCount, item);Long actualCount = redisTemplate.opsForList().remove(key, expectCount, item);log.info("lRemove(...) => actualCount -> {}", actualCount);if (actualCount == null) {throw new RedisOpsResultIsNullException();}return actualCount;}/*** 裁剪(即: 对list中的元素取交集。)* <p>* 举例说明: list中的元素索引范围是[0, 8], 而这个方法传入的[start, end]为 [3, 10],* 那么裁剪就是对[0, 8]和[3, 10]进行取交集, 得到[3, 8], 那么裁剪后* 的list中,只剩下(原来裁剪前)索引在[3, 8]之间的元素了。* <p>* 注: 若裁剪后的(key对应的)list就是空的,那么该key会被删除。** @param key   定位list的key* @param start 要删除的item集的起始项的索引* @param end   要删除的item集的结尾项的索引* @date 2020/3/10 1:16:58*/public static void lTrim(String key, long start, long end) {log.info("lTrim(...) => key -> {}, start -> {}, end -> {}", key, start, end);redisTemplate.opsForList().trim(key, start, end);}}/*** set相关操作* <p>* 提示: set中的元素,不可以重复。* 提示: set是无序的。* 提示: redis中String的数据结构可参考resources/data-structure/Set(集合)的数据结构(示例一).png* redis中String的数据结构可参考resources/data-structure/Set(集合)的数据结构(示例二).png** @date 2020/3/9 11:30:48*/public static class SetOps {/*** 向(key对应的)set中添加items* <p>* 注: 若key不存在,则会自动创建。* 注: set中的元素会去重。** @param key   定位set的key* @param items 要向(key对应的)set中添加的items* @return 此次添加操作, 添加到set中的元素的个数* @date 2020/3/11 8:16:00*/public static long sAdd(String key, String... items) {log.info("sAdd(...) => key -> {}, items -> {}", key, items);Long count = redisTemplate.opsForSet().add(key, items);log.info("sAdd(...) => count -> {}", count);if (count == null) {throw new RedisOpsResultIsNullException();}return count;}/*** 从(key对应的)set中删除items* <p>* 注: 若key不存在, 则返回0。* 注: 若已经将(key对应的)set中的项删除完了,那么对应的key也会被删除。** @param key   定位set的key* @param items 要移除的items* @return 实际删除了的个数* @date 2020/3/11 8:26:43*/public static long sRemove(String key, Object... items) {log.info("sRemove(...) => key -> {}, items -> {}", key, items);Long count = redisTemplate.opsForSet().remove(key, items);log.info("sRemove(...) => count -> {}", count);if (count == null) {throw new RedisOpsResultIsNullException();}return count;}/*** 从(key对应的)set中随机移出一个item, 并返回这个item* <p>* 注: 因为set是无序的,所以移出的这个item,是随机的; 并且,哪怕* 是数据一样的set,多次测试移出操作,移除的元素也是随机的。* <p>* 注: 若已经将(key对应的)set中的项pop完了,那么对应的key会被删除。** @param key 定位set的key* @return 移出的项* @date 2020/3/11 8:32:40*/public static String sPop(String key) {log.info("sPop(...) => key -> {}", key);String popItem = redisTemplate.opsForSet().pop(key);log.info("sPop(...) => popItem -> {}", popItem);return popItem;}/*** 将(sourceKey对应的)sourceSet中的元素item, 移动到(destinationKey对应的)destinationSet中* <p>* 注: 当sourceKey不存在时, 返回false* 注: 当item不存在时, 返回false* 注: 若destinationKey不存在, 那么在移动时会自动创建* 注: 若已经将(sourceKey对应的)set中的项move出去完了,那么对应的sourceKey会被删除。** @param sourceKey      定位sourceSet的key* @param item           要移动的项目* @param destinationKey 定位destinationSet的key* @return 移动成功与否* @date 2020/3/11 8:43:32*/public static boolean sMove(String sourceKey, String item, String destinationKey) {Boolean result = redisTemplate.opsForSet().move(sourceKey, item, destinationKey);log.info("sMove(...) => sourceKey -> {}, destinationKey -> {}, item -> {}",sourceKey, destinationKey, item);log.info("sMove(...) =>  result -> {}", result);if (result == null) {throw new RedisOpsResultIsNullException();}return result;}/*** 获取(key对应的)set中的元素个数* <p>* 注: 若key不存在,则返回0** @param key 定位set的key* @return (key对应的)set中的元素个数* @date 2020/3/11 8:57:19*/public static long sSize(String key) {log.info("sSize(...) => key -> {}", key);Long size = redisTemplate.opsForSet().size(key);log.info("sSize(...) => size -> {}", size);if (size == null) {throw new RedisOpsResultIsNullException();}return size;}/*** 判断(key对应的)set中是否含有item* <p>* 注: 若key不存在,则返回false。** @param key  定位set的key* @param item 被查找的项* @return (key对应的)set中是否含有item* @date 2020/3/11 9:03:29*/public static boolean sIsMember(String key, Object item) {log.info("sSize(...) => key -> {}, size -> {}", key, item);Boolean result = redisTemplate.opsForSet().isMember(key, item);log.info("sSize(...) => result -> {}", result);if (result == null) {throw new RedisOpsResultIsNullException();}return result;}/*** 获取两个(key对应的)Set的交集* <p>* 注: 若不存在任何交集,那么返回空的集合(, 而不是null)* 注: 若其中一个key不存在(或两个key都不存在),那么返回空的集合(, 而不是null)** @param key      定位其中一个set的键* @param otherKey 定位其中另一个set的键* @return item交集* @date 2020/3/11 9:31:25*/public static Set<String> sIntersect(String key, String otherKey) {log.info("sIntersect(...) => key -> {}, otherKey -> {}", key, otherKey);Set<String> intersectResult = redisTemplate.opsForSet().intersect(key, otherKey);log.info("sIntersect(...) => intersectResult -> {}", intersectResult);return intersectResult;}/*** 获取多个(key对应的)Set的交集* <p>* 注: 若不存在任何交集,那么返回空的集合(, 而不是null)* 注: 若>=1个key不存在,那么返回空的集合(, 而不是null)** @param key       定位其中一个set的键* @param otherKeys 定位其它set的键集* @return item交集* @date 2020/3/11 9:39:23*/public static Set<String> sIntersect(String key, Collection<String> otherKeys) {log.info("sIntersect(...) => key -> {}, otherKeys -> {}", key, otherKeys);Set<String> intersectResult = redisTemplate.opsForSet().intersect(key, otherKeys);log.info("sIntersect(...) => intersectResult -> {}", intersectResult);return intersectResult;}/*** 获取两个(key对应的)Set的交集, 并将结果add到storeKey对应的Set中。* <p>* case1: 交集不为空, storeKey不存在, 则 会创建对应的storeKey,并将交集添加到(storeKey对应的)set中* case2: 交集不为空, storeKey已存在, 则 会清除原(storeKey对应的)set中所有的项,然后将交集添加到(storeKey对应的)set中* case3: 交集为空, 则不进行下面的操作, 直接返回0* <p>* 注: 求交集的部分,详见{@link SetOps#sIntersect(String, String)}** @param key      定位其中一个set的键* @param otherKey 定位其中另一个set的键* @param storeKey 定位(要把交集添加到哪个)set的key* @return add到(storeKey对应的)Set后, 该set对应的size* @date 2020/3/11 9:46:46*/public static long sIntersectAndStore(String key, String otherKey, String storeKey) {log.info("sIntersectAndStore(...) => key -> {}, otherKey -> {}, storeKey -> {}",key, otherKey, storeKey);Long size = redisTemplate.opsForSet().intersectAndStore(key, otherKey, storeKey);log.info("sIntersectAndStore(...) => size -> {}", size);if (size == null) {throw new RedisOpsResultIsNullException();}return size;}/*** 获取多个(key对应的)Set的交集, 并将结果add到storeKey对应的Set中。* <p>* case1: 交集不为空, storeKey不存在, 则 会创建对应的storeKey,并将交集添加到(storeKey对应的)set中* case2: 交集不为空, storeKey已存在, 则 会清除原(storeKey对应的)set中所有的项,然后将交集添加到(storeKey对应的)set中* case3: 交集为空, 则不进行下面的操作, 直接返回0* <p>* 注: 求交集的部分,详见{@link SetOps#sIntersect(String, Collection)}** @date 2020/3/11 11:04:29*/public static long sIntersectAndStore(String key, Collection<String> otherKeys, String storeKey) {log.info("sIntersectAndStore(...) => key -> {}, otherKeys -> {}, storeKey -> {}", key, otherKeys, storeKey);Long size = redisTemplate.opsForSet().intersectAndStore(key, otherKeys, storeKey);log.info("sIntersectAndStore(...) => size -> {}", size);if (size == null) {throw new RedisOpsResultIsNullException();}return size;}/*** 获取两个(key对应的)Set的并集* <p>* 注: 并集中的元素也是唯一的,这是Set保证的。** @param key      定位其中一个set的键* @param otherKey 定位其中另一个set的键* @return item并集* @date 2020/3/11 11:18:35*/public static Set<String> sUnion(String key, String otherKey) {log.info("sUnion(...) => key -> {}, otherKey -> {}", key, otherKey);Set<String> unionResult = redisTemplate.opsForSet().union(key, otherKey);log.info("sUnion(...) => unionResult -> {}", unionResult);return unionResult;}/*** 获取两个(key对应的)Set的并集* <p>* 注: 并集中的元素也是唯一的,这是Set保证的。** @param key       定位其中一个set的键* @param otherKeys 定位其它set的键集* @return item并集* @date 2020/3/11 11:18:35*/public static Set<String> sUnion(String key, Collection<String> otherKeys) {log.info("sUnion(...) => key -> {}, otherKeys -> {}", key, otherKeys);Set<String> unionResult = redisTemplate.opsForSet().union(key, otherKeys);log.info("sUnion(...) => unionResult -> {}", unionResult);return unionResult;}/*** 获取两个(key对应的)Set的并集, 并将结果add到storeKey对应的Set中。* <p>* case1: 并集不为空, storeKey不存在, 则 会创建对应的storeKey,并将并集添加到(storeKey对应的)set中* case2: 并集不为空, storeKey已存在, 则 会清除原(storeKey对应的)set中所有的项,然后将并集添加到(storeKey对应的)set中* case3: 并集为空, 则不进行下面的操作, 直接返回0* <p>* 注: 求并集的部分,详见{@link SetOps#sUnion(String, String)}** @param key      定位其中一个set的键* @param otherKey 定位其中另一个set的键* @param storeKey 定位(要把并集添加到哪个)set的key* @return add到(storeKey对应的)Set后, 该set对应的size* @date 2020/3/11 12:26:24*/public static long sUnionAndStore(String key, String otherKey, String storeKey) {log.info("sUnionAndStore(...) => key -> {}, otherKey -> {}, storeKey -> {}",key, otherKey, storeKey);Long size = redisTemplate.opsForSet().unionAndStore(key, otherKey, storeKey);log.info("sUnionAndStore(...) => size -> {}", size);if (size == null) {throw new RedisOpsResultIsNullException();}return size;}/*** 获取两个(key对应的)Set的并集, 并将结果add到storeKey对应的Set中。* <p>* case1: 并集不为空, storeKey不存在, 则 会创建对应的storeKey,并将并集添加到(storeKey对应的)set中* case2: 并集不为空, storeKey已存在, 则 会清除原(storeKey对应的)set中所有的项,然后将并集添加到(storeKey对应的)set中* case3: 并集为空, 则不进行下面的操作, 直接返回0* <p>* 注: 求并集的部分,详见{@link SetOps#sUnion(String, Collection)}** @param key       定位其中一个set的键* @param otherKeys 定位其它set的键集* @param storeKey  定位(要把并集添加到哪个)set的key* @return add到(storeKey对应的)Set后, 该set对应的size* @date 2020/3/11 12:26:24*/public static long sUnionAndStore(String key, Collection<String> otherKeys, String storeKey) {log.info("sUnionAndStore(...) => key -> {}, otherKeys -> {}, storeKey -> {}",key, otherKeys, storeKey);Long size = redisTemplate.opsForSet().unionAndStore(key, otherKeys, storeKey);log.info("sUnionAndStore(...) => size -> {}", size);if (size == null) {throw new RedisOpsResultIsNullException();}return size;}/*** 获取 (key对应的)Set 减去 (otherKey对应的)Set 的差集* <p>* 注: 如果被减数key不存在, 那么结果为空的集合(,而不是null)* 注: 如果被减数key存在,但减数key不存在, 那么结果即为(被减数key对应的)Set** @param key      定位"被减数set"的键* @param otherKey 定位"减数set"的键* @return item差集* @date 2020/3/11 14:03:57*/public static Set<String> sDifference(String key, String otherKey) {log.info("sDifference(...) => key -> {}, otherKey -> {}",key, otherKey);Set<String> differenceResult = redisTemplate.opsForSet().difference(key, otherKey);log.info("sDifference(...) => differenceResult -> {}", differenceResult);return differenceResult;}/*** 获取 (key对应的)Set 减去 (otherKeys对应的)Sets 的差集* <p>* 注: 如果被减数key不存在, 那么结果为空的集合(,而不是null)* 注: 如果被减数key存在,但减数key不存在, 那么结果即为(被减数key对应的)Set* <p>* 提示: 当有多个减数时, 被减数先减去哪一个减数,后减去哪一个减数,是无所谓的,是不影响最终结果的。** @param key       定位"被减数set"的键* @param otherKeys 定位"减数集sets"的键集* @return item差集* @date 2020/3/11 14:03:57*/public static Set<String> sDifference(String key, Collection<String> otherKeys) {log.info("sDifference(...) => key -> {}, otherKeys -> {}", key, otherKeys);Set<String> differenceResult = redisTemplate.opsForSet().difference(key, otherKeys);log.info("sDifference(...) => differenceResult -> {}", differenceResult);return differenceResult;}/*** 获取 (key对应的)Set 减去 (otherKey对应的)Set 的差集, 并将结果add到storeKey对应的Set中。* <p>* case1: 差集不为空, storeKey不存在, 则 会创建对应的storeKey,并将差集添加到(storeKey对应的)set中* case2: 差集不为空, storeKey已存在, 则 会清除原(storeKey对应的)set中所有的项,然后将差集添加到(storeKey对应的)set中* case3: 差集为空, 则不进行下面的操作, 直接返回0* <p>* 注: 求并集的部分,详见{@link SetOps#sDifference(String, String)}** @param key      定位"被减数set"的键* @param otherKey 定位"减数set"的键* @param storeKey 定位(要把差集添加到哪个)set的key* @return add到(storeKey对应的)Set后, 该set对应的size* @date 2020/3/11 14:33:36*/public static long sDifferenceAndStore(String key, String otherKey, String storeKey) {log.info("sDifferenceAndStore(...) => key -> {}, otherKey -> {}, storeKey -> {}",key, otherKey, storeKey);Long size = redisTemplate.opsForSet().differenceAndStore(key, otherKey, storeKey);log.info("sDifferenceAndStore(...) => size -> {}", size);if (size == null) {throw new RedisOpsResultIsNullException();}return size;}/*** 获取 (key对应的)Set 减去 (otherKey对应的)Set 的差集, 并将结果add到storeKey对应的Set中。* <p>* case1: 差集不为空, storeKey不存在, 则 会创建对应的storeKey,并将差集添加到(storeKey对应的)set中* case2: 差集不为空, storeKey已存在, 则 会清除原(storeKey对应的)set中所有的项,然后将差集添加到(storeKey对应的)set中* case3: 差集为空, 则不进行下面的操作, 直接返回0* <p>* 注: 求并集的部分,详见{@link SetOps#sDifference(String, String)}** @param key       定位"被减数set"的键* @param otherKeys 定位"减数集sets"的键集* @param storeKey  定位(要把差集添加到哪个)set的key* @return add到(storeKey对应的)Set后, 该set对应的size* @date 2020/3/11 14:33:36*/public static long sDifferenceAndStore(String key, Collection<String> otherKeys, String storeKey) {log.info("sDifferenceAndStore(...) => key -> {}, otherKeys -> {}, storeKey -> {}",key, otherKeys, storeKey);Long size = redisTemplate.opsForSet().differenceAndStore(key, otherKeys, storeKey);log.info("sDifferenceAndStore(...) => size -> {}", size);if (size == null) {throw new RedisOpsResultIsNullException();}return size;}/*** 获取key对应的set* <p>* 注: 若key不存在, 则返回的是空的set(, 而不是null)** @param key 定位set的key* @return (key对应的)set* @date 2020/3/11 14:49:39*/public static Set<String> sMembers(String key) {log.info("sMembers(...) => key -> {}", key);Set<String> members = redisTemplate.opsForSet().members(key);log.info("sMembers(...) => members -> {}", members);return members;}/*** 从key对应的set中随机获取一项** @param key 定位set的key* @return 随机获取到的项* @date 2020/3/11 14:54:58*/public static String sRandomMember(String key) {log.info("sRandomMember(...) => key -> {}", key);String randomItem = redisTemplate.opsForSet().randomMember(key);log.info("sRandomMember(...) => randomItem -> {}", randomItem);return randomItem;}/*** 从key对应的set中获取count次随机项(, set中的同一个项可能被多次获取)* <p>* 注: count可大于set的size。* 注: 取出来的结果里可能存在相同的值。** @param key   定位set的key* @param count 要取多少项* @return 随机获取到的项集* @date 2020/3/11 14:54:58*/public static List<String> sRandomMembers(String key, long count) {log.info("sRandomMembers(...) => key -> {}, count -> {}", key, count);List<String> randomItems = redisTemplate.opsForSet().randomMembers(key, count);log.info("sRandomMembers(...) => randomItems -> {}", randomItems);return randomItems;}/*** 从key对应的set中随机获取count个项* <p>* 注: 若count >= set的size, 那么返回的即为这个key对应的set。* 注: 取出来的结果里没有重复的项。** @param key   定位set的key* @param count 要取多少项* @return 随机获取到的项集* @date 2020/3/11 14:54:58*/public static Set<String> sDistinctRandomMembers(String key, long count) {log.info("sDistinctRandomMembers(...) => key -> {}, count -> {}", key, count);Set<String> distinctRandomItems = redisTemplate.opsForSet().distinctRandomMembers(key, count);log.info("sDistinctRandomMembers(...) => distinctRandomItems -> {}", distinctRandomItems);return distinctRandomItems;}/*** 根据options匹配到(key对应的)set中的对应的item, 并返回对应的item集* <p>* <p>* 注: ScanOptions实例的创建方式举例:* 1、ScanOptions.NONE* 2、ScanOptions.scanOptions().match("n??e").build()** @param key     定位set的key* @param options 匹配set中的item的条件*                注: ScanOptions.NONE表示全部匹配。*                注: ScanOptions.scanOptions().match(pattern).build()表示按照pattern匹配,*                其中pattern中可以使用通配符 * ? 等,*                * 表示>=0个字符*                ? 表示有且只有一个字符*                此处的匹配规则与{@link KeyOps#keys(String)}处的一样。* @return 匹配到的(key对应的)set中的项* @date 2020/3/9 10:49:27*/public static Cursor<String> sScan(String key, ScanOptions options) {log.info("sScan(...) => key -> {}, options -> {}", key, JSON.toJSONString(options));Cursor<String> cursor = redisTemplate.opsForSet().scan(key, options);log.info("sScan(...) => cursor -> {}", JSON.toJSONString(cursor));return cursor;}}/*** ZSet相关操作* <p>* 特别说明: ZSet是有序的,* 不仅体现在: redis中的存储上有序。* 还体现在:   此工具类ZSetOps中返回值类型为Set<?>的方法, 实际返回类型是LinkedHashSet<?>* <p>* 提示: redis中的ZSet, 一定程度等于redis中的Set + redis中的Hash的结合体。* 提示: redis中String的数据结构可参考resources/data-structure/ZSet(有序集合)的数据结构(示例一).png* redis中String的数据结构可参考resources/data-structure/ZSet(有序集合)的数据结构(示例二).png* 提示: ZSet中的entryKey即为成员项, entryValue即为这个成员项的分值, ZSet根据成员的分值,来堆成员进行排序。** @date 2020/3/11 15:28:48*/public static class ZSetOps {/*** 向(key对应的)zset中添加(item, score)* <p>* 注: item为entryKey成员项, score为entryValue分数值。* <p>* 注: 若(key对应的)zset中已存在(与此次要添加的项)相同的item项,那么此次添加操作会失败,返回false;* 但是!!! zset中原item的score会被更新为此次add的相同item项的score。* 所以, 也可以通过zAdd达到更新item对应score的目的。* <p>* 注: score可为正、可为负、可为0; 总之, double范围内都可以。* <p>* 注: 若score的值一样,则按照item排序。** @param key   定位set的key* @param item  要往(key对应的)zset中添加的成员项* @param score item的分值* @return 是否添加成功* @date 2020/3/11 15:35:30*/public static boolean zAdd(String key, String item, double score) {log.info("zAdd(...) => key -> {}, item -> {}, score -> {}", key, item, score);Boolean result = redisTemplate.opsForZSet().add(key, item, score);log.info("zAdd(...) => result -> {}", result);if (result == null) {throw new RedisOpsResultIsNullException();}return result;}/*** 批量添加entry<item, score>* <p>* 注: 若entry<item, score>集中存在item相同的项(, score不一样),那么redis在执行真正的批量add操作前,会* 将其中一个item过滤掉。* 注: 同样的,若(key对应的)zset中已存在(与此次要添加的项)相同的item项,那么此次批量添加操作中,* 对该item项的添加会失败,会失败,成功计数器不会加1;但是!!! zset中原item的score会被更新为此* 次add的相同item项的score。所以, 也可以通过zAdd达到更新item对应score的目的。** @param key     定位set的key* @param entries 要添加的entry<item, score>集* @return 本次添加进(key对应的)zset中的entry的个数* @date 2020/3/11 16:45:45*/public static long zAdd(String key, Set<ZSetOperations.TypedTuple<String>> entries) {log.info("zAdd(...) => key -> {}, entries -> {}", key, JSON.toJSONString(entries));Long count = redisTemplate.opsForZSet().add(key, entries);log.info("zAdd(...) => count -> {}", count);if (count == null) {throw new RedisOpsResultIsNullException();}return count;}/*** 从(key对应的)zset中移除项* <p>* 注:若key不存在,则返回0** @param key   定位set的key* @param items 要移除的项集* @return 实际移除了的项的个数* @date 2020/3/11 17:20:12*/public static long zRemove(String key, Object... items) {log.info("zRemove(...) => key -> {}, items -> {}", key, items);Long count = redisTemplate.opsForZSet().remove(key, items);log.info("zRemove(...) => count -> {}", count);if (count == null) {throw new RedisOpsResultIsNullException();}return count;}/*** 移除(key对应的)zset中, 排名范围在[startIndex, endIndex]内的item* <p>* 注:默认的,按score.item升序排名, 排名从0开始* <p>* 注: 类似于List中的索引, 排名可以分为多个方式:* 从前到后(正向)的排名: 0、1、2...* 从后到前(反向)的排名: -1、-2、-3...* <p>* 注: 不论是使用正向排名,还是使用反向排名, 使用此方法时, 应保证 startRange代表的元素的位置* 在endRange代表的元素的位置的前面, 如:* 示例一: RedisUtil.ZSetOps.zRemoveRange("name", 0, 2);* 示例二: RedisUtil.ZSetOps.zRemoveRange("site", -2, -1);* 示例三: RedisUtil.ZSetOps.zRemoveRange("foo", 0, -1);* <p>* 注:若key不存在,则返回0** @param key        定位set的key* @param startRange 开始项的排名* @param endRange   结尾项的排名* @return 实际移除了的项的个数* @date 2020/3/11 17:20:12*/public static long zRemoveRange(String key, long startRange, long endRange) {log.info("zRemoveRange(...) => key -> {}, startRange -> {}, endRange -> {}",key, startRange, endRange);Long count = redisTemplate.opsForZSet().removeRange(key, startRange, endRange);log.info("zRemoveRange(...) => count -> {}", count);if (count == null) {throw new RedisOpsResultIsNullException();}return count;}/*** 移除(key对应的)zset中, score范围在[minScore, maxScore]内的item* <p>* 提示: 虽然删除范围包含两侧的端点(即:包含minScore和maxScore), 但是由于double存在精度问题,所以建议:* 设置值时,minScore应该设置得比要删除的项里,最小的score还小一点* maxScore应该设置得比要删除的项里,最大的score还大一点* 追注: 本人简单测试了几组数据,暂未出现精度问题。* <p>* 注:若key不存在,则返回0** @param key      定位set的key* @param minScore score下限(含这个值)* @param maxScore score上限(含这个值)* @return 实际移除了的项的个数* @date 2020/3/11 17:20:12*/public static long zRemoveRangeByScore(String key, double minScore, double maxScore) {log.info("zRemoveRangeByScore(...) => key -> {}, startIndex -> {}, startIndex -> {}",key, minScore, maxScore);Long count = redisTemplate.opsForZSet().removeRangeByScore(key, minScore, maxScore);log.info("zRemoveRangeByScore(...) => count -> {}", count);if (count == null) {throw new RedisOpsResultIsNullException();}return count;}/*** 增/减 (key对应的zset中,)item的分数值** @param key   定位zset的key* @param item  项* @param delta 变化量(正 - 增, 负 - 减)* @return 修改后的score值* @date 2020/3/12 8:55:38*/public static double zIncrementScore(String key, String item, double delta) {log.info("zIncrementScore(...) => key -> {}, item -> {}, delta -> {}", key, item, delta);Double scoreValue = redisTemplate.opsForZSet().incrementScore(key, item, delta);log.info("zIncrementScore(...) => scoreValue -> {}", scoreValue);if (scoreValue == null) {throw new RedisOpsResultIsNullException();}return scoreValue;}/*** 返回item在(key对应的)zset中的(按score从小到大的)排名* <p>* 注: 排名从0开始。 即意味着,此方法等价于: 返回item在(key对应的)zset中的位置索引。* 注: 若key或item不存在, 返回null。* 注: 排序规则是score,item, 即:优先以score排序,若score相同,则再按item排序。** @param key  定位zset的key* @param item 项* @return 排名(等价于 : 索引)* @date 2020/3/12 9:14:09*/public static long zRank(String key, Object item) {log.info("zRank(...) => key -> {}, item -> {}", key, item);Long rank = redisTemplate.opsForZSet().rank(key, item);log.info("zRank(...) => rank -> {}", rank);if (rank == null) {throw new RedisOpsResultIsNullException();}return rank;}/*** 返回item在(key对应的)zset中的(按score从大到小的)排名* <p>* 注: 排名从0开始。补充: 因为是按score从大到小排序的, 所以最大score对应的item的排名为0。* 注: 若key或item不存在, 返回null。* 注: 排序规则是score,item, 即:优先以score排序,若score相同,则再按item排序。** @param key  定位zset的key* @param item 项* @return 排名(等价于 : 索引)* @date 2020/3/12 9:14:09*/public static long zReverseRank(String key, Object item) {log.info("zReverseRank(...) => key -> {}, item -> {}", key, item);Long reverseRank = redisTemplate.opsForZSet().reverseRank(key, item);log.info("zReverseRank(...) => reverseRank -> {}", reverseRank);if (reverseRank == null) {throw new RedisOpsResultIsNullException();}return reverseRank;}/*** 根据索引位置, 获取(key对应的)zset中排名处于[start, end]中的item项集* <p>* 注: 不论是使用正向排名,还是使用反向排名, 使用此方法时, 应保证 startIndex代表的元素的* 位置在endIndex代表的元素的位置的前面, 如:* 示例一: RedisUtil.ZSetOps.zRange("name", 0, 2);* 示例二: RedisUtil.ZSetOps.zRange("site", -2, -1);* 示例三: RedisUtil.ZSetOps.zRange("foo", 0, -1);* <p>* 注: 若key不存在, 则返回空的集合。* <p>* 注: 当[start, end]的范围比实际zset的范围大时, 返回范围上"交集"对应的项集合。** @param key   定位zset的key* @param start 排名开始位置* @param end   排名结束位置* @return 对应的item项集* @date 2020/3/12 9:50:40*/public static Set<String> zRange(String key, long start, long end) {log.info("zRange(...) => key -> {}, start -> {}, end -> {}", key, start, end);Set<String> result = redisTemplate.opsForZSet().range(key, start, end);log.info("zRange(...) => result -> {}", result);return result;}/*** 获取(key对应的)zset中的所有item项** @param key 定位zset的键* @return (key对应的)zset中的所有item项* @date 2020/3/12 10:02:07* @see ZSetOps#zRange(String, long, long)*/public static Set<String> zWholeZSetItem(String key) {log.info("zWholeZSetItem(...) => key -> {}", key);Set<String> result = redisTemplate.opsForZSet().range(key, 0, -1);log.info("zWholeZSetItem(...) =>result -> {}", result);return result;}/*** 根据索引位置, 获取(key对应的)zset中排名处于[start, end]中的entry集* <p>* 注: 不论是使用正向排名,还是使用反向排名, 使用此方法时, 应保证 startIndex代表的元素的* 位置在endIndex代表的元素的位置的前面, 如:* 示例一: RedisUtil.ZSetOps.zRange("name", 0, 2);* 示例二: RedisUtil.ZSetOps.zRange("site", -2, -1);* 示例三: RedisUtil.ZSetOps.zRange("foo", 0, -1);* <p>* 注: 若key不存在, 则返回空的集合。* <p>* 注: 当[start, end]的范围比实际zset的范围大时, 返回范围上"交集"对应的项集合。* <p>* 注: 此方法和{@link ZSetOps#zRange(String, long, long)}类似,不过此方法返回的不是item集, 而是entry集** @param key   定位zset的key* @param start 排名开始位置* @param end   排名结束位置* @return 对应的entry集* @date 2020/3/12 9:50:40*/public static Set<ZSetOperations.TypedTuple<String>> zRangeWithScores(String key, long start, long end) {log.info("zRangeWithScores(...) => key -> {}, start -> {}, end -> {}", key, start, end);Set<ZSetOperations.TypedTuple<String>> entries = redisTemplate.opsForZSet().rangeWithScores(key, start, end);log.info("zRangeWithScores(...) => entries -> {}", JSON.toJSONString(entries));return entries;}/*** 获取(key对应的)zset中的所有entry** @param key 定位zset的键* @return (key对应的)zset中的所有entry* @date 2020/3/12 10:02:07* @see ZSetOps#zRangeWithScores(String, long, long)*/public static Set<ZSetOperations.TypedTuple<String>> zWholeZSetEntry(String key) {log.info("zWholeZSetEntry(...) => key -> {}", key);Set<ZSetOperations.TypedTuple<String>> entries = redisTemplate.opsForZSet().rangeWithScores(key, 0, -1);log.info("zWholeZSetEntry(...) => entries -> {}", key, JSON.toJSONString(entries));return entries;}/*** 根据score, 获取(key对应的)zset中分数值处于[minScore, maxScore]中的item项集* <p>* 注: 若key不存在, 则返回空的集合。* 注: 当[minScore, maxScore]的范围比实际zset中score的范围大时, 返回范围上"交集"对应的项集合。* <p>* 提示: 虽然删除范围包含两侧的端点(即:包含minScore和maxScore), 但是由于double存在精度问题,所以建议:* 设置值时,minScore应该设置得比要删除的项里,最小的score还小一点* maxScore应该设置得比要删除的项里,最大的score还大一点* 追注: 本人简单测试了几组数据,暂未出现精度问题。** @param key      定位zset的key* @param minScore score下限* @param maxScore score上限* @return 对应的item项集* @date 2020/3/12 9:50:40*/public static Set<String> zRangeByScore(String key, double minScore, double maxScore) {log.info("zRangeByScore(...) => key -> {}, minScore -> {}, maxScore -> {}", key, minScore, maxScore);Set<String> items = redisTemplate.opsForZSet().rangeByScore(key, minScore, maxScore);log.info("zRangeByScore(...) => items -> {}", items);return items;}/*** 根据score, 获取(key对应的)zset中分数值处于[minScore, maxScore]中的, score处于[minScore,* 排名大于等于offset的count个item项* <p>* 特别注意: 对于不是特别熟悉redis的人来说, offset 和 count最好都使用正数, 避免引起理解上的歧义。* <p>* 注: 若key不存在, 则返回空的集合。* <p>* 提示: 虽然删除范围包含两侧的端点(即:包含minScore和maxScore), 但是由于double存在精度问题,所以建议:* 设置值时,minScore应该设置得比要删除的项里,最小的score还小一点* maxScore应该设置得比要删除的项里,最大的score还大一点* 追注: 本人简单测试了几组数据,暂未出现精度问题。** @param key      定位zset的key* @param minScore score下限* @param maxScore score上限* @param offset   偏移量(即:排名下限)* @param count    期望获取到的元素个数* @return 对应的item项集* @date 2020/3/12 9:50:40*/public static Set<String> zRangeByScore(String key, double minScore, double maxScore,long offset, long count) {log.info("zRangeByScore(...) => key -> {}, minScore -> {}, maxScore -> {}, offset -> {}, "+ "count -> {}", key, minScore, maxScore, offset, count);Set<String> items = redisTemplate.opsForZSet().rangeByScore(key, minScore, maxScore, offset, count);log.info("zRangeByScore(...) => items -> {}", items);return items;}/*** 获取(key对应的)zset中的所有score处于[minScore, maxScore]中的entry** @param key      定位zset的键* @param minScore score下限* @param maxScore score上限* @return (key对应的)zset中的所有score处于[minScore, maxScore]中的entry* @date 2020/3/12 10:02:07* @see ZSetOps#zRangeByScore(String, double, double)* <p>* 注: 若key不存在, 则返回空的集合。* 注: 当[minScore, maxScore]的范围比实际zset中score的范围大时, 返回范围上"交集"对应的项集合。*/public static Set<ZSetOperations.TypedTuple<String>> zRangeByScoreWithScores(String key, double minScore, double maxScore) {log.info("zRangeByScoreWithScores(...) => key -> {}, minScore -> {}, maxScore -> {}",key, minScore, maxScore);Set<ZSetOperations.TypedTuple<String>> entries = redisTemplate.opsForZSet().rangeByScoreWithScores(key, minScore, maxScore);log.info("zRangeByScoreWithScores(...) => entries -> {}", JSON.toJSONString(entries));return entries;}/*** 获取(key对应的)zset中, score处于[minScore, maxScore]里的、排名大于等于offset的count个entry* <p>* 特别注意: 对于不是特别熟悉redis的人来说, offset 和 count最好都使用正数, 避免引起理解上的歧义。** @param key      定位zset的键* @param minScore score下限* @param maxScore score上限* @param offset   偏移量(即:排名下限)* @param count    期望获取到的元素个数* @return [startIndex, endIndex] & [minScore, maxScore]里的entry* @date 2020/3/12 11:09:06*/public static Set<ZSetOperations.TypedTuple<String>> zRangeByScoreWithScores(String key, double minScore,double maxScore, long offset,long count) {log.info("zRangeByScoreWithScores(...) => key -> {}, minScore -> {}, maxScore -> {},"+ " offset -> {}, count -> {}",key, minScore, maxScore, offset, count);Set<ZSetOperations.TypedTuple<String>> entries = redisTemplate.opsForZSet().rangeByScoreWithScores(key, minScore,maxScore, offset, count);log.info("zRangeByScoreWithScores(...) => entries -> {}", JSON.toJSONString(entries));return entries;}/*** 获取时, 先按score倒序, 然后根据索引位置, 获取(key对应的)zset中排名处于[start, end]中的item项集** @see ZSetOps#zRange(String, long, long)。 只是zReverseRange这里会提前多一个倒序。*/public static Set<String> zReverseRange(String key, long start, long end) {log.info("zReverseRange(...) => key -> {}, start -> {}, end -> {}", key, start, end);Set<String> entries = redisTemplate.opsForZSet().reverseRange(key, start, end);log.info("zReverseRange(...) => entries -> {}", entries);return entries;}/*** 获取时, 先按score倒序, 然后根据索引位置, 获取(key对应的)zset中排名处于[start, end]中的entry集** @see ZSetOps#zRangeWithScores(String, long, long)。 只是zReverseRangeWithScores这里会提前多一个倒序。*/public static Set<ZSetOperations.TypedTuple<String>> zReverseRangeWithScores(String key, long start, long end) {log.info("zReverseRangeWithScores(...) => key -> {}, start -> {}, end -> {}", key, start, end);Set<ZSetOperations.TypedTuple<String>> entries = redisTemplate.opsForZSet().reverseRangeWithScores(key, start, end);log.info("zReverseRangeWithScores(...) => entries -> {}", JSON.toJSONString(entries));return entries;}/*** 获取时, 先按score倒序, 然后根据score, 获取(key对应的)zset中分数值处于[minScore, maxScore]中的item项集** @see ZSetOps#zRangeByScore(String, double, double)。 只是zReverseRangeByScore这里会提前多一个倒序。*/public static Set<String> zReverseRangeByScore(String key, double minScore, double maxScore) {log.info("zReverseRangeByScore(...) => key -> {}, minScore -> {}, maxScore -> {}",key, minScore, maxScore);Set<String> items = redisTemplate.opsForZSet().reverseRangeByScore(key, minScore, maxScore);log.info("zReverseRangeByScore(...) => items -> {}", items);return items;}/*** 获取时, 先按score倒序, 然后获取(key对应的)zset中的所有score处于[minScore, maxScore]中的entry** @see ZSetOps#zRangeByScoreWithScores(String, double, double)。 只是zReverseRangeByScoreWithScores这里会提前多一个倒序。*/public static Set<ZSetOperations.TypedTuple<String>> zReverseRangeByScoreWithScores(String key, double minScore, double maxScore) {log.info("zReverseRangeByScoreWithScores(...) => key -> {}, minScore -> {}, maxScore -> {}",key, minScore, maxScore);Set<ZSetOperations.TypedTuple<String>> entries = redisTemplate.opsForZSet().reverseRangeByScoreWithScores(key,minScore, maxScore);log.info("zReverseRangeByScoreWithScores(...) => entries -> {}", JSON.toJSONString(entries));return entries;}/*** 获取时, 先按score倒序, 然后根据score, 获取(key对应的)zset中分数值处于[minScore, maxScore]中的,* score处于[minScore,排名大于等于offset的count个item项** @see ZSetOps#zRangeByScore(String, double, double, long, long)。 只是zReverseRangeByScore这里会提前多一个倒序。*/public static Set<String> zReverseRangeByScore(String key, double minScore, double maxScore, long offset, long count) {log.info("zReverseRangeByScore(...) => key -> {}, minScore -> {}, maxScore -> {}, offset -> {}, "+ "count -> {}", key, minScore, maxScore, offset, count);Set<String> items = redisTemplate.opsForZSet().reverseRangeByScore(key, minScore, maxScore, offset, count);log.info("items -> {}", items);return items;}/*** 统计(key对应的zset中)score处于[minScore, maxScore]中的item的个数** @param key      定位zset的key* @param minScore score下限* @param maxScore score上限* @return [minScore, maxScore]中item的个数* @date 2020/3/13 12:20:43*/public static long zCount(String key, double minScore, double maxScore) {log.info("zCount(...) => key -> {}, minScore -> {}, maxScore -> {}", key, minScore, maxScore);Long count = redisTemplate.opsForZSet().count(key, minScore, maxScore);log.info("zCount(...) => count -> {}", count);if (count == null) {throw new RedisOpsResultIsNullException();}return count;}/*** 统计(key对应的)zset中item的个数* <p>* 注: 此方法等价于{@link ZSetOps#zZCard(String)}** @param key 定位zset的key* @return zset中item的个数* @date 2020/3/13 12:20:43*/public static long zSize(String key) {log.info("zSize(...) => key -> {}", key);Long size = redisTemplate.opsForZSet().size(key);log.info("zSize(...) => size -> {}", size);if (size == null) {throw new RedisOpsResultIsNullException();}return size;}/*** 统计(key对应的)zset中item的个数* <p>* 注: 此方法等价于{@link ZSetOps#zSize(String)}** @param key 定位zset的key* @return zset中item的个数* @date 2020/3/13 12:20:43*/public static long zZCard(String key) {log.info("zZCard(...) => key -> {}", key);Long size = redisTemplate.opsForZSet().zCard(key);log.info("zZCard(...) => size -> {}", size);if (size == null) {throw new RedisOpsResultIsNullException();}return size;}/*** 统计(key对应的)zset中指定item的score** @param key  定位zset的key* @param item zset中的item* @return item的score* @date 2020/3/13 14:51:43*/public static double zScore(String key, Object item) {log.info("zScore(...) => key -> {}, item -> {}", key, item);Double score = redisTemplate.opsForZSet().score(key, item);log.info("zScore(...) => score -> {}", score);if (score == null) {throw new RedisOpsResultIsNullException();}return score;}/*** 获取两个(key对应的)ZSet的并集, 并将结果add到storeKey对应的ZSet中。* <p>* 注: 和set一样,zset中item是唯一的, 在多个zset进行Union时, 处理相同的item时, score的值会变为对应的score之和,如:* RedisUtil.ZSetOps.zAdd("name1", "a", 1);和RedisUtil.ZSetOps.zAdd("name2", "a", 2);* 对(name1和name2对应的)zset进行zUnionAndStore之后,新的zset中的项a,对应的score值为3* <p>* case1: 交集不为空, storeKey不存在, 则 会创建对应的storeKey,并将并集添加到(storeKey对应的)ZSet中* case2: 交集不为空, storeKey已存在, 则 会清除原(storeKey对应的)ZSet中所有的项,然后将并集添加到(storeKey对应的)ZSet中* case3: 交集为空, 则不进行下面的操作, 直接返回0** @param key      定位其中一个zset的键* @param otherKey 定位另外的zset的键* @param storeKey 定位(要把交集添加到哪个)set的key* @return add到(storeKey对应的)ZSet后, 该ZSet对应的size* @date 2020/3/11 12:26:24*/public static long zUnionAndStore(String key, String otherKey, String storeKey) {log.info("zUnionAndStore(...) => key -> {}, otherKey -> {}, storeKey -> {}", key, otherKey, storeKey);Long size = redisTemplate.opsForZSet().unionAndStore(key, otherKey, storeKey);log.info("zUnionAndStore(...) => size -> {}", size);if (size == null) {throw new RedisOpsResultIsNullException();}return size;}/*** 获取两个(key对应的)ZSet的并集, 并将结果add到storeKey对应的ZSet中。* <p>* 注: 和set一样,zset中item是唯一的, 在多个zset进行Union时, 处理相同的item时, score的值会变为对应的score之和,如:* RedisUtil.ZSetOps.zAdd("name1", "a", 1);和RedisUtil.ZSetOps.zAdd("name2", "a", 2);* 对(name1和name2对应的)zset进行zUnionAndStore之后,新的zset中的项a,对应的score值为3* <p>* case1: 并集不为空, storeKey不存在, 则 会创建对应的storeKey,并将并集添加到(storeKey对应的)ZSet中* case2: 并集不为空, storeKey已存在, 则 会清除原(storeKey对应的)ZSet中所有的项,然后将并集添加到(storeKey对应的)ZSet中* case3: 并集为空, 则不进行下面的操作, 直接返回0** @param key       定位其中一个set的键* @param otherKeys 定位其它set的键集* @param storeKey  定位(要把并集添加到哪个)set的key* @return add到(storeKey对应的)ZSet后, 该ZSet对应的size* @date 2020/3/11 12:26:24*/public static long zUnionAndStore(String key, Collection<String> otherKeys, String storeKey) {log.info("zUnionAndStore(...) => key -> {}, otherKeys -> {}, storeKey -> {}", key, otherKeys, storeKey);Long size = redisTemplate.opsForZSet().unionAndStore(key, otherKeys, storeKey);log.info("zUnionAndStore(...) => size -> {}", size);if (size == null) {throw new RedisOpsResultIsNullException();}return size;}/*** 获取两个(key对应的)ZSet的交集, 并将结果add到storeKey对应的ZSet中。* <p>* 注: 和set一样,zset中item是唯一的, 在多个zset进行Intersect时, 处理相同的item时, score的值会变为对应的score之和,如:* RedisUtil.ZSetOps.zAdd("name1", "a", 1);* RedisUtil.ZSetOps.zAdd("name1", "b", 100);* 和R* edisUtil.ZSetOps.zAdd("name2", "a", 2);* edisUtil.ZSetOps.zAdd("name2", "c", 200);* 对(name1和name2对应的)zset进行zIntersectAndStore之后,新的zset中的项a,对应的score值为3* <p>* case1: 交集不为空, storeKey不存在, 则 会创建对应的storeKey,并将交集添加到(storeKey对应的)ZSet中* case2: 交集不为空, storeKey已存在, 则 会清除原(storeKey对应的)ZSet中所有的项,然后将交集添加到(storeKey对应的)ZSet中* case3: 交集为空, 则不进行下面的操作, 直接返回0** @param key      定位其中一个ZSet的键* @param otherKey 定位其中另一个ZSet的键* @param storeKey 定位(要把交集添加到哪个)ZSet的key* @return add到(storeKey对应的)ZSet后, 该ZSet对应的size* @date 2020/3/11 9:46:46*/public static long zIntersectAndStore(String key, String otherKey, String storeKey) {log.info("zIntersectAndStore(...) => key -> {}, otherKey -> {}, storeKey -> {}", key, otherKey, storeKey);Long size = redisTemplate.opsForZSet().intersectAndStore(key, otherKey, storeKey);log.info("zIntersectAndStore(...) => size -> {}", size);if (size == null) {throw new RedisOpsResultIsNullException();}return size;}/*** 获取多个(key对应的)ZSet的交集, 并将结果add到storeKey对应的ZSet中。* <p>* case1: 交集不为空, storeKey不存在, 则 会创建对应的storeKey,并将交集添加到(storeKey对应的)ZSet中* case2: 交集不为空, storeKey已存在, 则 会清除原(storeKey对应的)ZSet中所有的项,然后将交集添加到(storeKey对应的)ZSet中* case3: 交集为空, 则不进行下面的操作, 直接返回0** @param key       定位其中一个set的键* @param otherKeys 定位其它set的键集* @param storeKey  定位(要把并集添加到哪个)set的key* @return add到(storeKey对应的)ZSet后, 该ZSet对应的size* @date 2020/3/11 11:04:29*/public static long zIntersectAndStore(String key, Collection<String> otherKeys, String storeKey) {log.info("zIntersectAndStore(...) => key -> {}, otherKeys -> {}, storeKey -> {}",key, otherKeys, storeKey);Long size = redisTemplate.opsForZSet().intersectAndStore(key, otherKeys, storeKey);log.info("zIntersectAndStore(...) => size -> {}", size);if (size == null) {throw new RedisOpsResultIsNullException();}return size;}}/*** redis分布式锁(单机版).* <p>* 使用方式(示例):* boolean flag = false;* String lockName = "sichuan:mianyang:fucheng:ds";* String lockValue = UUID.randomUUID().toString();* try {* //	非阻塞获取(锁的最大存活时间采用默认值)* flag = RedisUtil.LockOps.getLock(lockName, lockValue);* //	非阻塞获取e.g.* flag = RedisUtil.LockOps.getLock(lockName, lockValue, 3, TimeUnit.SECONDS);* // 阻塞获取(锁的最大存活时间采用默认值)* flag = RedisUtil.LockOps.getLockUntilTimeout(lockName, lockValue, 2000);* // 阻塞获取e.g.* flag = RedisUtil.LockOps.getLockUntilTimeout(lockName, lockValue, 2, TimeUnit.SECONDS, 2000);* if (!flag) {* throw new RuntimeException(" obtain redis-lock[" + lockName + "] fail");* }* // your logic* //	...* } finally {* if (flag) {* RedisUtil.LockOps.releaseLock(lockName, lockValue);* }* }* <p>* |--------------------------------------------------------------------------------------------------------------------|* |单机版分布式锁、集群版分布式锁,特别说明:                                                                                 |* |   - 此锁是针对单机Redis的分布式锁;                                                                                    |* |   - 对于Redis集群而言, 此锁可能存在失效的情况。考虑如下情况:                                                              |* |         首先,当客户端A通过key-value(假设为key名为key123)在Master上获取到一个锁。                                        |* |         然后,Master试着把这个数据同步到Slave的时候突然挂了(此时Slave上没有该分布式锁的key123)。                            |* |         接着,Slave变成了Master。                                                                                    |* |         不巧的是,客户端B此时也一以相同的key去获取分布式锁;                                                              |* |                 因为现在的Master上没有key123代表的分布式锁,                                                            |* |                 所以客户端B此时再通过key123去获取分布式锁时,                                                            |* |                 就能获取成功。                                                                                       |* |         那么此时,客户端A和客户端B同时获取到了同一把分布式锁,分布式锁失效。                                                 |* |   - 在Redis集群模式下,如果需要严格的分布式锁的话,可使用Redlock算法来实现。Redlock算法原理简述:                              |* |     - 获取分布式锁:                                                                                                 |* |           1. 客户端获取服务器当前的的时间t0。                                                                           |* |           2. 使用相同的key和value依次向5个实例获取锁。                                                                  |* |              注:为了避免在某个redis节点耗时太久而影响到对后面的Redis节点的锁的获取;                                         |* |                 客户端在获取每一个Redis节点的锁的时候,自身需要设置一个较小的等待获取锁超时的时间,                             |* |                 一旦都在某个节点获取分布式锁的时间超过了超时时间,那么就认为在这个节点获取分布式锁失败,                        |* |                 (不把时间浪费在这一个节点上),继续获取下一个节点的分布式锁。                                              |* |           3. 客户端通过当前时间(t1)减去t0,计算(从所有redis节点)获取锁所消耗的总时间t2(注:t2=t1-t0)。                      |* |              只有t2小于锁本身的锁定时长(注:若锁的锁定时长是1小时, 假设下午一点开始上锁,那么锁会在下午两点                     |* |              的时候失效, 而你却在两点后才获取到锁,这个时候已经没意义了),并且,客户端在至少在多半Redis                        |* |              节点上获取到锁, 我们才认为分布式锁获取成功。                                                                |* |           5. 如果锁已经获取,那么  锁的实际有效时长 = 锁的总有效时长 - 获取分布式锁所消耗的时长; 锁的实际有效时长 应保证 > 0。    |* |              注: 也就是说, 如果获取锁失败,那么                                                                        |* |                  A. 可能是   获取到的锁的个数,不满足大多数原则。                                                         |* |                  B. 也可能是 锁的实际有效时长不大于0。                                                                  |* |      - 释放分布式锁: 在每个redis节点上试着删除锁(, 不论有没有在该节点上获取到锁)。                                          |* |   - 集群下的分布式锁,可直接使用现有类库<a href="https://github.com/redisson/redisson"/>                                |* |                                                                                                                    |* |   注: 如果Redis集群项目能够容忍master宕机导致单机版分布式锁失效的情况的话,那么是直接使用单机版分布式锁在Redis集群的项目中的;     |* |       如果Redis集群项目不能容忍单机版分布式锁失效的情况的话,那么请使用基于RedLock算法的集群版分布式锁;                        |* |--------------------------------------------------------------------------------------------------------------------|** @date 2020/3/14 19:23:26*/public static class LockOps {/*** lua脚本, 保证 释放锁脚本 的原子性(以避免, 并发场景下, 释放了别人的锁)*/private static final String RELEASE_LOCK_LUA;/*** 分布式锁默认(最大)存活时长*/public static final long DEFAULT_LOCK_TIMEOUT = 3;/*** DEFAULT_LOCK_TIMEOUT的单位*/public static final TimeUnit DEFAULT_TIMEOUT_UNIT = TimeUnit.SECONDS;static {// 不论lua中0是否代表失败; 对于java的Boolean而言, 返回0, 则会被解析为falseRELEASE_LOCK_LUA = "if redis.call('get',KEYS[1]) == ARGV[1] "+ "then "+ "    return redis.call('del',KEYS[1]) "+ "else "+ "    return 0 "+ "end ";}/*** 获取(分布式)锁.* <p>* 注: 获取结果是即时返回的、是非阻塞的。** @see LockOps#getLock(String, String, long, TimeUnit)*/public static boolean getLock(final String key, final String value) {return getLock(key, value, DEFAULT_LOCK_TIMEOUT, DEFAULT_TIMEOUT_UNIT);}/*** 获取(分布式)锁。* 若成功, 则直接返回;* 若失败, 则进行重试, 直到成功 或 超时为止。* <p>* 注: 获取结果是阻塞的, 要么成功, 要么超时, 才返回。** @param retryTimeoutLimit 重试的超时时长(ms)*                          其它参数可详见:* @return 是否成功* @see LockOps#getLock(String, String, long, TimeUnit)*/public static boolean getLockUntilTimeout(final String key, final String value,final long retryTimeoutLimit) {return getLockUntilTimeout(key, value, DEFAULT_LOCK_TIMEOUT, DEFAULT_TIMEOUT_UNIT, retryTimeoutLimit);}/*** 获取(分布式)锁。* 若成功, 则直接返回;* 若失败, 则进行重试, 直到成功 或 超时为止。* <p>* 注: 获取结果是阻塞的, 要么成功, 要么超时, 才返回。** @param retryTimeoutLimit 重试的超时时长(ms)*                          其它参数可详见:* @return 是否成功* @see LockOps#getLock(String, String, long, TimeUnit, boolean)*/public static boolean getLockUntilTimeout(final String key, final String value,final long timeout, final TimeUnit unit,final long retryTimeoutLimit) {log.info("getLockUntilTimeout(...) => key -> {}, value -> {}, timeout -> {}, unit -> {}, "+ "retryTimeoutLimit -> {}ms", key, value, timeout, unit, retryTimeoutLimit);long startTime = Instant.now().toEpochMilli();long now = startTime;do {try {boolean alreadyGotLock = getLock(key, value, timeout, unit, false);if (alreadyGotLock) {log.info("getLockUntilTimeout(...) => consume time -> {}ms, result -> true", now - startTime);return true;}} catch (Exception e) {log.warn("getLockUntilTimeout(...) => try to get lock failure! e.getMessage -> {}",e.getMessage());}now = Instant.now().toEpochMilli();} while (now < startTime + retryTimeoutLimit);log.info("getLockUntilTimeout(...) => consume time -> {}ms, result -> false", now - startTime);return false;}/*** 获取(分布式)锁* <p>* 注: 获取结果是即时返回的、是非阻塞的。** @see LockOps#getLock(String, String, long, TimeUnit, boolean)*/public static boolean getLock(final String key, final String value,final long timeout, final TimeUnit unit) {return getLock(key, value, timeout, unit, true);}/*** 获取(分布式)锁* <p>* 注: 获取结果是即时返回的、是非阻塞的。** @param key       锁名* @param value     锁名对应的value*                  注: value一般采用全局唯一的值, 如: requestId、uuid等。*                  这样, 释放锁的时候, 可以再次验证value值,*                  保证自己上的锁只能被自己释放, 而不会被别人释放。*                  当然, 如果锁超时时, 会被redis自动删除释放。* @param timeout   锁的(最大)存活时长*                  注: 一般的, 获取锁与释放锁 都是成对使用的, 在锁在达到(最大)存活时长之前,都会被主动释放。*                  但是在某些情况下(如:程序获取锁后,释放锁前,崩了),锁得不到释放, 这时就需要等锁过*                  了(最大)存活时长后,被redis自动删除清理了。这样就能保证redis中不会留下死数据。* @param unit      timeout的单位* @param recordLog 是否记录日志* @return 是否成功*/public static boolean getLock(final String key, final String value,final long timeout, final TimeUnit unit,boolean recordLog) {if (recordLog) {log.info("getLock(...) => key -> {}, value -> {}, timeout -> {}, unit -> {}, recordLog -> {}",key, value, timeout, unit, recordLog);}Boolean result = redisTemplate.execute((RedisConnection connection) ->connection.set(key.getBytes(StandardCharsets.UTF_8),value.getBytes(StandardCharsets.UTF_8),Expiration.seconds(unit.toSeconds(timeout)),RedisStringCommands.SetOption.SET_IF_ABSENT));if (recordLog) {log.info("getLock(...) => result -> {}", result);}if (result == null) {throw new RedisOpsResultIsNullException();}return result;}/*** 释放(分布式)锁* <p>* 注: 此方式能(通过value的唯一性)保证: 自己加的锁, 只能被自己释放。* 注: 锁超时时, 也会被redis自动删除释放。** @param key   锁名* @param value 锁名对应的value* @return 释放锁是否成功* @date 2020/3/15 17:00:45*/public static boolean releaseLock(final String key, final String value) {log.info("releaseLock(...) => key -> {}, lockValue -> {}", key, value);Boolean result = redisTemplate.execute((RedisConnection connection) ->connection.eval(RELEASE_LOCK_LUA.getBytes(),ReturnType.BOOLEAN, 1,key.getBytes(StandardCharsets.UTF_8), value.getBytes(StandardCharsets.UTF_8)));log.info("releaseLock(...) => result -> {}", result);if (result == null) {throw new RedisOpsResultIsNullException();}return result;}/*** 释放锁, 不校验该key对应的value值* <p>* 注: 此方式释放锁,可能导致: 自己加的锁, 结果被别人释放了。* 所以不建议使用此方式释放锁。** @param key 锁名* @date 2020/3/15 18:56:59*/@Deprecatedpublic static void releaseLock(final String key) {KeyOps.delete(key);}}/*** 当使用Pipeline 或 Transaction操作redis时, (不论redis中实际操作是否成功, 这里)结果(都)会返回null。* 此时,如果试着将null转换为基本类型的数据时,会抛出此异常。* <p>* 即: 此工具类中的某些方法, 希望不要使用Pipeline或Transaction操作redis。* <p>* 注: Pipeline 或 Transaction默认是不启用的, 可详见源码:** @date 2020/3/14 21:22:39* @see LettuceConnection#isPipelined()* @see LettuceConnection#isQueueing()* @see JedisConnection#isPipelined()* @see JedisConnection#isQueueing()*/public static class RedisOpsResultIsNullException extends NullPointerException {public RedisOpsResultIsNullException() {super();}public RedisOpsResultIsNullException(String message) {super(message);}}/*** 提供一些基础功能支持** @date 2020/3/16 0:48:14*/public static class Helper {/*** 默认拼接符*/public static final String DEFAULT_SYMBOL = ":";/*** 拼接args** @see Helper#joinBySymbol(String, String...)*/public static String join(String... args) {return Helper.joinBySymbol(DEFAULT_SYMBOL, args);}/*** 使用symbol拼接args** @param symbol 分隔符, 如: 【:】* @param args   要拼接的元素数组, 如: 【a b c】* @return 拼接后的字符串, 如  【a:b:c】* @date 2019/9/8 16:11*/public static String joinBySymbol(String symbol, String... args) {if (symbol == null || symbol.trim().length() == 0) {throw new RuntimeException(" symbol must not be empty!");}if (args == null || args.length == 0) {throw new RuntimeException(" args must not be empty!");}StringBuilder sb = new StringBuilder(16);for (String arg : args) {sb.append(arg).append(symbol);}sb.replace(sb.length() - symbol.length(), sb.length(), "");return sb.toString();}}
}
  1. 测试
package com.applets.manager.core;import cn.hutool.core.bean.BeanUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.applets.manager.api.StartApplication;
import com.applets.manager.core.model.entity.YlCheckCar;
import com.applets.manager.core.service.WxService;
import com.applets.manager.core.util.RedisUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.ObjectUtils;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.test.context.junit4.SpringRunner;import java.math.BigDecimal;
import java.util.Map;/*** @author zr 2024/4/17*/
@RunWith(SpringRunner.class)
@SpringBootTest(classes = StartApplication.class)
@Slf4j
public class RedisTest {@Testpublic void cityInfoTest() {String s = RedisUtil.StringOps.get("cityInfo");log.info(s);}@Testpublic void suppliersTest() {Map<Object, Object> objectObjectMap = RedisUtil.LyHashOps.hGetAll("suppliers:");log.info("get(...) => result -> {} ", JSON.toJSONString(objectObjectMap));}}
  1. 测试结果

image.png
image.png

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/324610.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

uni-segmented-control插件使用

dcloud插件市场 前端/uniapp 1.HBuildX打开目标项目 2.进入dcloud插件市场下载目标插件 3.看到如下提示(已经可以在目标项目中使用插件啦) 4.项目正式使用

风丘方案助力车企升级 解决“国六”标准新难题

一 背景 尾气排放指标是衡量汽车质量和品质的主要指标之一&#xff0c;且汽车的尾气排放必须达到相应的标准才准许出厂&#xff0c;因此&#xff0c;对汽车排放的尾气进行检测是汽车生产过程的重要环节。汽车尾气检测过程是在排放实验室里进行的&#xff0c;这需要模拟汽车实际…

CSS-伪类选择器

结构伪类选择器 作用&#xff1a;根据元素的结构关系查找元素 分类&#xff1a; 选择器说明元素名:first-child查找第一个元素元素名:last-child查找最后一个元素元素名:nth-child(N)查找第N名元素 <!DOCTYPE html> <html lang"en"> <head><me…

FPGA OSD 方案,应用于XBOX游戏机收费等领域

FPGA方案&#xff0c;HDMI IN接收原始HDMI 信号&#xff0c;HDMI OUT输出叠加字符/图片后的HDMI信号 客户应用&#xff1a;XBOX游戏机收费 主要特性&#xff1a; 1.支持多分辨率格式显示 2.支持OSD 叠加多个图层 3.支持字体大小随意配置 4.支持字体格式随意配置 5.零延时&…

python环境下labelImg图片标注工具的使用

labelimg GitHub地址 python环境下labelImg图片标注工具的使用 1. 写在开头2. 如何使用2.1安装2.2 启动2.2.1 先启动后设置标注的目录2.2.2 指定标注的目录和预设置的标签 2.3 设置自动保存和显示类别。2.4 保存文件类型2.5 [快捷键](https://github.com/HumanSignal/labelImg…

MyCat实现分库分表

两个集群 两个库 两个表 搭建数据库服务使用docker启动两个mysql 3506 3507连接MyCat创建两个数据源连接MyCat创建集群 mycat创建逻辑库MyCat创建全局表广播表创建分片表mycat逻辑库MyCat插入数据mycat查看数据物理库3506查看数据物理库3507查看数据 ER表创建ER表mycat插入数据…

Linux-线程概念

1. 线程概念 线程&#xff1a;轻量级进程&#xff0c;在进程内部执行&#xff0c;是OS调度的基本单位&#xff1b;进程内部线程共用同一个地址空间&#xff0c;同一个页表&#xff0c;以及内存中的代码和数据&#xff0c;这些资源对于线程来说都是共享的资源 进程&#xff1a;…

Nginx反向代理配置

一、介绍 Nginx 的反向代理功能在现代网络架构中扮演着至关重要的角色。首先&#xff0c;它充当了客户端与后端服务器之间的中介。当客户端发送请求时&#xff0c;这些请求先到达 Nginx 服务器&#xff0c;Nginx 会根据预先设定的规则和配置&#xff0c;将请求准确地转发到相应…

Goland GC

Goland GC 引用Go 1.3 mark and sweep 标记法Go 1.5 三色标记法屏障机制插入屏障删除写屏障总结 Go 1.8 混合写屏障(hybrid write barrier)机制总结 引用 https://zhuanlan.zhihu.com/p/675127867 Garbage Collection&#xff0c;缩写为GC&#xff0c;一种内存管理回收的机制…

OpenCV | 项目 | 虚拟绘画

OpenCV | 项目 | 虚拟绘画 捕捉摄像头 如果在虚拟机中运行&#xff0c;请确保虚拟机摄像头打开。 #include<opencv2/opencv.hpp>using namespace cv; using namespace std;int main() {VideoCapture cap(0);Mat img;while(1) {cap.read(img);imshow("Image"…

【小红书采集工具】根据搜索关键词批量采集小红书笔记,含笔记正文、笔记链接、发布时间、转评赞藏等

一、背景介绍 1.1 爬取目标 熟悉我的小伙伴都了解&#xff0c;我之前开发过2款软件&#xff1a; 【GUI软件】小红书搜索结果批量采集&#xff0c;支持多个关键词同时抓取&#xff01; 【GUI软件】小红书详情数据批量采集&#xff0c;含笔记内容、转评赞藏等&#xff0c;支持…

视频剪辑达人分享:一键批量置入随机封面,创意无限

在数字化媒体飞速发展的今天&#xff0c;视频内容已经成为我们表达创意、分享故事、传递信息的主要方式之一。而在视频制作过程中&#xff0c;封面作为视频的“脸面”&#xff0c;往往决定了观众是否愿意点击观看。因此&#xff0c;为视频选择合适的封面变得至关重要。 在大量…

在M1芯片安装鸿蒙闪退解决方法

在M1芯片安装鸿蒙闪退解决方法 前言下载鸿蒙系统安装完成后&#xff0c;在M1 Macos14上打开闪退解决办法接下来就是按照提示一步一步安装。 前言 重新安装macos系统后&#xff0c;再次下载鸿蒙开发软件&#xff0c;竟然发现打不开。 下载鸿蒙系统 下载地址&#xff1a;http…

[机器学习-04] Scikit-Learn机器学习工具包进阶指南:集群化与校准功能实战【2024最新】

&#x1f3a9; 欢迎来到技术探索的奇幻世界&#x1f468;‍&#x1f4bb; &#x1f4dc; 个人主页&#xff1a;一伦明悦-CSDN博客 ✍&#x1f3fb; 作者简介&#xff1a; C软件开发、Python机器学习爱好者 &#x1f5e3;️ 互动与支持&#xff1a;&#x1f4ac;评论 &…

【Git】Commit后进行事务回滚

起因 因为一直使用git add .&#xff0c;在学习pytorch中添加了一个较大的数据集后&#xff0c;导致git push失败&#xff0c;而这个大数据集并不是必须要上传到仓库的&#xff0c;但是因为自己在设置.gitignore前已经进行了git comit&#xff0c;所以&#xff0c;需要进行事务…

辅助驾驶激光雷达测试白板的重要性

随着自动驾驶技术的迅猛发展&#xff0c;辅助驾驶系统&#xff08;ADAS&#xff09;已成为现代汽车安全的重要组成部分。在这一系统中&#xff0c;激光雷达&#xff08;LiDAR&#xff09;扮演着至关重要的角色&#xff0c;它通过发射激光脉冲并接收反射回来的信号来检测车辆周围…

TCP的延时应答和捎带应答详解

一、延时应答 延时应答是指TCP接收方在接收到数据包后&#xff0c;并不立即发送确认&#xff08;ACK&#xff09;消息&#xff0c;而是等待一段时间&#xff0c;以期望在该时间段内收到更多的数据包&#xff0c;从而实现合并多个ACK消息为一个&#xff0c;减少网络中的确认消息…

企业网站从传统服务器迁移到弹性云有什么优势呢?

现代企业对于网站和应用程序的可用性和性能要求越来越高&#xff0c;传统基础设施可能无法满足这些需求。弹性云作为一种新兴的云计算服务模式&#xff0c;对于企业网站的运行和管理带来了许多优势。下面是企业网站从传统服务器迁移到弹性云的五大优势&#xff1a; 灵活弹性&a…

C语言(指针)4

Hi~&#xff01;这里是奋斗的小羊&#xff0c;很荣幸各位能阅读我的文章&#xff0c;诚请评论指点&#xff0c;关注收藏&#xff0c;欢迎欢迎~~ &#x1f4a5;个人主页&#xff1a;小羊在奋斗 &#x1f4a5;所属专栏&#xff1a;C语言 本系列文章为个人学习笔记&#x…

vue+sortablejs来实现列表拖拽——sortablejs的使用

sortablejs官网:https://sortablejs.com/ 最近在看form-builder组件&#xff0c;发现里面有用到sortablejs插件&#xff0c;用于实现拖拽效果。 但是这个官网中的配置&#xff0c;实在是看不懂&#xff0c;太简单又太复杂&#xff0c;不实用。 下面记录一下我的使用&#xff…