当前位置:   article > 正文

Redis - 一篇走心的 RedisUtil 工具类_redisutils.lock

redisutils.lock

声明此工具类原型参考https://github.com/whvcse/RedisUtil并作了进一步进行整理、修正、补充、拓展。

此 RedisUtil 工具类的功能

在这里插入图片描述

 直接使用此 RedisUtil 工具类的所需条件

  1. 项目基于SpringBoot
  2. pom.xml中需要引入依赖spring-boot-starter-data-redis
  3. pom.xml中需要引入依赖lombok
  4. pom.xml中需要引入依赖fastjson

    注:其中第3、4点不是必须的,在此工具类中,第3、4点只是为了方便记录日志而已。

    注:当然,如果是非SpringBoot的项目,那么也可以通过xml配置等方式,来使用此工具类,自行摸索吧。

编写测试此 RedisUtil 工具类时,项目结构说明(可跳过)

提示:如果只是使用此工具类的话,可以不看这个,直接将下面的工具类复制到SpringBoot项目中即可;如果想要进一步了解此工具类、了解StringRedisTemplate的api的话,可以将本人放在github上的源码(跳转链接见文末)拽下来,自行测试。

在这里插入图片描述

 

 RedisUtil 工具类

  1. import com.alibaba.fastjson.JSON;
  2. import lombok.extern.slf4j.Slf4j;
  3. import org.springframework.beans.BeansException;
  4. import org.springframework.context.ApplicationContext;
  5. import org.springframework.context.ApplicationContextAware;
  6. import org.springframework.data.redis.RedisSystemException;
  7. import org.springframework.data.redis.connection.DataType;
  8. import org.springframework.data.redis.connection.RedisConnection;
  9. import org.springframework.data.redis.connection.RedisStringCommands;
  10. import org.springframework.data.redis.connection.ReturnType;
  11. import org.springframework.data.redis.connection.jedis.JedisConnection;
  12. import org.springframework.data.redis.connection.lettuce.LettuceConnection;
  13. import org.springframework.data.redis.core.Cursor;
  14. import org.springframework.data.redis.core.RedisOperations;
  15. import org.springframework.data.redis.core.ScanOptions;
  16. import org.springframework.data.redis.core.StringRedisTemplate;
  17. import org.springframework.data.redis.core.ZSetOperations.TypedTuple;
  18. import org.springframework.data.redis.core.types.Expiration;
  19. import org.springframework.stereotype.Component;
  20. import java.nio.charset.StandardCharsets;
  21. import java.time.Instant;
  22. import java.util.*;
  23. import java.util.Map.Entry;
  24. import java.util.concurrent.TimeUnit;
  25. /**
  26. * Redis工具类
  27. *
  28. * 声明: 此工具只简单包装了redisTemplate的大部分常用的api, 没有包装redisTemplate所有的api。
  29. * 如果对此工具类中的功能不太满意, 或对StringRedisTemplate提供的api不太满意,
  30. * 那么可自行实现相应的{@link StringRedisTemplate}类中的对应execute方法, 以达
  31. * 到自己想要的效果; 至于如何实现,则可参考源码或{@link RedisUtil.LockOps}中的方法。
  32. *
  33. * 注: 此工具类依赖spring-boot-starter-data-redis类库、以及可选的lombok、fastjson
  34. * 注: 更多javadoc细节,可详见{@link RedisOperations}
  35. *
  36. * 统一说明一: 方法中的key、 value都不能为null。
  37. * 统一说明二: 不能跨数据类型进行操作, 否者会操作失败/操作报错。
  38. * 如: 向一个String类型的做Hash操作,会失败/报错......等等
  39. *
  40. * @author JustryDeng
  41. * @date 2020/3/7 16:50:05
  42. */
  43. @Slf4j
  44. @Component
  45. @SuppressWarnings("unused")
  46. public class RedisUtil implements ApplicationContextAware {
  47. /** 使用StringRedisTemplate(,其是RedisTemplate的定制化升级) */
  48. private static StringRedisTemplate redisTemplate;
  49. @Override
  50. public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
  51. RedisUtil.redisTemplate = applicationContext.getBean(StringRedisTemplate.class);
  52. }
  53. /**
  54. * key相关操作
  55. *
  56. * @author JustryDeng
  57. * @date 2020/3/7 16:54:25
  58. */
  59. public static class KeyOps {
  60. /**
  61. * 根据key, 删除redis中的对应key-value
  62. *
  63. * 注: 若删除失败, 则返回false。
  64. *
  65. * 若redis中,不存在该key, 那么返回的也是false。
  66. * 所以,不能因为返回了false,就认为redis中一定还存
  67. * 在该key对应的key-value。
  68. *
  69. * @param key
  70. * 要删除的key
  71. * @return 删除是否成功
  72. * @date 2020/3/7 17:15:02
  73. */
  74. public static boolean delete(String key) {
  75. log.info("delete(...) => key -> {}", key);
  76. // 返回值只可能为true/false, 不可能为null
  77. Boolean result = redisTemplate.delete(key);
  78. log.info("delete(...) => result -> {}", result);
  79. if (result == null) {
  80. throw new RedisOpsResultIsNullException();
  81. }
  82. return result;
  83. }
  84. /**
  85. * 根据keys, 批量删除key-value
  86. *
  87. * 注: 若redis中,不存在对应的key, 那么计数不会加1, 即:
  88. * redis中存在的key-value里,有名为a1、a2的key,
  89. * 删除时,传的集合是a1、a2、a3,那么返回结果为2。
  90. *
  91. * @param keys
  92. * 要删除的key集合
  93. * @return 删除了的key-value个数
  94. * @date 2020/3/7 17:48:04
  95. */
  96. public static long delete(Collection<String> keys) {
  97. log.info("delete(...) => keys -> {}", keys);
  98. Long count = redisTemplate.delete(keys);
  99. log.info("delete(...) => count -> {}", count);
  100. if (count == null) {
  101. throw new RedisOpsResultIsNullException();
  102. }
  103. return count;
  104. }
  105. /**
  106. * 将key对应的value值进行序列化,并返回序列化后的value值。
  107. *
  108. * 注: 若不存在对应的key, 则返回null。
  109. * 注: dump时,并不会删除redis中的对应key-value。
  110. * 注: dump功能与restore相反。
  111. *
  112. * @param key
  113. * 要序列化的value的key
  114. * @return 序列化后的value值
  115. * @date 2020/3/8 11:34:13
  116. */
  117. public static byte[] dump(String key) {
  118. log.info("dump(...) =>key -> {}", key);
  119. byte[] result = redisTemplate.dump(key);
  120. log.info("dump(...) => result -> {}", result);
  121. return result;
  122. }
  123. /**
  124. * 将给定的value值,反序列化到redis中, 形成新的key-value。
  125. *
  126. * @param key
  127. * value对应的key
  128. * @param value
  129. * 要反序列的value值。
  130. * 注: 这个值可以由{@link this#dump(String)}获得
  131. * @param timeToLive
  132. * 反序列化后的key-value的存活时长
  133. * @param unit
  134. * timeToLive的单位
  135. *
  136. * @throws RedisSystemException
  137. * 如果redis中已存在同样的key时,抛出此异常
  138. * @date 2020/3/8 11:36:45
  139. */
  140. public static void restore(String key, byte[] value, long timeToLive, TimeUnit unit) {
  141. restore(key, value, timeToLive, unit, false);
  142. }
  143. /**
  144. * 将给定的value值,反序列化到redis中, 形成新的key-value。
  145. *
  146. * @param key
  147. * value对应的key
  148. * @param value
  149. * 要反序列的value值。
  150. * 注: 这个值可以由{@link this#dump(String)}获得
  151. * @param timeout
  152. * 反序列化后的key-value的存活时长
  153. * @param unit
  154. * timeout的单位
  155. * @param replace
  156. * 若redis中已经存在了相同的key, 是否替代原来的key-value
  157. *
  158. * @throws RedisSystemException
  159. * 如果redis中已存在同样的key, 且replace为false时,抛出此异常
  160. * @date 2020/3/8 11:36:45
  161. */
  162. public static void restore(String key, byte[] value, long timeout, TimeUnit unit, boolean replace) {
  163. log.info("restore(...) => key -> {}, value -> {}, timeout -> {}, unit -> {}, replace -> {}",
  164. key, value, timeout, unit, replace);
  165. redisTemplate.restore(key, value, timeout, unit, replace);
  166. }
  167. /**
  168. * redis中是否存在,指定key的key-value
  169. *
  170. * @param key
  171. * 指定的key
  172. * @return 是否存在对应的key-value
  173. * @date 2020/3/8 12:16:46
  174. */
  175. public static boolean hasKey(String key) {
  176. log.info("hasKey(...) => key -> {}", key);
  177. Boolean result = redisTemplate.hasKey(key);
  178. log.info("hasKey(...) => result -> {}", result);
  179. if (result == null) {
  180. throw new RedisOpsResultIsNullException();
  181. }
  182. return result;
  183. }
  184. /**
  185. * 给指定的key对应的key-value设置: 多久过时
  186. *
  187. * 注:过时后,redis会自动删除对应的key-value。
  188. * 注:若key不存在,那么也会返回false。
  189. *
  190. * @param key
  191. * 指定的key
  192. * @param timeout
  193. * 过时时间
  194. * @param unit
  195. * timeout的单位
  196. * @return 操作是否成功
  197. * @date 2020/3/8 12:18:58
  198. */
  199. public static boolean expire(String key, long timeout, TimeUnit unit) {
  200. log.info("expire(...) => key -> {}, timeout -> {}, unit -> {}", key, timeout, unit);
  201. Boolean result = redisTemplate.expire(key, timeout, unit);
  202. log.info("expire(...) => result is -> {}", result);
  203. if (result == null) {
  204. throw new RedisOpsResultIsNullException();
  205. }
  206. return result;
  207. }
  208. /**
  209. * 给指定的key对应的key-value设置: 什么时候过时
  210. *
  211. * 注:过时后,redis会自动删除对应的key-value。
  212. * 注:若key不存在,那么也会返回false。
  213. *
  214. * @param key
  215. * 指定的key
  216. * @param date
  217. * 啥时候过时
  218. *
  219. * @return 操作是否成功
  220. * @date 2020/3/8 12:19:29
  221. */
  222. public static boolean expireAt(String key, Date date) {
  223. log.info("expireAt(...) => key -> {}, date -> {}", key, date);
  224. Boolean result = redisTemplate.expireAt(key, date);
  225. log.info("expireAt(...) => result is -> {}", result);
  226. if (result == null) {
  227. throw new RedisOpsResultIsNullException();
  228. }
  229. return result;
  230. }
  231. /**
  232. * 找到所有匹配pattern的key,并返回该key的结合.
  233. *
  234. * 提示:若redis中键值对较多,此方法耗时相对较长,慎用!慎用!慎用!
  235. *
  236. * @param pattern
  237. * 匹配模板。
  238. * 注: 常用的通配符有:
  239. * ? 有且只有一个;
  240. * * >=0哥;
  241. *
  242. * @return 匹配pattern的key的集合。 可能为null。
  243. * @date 2020/3/8 12:38:38
  244. */
  245. public static Set<String> keys(String pattern) {
  246. log.info("keys(...) => pattern -> {}", pattern);
  247. Set<String> keys = redisTemplate.keys(pattern);
  248. log.info("keys(...) => keys -> {}", keys);
  249. return keys;
  250. }
  251. /**
  252. * 将当前数据库中的key对应的key-value,移动到对应位置的数据库中。
  253. *
  254. * 注:单机版的redis,默认将存储分为16个db, index为0 到 15。
  255. * 注:同一个db下,key唯一; 但是在不同db中,key可以相同。
  256. * 注:若目标db下,已存在相同的key, 那么move会失败,返回false。
  257. *
  258. * @param key
  259. * 定位要移动的key-value的key
  260. * @param dbIndex
  261. * 要移动到哪个db
  262. * @return 移动是否成功。
  263. * 注: 若目标db下,已存在相同的key, 那么move会失败,返回false。
  264. * @date 2020/3/8 13:01:00
  265. */
  266. public static boolean move(String key, int dbIndex) {
  267. log.info("move(...) => key -> {}, dbIndex -> {}", key, dbIndex);
  268. Boolean result = redisTemplate.move(key, dbIndex);
  269. log.info("move(...) =>result -> {}", result);
  270. if (result == null) {
  271. throw new RedisOpsResultIsNullException();
  272. }
  273. return result;
  274. }
  275. /**
  276. * 移除key对应的key-value的过期时间, 使该key-value一直存在
  277. *
  278. * 注: 若key对应的key-value,本身就是一直存在(无过期时间的), 那么persist方法会返回false;
  279. * 若没有key对应的key-value存在,本那么persist方法会返回false;
  280. *
  281. * @param key
  282. * 定位key-value的key
  283. * @return 操作是否成功
  284. * @date 2020/3/8 13:10:02
  285. */
  286. public static boolean persist(String key) {
  287. log.info("persist(...) => key -> {}", key);
  288. Boolean result = redisTemplate.persist(key);
  289. log.info("persist(...) => result -> {}", result);
  290. if (result == null) {
  291. throw new RedisOpsResultIsNullException();
  292. }
  293. return result;
  294. }
  295. /**
  296. * 获取key对应的key-value的过期时间
  297. *
  298. * 注: 若key-value永不过期, 那么返回的为-1。
  299. * 注: 若不存在key对应的key-value, 那么返回的为-2
  300. * 注:若存在零碎时间不足1 SECONDS,则(大体上)四舍五入到SECONDS级别。
  301. *
  302. * @param key
  303. * 定位key-value的key
  304. * @return 过期时间(单位s)
  305. * @date 2020/3/8 13:17:35
  306. */
  307. public static long getExpire(String key) {
  308. Long result = getExpire(key, TimeUnit.SECONDS);
  309. return result;
  310. }
  311. /**
  312. * 获取key对应的key-value的过期时间
  313. *
  314. * 注: 若key-value永不过期, 那么返回的为-1。
  315. * 注: 若不存在key对应的key-value, 那么返回的为-2
  316. * 注:若存在零碎时间不足1 unit,则(大体上)四舍五入到unit别。
  317. *
  318. * @param key
  319. * 定位key-value的key
  320. * @return 过期时间(单位unit)
  321. * @date 2020/3/8 13:17:35
  322. */
  323. public static long getExpire(String key, TimeUnit unit) {
  324. log.info("getExpire(...) =>key -> {}, unit is -> {}", key, unit);
  325. Long result = redisTemplate.getExpire(key, unit);
  326. log.info("getExpire(...) => result -> {}", result);
  327. if (result == null) {
  328. throw new RedisOpsResultIsNullException();
  329. }
  330. return result;
  331. }
  332. /**
  333. * 从redis的所有key中,随机获取一个key
  334. *
  335. * 注: 若redis中不存在任何key-value, 那么这里返回null
  336. *
  337. * @return 随机获取到的一个key
  338. * @date 2020/3/8 14:11:43
  339. */
  340. public static String randomKey() {
  341. String result = redisTemplate.randomKey();
  342. log.info("randomKey(...) => result is -> {}", result);
  343. return result;
  344. }
  345. /**
  346. * 重命名对应的oldKey为新的newKey
  347. *
  348. * 注: 若oldKey不存在, 则会抛出异常.
  349. * 注: 若redis中已存在与newKey一样的key,
  350. * 那么原key-value会被丢弃,
  351. * 只留下新的key,以及原来的value
  352. * 示例说明: 假设redis中已有 (keyAlpha, valueAlpha) 和 (keyBeta, valueBeat),
  353. * 在使用rename(keyAlpha, keyBeta)替换后, redis中只会剩下(keyBeta, valueAlpha)
  354. *
  355. * @param oldKey
  356. * 旧的key
  357. * @param newKey
  358. * 新的key
  359. * @throws RedisSystemException
  360. * 若oldKey不存在时, 抛出此异常
  361. * @date 2020/3/8 14:14:17
  362. */
  363. public static void rename(String oldKey, String newKey) {
  364. log.info("rename(...) => oldKey -> {}, newKey -> {}", oldKey, newKey);
  365. redisTemplate.rename(oldKey, newKey);
  366. }
  367. /**
  368. * 当redis中不存在newKey时, 重命名对应的oldKey为新的newKey。
  369. * 否者不进行重命名操作。
  370. *
  371. * 注: 若oldKey不存在, 则会抛出异常.
  372. *
  373. * @param oldKey
  374. * 旧的key
  375. * @param newKey
  376. * 新的key
  377. * @throws RedisSystemException
  378. * 若oldKey不存在时, 抛出此异常
  379. * @date 2020/3/8 14:14:17
  380. */
  381. public static boolean renameIfAbsent(String oldKey, String newKey) {
  382. log.info("renameIfAbsent(...) => oldKey -> {}, newKey -> {}", oldKey, newKey);
  383. Boolean result = redisTemplate.renameIfAbsent(oldKey, newKey);
  384. log.info("renameIfAbsent(...) => result -> {}", result);
  385. if (result == null) {
  386. throw new RedisOpsResultIsNullException();
  387. }
  388. return result;
  389. }
  390. /**
  391. * 获取key对应的value的数据类型
  392. *
  393. * 注: 若redis中不存在该key对应的key-value, 那么这里返回NONE。
  394. *
  395. * @param key
  396. * 用于定位的key
  397. * @return key对应的value的数据类型
  398. * @date 2020/3/8 14:40:16
  399. */
  400. public static DataType type(String key) {
  401. log.info("type(...) => key -> {}", key);
  402. DataType result = redisTemplate.type(key);
  403. log.info("type(...) => result -> {}", result);
  404. return result;
  405. }
  406. }
  407. /**
  408. * string相关操作
  409. *
  410. * 提示: redis中String的数据结构可参考resources/data-structure/String(字符串)的数据结构(示例一).png
  411. * redis中String的数据结构可参考resources/data-structure/String(字符串)的数据结构(示例二).png
  412. *
  413. * @author JustryDeng
  414. * @date 2020/3/7 16:54:25
  415. */
  416. public static class StringOps {
  417. /**
  418. * 设置key-value
  419. *
  420. * 注: 若已存在相同的key, 那么原来的key-value会被丢弃。
  421. *
  422. * @param key
  423. * key
  424. * @param value
  425. * key对应的value
  426. * @date 2020/3/8 15:40:59
  427. */
  428. public static void set(String key, String value) {
  429. log.info("set(...) => key -> {}, value -> {}", key, value);
  430. redisTemplate.opsForValue().set(key, value);
  431. }
  432. /**
  433. * 处理redis中key对应的value值, 将第offset位的值, 设置为1或0。
  434. *
  435. * 说明: 在redis中,存储的字符串都是以二级制的进行存在的; 如存储的key-value里,值为abc,实际上,
  436. * 在redis里面存储的是011000010110001001100011,前8为对应a,中间8为对应b,后面8位对应c。
  437. * 示例:这里如果setBit(key, 6, true)的话,就是将索引位置6的那个数,设置值为1,值就变成
  438. * 了011000110110001001100011
  439. * 追注:offset即index,从0开始。
  440. *
  441. * 注: 参数value为true, 则设置为1;参数value为false, 则设置为0。
  442. *
  443. * 注: 若redis中不存在对应的key,那么会自动创建新的。
  444. * 注: offset可以超过value在二进制下的索引长度。
  445. *
  446. * @param key
  447. * 定位value的key
  448. * @param offset
  449. * 要改变的bit的索引
  450. * @param value
  451. * 改为1或0, true - 改为1, false - 改为0
  452. *
  453. * @return set是否成功
  454. * @date 2020/3/8 16:30:37
  455. */
  456. public static boolean setBit(String key, long offset, boolean value) {
  457. log.info("setBit(...) => key -> {}, offset -> {}, value -> {}", key, offset, value);
  458. Boolean result = redisTemplate.opsForValue().setBit(key, offset, value);
  459. log.info("setBit(...) => result -> {}", result);
  460. if (result == null) {
  461. throw new RedisOpsResultIsNullException();
  462. }
  463. return result;
  464. }
  465. /**
  466. * 设置key-value
  467. *
  468. * 注: 若已存在相同的key, 那么原来的key-value会被丢弃
  469. *
  470. * @param key
  471. * key
  472. * @param value
  473. * key对应的value
  474. * @param timeout
  475. * 过时时长
  476. * @param unit
  477. * timeout的单位
  478. * @date 2020/3/8 15:40:59
  479. */
  480. public static void setEx(String key, String value, long timeout, TimeUnit unit) {
  481. log.info("setEx(...) => key -> {}, value -> {}, timeout -> {}, unit -> {}",
  482. key, value, timeout, unit);
  483. redisTemplate.opsForValue().set(key, value, timeout, unit);
  484. }
  485. /**
  486. * 若不存在key时, 向redis中添加key-value, 返回成功/失败。
  487. * 若存在,则不作任何操作, 返回false。
  488. *
  489. * @param key
  490. * key
  491. * @param value
  492. * key对应的value
  493. *
  494. * @return set是否成功
  495. * @date 2020/3/8 16:51:36
  496. */
  497. public static boolean setIfAbsent(String key, String value) {
  498. log.info("setIfAbsent(...) => key -> {}, value -> {}", key, value);
  499. Boolean result = redisTemplate.opsForValue().setIfAbsent(key, value);
  500. log.info("setIfAbsent(...) => result -> {}", result);
  501. if (result == null) {
  502. throw new RedisOpsResultIsNullException();
  503. }
  504. return result;
  505. }
  506. /**
  507. * 若不存在key时, 向redis中添加一个(具有超时时长的)key-value, 返回成功/失败。
  508. * 若存在,则不作任何操作, 返回false。
  509. *
  510. * @param key
  511. * key
  512. * @param value
  513. * key对应的value
  514. * @param timeout
  515. * 超时时长
  516. * @param unit
  517. * timeout的单位
  518. *
  519. * @return set是否成功
  520. * @date 2020/3/8 16:51:36
  521. */
  522. public static boolean setIfAbsent(String key, String value, long timeout, TimeUnit unit) {
  523. log.info("setIfAbsent(...) => key -> {}, value -> {}, key -> {}, value -> {}", key, value, timeout, unit);
  524. Boolean result = redisTemplate.opsForValue().setIfAbsent(key, value, timeout, unit);
  525. log.info("setIfAbsent(...) => result -> {}", result);
  526. if (result == null) {
  527. throw new RedisOpsResultIsNullException();
  528. }
  529. return result;
  530. }
  531. /**
  532. * 从(redis中key对应的)value的offset位置起(包含该位置),用replaceValue替换对应长度的值。
  533. *
  534. * 举例说明:
  535. * 1.假设redis中存在key-value ("ds", "0123456789"); 调
  536. * 用setRange("ds", "abcdefghijk", 3)后, redis中该value值就变为了[012abcdefghijk]
  537. *
  538. * 2.假设redis中存在key-value ("jd", "0123456789");调
  539. * 用setRange("jd", "xyz", 3)后, redis中该value值就变为了[012xyz6789]
  540. *
  541. * 3.假设redis中存在key-value ("ey", "0123456789");调
  542. * 用setRange("ey", "qwer", 15)后, redis中该value值就变为了[0123456789 qwer]
  543. * 注:case3比较特殊,offset超过了原value的长度了, 中间就会有一些空格来填充,但是如果在程序
  544. * 中直接输出的话,中间那部分空格可能会出现乱码。
  545. *
  546. * @param key
  547. * 定位key-value的key
  548. * @param replaceValue
  549. * 要替换的值
  550. * @param offset
  551. * 起始位置
  552. * @date 2020/3/8 17:04:31
  553. */
  554. public static void setRange(String key, String replaceValue, long offset) {
  555. log.info("setRange(...) => key -> {}, replaceValue -> {}, offset -> {}", key, replaceValue, offset);
  556. redisTemplate.opsForValue().set(key, replaceValue, offset);
  557. }
  558. /**
  559. * 获取到key对应的value的长度。
  560. *
  561. * 注: 长度等于{@link String#length}。
  562. * 注: 若redis中不存在对应的key-value, 则返回值为0.
  563. *
  564. * @param key
  565. * 定位value的key
  566. * @return value的长度
  567. * @date 2020/3/8 17:14:30
  568. */
  569. public static long size(String key) {
  570. log.info("size(...) => key -> {}", key);
  571. Long result = redisTemplate.opsForValue().size(key);
  572. log.info("size(...) => result -> {}", result);
  573. if (result == null) {
  574. throw new RedisOpsResultIsNullException();
  575. }
  576. return result;
  577. }
  578. /**
  579. * 批量设置 key-value
  580. *
  581. * 注: 若存在相同的key, 则原来的key-value会被丢弃。
  582. *
  583. * @param maps
  584. * key-value 集
  585. * @date 2020/3/8 17:21:19
  586. */
  587. public static void multiSet(Map<String, String> maps) {
  588. log.info("multiSet(...) => maps -> {}", maps);
  589. redisTemplate.opsForValue().multiSet(maps);
  590. }
  591. /**
  592. * 当redis中,不存在任何一个keys时, 才批量设置 key-value, 并返回成功/失败.
  593. * 否者,不进行任何操作, 并返回false。
  594. *
  595. * 即: 假设调用此方法时传入的参数map是这样的: {k1=v1, k2=v2, k3=v3}
  596. * 那么redis中, k1、k2、k3都不存在时,才会批量设置key-value;
  597. * 否则不会设置任何key-value。
  598. *
  599. * 注: 若存在相同的key, 则原来的key-value会被丢弃。
  600. *
  601. * 注:
  602. *
  603. * @param maps
  604. * key-value 集
  605. *
  606. * @return 操作是否成功
  607. * @date 2020/3/8 17:21:19
  608. */
  609. public static boolean multiSetIfAbsent(Map<String, String> maps) {
  610. log.info("multiSetIfAbsent(...) => maps -> {}", maps);
  611. Boolean result = redisTemplate.opsForValue().multiSetIfAbsent(maps);
  612. log.info("multiSetIfAbsent(...) => result -> {}", result);
  613. if (result == null) {
  614. throw new RedisOpsResultIsNullException();
  615. }
  616. return result;
  617. }
  618. /**
  619. * 增/减 整数
  620. *
  621. * 注: 负数则为减。
  622. * 注: 若key对应的value值不支持增/减操作(即: value不是数字), 那么会
  623. * 抛出org.springframework.data.redis.RedisSystemException
  624. *
  625. * @param key
  626. * 用于定位value的key
  627. * @param increment
  628. * 增加多少
  629. * @return 增加后的总值。
  630. * @throws RedisSystemException key对应的value值不支持增/减操作时
  631. * @date 2020/3/8 17:45:51
  632. */
  633. public static long incrBy(String key, long increment) {
  634. log.info("incrBy(...) => key -> {}, increment -> {}", key, increment);
  635. Long result = redisTemplate.opsForValue().increment(key, increment);
  636. log.info("incrBy(...) => result -> {}", result);
  637. if (result == null) {
  638. throw new RedisOpsResultIsNullException();
  639. }
  640. return result;
  641. }
  642. /**
  643. * 增/减 浮点数
  644. *
  645. * 注: 慎用浮点数,会有精度问题。
  646. * 如: 先 RedisUtil.StringOps.set("ds", "123");
  647. * 然后再RedisUtil.StringOps.incrByFloat("ds", 100.6);
  648. * 就会看到精度问题。
  649. * 注: 负数则为减。
  650. * 注: 若key对应的value值不支持增/减操作(即: value不是数字), 那么会
  651. * 抛出org.springframework.data.redis.RedisSystemException
  652. *
  653. * @param key
  654. * 用于定位value的key
  655. * @param increment
  656. * 增加多少
  657. * @return 增加后的总值。
  658. * @throws RedisSystemException key对应的value值不支持增/减操作时
  659. * @date 2020/3/8 17:45:51
  660. */
  661. public static double incrByFloat(String key, double increment) {
  662. log.info("incrByFloat(...) => key -> {}, increment -> {}", key, increment);
  663. Double result = redisTemplate.opsForValue().increment(key, increment);
  664. log.info("incrByFloat(...) => result -> {}", result);
  665. if (result == null) {
  666. throw new RedisOpsResultIsNullException();
  667. }
  668. return result;
  669. }
  670. /**
  671. * 追加值到末尾
  672. *
  673. * 注: 当redis中原本不存在key时,那么(从效果上来看)此方法就等价于{@link this#set(String, String)}
  674. *
  675. * @param key
  676. * 定位value的key
  677. * @param value
  678. * 要追加的value值
  679. * @return 追加后, 整个value的长度
  680. * @date 2020/3/8 17:59:21
  681. */
  682. public static int append(String key, String value) {
  683. log.info("append(...) => key -> {}, value -> {}", key, value);
  684. Integer result = redisTemplate.opsForValue().append(key, value);
  685. log.info("append(...) => result -> {}", result);
  686. if (result == null) {
  687. throw new RedisOpsResultIsNullException();
  688. }
  689. return result;
  690. }
  691. /**
  692. * 根据key,获取到对应的value值
  693. *
  694. * @param key
  695. * key-value对应的key
  696. * @return 该key对应的值。
  697. * 注: 若key不存在, 则返回null。
  698. *
  699. * @date 2020/3/8 16:27:41
  700. */
  701. public static String get(String key) {
  702. log.info("get(...) => key -> {}", key);
  703. String result = redisTemplate.opsForValue().get(key);
  704. log.info("get(...) => result -> {} ", result);
  705. return result;
  706. }
  707. /**
  708. * 对(key对应的)value进行截取, 截取范围为[start, end]
  709. *
  710. * 注: 若[start, end]的范围不在value的范围中,那么返回的是空字符串 ""
  711. * 注: 若value只有一部分在[start, end]的范围中,那么返回的是value对应部分的内容(即:不足的地方,并不会以空来填充)
  712. *
  713. * @param key
  714. * 定位value的key
  715. * @param start
  716. * 起始位置 (从0开始)
  717. * @param end
  718. * 结尾位置 (从0开始)
  719. * @return 截取后的字符串
  720. * @date 2020/3/8 18:08:45
  721. */
  722. public static String getRange(String key, long start, long end) {
  723. log.info("getRange(...) => kry -> {}", key);
  724. String result = redisTemplate.opsForValue().get(key, start, end);
  725. log.info("getRange(...) => result -> {} ", result);
  726. return result;
  727. }
  728. /**
  729. * 给指定key设置新的value, 并返回旧的value
  730. *
  731. * 注: 若redis中不存在key, 那么此操作仍然可以成功, 不过返回的旧值是null
  732. *
  733. * @param key
  734. * 定位value的key
  735. * @param newValue
  736. * 要为该key设置的新的value值
  737. * @return 旧的value值
  738. * @date 2020/3/8 18:14:24
  739. */
  740. public static String getAndSet(String key, String newValue) {
  741. log.info("getAndSet(...) => key -> {}, value -> {}", key, newValue);
  742. String oldValue = redisTemplate.opsForValue().getAndSet(key, newValue);
  743. log.info("getAndSet(...) => oldValue -> {}", oldValue);
  744. return oldValue;
  745. }
  746. /**
  747. * 获取(key对应的)value在二进制下,offset位置的bit值。
  748. *
  749. * 注: 当offset的值在(二进制下的value的)索引范围外时, 返回的也是false。
  750. *
  751. * 示例:
  752. * RedisUtil.StringOps.set("akey", "a");
  753. * 字符串a, 转换为二进制为01100001
  754. * 那么getBit("akey", 6)获取到的结果为false。
  755. *
  756. * @param key
  757. * 定位value的key
  758. * @param offset
  759. * 定位bit的索引
  760. * @return offset位置对应的bit的值(true - 1, false - 0)
  761. * @date 2020/3/8 18:21:10
  762. */
  763. public static boolean getBit(String key, long offset) {
  764. log.info("getBit(...) => key -> {}, offset -> {}", key, offset);
  765. Boolean result = redisTemplate.opsForValue().getBit(key, offset);
  766. log.info("getBit(...) => result -> {}", result);
  767. if (result == null) {
  768. throw new RedisOpsResultIsNullException();
  769. }
  770. return result;
  771. }
  772. /**
  773. * 批量获取value值
  774. *
  775. * 注: 若redis中,对应的key不存在,那么该key对应的返回的value值为null
  776. *
  777. * @param keys
  778. * key集
  779. * @return value值集合
  780. * @date 2020/3/8 18:26:33
  781. */
  782. public static List<String> multiGet(Collection<String> keys) {
  783. log.info("multiGet(...) => keys -> {}", keys);
  784. List<String> result = redisTemplate.opsForValue().multiGet(keys);
  785. log.info("multiGet(...) => result -> {}", result);
  786. return result;
  787. }
  788. }
  789. /**
  790. * hash相关操作
  791. *
  792. * 提示: 简单的,可以将redis中hash的数据结构看作是 Map<String, Map<HK, HV>>
  793. * 提示: redis中String的数据结构可参考resources/data-structure/Hash(散列)的数据结构(示例一).png
  794. * redis中String的数据结构可参考resources/data-structure/Hash(散列)的数据结构(示例二).png
  795. *
  796. * @author JustryDeng
  797. * @date 2020/3/8 23:39:26
  798. */
  799. public static class HashOps {
  800. /**
  801. * 向key对应的hash中,增加一个键值对entryKey-entryValue
  802. *
  803. * 注: 同一个hash里面,若已存在相同的entryKey, 那么此操作将丢弃原来的entryKey-entryValue,
  804. * 而使用新的entryKey-entryValue。
  805. *
  806. *
  807. * @param key
  808. * 定位hash的key
  809. * @param entryKey
  810. * 要向hash中增加的键值对里的 键
  811. * @param entryValue
  812. * 要向hash中增加的键值对里的 值
  813. * @date 2020/3/8 23:49:52
  814. */
  815. public static void hPut(String key, String entryKey, String entryValue) {
  816. log.info("hPut(...) => key -> {}, entryKey -> {}, entryValue -> {}", key, entryKey, entryValue);
  817. redisTemplate.opsForHash().put(key, entryKey, entryValue);
  818. }
  819. /**
  820. * 向key对应的hash中,增加maps(即: 批量增加entry集)
  821. *
  822. * 注: 同一个hash里面,若已存在相同的entryKey, 那么此操作将丢弃原来的entryKey-entryValue,
  823. * 而使用新的entryKey-entryValue
  824. *
  825. * @param key
  826. * 定位hash的key
  827. * @param maps
  828. * 要向hash中增加的键值对集
  829. * @date 2020/3/8 23:49:52
  830. */
  831. public static void hPutAll(String key, Map<String, String> maps) {
  832. log.info("hPutAll(...) => key -> {}, maps -> {}", key, maps);
  833. redisTemplate.opsForHash().putAll(key, maps);
  834. }
  835. /**
  836. * 当key对应的hash中,不存在entryKey时,才(向key对应的hash中,)增加entryKey-entryValue
  837. * 否者,不进行任何操作
  838. *
  839. * @param key
  840. * 定位hash的key
  841. * @param entryKey
  842. * 要向hash中增加的键值对里的 键
  843. * @param entryValue
  844. * 要向hash中增加的键值对里的 值
  845. *
  846. * @return 操作是否成功。
  847. * @date 2020/3/8 23:49:52
  848. */
  849. public static boolean hPutIfAbsent(String key, String entryKey, String entryValue) {
  850. log.info("hPutIfAbsent(...) => key -> {}, entryKey -> {}, entryValue -> {}",
  851. key, entryKey, entryValue);
  852. Boolean result = redisTemplate.opsForHash().putIfAbsent(key, entryKey, entryValue);
  853. log.info("hPutIfAbsent(...) => result -> {}", result);
  854. if (result == null) {
  855. throw new RedisOpsResultIsNullException();
  856. }
  857. return result;
  858. }
  859. /**
  860. * 获取到key对应的hash里面的对应字段的值
  861. *
  862. * 注: 若redis中不存在对应的key, 则返回null。
  863. * 若key对应的hash中不存在对应的entryKey, 也会返回null。
  864. *
  865. * @param key
  866. * 定位hash的key
  867. * @param entryKey
  868. * 定位hash里面的entryValue的entryKey
  869. *
  870. * @return key对应的hash里的entryKey对应的entryValue值
  871. * @date 2020/3/9 9:09:30
  872. */
  873. public static Object hGet(String key, String entryKey) {
  874. log.info("hGet(...) => key -> {}, entryKey -> {}", key, entryKey);
  875. Object entryValue = redisTemplate.opsForHash().get(key, entryKey);
  876. log.info("hGet(...) => entryValue -> {}", entryValue);
  877. return entryValue;
  878. }
  879. /**
  880. * 获取到key对应的hash(即: 获取到key对应的Map<HK, HV>)
  881. *
  882. * 注: 若redis中不存在对应的key, 则返回一个没有任何entry的空的Map(,而不是返回null)。
  883. *
  884. * @param key
  885. * 定位hash的key
  886. *
  887. * @return key对应的hash。
  888. * @date 2020/3/9 9:09:30
  889. */
  890. public static Map<Object, Object> hGetAll(String key) {
  891. log.info("hGetAll(...) => key -> {}", key);
  892. Map<Object, Object> result = redisTemplate.opsForHash().entries(key);
  893. log.info("hGetAll(...) => result -> {}", result);
  894. return result;
  895. }
  896. /**
  897. * 批量获取(key对应的)hash中的entryKey的entryValue
  898. *
  899. * 注: 若hash中对应的entryKey不存在,那么返回的对应的entryValue值为null
  900. * 注: redis中key不存在,那么返回的List中,每个元素都为null。
  901. * 追注: 这个List本身不为null, size也不为0, 只是每个list中的每个元素为null而已。
  902. *
  903. * @param key
  904. * 定位hash的key
  905. * @param entryKeys
  906. * 需要获取的hash中的字段集
  907. * @return hash中对应entryKeys的对应entryValue集
  908. * @date 2020/3/9 9:25:38
  909. */
  910. public static List<Object> hMultiGet(String key, Collection<Object> entryKeys) {
  911. log.info("hMultiGet(...) => key -> {}, entryKeys -> {}", key, entryKeys);
  912. List<Object> entryValues = redisTemplate.opsForHash().multiGet(key, entryKeys);
  913. log.info("hMultiGet(...) => entryValues -> {}", entryValues);
  914. return entryValues;
  915. }
  916. /**
  917. * (批量)删除(key对应的)hash中的对应entryKey-entryValue
  918. *
  919. * 注: 1、若redis中不存在对应的key, 则返回0;
  920. * 2、若要删除的entryKey,在key对应的hash中不存在,在count不会+1, 如:
  921. * RedisUtil.HashOps.hPut("ds", "name", "邓沙利文");
  922. * RedisUtil.HashOps.hPut("ds", "birthday", "1994-02-05");
  923. * RedisUtil.HashOps.hPut("ds", "hobby", "女");
  924. * 则调用RedisUtil.HashOps.hDelete("ds", "name", "birthday", "hobby", "non-exist-entryKey")
  925. * 的返回结果为3
  926. * 注: 若(key对应的)hash中的所有entry都被删除了,那么该key也会被删除
  927. *
  928. * @param key
  929. * 定位hash的key
  930. * @param entryKeys
  931. * 定位要删除的entryKey-entryValue的entryKey
  932. *
  933. * @return 删除了对应hash中多少个entry
  934. * @date 2020/3/9 9:37:47
  935. */
  936. public static long hDelete(String key, Object... entryKeys) {
  937. log.info("hDelete(...) => key -> {}, entryKeys -> {}", key, entryKeys);
  938. Long count = redisTemplate.opsForHash().delete(key, entryKeys);
  939. log.info("hDelete(...) => count -> {}", count);
  940. if (count == null) {
  941. throw new RedisOpsResultIsNullException();
  942. }
  943. return count;
  944. }
  945. /**
  946. * 查看(key对应的)hash中,是否存在entryKey对应的entry
  947. *
  948. * 注: 若redis中不存在key,则返回false。
  949. * 注: 若key对应的hash中不存在对应的entryKey, 也会返回false。
  950. *
  951. * @param key
  952. * 定位hash的key
  953. * @param entryKey
  954. * 定位hash中entry的entryKey
  955. *
  956. * @return hash中是否存在entryKey对应的entry.
  957. * @date 2020/3/9 9:51:55
  958. */
  959. public static boolean hExists(String key, String entryKey) {
  960. log.info("hDelete(...) => key -> {}, entryKeys -> {}", key, entryKey);
  961. Boolean exist = redisTemplate.opsForHash().hasKey(key, entryKey);
  962. log.info("hDelete(...) => exist -> {}", exist);
  963. return exist;
  964. }
  965. /**
  966. * 增/减(hash中的某个entryValue值) 整数
  967. *
  968. * 注: 负数则为减。
  969. * 注: 若key不存在,那么会自动创建对应的hash,并创建对应的entryKey、entryValue,entryValue的初始值为increment。
  970. * 注: 若entryKey不存在,那么会自动创建对应的entryValue,entryValue的初始值为increment。
  971. * 注: 若key对应的value值不支持增/减操作(即: value不是数字), 那么会
  972. * 抛出org.springframework.data.redis.RedisSystemException
  973. *
  974. * @param key
  975. * 用于定位hash的key
  976. * @param entryKey
  977. * 用于定位entryValue的entryKey
  978. * @param increment
  979. * 增加多少
  980. * @return 增加后的总值。
  981. * @throws RedisSystemException key对应的value值不支持增/减操作时
  982. * @date 2020/3/9 10:09:28
  983. */
  984. public static long hIncrBy(String key, Object entryKey, long increment) {
  985. log.info("hIncrBy(...) => key -> {}, entryKey -> {}, increment -> {}",
  986. key, entryKey, increment);
  987. Long result = redisTemplate.opsForHash().increment(key, entryKey, increment);
  988. log.info("hIncrBy(...) => result -> {}", result);
  989. if (result == null) {
  990. throw new RedisOpsResultIsNullException();
  991. }
  992. return result;
  993. }
  994. /**
  995. * 增/减(hash中的某个entryValue值) 浮点数
  996. *
  997. * 注: 负数则为减。
  998. * 注: 若key不存在,那么会自动创建对应的hash,并创建对应的entryKey、entryValue,entryValue的初始值为increment。
  999. * 注: 若entryKey不存在,那么会自动创建对应的entryValue,entryValue的初始值为increment。
  1000. * 注: 若key对应的value值不支持增/减操作(即: value不是数字), 那么会
  1001. * 抛出org.springframework.data.redis.RedisSystemException
  1002. * 注: 因为是浮点数, 所以可能会和{@link StringOps#incrByFloat(String, double)}一样, 出现精度问题。
  1003. * 追注: 本人简单测试了几组数据,暂未出现精度问题。
  1004. *
  1005. * @param key
  1006. * 用于定位hash的key
  1007. * @param entryKey
  1008. * 用于定位entryValue的entryKey
  1009. * @param increment
  1010. * 增加多少
  1011. * @return 增加后的总值。
  1012. * @throws RedisSystemException key对应的value值不支持增/减操作时
  1013. * @date 2020/3/9 10:09:28
  1014. */
  1015. public static double hIncrByFloat(String key, Object entryKey, double increment) {
  1016. log.info("hIncrByFloat(...) => key -> {}, entryKey -> {}, increment -> {}",
  1017. key, entryKey, increment);
  1018. Double result = redisTemplate.opsForHash().increment(key, entryKey, increment);
  1019. log.info("hIncrByFloat(...) => result -> {}", result);
  1020. if (result == null) {
  1021. throw new RedisOpsResultIsNullException();
  1022. }
  1023. return result;
  1024. }
  1025. /**
  1026. * 获取(key对应的)hash中的所有entryKey
  1027. *
  1028. * 注: 若key不存在,则返回的是一个空的Set(,而不是返回null)
  1029. *
  1030. * @param key
  1031. * 定位hash的key
  1032. *
  1033. * @return hash中的所有entryKey
  1034. * @date 2020/3/9 10:30:13
  1035. */
  1036. public static Set<Object> hKeys(String key) {
  1037. log.info("hKeys(...) => key -> {}", key);
  1038. Set<Object> entryKeys = redisTemplate.opsForHash().keys(key);
  1039. log.info("hKeys(...) => entryKeys -> {}", entryKeys);
  1040. return entryKeys;
  1041. }
  1042. /**
  1043. * 获取(key对应的)hash中的所有entryValue
  1044. *
  1045. * 注: 若key不存在,则返回的是一个空的List(,而不是返回null)
  1046. *
  1047. * @param key
  1048. * 定位hash的key
  1049. *
  1050. * @return hash中的所有entryValue
  1051. * @date 2020/3/9 10:30:13
  1052. */
  1053. public static List<Object> hValues(String key) {
  1054. log.info("hValues(...) => key -> {}", key);
  1055. List<Object> entryValues = redisTemplate.opsForHash().values(key);
  1056. log.info("hValues(...) => entryValues -> {}", entryValues);
  1057. return entryValues;
  1058. }
  1059. /**
  1060. * 获取(key对应的)hash中的所有entry的数量
  1061. *
  1062. * 注: 若redis中不存在对应的key, 则返回值为0
  1063. *
  1064. * @param key
  1065. * 定位hash的key
  1066. *
  1067. * @return (key对应的)hash中,entry的个数
  1068. * @date 2020/3/9 10:41:01
  1069. */
  1070. public static long hSize(String key) {
  1071. log.info("hSize(...) => key -> {}", key);
  1072. Long count = redisTemplate.opsForHash().size(key);
  1073. log.info("hSize(...) => count -> {}", count);
  1074. if (count == null) {
  1075. throw new RedisOpsResultIsNullException();
  1076. }
  1077. return count;
  1078. }
  1079. /**
  1080. * 根据options匹配到(key对应的)hash中的对应的entryKey, 并返回对应的entry集
  1081. *
  1082. *
  1083. * 注: ScanOptions实例的创建方式举例:
  1084. * 1、ScanOptions.NONE
  1085. * 2、ScanOptions.scanOptions().match("n??e").build()
  1086. *
  1087. * @param key
  1088. * 定位hash的key
  1089. * @param options
  1090. * 匹配entryKey的条件
  1091. * 注: ScanOptions.NONE表示全部匹配。
  1092. * 注: ScanOptions.scanOptions().match(pattern).build()表示按照pattern匹配,
  1093. * 其中pattern中可以使用通配符 * ? 等,
  1094. * * 表示>=0个字符
  1095. * ? 表示有且只有一个字符
  1096. * 此处的匹配规则与{@link KeyOps#keys(String)}处的一样。
  1097. *
  1098. * @return 匹配到的(key对应的)hash中的entry
  1099. * @date 2020/3/9 10:49:27
  1100. */
  1101. public static Cursor<Entry<Object, Object>> hScan(String key, ScanOptions options) {
  1102. log.info("hScan(...) => key -> {}, options -> {}", key, JSON.toJSONString(options));
  1103. Cursor<Entry<Object, Object>> cursor = redisTemplate.opsForHash().scan(key, options);
  1104. log.info("hScan(...) => cursor -> {}", JSON.toJSONString(cursor));
  1105. return cursor;
  1106. }
  1107. }
  1108. /**
  1109. * list相关操作
  1110. *
  1111. * 提示: 列表中的元素,可以重复。
  1112. *
  1113. * 提示: list是有序的。
  1114. *
  1115. * 提示: redis中的list中的索引,可分为两类,这两类都可以用来定位list中元素:
  1116. * 类别一: 从left到right, 是从0开始依次增大: 0, 1, 2, 3...
  1117. * 类别二: 从right到left, 是从-1开始依次减小: -1, -2, -3, -4...
  1118. *
  1119. * 提示: redis中String的数据结构可参考resources/data-structure/List(列表)的数据结构(示例一).png
  1120. * redis中String的数据结构可参考resources/data-structure/List(列表)的数据结构(示例二).png
  1121. *
  1122. * @author JustryDeng
  1123. * @date 2020/3/9 11:30:48
  1124. */
  1125. public static class ListOps {
  1126. /**
  1127. * 从左端推入元素进列表
  1128. *
  1129. * 注: 若redis中不存在对应的key, 那么会自动创建
  1130. *
  1131. * @param key
  1132. * 定位list的key
  1133. * @param item
  1134. * 要推入list的元素
  1135. *
  1136. * @return 推入后,(key对应的)list的size
  1137. * @date 2020/3/9 11:56:05
  1138. */
  1139. public static long lLeftPush(String key, String item) {
  1140. log.info("lLeftPush(...) => key -> {}, item -> {}", key, item);
  1141. Long size = redisTemplate.opsForList().leftPush(key, item);
  1142. log.info("lLeftPush(...) => size -> {}", size);
  1143. if (size == null) {
  1144. throw new RedisOpsResultIsNullException();
  1145. }
  1146. return size;
  1147. }
  1148. /**
  1149. * 从左端批量推入元素进列表
  1150. *
  1151. * 注: 若redis中不存在对应的key, 那么会自动创建
  1152. * 注: 这一批item中,先push左侧的, 后push右侧的
  1153. *
  1154. * @param key
  1155. * 定位list的key
  1156. * @param items
  1157. * 要批量推入list的元素集
  1158. *
  1159. * @return 推入后,(key对应的)list的size
  1160. * @date 2020/3/9 11:56:05
  1161. */
  1162. public static long lLeftPushAll(String key, String... items) {
  1163. log.info("lLeftPushAll(...) => key -> {}, items -> {}", key, items);
  1164. Long size = redisTemplate.opsForList().leftPushAll(key, items);
  1165. log.info("lLeftPushAll(...) => size -> {}", size);
  1166. if (size == null) {
  1167. throw new RedisOpsResultIsNullException();
  1168. }
  1169. return size;
  1170. }
  1171. /**
  1172. * 从左端批量推入元素进列表
  1173. *
  1174. * 注: 若redis中不存在对应的key, 那么会自动创建
  1175. * 注: 这一批item中,那个item先从Collection取出来,就先push哪个
  1176. *
  1177. * @param key
  1178. * 定位list的key
  1179. * @param items
  1180. * 要批量推入list的元素集
  1181. *
  1182. * @return 推入后,(key对应的)list的size
  1183. * @date 2020/3/9 11:56:05
  1184. */
  1185. public static long lLeftPushAll(String key, Collection<String> items) {
  1186. log.info("lLeftPushAll(...) => key -> {}, items -> {}", key, items);
  1187. Long size = redisTemplate.opsForList().leftPushAll(key, items);
  1188. log.info("lLeftPushAll(...) => size -> {}", size);
  1189. if (size == null) {
  1190. throw new RedisOpsResultIsNullException();
  1191. }
  1192. return size;
  1193. }
  1194. /**
  1195. * 如果redis中存在key, 则从左端批量推入元素进列表;
  1196. * 否则,不进行任何操作
  1197. *
  1198. * @param key
  1199. * 定位list的key
  1200. * @param item
  1201. * 要推入list的项
  1202. *
  1203. * @return 推入后,(key对应的)list的size
  1204. * @date 2020/3/9 13:40:08
  1205. */
  1206. public static long lLeftPushIfPresent(String key, String item) {
  1207. log.info("lLeftPushIfPresent(...) => key -> {}, item -> {}", key, item);
  1208. Long size = redisTemplate.opsForList().leftPushIfPresent(key, item);
  1209. log.info("lLeftPushIfPresent(...) => size -> {}", size);
  1210. if (size == null) {
  1211. throw new RedisOpsResultIsNullException();
  1212. }
  1213. return size;
  1214. }
  1215. /**
  1216. * 若key对应的list中存在pivot项, 那么将item放入第一个pivot项前(即:放在第一个pivot项左边);
  1217. * 若key对应的list中不存在pivot项, 那么不做任何操作, 直接返回-1。
  1218. *
  1219. * 注: 若redis中不存在对应的key, 那么会自动创建
  1220. *
  1221. * @param key
  1222. * 定位list的key
  1223. * @param item
  1224. * 要推入list的元素
  1225. *
  1226. * @return 推入后,(key对应的)list的size
  1227. * @date 2020/3/9 11:56:05
  1228. */
  1229. public static long lLeftPush(String key, String pivot, String item) {
  1230. log.info("lLeftPush(...) => key -> {}, pivot -> {}, item -> {}", key, pivot, item);
  1231. Long size = redisTemplate.opsForList().leftPush(key, pivot, item);
  1232. log.info("lLeftPush(...) => size -> {}", size);
  1233. if (size == null) {
  1234. throw new RedisOpsResultIsNullException();
  1235. }
  1236. return size;
  1237. }
  1238. /**
  1239. * 与{@link ListOps#lLeftPush(String, String)}类比即可, 不过是从list右侧推入元素
  1240. */
  1241. public static long lRightPush(String key, String item) {
  1242. log.info("lRightPush(...) => key -> {}, item -> {}", key, item);
  1243. Long size = redisTemplate.opsForList().rightPush(key, item);
  1244. log.info("lRightPush(...) => size -> {}", size);
  1245. if (size == null) {
  1246. throw new RedisOpsResultIsNullException();
  1247. }
  1248. return size;
  1249. }
  1250. /**
  1251. * 与{@link ListOps#lLeftPushAll(String, String...)}类比即可, 不过是从list右侧推入元素
  1252. */
  1253. public static long lRightPushAll(String key, String... items) {
  1254. log.info("lRightPushAll(...) => key -> {}, items -> {}", key, items);
  1255. Long size = redisTemplate.opsForList().rightPushAll(key, items);
  1256. log.info("lRightPushAll(...) => size -> {}", size);
  1257. if (size == null) {
  1258. throw new RedisOpsResultIsNullException();
  1259. }
  1260. return size;
  1261. }
  1262. /**
  1263. * 与{@link ListOps#lLeftPushAll(String, Collection<String>)}类比即可, 不过是从list右侧推入元素
  1264. */
  1265. public static long lRightPushAll(String key, Collection<String> items) {
  1266. log.info("lRightPushAll(...) => key -> {}, items -> {}", key, items);
  1267. Long size = redisTemplate.opsForList().rightPushAll(key, items);
  1268. log.info("lRightPushAll(...) => size -> {}", size);
  1269. if (size == null) {
  1270. throw new RedisOpsResultIsNullException();
  1271. }
  1272. return size;
  1273. }
  1274. /**
  1275. * 与{@link ListOps#lLeftPushIfPresent(String, String)}类比即可, 不过是从list右侧推入元素
  1276. */
  1277. public static long lRightPushIfPresent(String key, String item) {
  1278. log.info("lRightPushIfPresent(...) => key -> {}, item -> {}", key, item);
  1279. Long size = redisTemplate.opsForList().rightPushIfPresent(key, item);
  1280. log.info("lRightPushIfPresent(...) => size -> {}", size);
  1281. if (size == null) {
  1282. throw new RedisOpsResultIsNullException();
  1283. }
  1284. return size;
  1285. }
  1286. /**
  1287. * 与{@link ListOps#lLeftPush(String, String, String)}类比即可, 不过是从list右侧推入元素
  1288. */
  1289. public static long lRightPush(String key, String pivot, String item) {
  1290. log.info("lLeftPush(...) => key -> {}, pivot -> {}, item -> {}", key, pivot, item);
  1291. Long size = redisTemplate.opsForList().rightPush(key, pivot, item);
  1292. log.info("lLeftPush(...) => size -> {}", size);
  1293. if (size == null) {
  1294. throw new RedisOpsResultIsNullException();
  1295. }
  1296. return size;
  1297. }
  1298. /**
  1299. * 【非阻塞队列】 从左侧移出(key对应的)list中的第一个元素, 并将该元素返回
  1300. *
  1301. * 注: 此方法是非阻塞的, 即: 若(key对应的)list中的所有元素都被pop移出了,此时,再进行pop的话,会立即返回null
  1302. * 注: 此方法是非阻塞的, 即: 若redis中不存在对应的key,那么会立即返回null
  1303. * 注: 若将(key对应的)list中的所有元素都pop完了,那么该key会被删除
  1304. *
  1305. * @param key
  1306. * 定位list的key
  1307. * @return 移出的那个元素
  1308. * @date 2020/3/9 14:33:56
  1309. */
  1310. public static String lLeftPop(String key) {
  1311. log.info("lLeftPop(...) => key -> {}", key);
  1312. String item = redisTemplate.opsForList().leftPop(key);
  1313. log.info("lLeftPop(...) => item -> {}", item);
  1314. return item;
  1315. }
  1316. /**
  1317. * 【阻塞队列】 从左侧移出(key对应的)list中的第一个元素, 并将该元素返回
  1318. *
  1319. * 注: 此方法是阻塞的, 即: 若(key对应的)list中的所有元素都被pop移出了,此时,再进行pop的话,
  1320. * 会阻塞timeout这么久,然后返回null
  1321. * 注: 此方法是阻塞的, 即: 若redis中不存在对应的key,那么会阻塞timeout这么久,然后返回null
  1322. * 注: 若将(key对应的)list中的所有元素都pop完了,那么该key会被删除
  1323. *
  1324. * 提示: 若阻塞过程中, 目标key-list出现了,且里面有item了,那么会立马停止阻塞, 进行元素移出并返回
  1325. *
  1326. * @param key
  1327. * 定位list的key
  1328. * @param timeout
  1329. * 超时时间
  1330. * @param unit
  1331. * timeout的单位
  1332. * @return 移出的那个元素
  1333. * @date 2020/3/9 14:33:56
  1334. */
  1335. public static String lLeftPop(String key, long timeout, TimeUnit unit) {
  1336. log.info("lLeftPop(...) => key -> {}, timeout -> {}, unit -> {}", key, timeout, unit);
  1337. String item = redisTemplate.opsForList().leftPop(key, timeout, unit);
  1338. log.info("lLeftPop(...) => item -> {}", item);
  1339. return item;
  1340. }
  1341. /**
  1342. * 与{@link ListOps#lLeftPop(String)}类比即可, 不过是从list右侧移出元素
  1343. */
  1344. public static String lRightPop(String key) {
  1345. log.info("lRightPop(...) => key -> {}", key);
  1346. String item = redisTemplate.opsForList().rightPop(key);
  1347. log.info("lRightPop(...) => item -> {}", item);
  1348. return item;
  1349. }
  1350. /**
  1351. * 与{@link ListOps#lLeftPop(String, long, TimeUnit)}类比即可, 不过是从list右侧移出元素
  1352. */
  1353. public static String lRightPop(String key, long timeout, TimeUnit unit) {
  1354. log.info("lRightPop(...) => key -> {}, timeout -> {}, unit -> {}", key, timeout, unit);
  1355. String item = redisTemplate.opsForList().rightPop(key, timeout, unit);
  1356. log.info("lRightPop(...) => item -> {}", item);
  1357. return item;
  1358. }
  1359. /**
  1360. * 【非阻塞队列】 从sourceKey对应的sourceList右侧移出一个item, 并将这个item推
  1361. * 入(destinationKey对应的)destinationList的左侧
  1362. *
  1363. * 注: 若sourceKey对应的list中没有item了,则立马认为(从sourceKey对应的list中pop出来的)item为null,
  1364. * null并不会往destinationKey对应的list中push。
  1365. * 追注: 此时,此方法的返回值是null。
  1366. *
  1367. * 注: 若将(sourceKey对应的)list中的所有元素都pop完了,那么该sourceKey会被删除。
  1368. *
  1369. * @param sourceKey
  1370. * 定位sourceList的key
  1371. * @param destinationKey
  1372. * 定位destinationList的key
  1373. *
  1374. * @return 移动的这个元素
  1375. * @date 2020/3/9 15:06:59
  1376. */
  1377. public static String lRightPopAndLeftPush(String sourceKey, String destinationKey) {
  1378. log.info("lRightPopAndLeftPush(...) => sourceKey -> {}, destinationKey -> {}",
  1379. sourceKey, destinationKey);
  1380. String item = redisTemplate.opsForList().rightPopAndLeftPush(sourceKey, destinationKey);
  1381. log.info("lRightPopAndLeftPush(...) => item -> {}", item);
  1382. return item;
  1383. }
  1384. /**
  1385. * 【阻塞队列】 从sourceKey对应的sourceList右侧移出一个item, 并将这个item推
  1386. * 入(destinationKey对应的)destinationList的左侧
  1387. *
  1388. * 注: 若sourceKey对应的list中没有item了,则阻塞等待, 直到能从sourceList中移出一个非null的item(或等待时长超时);
  1389. * case1: 等到了一个非null的item, 那么继续下面的push操作,并返回这个item。
  1390. * case2: 超时了,还没等到非null的item, 那么pop出的结果就未null,此时并不会往destinationList进行push。
  1391. * 此时,此方法的返回值是null。
  1392. *
  1393. * 注: 若将(sourceKey对应的)list中的所有元素都pop完了,那么该sourceKey会被删除。
  1394. *
  1395. * @param sourceKey
  1396. * 定位sourceList的key
  1397. * @param destinationKey
  1398. * 定位destinationList的key
  1399. * @param timeout
  1400. * 超时时间
  1401. * @param unit
  1402. * timeout的单位
  1403. *
  1404. * @return 移动的这个元素
  1405. * @date 2020/3/9 15:06:59
  1406. */
  1407. public static String lRightPopAndLeftPush(String sourceKey, String destinationKey, long timeout,
  1408. TimeUnit unit) {
  1409. log.info("lRightPopAndLeftPush(...) => sourceKey -> {}, destinationKey -> {}, timeout -> {},"
  1410. + " unit -> {}", sourceKey, destinationKey, timeout, unit);
  1411. String item = redisTemplate.opsForList().rightPopAndLeftPush(sourceKey, destinationKey, timeout, unit);
  1412. log.info("lRightPopAndLeftPush(...) => item -> {}", item);
  1413. return item;
  1414. }
  1415. /**
  1416. * 设置(key对应的)list中对应索引位置index处的元素为item
  1417. *
  1418. * 注: 若key不存在,则会抛出org.springframework.data.redis.RedisSystemException
  1419. * 注: 若索引越界,也会抛出org.springframework.data.redis.RedisSystemException
  1420. *
  1421. * @param key
  1422. * 定位list的key
  1423. * @param index
  1424. * 定位list中的元素的索引
  1425. * @param item
  1426. * 要替换成的值
  1427. * @date 2020/3/9 15:39:50
  1428. */
  1429. public static void lSet(String key, long index, String item) {
  1430. log.info("lSet(...) => key -> {}, index -> {}, item -> {}", key, index, item);
  1431. redisTemplate.opsForList().set(key, index, item);
  1432. }
  1433. /**
  1434. * 通过索引index, 获取(key对应的)list中的元素
  1435. *
  1436. * 注: 若key不存在 或 index超出(key对应的)list的索引范围,那么返回null
  1437. *
  1438. * @param key
  1439. * 定位list的key
  1440. * @param index
  1441. * 定位list中的item的索引
  1442. *
  1443. * @return list中索引index对应的item
  1444. * @date 2020/3/10 0:27:23
  1445. */
  1446. public static String lIndex(String key, long index) {
  1447. log.info("lIndex(...) => key -> {}, index -> {}", key, index);
  1448. String item = redisTemplate.opsForList().index(key, index);
  1449. log.info("lIndex(...) => item -> {}", item);
  1450. return item;
  1451. }
  1452. /**
  1453. * 获取(key对应的)list中索引在[start, end]之间的item集
  1454. *
  1455. * 注: 含start、含end。
  1456. * 注: 当key不存在时,获取到的是空的集合。
  1457. * 注: 当获取的范围比list的范围还要大时,获取到的是这两个范围的交集。
  1458. *
  1459. * 提示: 可通过RedisUtil.ListOps.lRange(key, 0, -1)来获取到该key对应的整个list
  1460. *
  1461. * @param key
  1462. * 定位list的key
  1463. * @param start
  1464. * 起始元素的index
  1465. * @param end
  1466. * 结尾元素的index
  1467. *
  1468. * @return 对应的元素集合
  1469. * @date 2020/3/10 0:34:59
  1470. */
  1471. public static List<String> lRange(String key, long start, long end) {
  1472. log.info("lRange(...) => key -> {}, start -> {}, end -> {}", key, start, end);
  1473. List<String> result = redisTemplate.opsForList().range(key, start, end);
  1474. log.info("lRange(...) => result -> {}", result);
  1475. return result;
  1476. }
  1477. /**
  1478. * 获取(key对应的)list
  1479. *
  1480. * @see ListOps#lRange(String, long, long)
  1481. *
  1482. * @param key
  1483. * 定位list的key
  1484. * @return (key对应的)list
  1485. * @date 2020/3/10 0:46:50
  1486. */
  1487. public static List<String> lWholeList(String key) {
  1488. log.info("lWholeList(...) => key -> {}", key);
  1489. List<String> result = redisTemplate.opsForList().range(key, 0, -1);
  1490. log.info("lWholeList(...) => result -> {}", result);
  1491. return result;
  1492. }
  1493. /**
  1494. * 获取(key对应的)list的size
  1495. *
  1496. * 注: 当key不存在时,获取到的size为0.
  1497. *
  1498. * @param key
  1499. * 定位list的key
  1500. *
  1501. * @return list的size。
  1502. *
  1503. * @date 2020/3/10 0:48:40
  1504. */
  1505. public static long lSize(String key) {
  1506. log.info("lSize(...) => key -> {}", key);
  1507. Long size = redisTemplate.opsForList().size(key);
  1508. log.info("lSize(...) => size -> {}", size);
  1509. if (size == null) {
  1510. throw new RedisOpsResultIsNullException();
  1511. }
  1512. return size;
  1513. }
  1514. /**
  1515. * 删除(key对应的)list中,前expectCount个值等于item的项
  1516. *
  1517. * 注: 若expectCount == 0, 则表示删除list中所有的值等于item的项.
  1518. * 注: 若expectCount > 0, 则表示删除从左往右进行
  1519. * 注: 若expectCount < 0, 则表示删除从右往左进行
  1520. *
  1521. * 注: 若list中,值等于item的项的个数少于expectCount时,那么会删除list中所有的值等于item的项。
  1522. * 注: 当key不存在时, 返回0。
  1523. * 注: 若lRemove后, 将(key对应的)list中没有任何元素了,那么该key会被删除。
  1524. *
  1525. * @param key
  1526. * 定位list的key
  1527. * @param expectCount
  1528. * 要删除的item的个数
  1529. * @param item
  1530. * 要删除的item
  1531. *
  1532. * @return 实际删除了的item的个数
  1533. * @date 2020/3/10 0:52:57
  1534. */
  1535. public static long lRemove(String key, long expectCount, String item) {
  1536. log.info("lRemove(...) => key -> {}, expectCount -> {}, item -> {}", key, expectCount, item);
  1537. Long actualCount = redisTemplate.opsForList().remove(key, expectCount, item);
  1538. log.info("lRemove(...) => actualCount -> {}", actualCount);
  1539. if (actualCount == null) {
  1540. throw new RedisOpsResultIsNullException();
  1541. }
  1542. return actualCount;
  1543. }
  1544. /**
  1545. * 裁剪(即: 对list中的元素取交集。)
  1546. *
  1547. * 举例说明: list中的元素索引范围是[0, 8], 而这个方法传入的[start, end]为 [3, 10],
  1548. * 那么裁剪就是对[0, 8]和[3, 10]进行取交集, 得到[3, 8], 那么裁剪后
  1549. * 的list中,只剩下(原来裁剪前)索引在[3, 8]之间的元素了。
  1550. *
  1551. * 注: 若裁剪后的(key对应的)list就是空的,那么该key会被删除。
  1552. *
  1553. * @param key
  1554. * 定位list的key
  1555. * @param start
  1556. * 要删除的item集的起始项的索引
  1557. * @param end
  1558. * 要删除的item集的结尾项的索引
  1559. * @date 2020/3/10 1:16:58
  1560. */
  1561. public static void lTrim(String key, long start, long end) {
  1562. log.info("lTrim(...) => key -> {}, start -> {}, end -> {}", key, start, end);
  1563. redisTemplate.opsForList().trim(key, start, end);
  1564. }
  1565. }
  1566. /**
  1567. * set相关操作
  1568. *
  1569. * 提示: set中的元素,不可以重复。
  1570. * 提示: set是无序的。
  1571. * 提示: redis中String的数据结构可参考resources/data-structure/Set(集合)的数据结构(示例一).png
  1572. * redis中String的数据结构可参考resources/data-structure/Set(集合)的数据结构(示例二).png
  1573. *
  1574. * @author JustryDeng
  1575. * @date 2020/3/9 11:30:48
  1576. */
  1577. public static class SetOps {
  1578. /**
  1579. * 向(key对应的)set中添加items
  1580. *
  1581. * 注: 若key不存在,则会自动创建。
  1582. * 注: set中的元素会去重。
  1583. *
  1584. * @param key
  1585. * 定位set的key
  1586. * @param items
  1587. * 要向(key对应的)set中添加的items
  1588. *
  1589. * @return 此次添加操作,添加到set中的元素的个数
  1590. * @date 2020/3/11 8:16:00
  1591. */
  1592. public static long sAdd(String key, String... items) {
  1593. log.info("sAdd(...) => key -> {}, items -> {}", key, items);
  1594. Long count = redisTemplate.opsForSet().add(key, items);
  1595. log.info("sAdd(...) => count -> {}", count);
  1596. if (count == null) {
  1597. throw new RedisOpsResultIsNullException();
  1598. }
  1599. return count;
  1600. }
  1601. /**
  1602. * 从(key对应的)set中删除items
  1603. *
  1604. * 注: 若key不存在, 则返回0。
  1605. * 注: 若已经将(key对应的)set中的项删除完了,那么对应的key也会被删除。
  1606. *
  1607. * @param key
  1608. * 定位set的key
  1609. * @param items
  1610. * 要移除的items
  1611. *
  1612. * @return 实际删除了的个数
  1613. * @date 2020/3/11 8:26:43
  1614. */
  1615. public static long sRemove(String key, Object... items) {
  1616. log.info("sRemove(...) => key -> {}, items -> {}", key, items);
  1617. Long count = redisTemplate.opsForSet().remove(key, items);
  1618. log.info("sRemove(...) => count -> {}", count);
  1619. if (count == null) {
  1620. throw new RedisOpsResultIsNullException();
  1621. }
  1622. return count;
  1623. }
  1624. /**
  1625. * 从(key对应的)set中随机移出一个item, 并返回这个item
  1626. *
  1627. * 注: 因为set是无序的,所以移出的这个item,是随机的; 并且,哪怕
  1628. * 是数据一样的set,多次测试移出操作,移除的元素也是随机的。
  1629. *
  1630. * 注: 若已经将(key对应的)set中的项pop完了,那么对应的key会被删除。
  1631. *
  1632. * @param key
  1633. * 定位set的key
  1634. *
  1635. * @return 移出的项
  1636. * @date 2020/3/11 8:32:40
  1637. */
  1638. public static String sPop(String key) {
  1639. log.info("sPop(...) => key -> {}", key);
  1640. String popItem = redisTemplate.opsForSet().pop(key);
  1641. log.info("sPop(...) => popItem -> {}", popItem);
  1642. return popItem;
  1643. }
  1644. /**
  1645. * 将(sourceKey对应的)sourceSet中的元素item, 移动到(destinationKey对应的)destinationSet中
  1646. *
  1647. * 注: 当sourceKey不存在时, 返回false
  1648. * 注: 当item不存在时, 返回false
  1649. * 注: 若destinationKey不存在, 那么在移动时会自动创建
  1650. * 注: 若已经将(sourceKey对应的)set中的项move出去完了,那么对应的sourceKey会被删除。
  1651. *
  1652. * @param sourceKey
  1653. * 定位sourceSet的key
  1654. * @param item
  1655. * 要移动的项目
  1656. * @param destinationKey
  1657. * 定位destinationSet的key
  1658. *
  1659. * @return 移动成功与否
  1660. * @date 2020/3/11 8:43:32
  1661. */
  1662. public static boolean sMove(String sourceKey, String item, String destinationKey) {
  1663. Boolean result = redisTemplate.opsForSet().move(sourceKey, item, destinationKey);
  1664. log.info("sMove(...) => sourceKey -> {}, destinationKey -> {}, item -> {}",
  1665. sourceKey, destinationKey, item);
  1666. log.info("sMove(...) => result -> {}", result);
  1667. if (result == null) {
  1668. throw new RedisOpsResultIsNullException();
  1669. }
  1670. return result;
  1671. }
  1672. /**
  1673. * 获取(key对应的)set中的元素个数
  1674. *
  1675. * 注: 若key不存在,则返回0
  1676. *
  1677. * @param key
  1678. * 定位set的key
  1679. *
  1680. * @return (key对应的)set中的元素个数
  1681. * @date 2020/3/11 8:57:19
  1682. */
  1683. public static long sSize(String key) {
  1684. log.info("sSize(...) => key -> {}", key);
  1685. Long size = redisTemplate.opsForSet().size(key);
  1686. log.info("sSize(...) => size -> {}", size);
  1687. if (size == null) {
  1688. throw new RedisOpsResultIsNullException();
  1689. }
  1690. return size;
  1691. }
  1692. /**
  1693. * 判断(key对应的)set中是否含有item
  1694. *
  1695. * 注: 若key不存在,则返回false。
  1696. *
  1697. * @param key
  1698. * 定位set的key
  1699. * @param item
  1700. * 被查找的项
  1701. *
  1702. * @return (key对应的)set中是否含有item
  1703. * @date 2020/3/11 9:03:29
  1704. */
  1705. public static boolean sIsMember(String key, Object item) {
  1706. log.info("sSize(...) => key -> {}, size -> {}", key, item);
  1707. Boolean result = redisTemplate.opsForSet().isMember(key, item);
  1708. log.info("sSize(...) => result -> {}", result);
  1709. if (result == null) {
  1710. throw new RedisOpsResultIsNullException();
  1711. }
  1712. return result;
  1713. }
  1714. /**
  1715. * 获取两个(key对应的)Set的交集
  1716. *
  1717. * 注: 若不存在任何交集,那么返回空的集合(, 而不是null)
  1718. * 注: 若其中一个key不存在(或两个key都不存在),那么返回空的集合(, 而不是null)
  1719. *
  1720. * @param key
  1721. * 定位其中一个set的键
  1722. * @param otherKey
  1723. * 定位其中另一个set的键
  1724. *
  1725. * @return item交集
  1726. * @date 2020/3/11 9:31:25
  1727. */
  1728. public static Set<String> sIntersect(String key, String otherKey) {
  1729. log.info("sIntersect(...) => key -> {}, otherKey -> {}", key, otherKey);
  1730. Set<String> intersectResult = redisTemplate.opsForSet().intersect(key, otherKey);
  1731. log.info("sIntersect(...) => intersectResult -> {}", intersectResult);
  1732. return intersectResult;
  1733. }
  1734. /**
  1735. * 获取多个(key对应的)Set的交集
  1736. *
  1737. * 注: 若不存在任何交集,那么返回空的集合(, 而不是null)
  1738. * 注: 若>=1个key不存在,那么返回空的集合(, 而不是null)
  1739. *
  1740. * @param key
  1741. * 定位其中一个set的键
  1742. * @param otherKeys
  1743. * 定位其它set的键集
  1744. *
  1745. * @return item交集
  1746. * @date 2020/3/11 9:39:23
  1747. */
  1748. public static Set<String> sIntersect(String key, Collection<String> otherKeys) {
  1749. log.info("sIntersect(...) => key -> {}, otherKeys -> {}", key, otherKeys);
  1750. Set<String> intersectResult = redisTemplate.opsForSet().intersect(key, otherKeys);
  1751. log.info("sIntersect(...) => intersectResult -> {}", intersectResult);
  1752. return intersectResult;
  1753. }
  1754. /**
  1755. * 获取两个(key对应的)Set的交集, 并将结果add到storeKey对应的Set中。
  1756. *
  1757. * case1: 交集不为空, storeKey不存在, 则 会创建对应的storeKey,并将交集添加到(storeKey对应的)set中
  1758. * case2: 交集不为空, storeKey已存在, 则 会清除原(storeKey对应的)set中所有的项,然后将交集添加到(storeKey对应的)set中
  1759. * case3: 交集为空, 则不进行下面的操作, 直接返回0
  1760. *
  1761. * 注: 求交集的部分,详见{@link SetOps#sIntersect(String, String)}
  1762. *
  1763. * @param key
  1764. * 定位其中一个set的键
  1765. * @param otherKey
  1766. * 定位其中另一个set的键
  1767. * @param storeKey
  1768. * 定位(要把交集添加到哪个)set的key
  1769. *
  1770. * @return add到(storeKey对应的)Set后, 该set对应的size
  1771. * @date 2020/3/11 9:46:46
  1772. */
  1773. public static long sIntersectAndStore(String key, String otherKey, String storeKey) {
  1774. log.info("sIntersectAndStore(...) => key -> {}, otherKey -> {}, storeKey -> {}",
  1775. key, otherKey, storeKey);
  1776. Long size = redisTemplate.opsForSet().intersectAndStore(key, otherKey, storeKey);
  1777. log.info("sIntersectAndStore(...) => size -> {}", size);
  1778. if (size == null) {
  1779. throw new RedisOpsResultIsNullException();
  1780. }
  1781. return size;
  1782. }
  1783. /**
  1784. * 获取多个(key对应的)Set的交集, 并将结果add到storeKey对应的Set中。
  1785. *
  1786. * case1: 交集不为空, storeKey不存在, 则 会创建对应的storeKey,并将交集添加到(storeKey对应的)set中
  1787. * case2: 交集不为空, storeKey已存在, 则 会清除原(storeKey对应的)set中所有的项,然后将交集添加到(storeKey对应的)set中
  1788. * case3: 交集为空, 则不进行下面的操作, 直接返回0
  1789. *
  1790. * 注: 求交集的部分,详见{@link SetOps#sIntersect(String, Collection)}
  1791. *
  1792. * @date 2020/3/11 11:04:29
  1793. */
  1794. public static long sIntersectAndStore(String key, Collection<String> otherKeys, String storeKey) {
  1795. log.info("sIntersectAndStore(...) => key -> {}, otherKeys -> {}, storeKey -> {}", key, otherKeys, storeKey);
  1796. Long size = redisTemplate.opsForSet().intersectAndStore(key, otherKeys, storeKey);
  1797. log.info("sIntersectAndStore(...) => size -> {}", size);
  1798. if (size == null) {
  1799. throw new RedisOpsResultIsNullException();
  1800. }
  1801. return size;
  1802. }
  1803. /**
  1804. * 获取两个(key对应的)Set的并集
  1805. *
  1806. * 注: 并集中的元素也是唯一的,这是Set保证的。
  1807. *
  1808. * @param key
  1809. * 定位其中一个set的键
  1810. * @param otherKey
  1811. * 定位其中另一个set的键
  1812. *
  1813. * @return item并集
  1814. * @date 2020/3/11 11:18:35
  1815. */
  1816. public static Set<String> sUnion(String key, String otherKey) {
  1817. log.info("sUnion(...) => key -> {}, otherKey -> {}", key, otherKey);
  1818. Set<String> unionResult = redisTemplate.opsForSet().union(key, otherKey);
  1819. log.info("sUnion(...) => unionResult -> {}", unionResult);
  1820. return unionResult;
  1821. }
  1822. /**
  1823. * 获取两个(key对应的)Set的并集
  1824. *
  1825. * 注: 并集中的元素也是唯一的,这是Set保证的。
  1826. *
  1827. * @param key
  1828. * 定位其中一个set的键
  1829. * @param otherKeys
  1830. * 定位其它set的键集
  1831. *
  1832. * @return item并集
  1833. * @date 2020/3/11 11:18:35
  1834. */
  1835. public static Set<String> sUnion(String key, Collection<String> otherKeys) {
  1836. log.info("sUnion(...) => key -> {}, otherKeys -> {}", key, otherKeys);
  1837. Set<String> unionResult = redisTemplate.opsForSet().union(key, otherKeys);
  1838. log.info("sUnion(...) => unionResult -> {}", unionResult);
  1839. return unionResult;
  1840. }
  1841. /**
  1842. * 获取两个(key对应的)Set的并集, 并将结果add到storeKey对应的Set中。
  1843. *
  1844. * case1: 并集不为空, storeKey不存在, 则 会创建对应的storeKey,并将并集添加到(storeKey对应的)set中
  1845. * case2: 并集不为空, storeKey已存在, 则 会清除原(storeKey对应的)set中所有的项,然后将并集添加到(storeKey对应的)set中
  1846. * case3: 并集为空, 则不进行下面的操作, 直接返回0
  1847. *
  1848. * 注: 求并集的部分,详见{@link SetOps#sUnion(String, String)}
  1849. *
  1850. * @param key
  1851. * 定位其中一个set的键
  1852. * @param otherKey
  1853. * 定位其中另一个set的键
  1854. * @param storeKey
  1855. * 定位(要把并集添加到哪个)set的key
  1856. *
  1857. * @return add到(storeKey对应的)Set后, 该set对应的size
  1858. * @date 2020/3/11 12:26:24
  1859. */
  1860. public static long sUnionAndStore(String key, String otherKey, String storeKey) {
  1861. log.info("sUnionAndStore(...) => key -> {}, otherKey -> {}, storeKey -> {}",
  1862. key, otherKey, storeKey);
  1863. Long size = redisTemplate.opsForSet().unionAndStore(key, otherKey, storeKey);
  1864. log.info("sUnionAndStore(...) => size -> {}", size);
  1865. if (size == null) {
  1866. throw new RedisOpsResultIsNullException();
  1867. }
  1868. return size;
  1869. }
  1870. /**
  1871. * 获取两个(key对应的)Set的并集, 并将结果add到storeKey对应的Set中。
  1872. *
  1873. * case1: 并集不为空, storeKey不存在, 则 会创建对应的storeKey,并将并集添加到(storeKey对应的)set中
  1874. * case2: 并集不为空, storeKey已存在, 则 会清除原(storeKey对应的)set中所有的项,然后将并集添加到(storeKey对应的)set中
  1875. * case3: 并集为空, 则不进行下面的操作, 直接返回0
  1876. *
  1877. * 注: 求并集的部分,详见{@link SetOps#sUnion(String, Collection)}
  1878. *
  1879. * @param key
  1880. * 定位其中一个set的键
  1881. * @param otherKeys
  1882. * 定位其它set的键集
  1883. * @param storeKey
  1884. * 定位(要把并集添加到哪个)set的key
  1885. *
  1886. * @return add到(storeKey对应的)Set后, 该set对应的size
  1887. * @date 2020/3/11 12:26:24
  1888. */
  1889. public static long sUnionAndStore(String key, Collection<String> otherKeys, String storeKey) {
  1890. log.info("sUnionAndStore(...) => key -> {}, otherKeys -> {}, storeKey -> {}",
  1891. key, otherKeys, storeKey);
  1892. Long size = redisTemplate.opsForSet().unionAndStore(key, otherKeys, storeKey);
  1893. log.info("sUnionAndStore(...) => size -> {}", size);
  1894. if (size == null) {
  1895. throw new RedisOpsResultIsNullException();
  1896. }
  1897. return size;
  1898. }
  1899. /**
  1900. * 获取 (key对应的)Set 减去 (otherKey对应的)Set 的差集
  1901. *
  1902. * 注: 如果被减数key不存在, 那么结果为空的集合(,而不是null)
  1903. * 注: 如果被减数key存在,但减数key不存在, 那么结果即为(被减数key对应的)Set
  1904. *
  1905. * @param key
  1906. * 定位"被减数set"的键
  1907. * @param otherKey
  1908. * 定位"减数set"的键
  1909. *
  1910. * @return item差集
  1911. * @date 2020/3/11 14:03:57
  1912. */
  1913. public static Set<String> sDifference(String key, String otherKey) {
  1914. log.info("sDifference(...) => key -> {}, otherKey -> {}",
  1915. key, otherKey);
  1916. Set<String> differenceResult = redisTemplate.opsForSet().difference(key, otherKey);
  1917. log.info("sDifference(...) => differenceResult -> {}", differenceResult);
  1918. return differenceResult;
  1919. }
  1920. /**
  1921. * 获取 (key对应的)Set 减去 (otherKeys对应的)Sets 的差集
  1922. *
  1923. * 注: 如果被减数key不存在, 那么结果为空的集合(,而不是null)
  1924. * 注: 如果被减数key存在,但减数key不存在, 那么结果即为(被减数key对应的)Set
  1925. *
  1926. * 提示: 当有多个减数时, 被减数先减去哪一个减数,后减去哪一个减数,是无所谓的,是不影响最终结果的。
  1927. *
  1928. * @param key
  1929. * 定位"被减数set"的键
  1930. * @param otherKeys
  1931. * 定位"减数集sets"的键集
  1932. *
  1933. * @return item差集
  1934. * @date 2020/3/11 14:03:57
  1935. */
  1936. public static Set<String> sDifference(String key, Collection<String> otherKeys) {
  1937. log.info("sDifference(...) => key -> {}, otherKeys -> {}", key, otherKeys);
  1938. Set<String> differenceResult = redisTemplate.opsForSet().difference(key, otherKeys);
  1939. log.info("sDifference(...) => differenceResult -> {}", differenceResult);
  1940. return differenceResult;
  1941. }
  1942. /**
  1943. * 获取 (key对应的)Set 减去 (otherKey对应的)Set 的差集, 并将结果add到storeKey对应的Set中。
  1944. *
  1945. * case1: 差集不为空, storeKey不存在, 则 会创建对应的storeKey,并将差集添加到(storeKey对应的)set中
  1946. * case2: 差集不为空, storeKey已存在, 则 会清除原(storeKey对应的)set中所有的项,然后将差集添加到(storeKey对应的)set中
  1947. * case3: 差集为空, 则不进行下面的操作, 直接返回0
  1948. *
  1949. * 注: 求并集的部分,详见{@link SetOps#sDifference(String, String)}
  1950. *
  1951. * @param key
  1952. * 定位"被减数set"的键
  1953. * @param otherKey
  1954. * 定位"减数set"的键
  1955. * @param storeKey
  1956. * 定位(要把差集添加到哪个)set的key
  1957. *
  1958. * @return add到(storeKey对应的)Set后, 该set对应的size
  1959. * @date 2020/3/11 14:33:36
  1960. */
  1961. public static long sDifferenceAndStore(String key, String otherKey, String storeKey) {
  1962. log.info("sDifferenceAndStore(...) => key -> {}, otherKey -> {}, storeKey -> {}",
  1963. key, otherKey, storeKey);
  1964. Long size = redisTemplate.opsForSet().differenceAndStore(key, otherKey, storeKey);
  1965. log.info("sDifferenceAndStore(...) => size -> {}", size);
  1966. if (size == null) {
  1967. throw new RedisOpsResultIsNullException();
  1968. }
  1969. return size;
  1970. }
  1971. /**
  1972. * 获取 (key对应的)Set 减去 (otherKey对应的)Set 的差集, 并将结果add到storeKey对应的Set中。
  1973. *
  1974. * case1: 差集不为空, storeKey不存在, 则 会创建对应的storeKey,并将差集添加到(storeKey对应的)set中
  1975. * case2: 差集不为空, storeKey已存在, 则 会清除原(storeKey对应的)set中所有的项,然后将差集添加到(storeKey对应的)set中
  1976. * case3: 差集为空, 则不进行下面的操作, 直接返回0
  1977. *
  1978. * 注: 求并集的部分,详见{@link SetOps#sDifference(String, String)}
  1979. *
  1980. * @param key
  1981. * 定位"被减数set"的键
  1982. * @param otherKeys
  1983. * 定位"减数集sets"的键集
  1984. * @param storeKey
  1985. * 定位(要把差集添加到哪个)set的key
  1986. *
  1987. * @return add到(storeKey对应的)Set后, 该set对应的size
  1988. * @date 2020/3/11 14:33:36
  1989. */
  1990. public static long sDifferenceAndStore(String key, Collection<String> otherKeys, String storeKey) {
  1991. log.info("sDifferenceAndStore(...) => key -> {}, otherKeys -> {}, storeKey -> {}",
  1992. key, otherKeys, storeKey);
  1993. Long size = redisTemplate.opsForSet().differenceAndStore(key, otherKeys, storeKey);
  1994. log.info("sDifferenceAndStore(...) => size -> {}", size);
  1995. if (size == null) {
  1996. throw new RedisOpsResultIsNullException();
  1997. }
  1998. return size;
  1999. }
  2000. /**
  2001. * 获取key对应的set
  2002. *
  2003. * 注: 若key不存在, 则返回的是空的set(, 而不是null)
  2004. *
  2005. * @param key
  2006. * 定位set的key
  2007. * @return (key对应的)set
  2008. * @date 2020/3/11 14:49:39
  2009. */
  2010. public static Set<String> sMembers(String key) {
  2011. log.info("sMembers(...) => key -> {}", key);
  2012. Set<String> members = redisTemplate.opsForSet().members(key);
  2013. log.info("sMembers(...) => members -> {}", members);
  2014. return members;
  2015. }
  2016. /**
  2017. * 从key对应的set中随机获取一项
  2018. *
  2019. * @param key
  2020. * 定位set的key
  2021. * @return 随机获取到的项
  2022. * @date 2020/3/11 14:54:58
  2023. */
  2024. public static String sRandomMember(String key) {
  2025. log.info("sRandomMember(...) => key -> {}", key);
  2026. String randomItem = redisTemplate.opsForSet().randomMember(key);
  2027. log.info("sRandomMember(...) => randomItem -> {}", randomItem);
  2028. return randomItem;
  2029. }
  2030. /**
  2031. * 从key对应的set中获取count次随机项(, set中的同一个项可能被多次获取)
  2032. *
  2033. * 注: count可大于set的size。
  2034. * 注: 取出来的结果里可能存在相同的值。
  2035. *
  2036. * @param key
  2037. * 定位set的key
  2038. * @param count
  2039. * 要取多少项
  2040. *
  2041. * @return 随机获取到的项集
  2042. * @date 2020/3/11 14:54:58
  2043. */
  2044. public static List<String> sRandomMembers(String key, long count) {
  2045. log.info("sRandomMembers(...) => key -> {}, count -> {}", key, count);
  2046. List<String> randomItems = redisTemplate.opsForSet().randomMembers(key, count);
  2047. log.info("sRandomMembers(...) => randomItems -> {}", randomItems);
  2048. return randomItems;
  2049. }
  2050. /**
  2051. * 从key对应的set中随机获取count个项
  2052. *
  2053. * 注: 若count >= set的size, 那么返回的即为这个key对应的set。
  2054. * 注: 取出来的结果里没有重复的项。
  2055. *
  2056. * @param key
  2057. * 定位set的key
  2058. * @param count
  2059. * 要取多少项
  2060. *
  2061. * @return 随机获取到的项集
  2062. * @date 2020/3/11 14:54:58
  2063. */
  2064. public static Set<String> sDistinctRandomMembers(String key, long count) {
  2065. log.info("sDistinctRandomMembers(...) => key -> {}, count -> {}", key, count);
  2066. Set<String> distinctRandomItems = redisTemplate.opsForSet().distinctRandomMembers(key, count);
  2067. log.info("sDistinctRandomMembers(...) => distinctRandomItems -> {}", distinctRandomItems);
  2068. return distinctRandomItems;
  2069. }
  2070. /**
  2071. * 根据options匹配到(key对应的)set中的对应的item, 并返回对应的item集
  2072. *
  2073. *
  2074. * 注: ScanOptions实例的创建方式举例:
  2075. * 1、ScanOptions.NONE
  2076. * 2、ScanOptions.scanOptions().match("n??e").build()
  2077. *
  2078. * @param key
  2079. * 定位set的key
  2080. * @param options
  2081. * 匹配set中的item的条件
  2082. * 注: ScanOptions.NONE表示全部匹配。
  2083. * 注: ScanOptions.scanOptions().match(pattern).build()表示按照pattern匹配,
  2084. * 其中pattern中可以使用通配符 * ? 等,
  2085. * * 表示>=0个字符
  2086. * ? 表示有且只有一个字符
  2087. * 此处的匹配规则与{@link KeyOps#keys(String)}处的一样。
  2088. *
  2089. * @return 匹配到的(key对应的)set中的项
  2090. * @date 2020/3/9 10:49:27
  2091. */
  2092. public static Cursor<String> sScan(String key, ScanOptions options) {
  2093. log.info("sScan(...) => key -> {}, options -> {}", key, JSON.toJSONString(options));
  2094. Cursor<String> cursor = redisTemplate.opsForSet().scan(key, options);
  2095. log.info("sScan(...) => cursor -> {}", JSON.toJSONString(cursor));
  2096. return cursor;
  2097. }
  2098. }
  2099. /**
  2100. * ZSet相关操作
  2101. *
  2102. * 特别说明: ZSet是有序的,
  2103. * 不仅体现在: redis中的存储上有序。
  2104. * 还体现在: 此工具类ZSetOps中返回值类型为Set<?>的方法, 实际返回类型是LinkedHashSet<?>
  2105. *
  2106. * 提示: redis中的ZSet, 一定程度等于redis中的Set + redis中的Hash的结合体。
  2107. * 提示: redis中String的数据结构可参考resources/data-structure/ZSet(有序集合)的数据结构(示例一).png
  2108. * redis中String的数据结构可参考resources/data-structure/ZSet(有序集合)的数据结构(示例二).png
  2109. * 提示: ZSet中的entryKey即为成员项, entryValue即为这个成员项的分值, ZSet根据成员的分值,来堆成员进行排序。
  2110. *
  2111. * @author JustryDeng
  2112. * @date 2020/3/11 15:28:48
  2113. */
  2114. public static class ZSetOps {
  2115. /**
  2116. * 向(key对应的)zset中添加(item, score)
  2117. *
  2118. * 注: item为entryKey成员项, score为entryValue分数值。
  2119. *
  2120. * 注: 若(key对应的)zset中已存在(与此次要添加的项)相同的item项,那么此次添加操作会失败,返回false;
  2121. * 但是!!! zset中原item的score会被更新为此次add的相同item项的score。
  2122. * 所以, 也可以通过zAdd达到更新item对应score的目的。
  2123. *
  2124. * 注: score可为正、可为负、可为0; 总之, double范围内都可以。
  2125. *
  2126. * 注: 若score的值一样,则按照item排序。
  2127. *
  2128. * @param key
  2129. * 定位set的key
  2130. * @param item
  2131. * 要往(key对应的)zset中添加的成员项
  2132. * @param score
  2133. * item的分值
  2134. *
  2135. * @return 是否添加成功
  2136. * @date 2020/3/11 15:35:30
  2137. */
  2138. public static boolean zAdd(String key, String item, double score) {
  2139. log.info("zAdd(...) => key -> {}, item -> {}, score -> {}", key, item, score);
  2140. Boolean result = redisTemplate.opsForZSet().add(key, item, score);
  2141. log.info("zAdd(...) => result -> {}", result);
  2142. if (result == null) {
  2143. throw new RedisOpsResultIsNullException();
  2144. }
  2145. return result;
  2146. }
  2147. /**
  2148. * 批量添加entry<item, score>
  2149. *
  2150. * 注: 若entry<item, score>集中存在item相同的项(, score不一样),那么redis在执行真正的批量add操作前,会
  2151. * 将其中一个item过滤掉。
  2152. * 注: 同样的,若(key对应的)zset中已存在(与此次要添加的项)相同的item项,那么此次批量添加操作中,
  2153. * 对该item项的添加会失败,会失败,成功计数器不会加1;但是!!! zset中原item的score会被更新为此
  2154. * 次add的相同item项的score。所以, 也可以通过zAdd达到更新item对应score的目的。
  2155. *
  2156. * @param key
  2157. * 定位set的key
  2158. * @param entries
  2159. * 要添加的entry<item, score>集
  2160. *
  2161. * @return 本次添加进(key对应的)zset中的entry的个数
  2162. * @date 2020/3/11 16:45:45
  2163. */
  2164. public static long zAdd(String key, Set<TypedTuple<String>> entries) {
  2165. log.info("zAdd(...) => key -> {}, entries -> {}", key, JSON.toJSONString(entries));
  2166. Long count = redisTemplate.opsForZSet().add(key, entries);
  2167. log.info("zAdd(...) => count -> {}", count);
  2168. if (count == null) {
  2169. throw new RedisOpsResultIsNullException();
  2170. }
  2171. return count;
  2172. }
  2173. /**
  2174. * 从(key对应的)zset中移除项
  2175. *
  2176. * 注:若key不存在,则返回0
  2177. *
  2178. * @param key
  2179. * 定位set的key
  2180. * @param items
  2181. * 要移除的项集
  2182. *
  2183. * @return 实际移除了的项的个数
  2184. * @date 2020/3/11 17:20:12
  2185. */
  2186. public static long zRemove(String key, Object... items) {
  2187. log.info("zRemove(...) => key -> {}, items -> {}", key, items);
  2188. Long count = redisTemplate.opsForZSet().remove(key, items);
  2189. log.info("zRemove(...) => count -> {}", count);
  2190. if (count == null) {
  2191. throw new RedisOpsResultIsNullException();
  2192. }
  2193. return count;
  2194. }
  2195. /**
  2196. * 移除(key对应的)zset中, 排名范围在[startIndex, endIndex]内的item
  2197. *
  2198. * 注:默认的,按score.item升序排名, 排名从0开始
  2199. *
  2200. * 注: 类似于List中的索引, 排名可以分为多个方式:
  2201. * 从前到后(正向)的排名: 0、1、2...
  2202. * 从后到前(反向)的排名: -1、-2、-3...
  2203. *
  2204. * 注: 不论是使用正向排名,还是使用反向排名, 使用此方法时, 应保证 startRange代表的元素的位置
  2205. * 在endRange代表的元素的位置的前面, 如:
  2206. * 示例一: RedisUtil.ZSetOps.zRemoveRange("name", 0, 2);
  2207. * 示例二: RedisUtil.ZSetOps.zRemoveRange("site", -2, -1);
  2208. * 示例三: RedisUtil.ZSetOps.zRemoveRange("foo", 0, -1);
  2209. *
  2210. * 注:若key不存在,则返回0
  2211. *
  2212. * @param key
  2213. * 定位set的key
  2214. * @param startRange
  2215. * 开始项的排名
  2216. * @param endRange
  2217. * 结尾项的排名
  2218. *
  2219. * @return 实际移除了的项的个数
  2220. * @date 2020/3/11 17:20:12
  2221. */
  2222. public static long zRemoveRange(String key, long startRange, long endRange) {
  2223. log.info("zRemoveRange(...) => key -> {}, startRange -> {}, endRange -> {}",
  2224. key, startRange, endRange);
  2225. Long count = redisTemplate.opsForZSet().removeRange(key, startRange, endRange);
  2226. log.info("zRemoveRange(...) => count -> {}", count);
  2227. if (count == null) {
  2228. throw new RedisOpsResultIsNullException();
  2229. }
  2230. return count;
  2231. }
  2232. /**
  2233. * 移除(key对应的)zset中, score范围在[minScore, maxScore]内的item
  2234. *
  2235. * 提示: 虽然删除范围包含两侧的端点(即:包含minScore和maxScore), 但是由于double存在精度问题,所以建议:
  2236. * 设置值时,minScore应该设置得比要删除的项里,最小的score还小一点
  2237. * maxScore应该设置得比要删除的项里,最大的score还大一点
  2238. * 追注: 本人简单测试了几组数据,暂未出现精度问题。
  2239. *
  2240. * 注:若key不存在,则返回0
  2241. *
  2242. * @param key
  2243. * 定位set的key
  2244. * @param minScore
  2245. * score下限(含这个值)
  2246. * @param maxScore
  2247. * score上限(含这个值)
  2248. *
  2249. * @return 实际移除了的项的个数
  2250. * @date 2020/3/11 17:20:12
  2251. */
  2252. public static long zRemoveRangeByScore(String key, double minScore, double maxScore) {
  2253. log.info("zRemoveRangeByScore(...) => key -> {}, startIndex -> {}, startIndex -> {}",
  2254. key, minScore, maxScore);
  2255. Long count = redisTemplate.opsForZSet().removeRangeByScore(key, minScore, maxScore);
  2256. log.info("zRemoveRangeByScore(...) => count -> {}", count);
  2257. if (count == null) {
  2258. throw new RedisOpsResultIsNullException();
  2259. }
  2260. return count;
  2261. }
  2262. /**
  2263. * 增/减 (key对应的zset中,)item的分数值
  2264. *
  2265. * @param key
  2266. * 定位zset的key
  2267. * @param item
  2268. * 项
  2269. * @param delta
  2270. * 变化量(正 - 增, 负 - 减)
  2271. * @return 修改后的score值
  2272. * @date 2020/3/12 8:55:38
  2273. */
  2274. public static double zIncrementScore(String key, String item, double delta) {
  2275. log.info("zIncrementScore(...) => key -> {}, item -> {}, delta -> {}", key, item, delta);
  2276. Double scoreValue = redisTemplate.opsForZSet().incrementScore(key, item, delta);
  2277. log.info("zIncrementScore(...) => scoreValue -> {}", scoreValue);
  2278. if (scoreValue == null) {
  2279. throw new RedisOpsResultIsNullException();
  2280. }
  2281. return scoreValue;
  2282. }
  2283. /**
  2284. * 返回item在(key对应的)zset中的(按score从小到大的)排名
  2285. *
  2286. * 注: 排名从0开始。 即意味着,此方法等价于: 返回item在(key对应的)zset中的位置索引。
  2287. * 注: 若key或item不存在, 返回null。
  2288. * 注: 排序规则是score,item, 即:优先以score排序,若score相同,则再按item排序。
  2289. *
  2290. * @param key
  2291. * 定位zset的key
  2292. * @param item
  2293. * 项
  2294. *
  2295. * @return 排名(等价于: 索引)
  2296. * @date 2020/3/12 9:14:09
  2297. */
  2298. public static long zRank(String key, Object item) {
  2299. log.info("zRank(...) => key -> {}, item -> {}", key, item);
  2300. Long rank = redisTemplate.opsForZSet().rank(key, item);
  2301. log.info("zRank(...) => rank -> {}", rank);
  2302. if (rank == null) {
  2303. throw new RedisOpsResultIsNullException();
  2304. }
  2305. return rank;
  2306. }
  2307. /**
  2308. * 返回item在(key对应的)zset中的(按score从大到小的)排名
  2309. *
  2310. * 注: 排名从0开始。补充: 因为是按score从大到小排序的, 所以最大score对应的item的排名为0。
  2311. * 注: 若key或item不存在, 返回null。
  2312. * 注: 排序规则是score,item, 即:优先以score排序,若score相同,则再按item排序。
  2313. *
  2314. * @param key
  2315. * 定位zset的key
  2316. * @param item
  2317. * 项
  2318. *
  2319. * @return 排名(等价于: 索引)
  2320. * @date 2020/3/12 9:14:09
  2321. */
  2322. public static long zReverseRank(String key, Object item) {
  2323. log.info("zReverseRank(...) => key -> {}, item -> {}", key, item);
  2324. Long reverseRank = redisTemplate.opsForZSet().reverseRank(key, item);
  2325. log.info("zReverseRank(...) => reverseRank -> {}", reverseRank);
  2326. if (reverseRank == null) {
  2327. throw new RedisOpsResultIsNullException();
  2328. }
  2329. return reverseRank;
  2330. }
  2331. /**
  2332. * 根据索引位置, 获取(key对应的)zset中排名处于[start, end]中的item项集
  2333. *
  2334. * 注: 不论是使用正向排名,还是使用反向排名, 使用此方法时, 应保证 startIndex代表的元素的
  2335. * 位置在endIndex代表的元素的位置的前面, 如:
  2336. * 示例一: RedisUtil.ZSetOps.zRange("name", 0, 2);
  2337. * 示例二: RedisUtil.ZSetOps.zRange("site", -2, -1);
  2338. * 示例三: RedisUtil.ZSetOps.zRange("foo", 0, -1);
  2339. *
  2340. * 注: 若key不存在, 则返回空的集合。
  2341. *
  2342. * 注: 当[start, end]的范围比实际zset的范围大时, 返回范围上"交集"对应的项集合。
  2343. *
  2344. * @param key
  2345. * 定位zset的key
  2346. * @param start
  2347. * 排名开始位置
  2348. * @param end
  2349. * 排名结束位置
  2350. *
  2351. * @return 对应的item项集
  2352. * @date 2020/3/12 9:50:40
  2353. */
  2354. public static Set<String> zRange(String key, long start, long end) {
  2355. log.info("zRange(...) => key -> {}, start -> {}, end -> {}", key, start, end);
  2356. Set<String> result = redisTemplate.opsForZSet().range(key, start, end);
  2357. log.info("zRange(...) => result -> {}", result);
  2358. return result;
  2359. }
  2360. /**
  2361. * 获取(key对应的)zset中的所有item项
  2362. *
  2363. * @see ZSetOps#zRange(String, long, long)
  2364. *
  2365. * @param key
  2366. * 定位zset的键
  2367. *
  2368. * @return (key对应的)zset中的所有item项
  2369. * @date 2020/3/12 10:02:07
  2370. */
  2371. public static Set<String> zWholeZSetItem(String key) {
  2372. log.info("zWholeZSetItem(...) => key -> {}", key);
  2373. Set<String> result = redisTemplate.opsForZSet().range(key, 0, -1);
  2374. log.info("zWholeZSetItem(...) =>result -> {}", result);
  2375. return result;
  2376. }
  2377. /**
  2378. * 根据索引位置, 获取(key对应的)zset中排名处于[start, end]中的entry集
  2379. *
  2380. * 注: 不论是使用正向排名,还是使用反向排名, 使用此方法时, 应保证 startIndex代表的元素的
  2381. * 位置在endIndex代表的元素的位置的前面, 如:
  2382. * 示例一: RedisUtil.ZSetOps.zRange("name", 0, 2);
  2383. * 示例二: RedisUtil.ZSetOps.zRange("site", -2, -1);
  2384. * 示例三: RedisUtil.ZSetOps.zRange("foo", 0, -1);
  2385. *
  2386. * 注: 若key不存在, 则返回空的集合。
  2387. *
  2388. * 注: 当[start, end]的范围比实际zset的范围大时, 返回范围上"交集"对应的项集合。
  2389. *
  2390. * 注: 此方法和{@link ZSetOps#zRange(String, long, long)}类似,不过此方法返回的不是item集, 而是entry集
  2391. *
  2392. * @param key
  2393. * 定位zset的key
  2394. * @param start
  2395. * 排名开始位置
  2396. * @param end
  2397. * 排名结束位置
  2398. *
  2399. * @return 对应的entry集
  2400. * @date 2020/3/12 9:50:40
  2401. */
  2402. public static Set<TypedTuple<String>> zRangeWithScores(String key, long start, long end) {
  2403. log.info("zRangeWithScores(...) => key -> {}, start -> {}, end -> {}", key, start, end);
  2404. Set<TypedTuple<String>> entries = redisTemplate.opsForZSet().rangeWithScores(key, start, end);
  2405. log.info("zRangeWithScores(...) => entries -> {}", JSON.toJSONString(entries));
  2406. return entries;
  2407. }
  2408. /**
  2409. * 获取(key对应的)zset中的所有entry
  2410. *
  2411. * @see ZSetOps#zRangeWithScores(String, long, long)
  2412. *
  2413. * @param key
  2414. * 定位zset的键
  2415. *
  2416. * @return (key对应的)zset中的所有entry
  2417. * @date 2020/3/12 10:02:07
  2418. */
  2419. public static Set<TypedTuple<String>> zWholeZSetEntry(String key) {
  2420. log.info("zWholeZSetEntry(...) => key -> {}", key);
  2421. Set<TypedTuple<String>> entries = redisTemplate.opsForZSet().rangeWithScores(key, 0, -1);
  2422. log.info("zWholeZSetEntry(...) => entries -> {}", key, JSON.toJSONString(entries));
  2423. return entries;
  2424. }
  2425. /**
  2426. * 根据score, 获取(key对应的)zset中分数值处于[minScore, maxScore]中的item项集
  2427. *
  2428. * 注: 若key不存在, 则返回空的集合。
  2429. * 注: 当[minScore, maxScore]的范围比实际zset中score的范围大时, 返回范围上"交集"对应的项集合。
  2430. *
  2431. * 提示: 虽然删除范围包含两侧的端点(即:包含minScore和maxScore), 但是由于double存在精度问题,所以建议:
  2432. * 设置值时,minScore应该设置得比要删除的项里,最小的score还小一点
  2433. * maxScore应该设置得比要删除的项里,最大的score还大一点
  2434. * 追注: 本人简单测试了几组数据,暂未出现精度问题。
  2435. *
  2436. * @param key
  2437. * 定位zset的key
  2438. * @param minScore
  2439. * score下限
  2440. * @param maxScore
  2441. * score上限
  2442. *
  2443. * @return 对应的item项集
  2444. * @date 2020/3/12 9:50:40
  2445. */
  2446. public static Set<String> zRangeByScore(String key, double minScore, double maxScore) {
  2447. log.info("zRangeByScore(...) => key -> {}, minScore -> {}, maxScore -> {}", key, minScore, maxScore);
  2448. Set<String> items = redisTemplate.opsForZSet().rangeByScore(key, minScore, maxScore);
  2449. log.info("zRangeByScore(...) => items -> {}", items);
  2450. return items;
  2451. }
  2452. /**
  2453. * 根据score, 获取(key对应的)zset中分数值处于[minScore, maxScore]中的, score处于[minScore,
  2454. * 排名大于等于offset的count个item项
  2455. *
  2456. * 特别注意: 对于不是特别熟悉redis的人来说, offset 和 count最好都使用正数, 避免引起理解上的歧义。
  2457. *
  2458. * 注: 若key不存在, 则返回空的集合。
  2459. *
  2460. * 提示: 虽然删除范围包含两侧的端点(即:包含minScore和maxScore), 但是由于double存在精度问题,所以建议:
  2461. * 设置值时,minScore应该设置得比要删除的项里,最小的score还小一点
  2462. * maxScore应该设置得比要删除的项里,最大的score还大一点
  2463. * 追注: 本人简单测试了几组数据,暂未出现精度问题。
  2464. *
  2465. * @param key
  2466. * 定位zset的key
  2467. * @param minScore
  2468. * score下限
  2469. * @param maxScore
  2470. * score上限
  2471. * @param offset
  2472. * 偏移量(即:排名下限)
  2473. * @param count
  2474. * 期望获取到的元素个数
  2475. *
  2476. * @return 对应的item项集
  2477. * @date 2020/3/12 9:50:40
  2478. */
  2479. public static Set<String> zRangeByScore(String key, double minScore, double maxScore,
  2480. long offset, long count) {
  2481. log.info("zRangeByScore(...) => key -> {}, minScore -> {}, maxScore -> {}, offset -> {}, "
  2482. + "count -> {}", key, minScore, maxScore, offset, count);
  2483. Set<String> items = redisTemplate.opsForZSet().rangeByScore(key, minScore, maxScore, offset, count);
  2484. log.info("zRangeByScore(...) => items -> {}", items);
  2485. return items;
  2486. }
  2487. /**
  2488. * 获取(key对应的)zset中的所有score处于[minScore, maxScore]中的entry
  2489. *
  2490. * @see ZSetOps#zRangeByScore(String, double, double)
  2491. *
  2492. * 注: 若key不存在, 则返回空的集合。
  2493. * 注: 当[minScore, maxScore]的范围比实际zset中score的范围大时, 返回范围上"交集"对应的项集合。
  2494. *
  2495. * @param key
  2496. * 定位zset的键
  2497. * @param minScore
  2498. * score下限
  2499. * @param maxScore
  2500. * score上限
  2501. *
  2502. * @return (key对应的)zset中的所有score处于[minScore, maxScore]中的entry
  2503. * @date 2020/3/12 10:02:07
  2504. */
  2505. public static Set<TypedTuple<String>> zRangeByScoreWithScores(String key, double minScore, double maxScore) {
  2506. log.info("zRangeByScoreWithScores(...) => key -> {}, minScore -> {}, maxScore -> {}",
  2507. key, minScore, maxScore);
  2508. Set<TypedTuple<String>> entries = redisTemplate.opsForZSet().rangeByScoreWithScores(key, minScore, maxScore);
  2509. log.info("zRangeByScoreWithScores(...) => entries -> {}", JSON.toJSONString(entries));
  2510. return entries;
  2511. }
  2512. /**
  2513. * 获取(key对应的)zset中, score处于[minScore, maxScore]里的、排名大于等于offset的count个entry
  2514. *
  2515. * 特别注意: 对于不是特别熟悉redis的人来说, offset 和 count最好都使用正数, 避免引起理解上的歧义。
  2516. *
  2517. * @param key
  2518. * 定位zset的键
  2519. * @param minScore
  2520. * score下限
  2521. * @param maxScore
  2522. * score上限
  2523. * @param offset
  2524. * 偏移量(即:排名下限)
  2525. * @param count
  2526. * 期望获取到的元素个数
  2527. *
  2528. * @return [startIndex, endIndex] & [minScore, maxScore]里的entry
  2529. * @date 2020/3/12 11:09:06
  2530. */
  2531. public static Set<TypedTuple<String>> zRangeByScoreWithScores(String key, double minScore,
  2532. double maxScore, long offset,
  2533. long count) {
  2534. log.info("zRangeByScoreWithScores(...) => key -> {}, minScore -> {}, maxScore -> {},"
  2535. + " offset -> {}, count -> {}",
  2536. key, minScore, maxScore, offset, count);
  2537. Set<TypedTuple<String>> entries = redisTemplate.opsForZSet().rangeByScoreWithScores(key, minScore,
  2538. maxScore, offset, count);
  2539. log.info("zRangeByScoreWithScores(...) => entries -> {}", JSON.toJSONString(entries));
  2540. return entries;
  2541. }
  2542. /**
  2543. * 获取时, 先按score倒序, 然后根据索引位置, 获取(key对应的)zset中排名处于[start, end]中的item项集
  2544. *
  2545. * @see ZSetOps#zRange(String, long, long)。 只是zReverseRange这里会提前多一个倒序。
  2546. */
  2547. public static Set<String> zReverseRange(String key, long start, long end) {
  2548. log.info("zReverseRange(...) => key -> {}, start -> {}, end -> {}", key, start, end);
  2549. Set<String> entries = redisTemplate.opsForZSet().reverseRange(key, start, end);
  2550. log.info("zReverseRange(...) => entries -> {}", entries);
  2551. return entries;
  2552. }
  2553. /**
  2554. * 获取时, 先按score倒序, 然后根据索引位置, 获取(key对应的)zset中排名处于[start, end]中的entry集
  2555. *
  2556. * @see ZSetOps#zRangeWithScores(String, long, long)。 只是zReverseRangeWithScores这里会提前多一个倒序。
  2557. */
  2558. public static Set<TypedTuple<String>> zReverseRangeWithScores(String key, long start, long end) {
  2559. log.info("zReverseRangeWithScores(...) => key -> {}, start -> {}, end -> {}", key, start, end);
  2560. Set<TypedTuple<String>> entries = redisTemplate.opsForZSet().reverseRangeWithScores(key, start, end);
  2561. log.info("zReverseRangeWithScores(...) => entries -> {}", JSON.toJSONString(entries));
  2562. return entries;
  2563. }
  2564. /**
  2565. * 获取时, 先按score倒序, 然后根据score, 获取(key对应的)zset中分数值处于[minScore, maxScore]中的item项集
  2566. *
  2567. * @see ZSetOps#zRangeByScore(String, double, double)。 只是zReverseRangeByScore这里会提前多一个倒序。
  2568. */
  2569. public static Set<String> zReverseRangeByScore(String key, double minScore, double maxScore) {
  2570. log.info("zReverseRangeByScore(...) => key -> {}, minScore -> {}, maxScore -> {}",
  2571. key, minScore, maxScore);
  2572. Set<String> items = redisTemplate.opsForZSet().reverseRangeByScore(key, minScore, maxScore);
  2573. log.info("zReverseRangeByScore(...) => items -> {}", items);
  2574. return items;
  2575. }
  2576. /**
  2577. * 获取时, 先按score倒序, 然后获取(key对应的)zset中的所有score处于[minScore, maxScore]中的entry
  2578. *
  2579. * @see ZSetOps#zRangeByScoreWithScores(String, double, double)。 只是zReverseRangeByScoreWithScores这里会提前多一个倒序。
  2580. */
  2581. public static Set<TypedTuple<String>> zReverseRangeByScoreWithScores(String key, double minScore, double maxScore) {
  2582. log.info("zReverseRangeByScoreWithScores(...) => key -> {}, minScore -> {}, maxScore -> {}",
  2583. key, minScore, maxScore);
  2584. Set<TypedTuple<String>> entries = redisTemplate.opsForZSet().reverseRangeByScoreWithScores(key,
  2585. minScore, maxScore);
  2586. log.info("zReverseRangeByScoreWithScores(...) => entries -> {}", JSON.toJSONString(entries));
  2587. return entries;
  2588. }
  2589. /**
  2590. * 获取时, 先按score倒序, 然后根据score, 获取(key对应的)zset中分数值处于[minScore, maxScore]中的,
  2591. * score处于[minScore,排名大于等于offset的count个item项
  2592. *
  2593. * @see ZSetOps#zRangeByScore(String, double, double, long, long)。 只是zReverseRangeByScore这里会提前多一个倒序。
  2594. */
  2595. public static Set<String> zReverseRangeByScore(String key, double minScore, double maxScore, long offset, long count) {
  2596. log.info("zReverseRangeByScore(...) => key -> {}, minScore -> {}, maxScore -> {}, offset -> {}, "
  2597. + "count -> {}", key, minScore, maxScore, offset, count);
  2598. Set<String> items = redisTemplate.opsForZSet().reverseRangeByScore(key, minScore, maxScore, offset, count);
  2599. log.info("items -> {}", items);
  2600. return items;
  2601. }
  2602. /**
  2603. * 统计(key对应的zset中)score处于[minScore, maxScore]中的item的个数
  2604. *
  2605. * @param key
  2606. * 定位zset的key
  2607. * @param minScore
  2608. * score下限
  2609. * @param maxScore
  2610. * score上限
  2611. *
  2612. * @return [minScore, maxScore]中item的个数
  2613. * @date 2020/3/13 12:20:43
  2614. */
  2615. public static long zCount(String key, double minScore, double maxScore) {
  2616. log.info("zCount(...) => key -> {}, minScore -> {}, maxScore -> {}",key, minScore, maxScore);
  2617. Long count = redisTemplate.opsForZSet().count(key, minScore, maxScore);
  2618. log.info("zCount(...) => count -> {}", count);
  2619. if (count == null) {
  2620. throw new RedisOpsResultIsNullException();
  2621. }
  2622. return count;
  2623. }
  2624. /**
  2625. * 统计(key对应的)zset中item的个数
  2626. *
  2627. * 注: 此方法等价于{@link ZSetOps#zZCard(String)}
  2628. *
  2629. * @param key
  2630. * 定位zset的key
  2631. *
  2632. * @return zset中item的个数
  2633. * @date 2020/3/13 12:20:43
  2634. */
  2635. public static long zSize(String key) {
  2636. log.info("zSize(...) => key -> {}", key);
  2637. Long size = redisTemplate.opsForZSet().size(key);
  2638. log.info("zSize(...) => size -> {}", size);
  2639. if (size == null) {
  2640. throw new RedisOpsResultIsNullException();
  2641. }
  2642. return size;
  2643. }
  2644. /**
  2645. * 统计(key对应的)zset中item的个数
  2646. *
  2647. * 注: 此方法等价于{@link ZSetOps#zSize(String)}
  2648. *
  2649. * @param key
  2650. * 定位zset的key
  2651. *
  2652. * @return zset中item的个数
  2653. * @date 2020/3/13 12:20:43
  2654. */
  2655. public static long zZCard(String key) {
  2656. log.info("zZCard(...) => key -> {}", key);
  2657. Long size = redisTemplate.opsForZSet().zCard(key);
  2658. log.info("zZCard(...) => size -> {}", size);
  2659. if (size == null) {
  2660. throw new RedisOpsResultIsNullException();
  2661. }
  2662. return size;
  2663. }
  2664. /**
  2665. * 统计(key对应的)zset中指定item的score
  2666. *
  2667. * @param key
  2668. * 定位zset的key
  2669. * @param item
  2670. * zset中的item
  2671. *
  2672. * @return item的score
  2673. * @date 2020/3/13 14:51:43
  2674. */
  2675. public static double zScore(String key, Object item) {
  2676. log.info("zScore(...) => key -> {}, item -> {}", key, item);
  2677. Double score = redisTemplate.opsForZSet().score(key, item);
  2678. log.info("zScore(...) => score -> {}", score);
  2679. if (score == null) {
  2680. throw new RedisOpsResultIsNullException();
  2681. }
  2682. return score;
  2683. }
  2684. /**
  2685. * 获取两个(key对应的)ZSet的并集, 并将结果add到storeKey对应的ZSet中。
  2686. *
  2687. * 注: 和set一样,zset中item是唯一的, 在多个zset进行Union时, 处理相同的item时, score的值会变为对应的score之和,如:
  2688. * RedisUtil.ZSetOps.zAdd("name1", "a", 1);和RedisUtil.ZSetOps.zAdd("name2", "a", 2);
  2689. * 对(name1和name2对应的)zset进行zUnionAndStore之后,新的zset中的项a,对应的score值为3
  2690. *
  2691. * case1: 交集不为空, storeKey不存在, 则 会创建对应的storeKey,并将并集添加到(storeKey对应的)ZSet中
  2692. * case2: 交集不为空, storeKey已存在, 则 会清除原(storeKey对应的)ZSet中所有的项,然后将并集添加到(storeKey对应的)ZSet中
  2693. * case3: 交集为空, 则不进行下面的操作, 直接返回0
  2694. *
  2695. * @param key
  2696. * 定位其中一个zset的键
  2697. * @param otherKey
  2698. * 定位另外的zset的键
  2699. * @param storeKey
  2700. * 定位(要把交集添加到哪个)set的key
  2701. *
  2702. * @return add到(storeKey对应的)ZSet后, 该ZSet对应的size
  2703. * @date 2020/3/11 12:26:24
  2704. */
  2705. public static long zUnionAndStore(String key, String otherKey, String storeKey) {
  2706. log.info("zUnionAndStore(...) => key -> {}, otherKey -> {}, storeKey -> {}", key, otherKey, storeKey);
  2707. Long size = redisTemplate.opsForZSet().unionAndStore(key, otherKey, storeKey);
  2708. log.info("zUnionAndStore(...) => size -> {}", size);
  2709. if (size == null) {
  2710. throw new RedisOpsResultIsNullException();
  2711. }
  2712. return size;
  2713. }
  2714. /**
  2715. * 获取两个(key对应的)ZSet的并集, 并将结果add到storeKey对应的ZSet中。
  2716. *
  2717. * 注: 和set一样,zset中item是唯一的, 在多个zset进行Union时, 处理相同的item时, score的值会变为对应的score之和,如:
  2718. * RedisUtil.ZSetOps.zAdd("name1", "a", 1);和RedisUtil.ZSetOps.zAdd("name2", "a", 2);
  2719. * 对(name1和name2对应的)zset进行zUnionAndStore之后,新的zset中的项a,对应的score值为3
  2720. *
  2721. * case1: 并集不为空, storeKey不存在, 则 会创建对应的storeKey,并将并集添加到(storeKey对应的)ZSet中
  2722. * case2: 并集不为空, storeKey已存在, 则 会清除原(storeKey对应的)ZSet中所有的项,然后将并集添加到(storeKey对应的)ZSet中
  2723. * case3: 并集为空, 则不进行下面的操作, 直接返回0
  2724. *
  2725. * @param key
  2726. * 定位其中一个set的键
  2727. * @param otherKeys
  2728. * 定位其它set的键集
  2729. * @param storeKey
  2730. * 定位(要把并集添加到哪个)set的key
  2731. *
  2732. * @return add到(storeKey对应的)ZSet后, 该ZSet对应的size
  2733. * @date 2020/3/11 12:26:24
  2734. */
  2735. public static long zUnionAndStore(String key, Collection<String> otherKeys, String storeKey) {
  2736. log.info("zUnionAndStore(...) => key -> {}, otherKeys -> {}, storeKey -> {}", key, otherKeys, storeKey);
  2737. Long size = redisTemplate.opsForZSet().unionAndStore(key, otherKeys, storeKey);
  2738. log.info("zUnionAndStore(...) => size -> {}", size);
  2739. if (size == null) {
  2740. throw new RedisOpsResultIsNullException();
  2741. }
  2742. return size;
  2743. }
  2744. /**
  2745. * 获取两个(key对应的)ZSet的交集, 并将结果add到storeKey对应的ZSet中。
  2746. *
  2747. * 注: 和set一样,zset中item是唯一的, 在多个zset进行Intersect时, 处理相同的item时, score的值会变为对应的score之和,如:
  2748. * RedisUtil.ZSetOps.zAdd("name1", "a", 1);
  2749. * RedisUtil.ZSetOps.zAdd("name1", "b", 100);
  2750. * 和R
  2751. * edisUtil.ZSetOps.zAdd("name2", "a", 2);
  2752. * edisUtil.ZSetOps.zAdd("name2", "c", 200);
  2753. * 对(name1和name2对应的)zset进行zIntersectAndStore之后,新的zset中的项a,对应的score值为3
  2754. *
  2755. * case1: 交集不为空, storeKey不存在, 则 会创建对应的storeKey,并将交集添加到(storeKey对应的)ZSet中
  2756. * case2: 交集不为空, storeKey已存在, 则 会清除原(storeKey对应的)ZSet中所有的项,然后将交集添加到(storeKey对应的)ZSet中
  2757. * case3: 交集为空, 则不进行下面的操作, 直接返回0
  2758. *
  2759. * @param key
  2760. * 定位其中一个ZSet的键
  2761. * @param otherKey
  2762. * 定位其中另一个ZSet的键
  2763. * @param storeKey
  2764. * 定位(要把交集添加到哪个)ZSet的key
  2765. *
  2766. * @return add到(storeKey对应的)ZSet后, 该ZSet对应的size
  2767. * @date 2020/3/11 9:46:46
  2768. */
  2769. public static long zIntersectAndStore(String key, String otherKey, String storeKey) {
  2770. log.info("zIntersectAndStore(...) => key -> {}, otherKey -> {}, storeKey -> {}", key, otherKey, storeKey);
  2771. Long size = redisTemplate.opsForZSet().intersectAndStore(key, otherKey, storeKey);
  2772. log.info("zIntersectAndStore(...) => size -> {}", size);
  2773. if (size == null) {
  2774. throw new RedisOpsResultIsNullException();
  2775. }
  2776. return size;
  2777. }
  2778. /**
  2779. * 获取多个(key对应的)ZSet的交集, 并将结果add到storeKey对应的ZSet中。
  2780. *
  2781. * case1: 交集不为空, storeKey不存在, 则 会创建对应的storeKey,并将交集添加到(storeKey对应的)ZSet中
  2782. * case2: 交集不为空, storeKey已存在, 则 会清除原(storeKey对应的)ZSet中所有的项,然后将交集添加到(storeKey对应的)ZSet中
  2783. * case3: 交集为空, 则不进行下面的操作, 直接返回0
  2784. *
  2785. * @param key
  2786. * 定位其中一个set的键
  2787. * @param otherKeys
  2788. * 定位其它set的键集
  2789. * @param storeKey
  2790. * 定位(要把并集添加到哪个)set的key
  2791. *
  2792. * @return add到(storeKey对应的)ZSet后, 该ZSet对应的size
  2793. * @date 2020/3/11 11:04:29
  2794. */
  2795. public static long zIntersectAndStore(String key, Collection<String> otherKeys, String storeKey) {
  2796. log.info("zIntersectAndStore(...) => key -> {}, otherKeys -> {}, storeKey -> {}",
  2797. key, otherKeys, storeKey);
  2798. Long size = redisTemplate.opsForZSet().intersectAndStore(key, otherKeys, storeKey);
  2799. log.info("zIntersectAndStore(...) => size -> {}", size);
  2800. if (size == null) {
  2801. throw new RedisOpsResultIsNullException();
  2802. }
  2803. return size;
  2804. }
  2805. }
  2806. /**
  2807. * redis分布式锁.
  2808. *
  2809. * 使用方式(示例):
  2810. * boolean flag = false;
  2811. * String lockName = "sichuan:mianyang:fucheng:ds";
  2812. * String lockValue = UUID.randomUUID().toString();
  2813. * try {
  2814. * // 非阻塞获取(锁的最大存活时间采用默认值)
  2815. * flag = RedisUtil.LockOps.getLock(lockName, lockValue);
  2816. * // 非阻塞获取e.g.
  2817. * flag = RedisUtil.LockOps.getLock(lockName, lockValue, 3, TimeUnit.SECONDS);
  2818. * // 阻塞获取(锁的最大存活时间采用默认值)
  2819. * flag = RedisUtil.LockOps.getLockUntilTimeout(lockName, lockValue, 2000);
  2820. * // 阻塞获取e.g.
  2821. * flag = RedisUtil.LockOps.getLockUntilTimeout(lockName, lockValue, 2, TimeUnit.SECONDS, 2000);
  2822. * // your logic
  2823. * // ...
  2824. * } finally {
  2825. * if (flag) {
  2826. * RedisUtil.LockOps.releaseLock(lockName, lockValue);
  2827. * }
  2828. * }
  2829. *
  2830. * @author JustryDeng
  2831. * @date 2020/3/14 19:23:26
  2832. */
  2833. public static class LockOps {
  2834. /** lua脚本, 保证 释放锁脚本 的原子性(以避免, 并发场景下, 释放了别人的锁) */
  2835. private static final String RELEASE_LOCK_LUA;
  2836. /** 分布式锁默认(最大)存活时长 */
  2837. public static final long DEFAULT_LOCK_TIMEOUT = 3;
  2838. /** DEFAULT_LOCK_TIMEOUT的单位 */
  2839. public static final TimeUnit DEFAULT_TIMEOUT_UNIT = TimeUnit.SECONDS;
  2840. static {
  2841. // 不论lua中0是否代表失败; 对于java的Boolean而言, 返回0, 则会被解析为false
  2842. RELEASE_LOCK_LUA = "if redis.call('get',KEYS[1]) == ARGV[1] "
  2843. + "then "
  2844. + " return redis.call('del',KEYS[1]) "
  2845. + "else "
  2846. + " return 0 "
  2847. + "end ";
  2848. }
  2849. /**
  2850. * 获取(分布式)锁.
  2851. *
  2852. * 注: 获取结果是即时返回的、是非阻塞的。
  2853. *
  2854. * @see RedisUtil.LockOps#getLock(String, String, long, TimeUnit)
  2855. */
  2856. public static boolean getLock(final String key, final String value) {
  2857. return getLock(key, value, DEFAULT_LOCK_TIMEOUT, DEFAULT_TIMEOUT_UNIT);
  2858. }
  2859. /**
  2860. * 获取(分布式)锁。
  2861. * 若成功, 则直接返回;
  2862. * 若失败, 则进行重试, 直到成功 或 超时为止。
  2863. *
  2864. * 注: 获取结果是阻塞的, 要么成功, 要么超时, 才返回。
  2865. *
  2866. * @param retryTimeoutLimit
  2867. * 重试的超时时长(ms)
  2868. * 其它参数可详见:
  2869. * @see RedisUtil.LockOps#getLock(String, String, long, TimeUnit)
  2870. *
  2871. * @return 是否成功
  2872. */
  2873. public static boolean getLockUntilTimeout(final String key, final String value,
  2874. final long retryTimeoutLimit) {
  2875. return getLockUntilTimeout(key, value, DEFAULT_LOCK_TIMEOUT, DEFAULT_TIMEOUT_UNIT, retryTimeoutLimit);
  2876. }
  2877. /**
  2878. * 获取(分布式)锁。
  2879. * 若成功, 则直接返回;
  2880. * 若失败, 则进行重试, 直到成功 或 超时为止。
  2881. *
  2882. * 注: 获取结果是阻塞的, 要么成功, 要么超时, 才返回。
  2883. *
  2884. * @param retryTimeoutLimit
  2885. * 重试的超时时长(ms)
  2886. * 其它参数可详见:
  2887. * @see RedisUtil.LockOps#getLock(String, String, long, TimeUnit, boolean)
  2888. *
  2889. * @return 是否成功
  2890. */
  2891. public static boolean getLockUntilTimeout(final String key, final String value,
  2892. final long timeout, final TimeUnit unit,
  2893. final long retryTimeoutLimit) {
  2894. log.info("getLockUntilTimeout(...) => key -> {}, value -> {}, timeout -> {}, unit -> {}, "
  2895. + "retryTimeoutLimit -> {}ms", key, value, timeout, unit, retryTimeoutLimit);
  2896. long startTime = Instant.now().toEpochMilli();
  2897. long now = startTime;
  2898. do {
  2899. try {
  2900. boolean alreadyGotLock = getLock(key, value, timeout, unit, false);
  2901. if (alreadyGotLock) {
  2902. log.info("getLockUntilTimeout(...) => consume time -> {}ms, result -> true", now - startTime);
  2903. return true;
  2904. }
  2905. } catch (Exception e) {
  2906. log.warn("getLockUntilTimeout(...) => try to get lock failure! e.getMessage -> {}",
  2907. e.getMessage());
  2908. }
  2909. now = Instant.now().toEpochMilli();
  2910. } while (now < startTime + retryTimeoutLimit);
  2911. log.info("getLockUntilTimeout(...) => consume time -> {}ms, result -> false", now - startTime);
  2912. return false;
  2913. }
  2914. /**
  2915. * 获取(分布式)锁
  2916. *
  2917. * 注: 获取结果是即时返回的、是非阻塞的。
  2918. *
  2919. * @see RedisUtil.LockOps#getLock(String, String, long, TimeUnit, boolean)
  2920. */
  2921. public static boolean getLock(final String key, final String value,
  2922. final long timeout, final TimeUnit unit) {
  2923. return getLock(key, value, timeout, unit, true);
  2924. }
  2925. /**
  2926. * 获取(分布式)锁
  2927. *
  2928. * 注: 获取结果是即时返回的、是非阻塞的。
  2929. *
  2930. * @param key
  2931. * 锁名
  2932. * @param value
  2933. * 锁名对应的value
  2934. * 注: value一般采用全局唯一的值, 如: requestId、uuid等。
  2935. * 这样, 释放锁的时候, 可以再次验证value值,
  2936. * 保证自己上的锁只能被自己释放, 而不会被别人释放。
  2937. * 当然, 如果锁超时时, 会被redis自动删除释放。
  2938. * @param timeout
  2939. * 锁的(最大)存活时长
  2940. * 注: 一般的, 获取锁与释放锁 都是成对使用的, 在锁在达到(最大)存活时长之前,都会被主动释放。
  2941. * 但是在某些情况下(如:程序获取锁后,释放锁前,崩了),锁得不到释放, 这时就需要等锁过
  2942. * 了(最大)存活时长后,被redis自动删除清理了。这样就能保证redis中不会留下死数据。
  2943. * @param unit
  2944. * timeout的单位
  2945. * @param recordLog
  2946. * 是否记录日志
  2947. *
  2948. * @return 是否成功
  2949. */
  2950. public static boolean getLock(final String key, final String value,
  2951. final long timeout, final TimeUnit unit,
  2952. boolean recordLog) {
  2953. if (recordLog) {
  2954. log.info("getLock(...) => key -> {}, value -> {}, timeout -> {}, unit -> {}, recordLog -> {}",
  2955. key, value, timeout, unit, recordLog);
  2956. }
  2957. Boolean result = redisTemplate.execute((RedisConnection connection) ->
  2958. connection.set(key.getBytes(StandardCharsets.UTF_8),
  2959. value.getBytes(StandardCharsets.UTF_8),
  2960. Expiration.seconds(unit.toSeconds(timeout)),
  2961. RedisStringCommands.SetOption.SET_IF_ABSENT)
  2962. );
  2963. if (recordLog) {
  2964. log.info("getLock(...) => result -> {}", result);
  2965. }
  2966. if (result == null) {
  2967. throw new RedisOpsResultIsNullException();
  2968. }
  2969. return result;
  2970. }
  2971. /**
  2972. * 释放(分布式)锁
  2973. *
  2974. * 注: 此方式能(通过value的唯一性)保证: 自己加的锁, 只能被自己释放。
  2975. * 注: 锁超时时, 也会被redis自动删除释放。
  2976. *
  2977. * @param key
  2978. * 锁名
  2979. * @param value
  2980. * 锁名对应的value
  2981. *
  2982. * @return 释放锁是否成功
  2983. * @date 2020/3/15 17:00:45
  2984. */
  2985. public static boolean releaseLock(final String key, final String value) {
  2986. log.info("releaseLock(...) => key -> {}, lockValue -> {}", key, value);
  2987. Boolean result = redisTemplate.execute((RedisConnection connection) ->
  2988. connection.eval(RELEASE_LOCK_LUA.getBytes(),
  2989. ReturnType.BOOLEAN ,1,
  2990. key.getBytes(StandardCharsets.UTF_8), value.getBytes(StandardCharsets.UTF_8))
  2991. );
  2992. log.info("releaseLock(...) => result -> {}", result);
  2993. if (result == null) {
  2994. throw new RedisOpsResultIsNullException();
  2995. }
  2996. return result;
  2997. }
  2998. /**
  2999. * 释放锁, 不校验该key对应的value值
  3000. *
  3001. * 注: 此方式释放锁,可能导致: 自己加的锁, 结果被别人释放了。
  3002. * 所以不建议使用此方式释放锁。
  3003. *
  3004. * @param key
  3005. * 锁名
  3006. * @date 2020/3/15 18:56:59
  3007. */
  3008. @Deprecated
  3009. public static void releaseLock(final String key) {
  3010. RedisUtil.KeyOps.delete(key);
  3011. }
  3012. }
  3013. /**
  3014. * 当使用Pipeline 或 Transaction操作redis时, (不论redis中实际操作是否成功, 这里)结果(都)会返回null。
  3015. * 此时,如果试着将null转换为基本类型的数据时,会抛出此异常。
  3016. *
  3017. * 即: 此工具类中的某些方法, 希望不要使用Pipeline或Transaction操作redis。
  3018. *
  3019. * 注: Pipeline 或 Transaction默认是不启用的, 可详见源码:
  3020. * @see LettuceConnection#isPipelined()
  3021. * @see LettuceConnection#isQueueing()
  3022. * @see JedisConnection#isPipelined()
  3023. * @see JedisConnection#isQueueing()
  3024. *
  3025. * @author JustryDeng
  3026. * @date 2020/3/14 21:22:39
  3027. */
  3028. public static class RedisOpsResultIsNullException extends NullPointerException {
  3029. public RedisOpsResultIsNullException() {
  3030. super();
  3031. }
  3032. public RedisOpsResultIsNullException(String message) {
  3033. super(message);
  3034. }
  3035. }
  3036. /**
  3037. * 提供一些基础功能支持
  3038. *
  3039. * @author JustryDeng
  3040. * @date 2020/3/16 0:48:14
  3041. */
  3042. public static class Helper {
  3043. /** 默认拼接符 */
  3044. public static final String DEFAULT_SYMBOL = ":";
  3045. /**
  3046. * 拼接args
  3047. *
  3048. * @see Helper#joinBySymbol(String, String...)
  3049. */
  3050. public static String join(String... args) {
  3051. return Helper.joinBySymbol(DEFAULT_SYMBOL, args);
  3052. }
  3053. /**
  3054. * 使用symbol拼接args
  3055. *
  3056. * @param symbol
  3057. * 分隔符, 如: 【:】
  3058. * @param args
  3059. * 要拼接的元素数组, 如: 【a b c】
  3060. *
  3061. * @return 拼接后的字符串, 如 【a:b:c】
  3062. * @date 2019/9/8 16:11
  3063. */
  3064. public static String joinBySymbol(String symbol, String... args) {
  3065. if (symbol == null || symbol.trim().length() == 0) {
  3066. throw new RuntimeException(" symbol must not be empty!");
  3067. }
  3068. if (args == null || args.length == 0) {
  3069. throw new RuntimeException(" args must not be empty!");
  3070. }
  3071. StringBuilder sb = new StringBuilder(16);
  3072. for (String arg : args) {
  3073. sb.append(arg).append(symbol);
  3074. }
  3075. sb.replace(sb.length() - symbol.length(), sb.length(), "");
  3076. return sb.toString();
  3077. }
  3078. }
  3079. }
声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/weixin_40725706/article/detail/240534
推荐阅读
相关标签
  

闽ICP备14008679号