mirror of
https://github.com/chillzhuang/blade-tool
synced 2024-12-12 12:19:27 +08:00
🎉 新增redis模块,提供BladeRedis新版工具,支持redis pub/sub 发布
This commit is contained in:
parent
ae90973dd4
commit
9d7400f381
@ -67,6 +67,10 @@
|
||||
<groupId>org.springblade</groupId>
|
||||
<artifactId>blade-starter-tenant</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springblade</groupId>
|
||||
<artifactId>blade-starter-redis</artifactId>
|
||||
</dependency>
|
||||
<!--MyBatis-->
|
||||
<dependency>
|
||||
<groupId>org.springblade</groupId>
|
||||
|
@ -22,19 +22,13 @@ import org.springframework.boot.autoconfigure.AutoConfigureBefore;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
||||
import org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration;
|
||||
import org.springframework.cache.CacheManager;
|
||||
import org.springframework.cache.annotation.EnableCaching;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.data.redis.cache.RedisCacheConfiguration;
|
||||
import org.springframework.data.redis.cache.RedisCacheManager;
|
||||
import org.springframework.data.redis.cache.RedisCacheWriter;
|
||||
import org.springframework.data.redis.connection.RedisConnectionFactory;
|
||||
import org.springframework.data.redis.core.RedisTemplate;
|
||||
import org.springframework.data.redis.serializer.JdkSerializationRedisSerializer;
|
||||
import org.springframework.data.redis.serializer.RedisSerializer;
|
||||
|
||||
import java.time.Duration;
|
||||
|
||||
/**
|
||||
* RedisTemplate 配置
|
||||
*
|
||||
@ -43,7 +37,7 @@ import java.time.Duration;
|
||||
@EnableCaching
|
||||
@AutoConfiguration
|
||||
@AutoConfigureBefore(RedisAutoConfiguration.class)
|
||||
public class RedisTemplateConfiguration {
|
||||
public class RedisConfiguration {
|
||||
|
||||
/**
|
||||
* value 值 序列化
|
||||
@ -71,15 +65,6 @@ public class RedisTemplateConfiguration {
|
||||
return redisTemplate;
|
||||
}
|
||||
|
||||
@Bean
|
||||
public CacheManager cacheManager(RedisConnectionFactory redisConnectionFactory) {
|
||||
RedisCacheConfiguration redisCacheConfiguration = RedisCacheConfiguration.defaultCacheConfig()
|
||||
.entryTtl(Duration.ofHours(1));
|
||||
return RedisCacheManager
|
||||
.builder(RedisCacheWriter.nonLockingRedisCacheWriter(redisConnectionFactory))
|
||||
.cacheDefaults(redisCacheConfiguration).build();
|
||||
}
|
||||
|
||||
@Bean(name = "redisUtil")
|
||||
@ConditionalOnBean(RedisTemplate.class)
|
||||
public RedisUtil redisUtils(RedisTemplate<String, Object> redisTemplate) {
|
@ -25,6 +25,7 @@ import java.util.concurrent.Callable;
|
||||
* 缓存工具类
|
||||
*
|
||||
* @author Chill
|
||||
* @deprecated in favor of {org.springblade.core.cache.utils.CacheUtil}.
|
||||
*/
|
||||
@Deprecated
|
||||
public class CacheUtil {
|
||||
|
@ -20,6 +20,7 @@ import org.springframework.util.CollectionUtils;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
@ -79,4 +80,26 @@ public class CollectionUtil extends org.springframework.util.CollectionUtils {
|
||||
return !CollectionUtils.isEmpty(map);
|
||||
}
|
||||
|
||||
/**
|
||||
* 将key value 数组转为 map
|
||||
*
|
||||
* @param keysValues key value 数组
|
||||
* @param <K> key
|
||||
* @param <V> value
|
||||
* @return map 集合
|
||||
*/
|
||||
public static <K, V> Map<K, V> toMap(Object... keysValues) {
|
||||
int kvLength = keysValues.length;
|
||||
if (kvLength % 2 != 0) {
|
||||
throw new IllegalArgumentException("wrong number of arguments for met, keysValues length can not be odd");
|
||||
}
|
||||
Map<K, V> keyValueMap = new HashMap<>(kvLength);
|
||||
for (int i = kvLength - 2; i >= 0; i -= 2) {
|
||||
Object key = keysValues[i];
|
||||
Object value = keysValues[i + 1];
|
||||
keyValueMap.put((K) key, (V) value);
|
||||
}
|
||||
return keyValueMap;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -13,7 +13,9 @@ import java.util.concurrent.TimeUnit;
|
||||
* Redis工具类
|
||||
*
|
||||
* @author Chill
|
||||
* @deprecated in favor of {org.springblade.core.redis.cache.BladeRedis}.
|
||||
*/
|
||||
@Deprecated
|
||||
@AllArgsConstructor
|
||||
public class RedisUtil {
|
||||
|
||||
|
46
blade-starter-redis/pom.xml
Normal file
46
blade-starter-redis/pom.xml
Normal file
@ -0,0 +1,46 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<parent>
|
||||
<artifactId>blade-tool</artifactId>
|
||||
<groupId>org.springblade</groupId>
|
||||
<version>${revision}</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<artifactId>blade-starter-redis</artifactId>
|
||||
<name>${project.artifactId}</name>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<properties>
|
||||
<module.name>org.springblade.blade.starter.redis</module.name>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
<!--Blade-->
|
||||
<dependency>
|
||||
<groupId>org.springblade</groupId>
|
||||
<artifactId>blade-core-tool</artifactId>
|
||||
</dependency>
|
||||
<!--Redis-->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-data-redis</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.redisson</groupId>
|
||||
<artifactId>redisson</artifactId>
|
||||
</dependency>
|
||||
<!-- protostuff -->
|
||||
<dependency>
|
||||
<groupId>io.protostuff</groupId>
|
||||
<artifactId>protostuff-core</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.protostuff</groupId>
|
||||
<artifactId>protostuff-runtime</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
876
blade-starter-redis/src/main/java/org/springblade/core/redis/cache/BladeRedis.java
vendored
Normal file
876
blade-starter-redis/src/main/java/org/springblade/core/redis/cache/BladeRedis.java
vendored
Normal file
@ -0,0 +1,876 @@
|
||||
/**
|
||||
* Copyright (c) 2018-2099, DreamLu 卢春梦 (qq596392912@gmail.com).
|
||||
* <p>
|
||||
* Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* <p>
|
||||
* http://www.gnu.org/licenses/lgpl.html
|
||||
* <p>
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springblade.core.redis.cache;
|
||||
|
||||
import lombok.Getter;
|
||||
import org.springblade.core.tool.utils.CollectionUtil;
|
||||
import org.springblade.core.tool.utils.NumberUtil;
|
||||
import org.springframework.data.redis.core.*;
|
||||
import org.springframework.data.redis.serializer.RedisSerializer;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
/**
|
||||
* redis 工具
|
||||
*
|
||||
* @author L.cm
|
||||
*/
|
||||
@Getter
|
||||
@SuppressWarnings("unchecked")
|
||||
public class BladeRedis {
|
||||
private final RedisTemplate<String, Object> redisTemplate;
|
||||
private final StringRedisTemplate stringRedisTemplate;
|
||||
private final ValueOperations<String, Object> valueOps;
|
||||
private final HashOperations<String, Object, Object> hashOps;
|
||||
private final ListOperations<String, Object> listOps;
|
||||
private final SetOperations<String, Object> setOps;
|
||||
private final ZSetOperations<String, Object> zSetOps;
|
||||
|
||||
public BladeRedis(RedisTemplate<String, Object> redisTemplate, StringRedisTemplate stringRedisTemplate) {
|
||||
this.redisTemplate = redisTemplate;
|
||||
this.stringRedisTemplate = stringRedisTemplate;
|
||||
Assert.notNull(redisTemplate, "redisTemplate is null");
|
||||
valueOps = redisTemplate.opsForValue();
|
||||
hashOps = redisTemplate.opsForHash();
|
||||
listOps = redisTemplate.opsForList();
|
||||
setOps = redisTemplate.opsForSet();
|
||||
zSetOps = redisTemplate.opsForZSet();
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置缓存
|
||||
*
|
||||
* @param cacheKey 缓存key
|
||||
* @param value 缓存value
|
||||
*/
|
||||
public void set(CacheKey cacheKey, Object value) {
|
||||
String key = cacheKey.getKey();
|
||||
Duration expire = cacheKey.getExpire();
|
||||
if (expire == null) {
|
||||
set(key, value);
|
||||
} else {
|
||||
setEx(key, value, expire);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 存放 key value 对到 redis。
|
||||
*/
|
||||
public void set(String key, Object value) {
|
||||
valueOps.set(key, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* 存放 key value 对到 redis,并将 key 的生存时间设为 seconds (以秒为单位)。
|
||||
* 如果 key 已经存在, SETEX 命令将覆写旧值。
|
||||
*/
|
||||
public void setEx(String key, Object value, Duration timeout) {
|
||||
valueOps.set(key, value, timeout);
|
||||
}
|
||||
|
||||
/**
|
||||
* 存放 key value 对到 redis,并将 key 的生存时间设为 seconds (以秒为单位)。
|
||||
* 如果 key 已经存在, SETEX 命令将覆写旧值。
|
||||
*/
|
||||
public void setEx(String key, Object value, Long seconds) {
|
||||
valueOps.set(key, value, seconds, TimeUnit.SECONDS);
|
||||
}
|
||||
|
||||
/**
|
||||
* 存放 key value 对到 redis,并将 key 的生存时间设为自定义单位。
|
||||
* 如果 key 已经存在, SETEX 命令将覆写旧值。
|
||||
*/
|
||||
public void setEx(String key, Object value, long timeout, TimeUnit unit) {
|
||||
valueOps.set(key, value, timeout, unit);
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回 key 所关联的 value 值
|
||||
* 如果 key 不存在那么返回特殊值 nil 。
|
||||
*/
|
||||
@Nullable
|
||||
public <T> T get(String key) {
|
||||
return (T) valueOps.get(key);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取cache 为 null 时使用加载器,然后设置缓存
|
||||
*
|
||||
* @param key cacheKey
|
||||
* @param loader cache loader
|
||||
* @param <T> 泛型
|
||||
* @return 结果
|
||||
*/
|
||||
@Nullable
|
||||
public <T> T get(String key, Supplier<T> loader) {
|
||||
T value = this.get(key);
|
||||
if (value != null) {
|
||||
return value;
|
||||
}
|
||||
value = loader.get();
|
||||
if (value == null) {
|
||||
return null;
|
||||
}
|
||||
this.set(key, value);
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回 key 所关联的 value 值
|
||||
* 如果 key 不存在那么返回特殊值 nil 。
|
||||
*/
|
||||
@Nullable
|
||||
public <T> T get(CacheKey cacheKey) {
|
||||
return (T) valueOps.get(cacheKey.getKey());
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取cache 为 null 时使用加载器,然后设置缓存
|
||||
*
|
||||
* @param cacheKey cacheKey
|
||||
* @param loader cache loader
|
||||
* @param <T> 泛型
|
||||
* @return 结果
|
||||
*/
|
||||
@Nullable
|
||||
public <T> T get(CacheKey cacheKey, Supplier<T> loader) {
|
||||
String key = cacheKey.getKey();
|
||||
T value = this.get(key);
|
||||
if (value != null) {
|
||||
return value;
|
||||
}
|
||||
value = loader.get();
|
||||
if (value == null) {
|
||||
return null;
|
||||
}
|
||||
this.set(cacheKey, value);
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除给定的一个 key
|
||||
* 不存在的 key 会被忽略。
|
||||
*/
|
||||
public Boolean del(String key) {
|
||||
return redisTemplate.delete(key);
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除给定的一个 key
|
||||
* 不存在的 key 会被忽略。
|
||||
*/
|
||||
public Boolean del(CacheKey key) {
|
||||
return redisTemplate.delete(key.getKey());
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除给定的多个 key
|
||||
* 不存在的 key 会被忽略。
|
||||
*/
|
||||
public Long del(String... keys) {
|
||||
return del(Arrays.asList(keys));
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除给定的多个 key
|
||||
* 不存在的 key 会被忽略。
|
||||
*/
|
||||
public Long del(Collection<String> keys) {
|
||||
return redisTemplate.delete(keys);
|
||||
}
|
||||
|
||||
/**
|
||||
* 查找所有符合给定模式 pattern 的 key 。
|
||||
* KEYS * 匹配数据库中所有 key 。
|
||||
* KEYS h?llo 匹配 hello , hallo 和 hxllo 等。
|
||||
* KEYS h*llo 匹配 hllo 和 heeeeello 等。
|
||||
* KEYS h[ae]llo 匹配 hello 和 hallo ,但不匹配 hillo 。
|
||||
* 特殊符号用 \ 隔开
|
||||
*/
|
||||
public Set<String> keys(String pattern) {
|
||||
return redisTemplate.keys(pattern);
|
||||
}
|
||||
|
||||
/**
|
||||
* 同时设置一个或多个 key-value 对。
|
||||
* 如果某个给定 key 已经存在,那么 MSET 会用新值覆盖原来的旧值,如果这不是你所希望的效果,请考虑使用 MSETNX 命令:它只会在所有给定 key 都不存在的情况下进行设置操作。
|
||||
* MSET 是一个原子性(atomic)操作,所有给定 key 都会在同一时间内被设置,某些给定 key 被更新而另一些给定 key 没有改变的情况,不可能发生。
|
||||
* <pre>
|
||||
* 例子:
|
||||
* Cache cache = RedisKit.use(); // 使用 Redis 的 cache
|
||||
* cache.mset("k1", "v1", "k2", "v2"); // 放入多个 key value 键值对
|
||||
* List list = cache.mget("k1", "k2"); // 利用多个键值得到上面代码放入的值
|
||||
* </pre>
|
||||
*/
|
||||
public void mSet(Object... keysValues) {
|
||||
valueOps.multiSet(CollectionUtil.toMap(keysValues));
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回所有(一个或多个)给定 key 的值。
|
||||
* 如果给定的 key 里面,有某个 key 不存在,那么这个 key 返回特殊值 nil 。因此,该命令永不失败。
|
||||
*/
|
||||
public List<Object> mGet(String... keys) {
|
||||
return mGet(Arrays.asList(keys));
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回所有(一个或多个)给定 key 的值。
|
||||
* 如果给定的 key 里面,有某个 key 不存在,那么这个 key 返回特殊值 nil 。因此,该命令永不失败。
|
||||
*/
|
||||
public List<Object> mGet(Collection<String> keys) {
|
||||
return valueOps.multiGet(keys);
|
||||
}
|
||||
|
||||
/**
|
||||
* 将 key 中储存的数字值减一。
|
||||
* 如果 key 不存在,那么 key 的值会先被初始化为 0 ,然后再执行 DECR 操作。
|
||||
* 如果值包含错误的类型,或字符串类型的值不能表示为数字,那么返回一个错误。
|
||||
* 本操作的值限制在 64 位(bit)有符号数字表示之内。
|
||||
* 关于递增(increment) / 递减(decrement)操作的更多信息,请参见 INCR 命令。
|
||||
*/
|
||||
public Long decr(String key) {
|
||||
return stringRedisTemplate.opsForValue().decrement(key);
|
||||
}
|
||||
|
||||
/**
|
||||
* 将 key 所储存的值减去减量 decrement 。
|
||||
* 如果 key 不存在,那么 key 的值会先被初始化为 0 ,然后再执行 DECRBY 操作。
|
||||
* 如果值包含错误的类型,或字符串类型的值不能表示为数字,那么返回一个错误。
|
||||
* 本操作的值限制在 64 位(bit)有符号数字表示之内。
|
||||
* 关于更多递增(increment) / 递减(decrement)操作的更多信息,请参见 INCR 命令。
|
||||
*/
|
||||
public Long decrBy(String key, long longValue) {
|
||||
return stringRedisTemplate.opsForValue().decrement(key, longValue);
|
||||
}
|
||||
|
||||
/**
|
||||
* 将 key 中储存的数字值增一。
|
||||
* 如果 key 不存在,那么 key 的值会先被初始化为 0 ,然后再执行 INCR 操作。
|
||||
* 如果值包含错误的类型,或字符串类型的值不能表示为数字,那么返回一个错误。
|
||||
* 本操作的值限制在 64 位(bit)有符号数字表示之内。
|
||||
*/
|
||||
public Long incr(String key) {
|
||||
return stringRedisTemplate.opsForValue().increment(key);
|
||||
}
|
||||
|
||||
/**
|
||||
* 将 key 所储存的值加上增量 increment 。
|
||||
* 如果 key 不存在,那么 key 的值会先被初始化为 0 ,然后再执行 INCRBY 命令。
|
||||
* 如果值包含错误的类型,或字符串类型的值不能表示为数字,那么返回一个错误。
|
||||
* 本操作的值限制在 64 位(bit)有符号数字表示之内。
|
||||
* 关于递增(increment) / 递减(decrement)操作的更多信息,参见 INCR 命令。
|
||||
*/
|
||||
public Long incrBy(String key, long longValue) {
|
||||
return stringRedisTemplate.opsForValue().increment(key, longValue);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据 key 获取递减的参数值
|
||||
*/
|
||||
public Long getDecr(String key) {
|
||||
return NumberUtil.toLong(stringRedisTemplate.opsForValue().get(key));
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据 key 获取递增的参数值
|
||||
*/
|
||||
public Long getIncr(String key) {
|
||||
return NumberUtil.toLong(stringRedisTemplate.opsForValue().get(key));
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取记数器的值
|
||||
*/
|
||||
public Long getCounter(String key) {
|
||||
return Long.valueOf(String.valueOf(valueOps.get(key)));
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查给定 key 是否存在。
|
||||
*/
|
||||
public Boolean exists(String key) {
|
||||
return redisTemplate.hasKey(key);
|
||||
}
|
||||
|
||||
/**
|
||||
* 从当前数据库中随机返回(不删除)一个 key 。
|
||||
*/
|
||||
public String randomKey() {
|
||||
return redisTemplate.randomKey();
|
||||
}
|
||||
|
||||
/**
|
||||
* 将 key 改名为 newkey 。
|
||||
* 当 key 和 newkey 相同,或者 key 不存在时,返回一个错误。
|
||||
* 当 newkey 已经存在时, RENAME 命令将覆盖旧值。
|
||||
*/
|
||||
public void rename(String oldkey, String newkey) {
|
||||
redisTemplate.rename(oldkey, newkey);
|
||||
}
|
||||
|
||||
/**
|
||||
* 将当前数据库的 key 移动到给定的数据库 db 当中。
|
||||
* 如果当前数据库(源数据库)和给定数据库(目标数据库)有相同名字的给定 key ,或者 key 不存在于当前数据库,那么 MOVE 没有任何效果。
|
||||
* 因此,也可以利用这一特性,将 MOVE 当作锁(locking)原语(primitive)。
|
||||
*/
|
||||
public Boolean move(String key, int dbIndex) {
|
||||
return redisTemplate.move(key, dbIndex);
|
||||
}
|
||||
|
||||
/**
|
||||
* 为给定 key 设置生存时间,当 key 过期时(生存时间为 0 ),它会被自动删除。
|
||||
* 在 Redis 中,带有生存时间的 key 被称为『易失的』(volatile)。
|
||||
*/
|
||||
public Boolean expire(String key, long seconds) {
|
||||
return redisTemplate.expire(key, seconds, TimeUnit.SECONDS);
|
||||
}
|
||||
|
||||
/**
|
||||
* 为给定 key 设置生存时间,当 key 过期时(生存时间为 0 ),它会被自动删除。
|
||||
* 在 Redis 中,带有生存时间的 key 被称为『易失的』(volatile)。
|
||||
*/
|
||||
public Boolean expire(String key, Duration timeout) {
|
||||
return expire(key, timeout.getSeconds());
|
||||
}
|
||||
|
||||
/**
|
||||
* EXPIREAT 的作用和 EXPIRE 类似,都用于为 key 设置生存时间。不同在于 EXPIREAT 命令接受的时间参数是 UNIX 时间戳(unix timestamp)。
|
||||
*/
|
||||
public Boolean expireAt(String key, Date date) {
|
||||
return redisTemplate.expireAt(key, date);
|
||||
}
|
||||
|
||||
/**
|
||||
* EXPIREAT 的作用和 EXPIRE 类似,都用于为 key 设置生存时间。不同在于 EXPIREAT 命令接受的时间参数是 UNIX 时间戳(unix timestamp)。
|
||||
*/
|
||||
public Boolean expireAt(String key, long unixTime) {
|
||||
return expireAt(key, new Date(unixTime));
|
||||
}
|
||||
|
||||
/**
|
||||
* 这个命令和 EXPIRE 命令的作用类似,但是它以毫秒为单位设置 key 的生存时间,而不像 EXPIRE 命令那样,以秒为单位。
|
||||
*/
|
||||
public Boolean pexpire(String key, long milliseconds) {
|
||||
return redisTemplate.expire(key, milliseconds, TimeUnit.MILLISECONDS);
|
||||
}
|
||||
|
||||
/**
|
||||
* 将给定 key 的值设为 value ,并返回 key 的旧值(old value)。
|
||||
* 当 key 存在但不是字符串类型时,返回一个错误。
|
||||
*/
|
||||
public <T> T getSet(String key, Object value) {
|
||||
return (T) valueOps.getAndSet(key, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* 移除给定 key 的生存时间,将这个 key 从『易失的』(带生存时间 key )转换成『持久的』(一个不带生存时间、永不过期的 key )。
|
||||
*/
|
||||
public Boolean persist(String key) {
|
||||
return redisTemplate.persist(key);
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回 key 所储存的值的类型。
|
||||
*/
|
||||
public String type(String key) {
|
||||
return redisTemplate.type(key).code();
|
||||
}
|
||||
|
||||
/**
|
||||
* 以秒为单位,返回给定 key 的剩余生存时间(TTL, time to live)。
|
||||
*/
|
||||
public Long ttl(String key) {
|
||||
return redisTemplate.getExpire(key);
|
||||
}
|
||||
|
||||
/**
|
||||
* 这个命令类似于 TTL 命令,但它以毫秒为单位返回 key 的剩余生存时间,而不是像 TTL 命令那样,以秒为单位。
|
||||
*/
|
||||
public Long pttl(String key) {
|
||||
return redisTemplate.getExpire(key, TimeUnit.MILLISECONDS);
|
||||
}
|
||||
|
||||
/**
|
||||
* 将哈希表 key 中的域 field 的值设为 value 。
|
||||
* 如果 key 不存在,一个新的哈希表被创建并进行 HSET 操作。
|
||||
* 如果域 field 已经存在于哈希表中,旧值将被覆盖。
|
||||
*/
|
||||
public void hSet(String key, Object field, Object value) {
|
||||
hashOps.put(key, field, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* 同时将多个 field-value (域-值)对设置到哈希表 key 中。
|
||||
* 此命令会覆盖哈希表中已存在的域。
|
||||
* 如果 key 不存在,一个空哈希表被创建并执行 HMSET 操作。
|
||||
*/
|
||||
public void hMset(String key, Map<Object, Object> hash) {
|
||||
hashOps.putAll(key, hash);
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回哈希表 key 中给定域 field 的值。
|
||||
*/
|
||||
public <T> T hGet(String key, Object field) {
|
||||
return (T) hashOps.get(key, field);
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回哈希表 key 中,一个或多个给定域的值。
|
||||
* 如果给定的域不存在于哈希表,那么返回一个 nil 值。
|
||||
* 因为不存在的 key 被当作一个空哈希表来处理,所以对一个不存在的 key 进行 HMGET 操作将返回一个只带有 nil 值的表。
|
||||
*/
|
||||
public List hmGet(String key, Object... fields) {
|
||||
return hmGet(key, Arrays.asList(fields));
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回哈希表 key 中,一个或多个给定域的值。
|
||||
* 如果给定的域不存在于哈希表,那么返回一个 nil 值。
|
||||
* 因为不存在的 key 被当作一个空哈希表来处理,所以对一个不存在的 key 进行 HMGET 操作将返回一个只带有 nil 值的表。
|
||||
*/
|
||||
public List hmGet(String key, Collection<Object> hashKeys) {
|
||||
return hashOps.multiGet(key, hashKeys);
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除哈希表 key 中的一个或多个指定域,不存在的域将被忽略。
|
||||
*/
|
||||
public Long hDel(String key, Object... fields) {
|
||||
return hashOps.delete(key, fields);
|
||||
}
|
||||
|
||||
/**
|
||||
* 查看哈希表 key 中,给定域 field 是否存在。
|
||||
*/
|
||||
public Boolean hExists(String key, Object field) {
|
||||
return hashOps.hasKey(key, field);
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回哈希表 key 中,所有的域和值。
|
||||
* 在返回值里,紧跟每个域名(field name)之后是域的值(value),所以返回值的长度是哈希表大小的两倍。
|
||||
*/
|
||||
public Map hGetAll(String key) {
|
||||
return hashOps.entries(key);
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回哈希表 key 中所有域的值。
|
||||
*/
|
||||
public List hVals(String key) {
|
||||
return hashOps.values(key);
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回哈希表 key 中的所有域。
|
||||
* 底层实现此方法取名为 hfields 更为合适,在此仅为与底层保持一致
|
||||
*/
|
||||
public Set<Object> hKeys(String key) {
|
||||
return hashOps.keys(key);
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回哈希表 key 中域的数量。
|
||||
*/
|
||||
public Long hLen(String key) {
|
||||
return hashOps.size(key);
|
||||
}
|
||||
|
||||
/**
|
||||
* 为哈希表 key 中的域 field 的值加上增量 increment 。
|
||||
* 增量也可以为负数,相当于对给定域进行减法操作。
|
||||
* 如果 key 不存在,一个新的哈希表被创建并执行 HINCRBY 命令。
|
||||
* 如果域 field 不存在,那么在执行命令前,域的值被初始化为 0 。
|
||||
* 对一个储存字符串值的域 field 执行 HINCRBY 命令将造成一个错误。
|
||||
* 本操作的值被限制在 64 位(bit)有符号数字表示之内。
|
||||
*/
|
||||
public Long hIncrBy(String key, Object field, long value) {
|
||||
return hashOps.increment(key, field, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* 为哈希表 key 中的域 field 加上浮点数增量 increment 。
|
||||
* 如果哈希表中没有域 field ,那么 HINCRBYFLOAT 会先将域 field 的值设为 0 ,然后再执行加法操作。
|
||||
* 如果键 key 不存在,那么 HINCRBYFLOAT 会先创建一个哈希表,再创建域 field ,最后再执行加法操作。
|
||||
* 当以下任意一个条件发生时,返回一个错误:
|
||||
* 1:域 field 的值不是字符串类型(因为 redis 中的数字和浮点数都以字符串的形式保存,所以它们都属于字符串类型)
|
||||
* 2:域 field 当前的值或给定的增量 increment 不能解释(parse)为双精度浮点数(double precision floating point number)
|
||||
* HINCRBYFLOAT 命令的详细功能和 INCRBYFLOAT 命令类似,请查看 INCRBYFLOAT 命令获取更多相关信息。
|
||||
*/
|
||||
public Double hIncrByFloat(String key, Object field, double value) {
|
||||
return hashOps.increment(key, field, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回列表 key 中,下标为 index 的元素。
|
||||
* 下标(index)参数 start 和 stop 都以 0 为底,也就是说,以 0 表示列表的第一个元素,
|
||||
* 以 1 表示列表的第二个元素,以此类推。
|
||||
* 你也可以使用负数下标,以 -1 表示列表的最后一个元素, -2 表示列表的倒数第二个元素,以此类推。
|
||||
* 如果 key 不是列表类型,返回一个错误。
|
||||
*/
|
||||
public <T> T lIndex(String key, long index) {
|
||||
return (T) listOps.index(key, index);
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回列表 key 的长度。
|
||||
* 如果 key 不存在,则 key 被解释为一个空列表,返回 0 .
|
||||
* 如果 key 不是列表类型,返回一个错误。
|
||||
*/
|
||||
public Long lLen(String key) {
|
||||
return listOps.size(key);
|
||||
}
|
||||
|
||||
/**
|
||||
* 移除并返回列表 key 的头元素。
|
||||
*/
|
||||
public <T> T lPop(String key) {
|
||||
return (T) listOps.leftPop(key);
|
||||
}
|
||||
|
||||
/**
|
||||
* 将一个或多个值 value 插入到列表 key 的表头
|
||||
* 如果有多个 value 值,那么各个 value 值按从左到右的顺序依次插入到表头: 比如说,
|
||||
* 对空列表 mylist 执行命令 LPUSH mylist a b c ,列表的值将是 c b a ,
|
||||
* 这等同于原子性地执行 LPUSH mylist a 、 LPUSH mylist b 和 LPUSH mylist c 三个命令。
|
||||
* 如果 key 不存在,一个空列表会被创建并执行 LPUSH 操作。
|
||||
* 当 key 存在但不是列表类型时,返回一个错误。
|
||||
*/
|
||||
public Long lPush(String key, Object... values) {
|
||||
return listOps.leftPush(key, values);
|
||||
}
|
||||
|
||||
/**
|
||||
* 将列表 key 下标为 index 的元素的值设置为 value 。
|
||||
* 当 index 参数超出范围,或对一个空列表( key 不存在)进行 LSET 时,返回一个错误。
|
||||
* 关于列表下标的更多信息,请参考 LINDEX 命令。
|
||||
*/
|
||||
public void lSet(String key, long index, Object value) {
|
||||
listOps.set(key, index, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据参数 count 的值,移除列表中与参数 value 相等的元素。
|
||||
* count 的值可以是以下几种:
|
||||
* count > 0 : 从表头开始向表尾搜索,移除与 value 相等的元素,数量为 count 。
|
||||
* count < 0 : 从表尾开始向表头搜索,移除与 value 相等的元素,数量为 count 的绝对值。
|
||||
* count = 0 : 移除表中所有与 value 相等的值。
|
||||
*/
|
||||
public Long lRem(String key, long count, Object value) {
|
||||
return listOps.remove(key, count, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回列表 key 中指定区间内的元素,区间以偏移量 start 和 stop 指定。
|
||||
* 下标(index)参数 start 和 stop 都以 0 为底,也就是说,以 0 表示列表的第一个元素,以 1 表示列表的第二个元素,以此类推。
|
||||
* 你也可以使用负数下标,以 -1 表示列表的最后一个元素, -2 表示列表的倒数第二个元素,以此类推。
|
||||
* <pre>
|
||||
* 例子:
|
||||
* 获取 list 中所有数据:cache.lrange(listKey, 0, -1);
|
||||
* 获取 list 中下标 1 到 3 的数据: cache.lrange(listKey, 1, 3);
|
||||
* </pre>
|
||||
*/
|
||||
public List lRange(String key, long start, long end) {
|
||||
return listOps.range(key, start, end);
|
||||
}
|
||||
|
||||
/**
|
||||
* 对一个列表进行修剪(trim),就是说,让列表只保留指定区间内的元素,不在指定区间之内的元素都将被删除。
|
||||
* 举个例子,执行命令 LTRIM list 0 2 ,表示只保留列表 list 的前三个元素,其余元素全部删除。
|
||||
* 下标(index)参数 start 和 stop 都以 0 为底,也就是说,以 0 表示列表的第一个元素,以 1 表示列表的第二个元素,以此类推。
|
||||
* 你也可以使用负数下标,以 -1 表示列表的最后一个元素, -2 表示列表的倒数第二个元素,以此类推。
|
||||
* 当 key 不是列表类型时,返回一个错误。
|
||||
*/
|
||||
public void lTrim(String key, long start, long end) {
|
||||
listOps.trim(key, start, end);
|
||||
}
|
||||
|
||||
/**
|
||||
* 移除并返回列表 key 的尾元素。
|
||||
*/
|
||||
public <T> T rPop(String key) {
|
||||
return (T) listOps.rightPop(key);
|
||||
}
|
||||
|
||||
/**
|
||||
* 将一个或多个值 value 插入到列表 key 的表尾(最右边)。
|
||||
* 如果有多个 value 值,那么各个 value 值按从左到右的顺序依次插入到表尾:比如
|
||||
* 对一个空列表 mylist 执行 RPUSH mylist a b c ,得出的结果列表为 a b c ,
|
||||
* 等同于执行命令 RPUSH mylist a 、 RPUSH mylist b 、 RPUSH mylist c 。
|
||||
* 如果 key 不存在,一个空列表会被创建并执行 RPUSH 操作。
|
||||
* 当 key 存在但不是列表类型时,返回一个错误。
|
||||
*/
|
||||
public Long rPush(String key, Object... values) {
|
||||
return listOps.rightPushAll(key, values);
|
||||
}
|
||||
|
||||
/**
|
||||
* 命令 RPOPLPUSH 在一个原子时间内,执行以下两个动作:
|
||||
* 将列表 source 中的最后一个元素(尾元素)弹出,并返回给客户端。
|
||||
* 将 source 弹出的元素插入到列表 destination ,作为 destination 列表的的头元素。
|
||||
*/
|
||||
public <T> T rPopLPush(String srcKey, String dstKey) {
|
||||
return (T) listOps.rightPopAndLeftPush(srcKey, dstKey);
|
||||
}
|
||||
|
||||
/**
|
||||
* 将一个或多个 member 元素加入到集合 key 当中,已经存在于集合的 member 元素将被忽略。
|
||||
* 假如 key 不存在,则创建一个只包含 member 元素作成员的集合。
|
||||
* 当 key 不是集合类型时,返回一个错误。
|
||||
*/
|
||||
public Long sAdd(String key, Object... members) {
|
||||
return setOps.add(key, members);
|
||||
}
|
||||
|
||||
/**
|
||||
* 移除并返回集合中的一个随机元素。
|
||||
* 如果只想获取一个随机元素,但不想该元素从集合中被移除的话,可以使用 SRANDMEMBER 命令。
|
||||
*/
|
||||
public <T> T sPop(String key) {
|
||||
return (T) setOps.pop(key);
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回集合 key 中的所有成员。
|
||||
* 不存在的 key 被视为空集合。
|
||||
*/
|
||||
public Set sMembers(String key) {
|
||||
return setOps.members(key);
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断 member 元素是否集合 key 的成员。
|
||||
*/
|
||||
public boolean sIsMember(String key, Object member) {
|
||||
return setOps.isMember(key, member);
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回多个集合的交集,多个集合由 keys 指定
|
||||
*/
|
||||
public Set sInter(String key, String otherKey) {
|
||||
return setOps.intersect(key, otherKey);
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回多个集合的交集,多个集合由 keys 指定
|
||||
*/
|
||||
public Set sInter(String key, Collection<String> otherKeys) {
|
||||
return setOps.intersect(key, otherKeys);
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回集合中的一个随机元素。
|
||||
*/
|
||||
public <T> T sRandMember(String key) {
|
||||
return (T) setOps.randomMember(key);
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回集合中的 count 个随机元素。
|
||||
* 从 Redis 2.6 版本开始, SRANDMEMBER 命令接受可选的 count 参数:
|
||||
* 如果 count 为正数,且小于集合基数,那么命令返回一个包含 count 个元素的数组,数组中的元素各不相同。
|
||||
* 如果 count 大于等于集合基数,那么返回整个集合。
|
||||
* 如果 count 为负数,那么命令返回一个数组,数组中的元素可能会重复出现多次,而数组的长度为 count 的绝对值。
|
||||
* 该操作和 SPOP 相似,但 SPOP 将随机元素从集合中移除并返回,而 SRANDMEMBER 则仅仅返回随机元素,而不对集合进行任何改动。
|
||||
*/
|
||||
public List sRandMember(String key, int count) {
|
||||
return setOps.randomMembers(key, count);
|
||||
}
|
||||
|
||||
/**
|
||||
* 移除集合 key 中的一个或多个 member 元素,不存在的 member 元素会被忽略。
|
||||
*/
|
||||
public Long sRem(String key, Object... members) {
|
||||
return setOps.remove(key, members);
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回多个集合的并集,多个集合由 keys 指定
|
||||
* 不存在的 key 被视为空集。
|
||||
*/
|
||||
public Set sUnion(String key, String otherKey) {
|
||||
return setOps.union(key, otherKey);
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回多个集合的并集,多个集合由 keys 指定
|
||||
* 不存在的 key 被视为空集。
|
||||
*/
|
||||
public Set sUnion(String key, Collection<String> otherKeys) {
|
||||
return setOps.union(key, otherKeys);
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回一个集合的全部成员,该集合是所有给定集合之间的差集。
|
||||
* 不存在的 key 被视为空集。
|
||||
*/
|
||||
public Set sDiff(String key, String otherKey) {
|
||||
return setOps.difference(key, otherKey);
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回一个集合的全部成员,该集合是所有给定集合之间的差集。
|
||||
* 不存在的 key 被视为空集。
|
||||
*/
|
||||
public Set sDiff(String key, Collection<String> otherKeys) {
|
||||
return setOps.difference(key, otherKeys);
|
||||
}
|
||||
|
||||
/**
|
||||
* 将一个或多个 member 元素及其 score 值加入到有序集 key 当中。
|
||||
* 如果某个 member 已经是有序集的成员,那么更新这个 member 的 score 值,
|
||||
* 并通过重新插入这个 member 元素,来保证该 member 在正确的位置上。
|
||||
*/
|
||||
public Boolean zAdd(String key, Object member, double score) {
|
||||
return zSetOps.add(key, member, score);
|
||||
}
|
||||
|
||||
/**
|
||||
* 将一个或多个 member 元素及其 score 值加入到有序集 key 当中。
|
||||
* 如果某个 member 已经是有序集的成员,那么更新这个 member 的 score 值,
|
||||
* 并通过重新插入这个 member 元素,来保证该 member 在正确的位置上。
|
||||
*/
|
||||
public Long zAdd(String key, Map<Object, Double> scoreMembers) {
|
||||
Set<ZSetOperations.TypedTuple<Object>> tuples = new HashSet<>();
|
||||
scoreMembers.forEach((k, v) -> {
|
||||
tuples.add(new DefaultTypedTuple<>(k, v));
|
||||
});
|
||||
return zSetOps.add(key, tuples);
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回有序集 key 的基数。
|
||||
*/
|
||||
public Long zCard(String key) {
|
||||
return zSetOps.zCard(key);
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回有序集 key 中, score 值在 min 和 max 之间(默认包括 score 值等于 min 或 max )的成员的数量。
|
||||
* 关于参数 min 和 max 的详细使用方法,请参考 ZRANGEBYSCORE 命令。
|
||||
*/
|
||||
public Long zCount(String key, double min, double max) {
|
||||
return zSetOps.count(key, min, max);
|
||||
}
|
||||
|
||||
/**
|
||||
* 为有序集 key 的成员 member 的 score 值加上增量 increment 。
|
||||
*/
|
||||
public Double zIncrBy(String key, Object member, double score) {
|
||||
return zSetOps.incrementScore(key, member, score);
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回有序集 key 中,指定区间内的成员。
|
||||
* 其中成员的位置按 score 值递增(从小到大)来排序。
|
||||
* 具有相同 score 值的成员按字典序(lexicographical order )来排列。
|
||||
* 如果你需要成员按 score 值递减(从大到小)来排列,请使用 ZREVRANGE 命令。
|
||||
*/
|
||||
public Set zRange(String key, long start, long end) {
|
||||
return zSetOps.range(key, start, end);
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回有序集 key 中,指定区间内的成员。
|
||||
* 其中成员的位置按 score 值递减(从大到小)来排列。
|
||||
* 具有相同 score 值的成员按字典序的逆序(reverse lexicographical order)排列。
|
||||
* 除了成员按 score 值递减的次序排列这一点外, ZREVRANGE 命令的其他方面和 ZRANGE 命令一样。
|
||||
*/
|
||||
public Set zRevrange(String key, long start, long end) {
|
||||
return zSetOps.reverseRange(key, start, end);
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回有序集 key 中,所有 score 值介于 min 和 max 之间(包括等于 min 或 max )的成员。
|
||||
* 有序集成员按 score 值递增(从小到大)次序排列。
|
||||
*/
|
||||
public Set zRangeByScore(String key, double min, double max) {
|
||||
return zSetOps.rangeByScore(key, min, max);
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回有序集 key 中成员 member 的排名。其中有序集成员按 score 值递增(从小到大)顺序排列。
|
||||
* 排名以 0 为底,也就是说, score 值最小的成员排名为 0 。
|
||||
* 使用 ZREVRANK 命令可以获得成员按 score 值递减(从大到小)排列的排名。
|
||||
*/
|
||||
public Long zRank(String key, Object member) {
|
||||
return zSetOps.rank(key, member);
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回有序集 key 中成员 member 的排名。其中有序集成员按 score 值递减(从大到小)排序。
|
||||
* 排名以 0 为底,也就是说, score 值最大的成员排名为 0 。
|
||||
* 使用 ZRANK 命令可以获得成员按 score 值递增(从小到大)排列的排名。
|
||||
*/
|
||||
public Long zRevrank(String key, Object member) {
|
||||
return zSetOps.reverseRank(key, member);
|
||||
}
|
||||
|
||||
/**
|
||||
* 移除有序集 key 中的一个或多个成员,不存在的成员将被忽略。
|
||||
* 当 key 存在但不是有序集类型时,返回一个错误。
|
||||
*/
|
||||
public Long zRem(String key, Object... members) {
|
||||
return zSetOps.remove(key, members);
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回有序集 key 中,成员 member 的 score 值。
|
||||
* 如果 member 元素不是有序集 key 的成员,或 key 不存在,返回 nil 。
|
||||
*/
|
||||
public Double zScore(String key, Object member) {
|
||||
return zSetOps.score(key, member);
|
||||
}
|
||||
|
||||
/**
|
||||
* redis publish
|
||||
*
|
||||
* @param channel channel
|
||||
* @param message message
|
||||
* @param mapper mapper
|
||||
* @param <T> 泛型标记
|
||||
* @return Long
|
||||
*/
|
||||
@Nullable
|
||||
public <T> Long publish(String channel, T message, Function<T, byte[]> mapper) {
|
||||
return redisTemplate.execute((RedisCallback<Long>) redis -> {
|
||||
byte[] channelBytes = keySerialize(channel);
|
||||
return redis.publish(channelBytes, mapper.apply(message));
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* redisKey 序列化
|
||||
*
|
||||
* @param redisKey redisKey
|
||||
* @return byte array
|
||||
*/
|
||||
public byte[] keySerialize(String redisKey) {
|
||||
RedisSerializer<String> keySerializer = (RedisSerializer<String>) this.redisTemplate.getKeySerializer();
|
||||
return Objects.requireNonNull(keySerializer.serialize(redisKey), "Redis key is null.");
|
||||
}
|
||||
|
||||
}
|
49
blade-starter-redis/src/main/java/org/springblade/core/redis/cache/CacheKey.java
vendored
Normal file
49
blade-starter-redis/src/main/java/org/springblade/core/redis/cache/CacheKey.java
vendored
Normal file
@ -0,0 +1,49 @@
|
||||
/**
|
||||
* Copyright (c) 2018-2099, DreamLu 卢春梦 (qq596392912@gmail.com).
|
||||
* <p>
|
||||
* Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* <p>
|
||||
* http://www.gnu.org/licenses/lgpl.html
|
||||
* <p>
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springblade.core.redis.cache;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
import lombok.ToString;
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
import java.time.Duration;
|
||||
|
||||
/**
|
||||
* cache key 封装
|
||||
*
|
||||
* @author L.cm
|
||||
*/
|
||||
@Getter
|
||||
@ToString
|
||||
@AllArgsConstructor
|
||||
public class CacheKey {
|
||||
/**
|
||||
* redis key
|
||||
*/
|
||||
private final String key;
|
||||
/**
|
||||
* 超时时间 秒
|
||||
*/
|
||||
@Nullable
|
||||
private Duration expire;
|
||||
|
||||
public CacheKey(String key) {
|
||||
this.key = key;
|
||||
}
|
||||
|
||||
}
|
70
blade-starter-redis/src/main/java/org/springblade/core/redis/cache/ICacheKey.java
vendored
Normal file
70
blade-starter-redis/src/main/java/org/springblade/core/redis/cache/ICacheKey.java
vendored
Normal file
@ -0,0 +1,70 @@
|
||||
/**
|
||||
* Copyright (c) 2018-2099, DreamLu 卢春梦 (qq596392912@gmail.com).
|
||||
* <p>
|
||||
* Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* <p>
|
||||
* http://www.gnu.org/licenses/lgpl.html
|
||||
* <p>
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springblade.core.redis.cache;
|
||||
|
||||
|
||||
import org.springblade.core.tool.utils.ObjectUtil;
|
||||
import org.springblade.core.tool.utils.StringPool;
|
||||
import org.springblade.core.tool.utils.StringUtil;
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
import java.time.Duration;
|
||||
|
||||
/**
|
||||
* cache key
|
||||
*
|
||||
* @author L.cm
|
||||
*/
|
||||
public interface ICacheKey {
|
||||
|
||||
/**
|
||||
* 获取前缀
|
||||
*
|
||||
* @return key 前缀
|
||||
*/
|
||||
String getPrefix();
|
||||
|
||||
/**
|
||||
* 超时时间
|
||||
*
|
||||
* @return 超时时间
|
||||
*/
|
||||
@Nullable
|
||||
default Duration getExpire() {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 组装 cache key
|
||||
*
|
||||
* @param suffix 参数
|
||||
* @return cache key
|
||||
*/
|
||||
default CacheKey getKey(Object... suffix) {
|
||||
String prefix = this.getPrefix();
|
||||
// 拼接参数
|
||||
String key;
|
||||
if (ObjectUtil.isEmpty(suffix)) {
|
||||
key = prefix;
|
||||
} else {
|
||||
key = prefix.concat(StringUtil.join(suffix, StringPool.COLON));
|
||||
}
|
||||
Duration expire = this.getExpire();
|
||||
return expire == null ? new CacheKey(key) : new CacheKey(key, expire);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,113 @@
|
||||
/**
|
||||
* Copyright (c) 2018-2099, DreamLu 卢春梦 (qq596392912@gmail.com).
|
||||
* <p>
|
||||
* Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* <p>
|
||||
* http://www.gnu.org/licenses/lgpl.html
|
||||
* <p>
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springblade.core.redis.config;
|
||||
|
||||
import org.springblade.core.tool.config.RedisConfiguration;
|
||||
import org.springframework.beans.factory.ObjectProvider;
|
||||
import org.springframework.boot.autoconfigure.AutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.cache.CacheManagerCustomizers;
|
||||
import org.springframework.boot.autoconfigure.cache.CacheProperties;
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Primary;
|
||||
import org.springframework.data.redis.cache.RedisCacheConfiguration;
|
||||
import org.springframework.data.redis.cache.RedisCacheManager;
|
||||
import org.springframework.data.redis.cache.RedisCacheWriter;
|
||||
import org.springframework.data.redis.connection.RedisConnectionFactory;
|
||||
import org.springframework.data.redis.serializer.RedisSerializationContext;
|
||||
import org.springframework.data.redis.serializer.RedisSerializer;
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 扩展redis-cache支持注解cacheName添加超时时间
|
||||
* <p>
|
||||
*
|
||||
* @author L.cm
|
||||
*/
|
||||
@AutoConfiguration(before = RedisConfiguration.class)
|
||||
@EnableConfigurationProperties(CacheProperties.class)
|
||||
public class BladeRedisCacheAutoConfiguration {
|
||||
|
||||
/**
|
||||
* 序列化方式
|
||||
*/
|
||||
private final RedisSerializer<Object> redisSerializer;
|
||||
private final CacheProperties cacheProperties;
|
||||
private final CacheManagerCustomizers customizerInvoker;
|
||||
@Nullable
|
||||
private final RedisCacheConfiguration redisCacheConfiguration;
|
||||
|
||||
BladeRedisCacheAutoConfiguration(RedisSerializer<Object> redisSerializer,
|
||||
CacheProperties cacheProperties,
|
||||
CacheManagerCustomizers customizerInvoker,
|
||||
ObjectProvider<RedisCacheConfiguration> redisCacheConfiguration) {
|
||||
this.redisSerializer = redisSerializer;
|
||||
this.cacheProperties = cacheProperties;
|
||||
this.customizerInvoker = customizerInvoker;
|
||||
this.redisCacheConfiguration = redisCacheConfiguration.getIfAvailable();
|
||||
}
|
||||
|
||||
@Primary
|
||||
@Bean("redisCacheManager")
|
||||
public RedisCacheManager redisCacheManager(RedisConnectionFactory connectionFactory) {
|
||||
RedisCacheWriter redisCacheWriter = RedisCacheWriter.nonLockingRedisCacheWriter(connectionFactory);
|
||||
RedisCacheConfiguration cacheConfiguration = this.determineConfiguration();
|
||||
List<String> cacheNames = this.cacheProperties.getCacheNames();
|
||||
Map<String, RedisCacheConfiguration> initialCaches = new LinkedHashMap<>();
|
||||
if (!cacheNames.isEmpty()) {
|
||||
Map<String, RedisCacheConfiguration> cacheConfigMap = new LinkedHashMap<>(cacheNames.size());
|
||||
cacheNames.forEach(it -> cacheConfigMap.put(it, cacheConfiguration));
|
||||
initialCaches.putAll(cacheConfigMap);
|
||||
}
|
||||
boolean allowInFlightCacheCreation = true;
|
||||
boolean enableTransactions = false;
|
||||
RedisAutoCacheManager cacheManager = new RedisAutoCacheManager(redisCacheWriter, cacheConfiguration, initialCaches, allowInFlightCacheCreation);
|
||||
cacheManager.setTransactionAware(enableTransactions);
|
||||
return this.customizerInvoker.customize(cacheManager);
|
||||
}
|
||||
|
||||
private RedisCacheConfiguration determineConfiguration() {
|
||||
if (this.redisCacheConfiguration != null) {
|
||||
return this.redisCacheConfiguration;
|
||||
} else {
|
||||
CacheProperties.Redis redisProperties = this.cacheProperties.getRedis();
|
||||
RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig();
|
||||
config = config.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(redisSerializer));
|
||||
if (redisProperties.getTimeToLive() != null) {
|
||||
config = config.entryTtl(redisProperties.getTimeToLive());
|
||||
}
|
||||
|
||||
if (redisProperties.getKeyPrefix() != null) {
|
||||
config = config.prefixCacheNameWith(redisProperties.getKeyPrefix());
|
||||
}
|
||||
|
||||
if (!redisProperties.isCacheNullValues()) {
|
||||
config = config.disableCachingNullValues();
|
||||
}
|
||||
|
||||
if (!redisProperties.isUseKeyPrefix()) {
|
||||
config = config.disableKeyPrefix();
|
||||
}
|
||||
|
||||
return config;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,41 @@
|
||||
/**
|
||||
* Copyright (c) 2018-2099, DreamLu 卢春梦 (qq596392912@gmail.com).
|
||||
* <p>
|
||||
* Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* <p>
|
||||
* http://www.gnu.org/licenses/lgpl.html
|
||||
* <p>
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springblade.core.redis.config;
|
||||
|
||||
import org.springblade.core.redis.serializer.ProtoStuffSerializer;
|
||||
import org.springframework.boot.autoconfigure.AutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.data.redis.serializer.RedisSerializer;
|
||||
|
||||
/**
|
||||
* ProtoStuff 序列化配置
|
||||
*
|
||||
* @author L.cm
|
||||
*/
|
||||
@AutoConfiguration(before = RedisTemplateConfiguration.class)
|
||||
@ConditionalOnClass(name = "io.protostuff.Schema")
|
||||
public class ProtoStuffSerializerConfiguration {
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
public RedisSerializer<Object> redisSerializer() {
|
||||
return new ProtoStuffSerializer();
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,63 @@
|
||||
/**
|
||||
* Copyright (c) 2018-2099, DreamLu 卢春梦 (qq596392912@gmail.com).
|
||||
* <p>
|
||||
* Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* <p>
|
||||
* http://www.gnu.org/licenses/lgpl.html
|
||||
* <p>
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springblade.core.redis.config;
|
||||
|
||||
import org.springblade.core.tool.utils.StringPool;
|
||||
import org.springblade.core.tool.utils.StringUtil;
|
||||
import org.springframework.boot.convert.DurationStyle;
|
||||
import org.springframework.data.redis.cache.RedisCache;
|
||||
import org.springframework.data.redis.cache.RedisCacheConfiguration;
|
||||
import org.springframework.data.redis.cache.RedisCacheManager;
|
||||
import org.springframework.data.redis.cache.RedisCacheWriter;
|
||||
import org.springframework.lang.NonNull;
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.time.temporal.ChronoUnit;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* redis cache 扩展cache name自动化配置
|
||||
*
|
||||
* @author L.cm
|
||||
*/
|
||||
public class RedisAutoCacheManager extends RedisCacheManager {
|
||||
|
||||
public RedisAutoCacheManager(RedisCacheWriter cacheWriter, RedisCacheConfiguration defaultCacheConfiguration,
|
||||
Map<String, RedisCacheConfiguration> initialCacheConfigurations, boolean allowInFlightCacheCreation) {
|
||||
super(cacheWriter, defaultCacheConfiguration, initialCacheConfigurations, allowInFlightCacheCreation);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
protected RedisCache createRedisCache(@NonNull String name, @Nullable RedisCacheConfiguration cacheConfig) {
|
||||
if (StringUtil.isBlank(name) || !name.contains(StringPool.HASH)) {
|
||||
return super.createRedisCache(name, cacheConfig);
|
||||
}
|
||||
String[] cacheArray = name.split(StringPool.HASH);
|
||||
if (cacheArray.length < 2) {
|
||||
return super.createRedisCache(name, cacheConfig);
|
||||
}
|
||||
String cacheName = cacheArray[0];
|
||||
if (cacheConfig != null) {
|
||||
Duration cacheAge = DurationStyle.detectAndParse(cacheArray[1], ChronoUnit.SECONDS);;
|
||||
cacheConfig = cacheConfig.entryTtl(cacheAge);
|
||||
}
|
||||
return super.createRedisCache(cacheName, cacheConfig);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,42 @@
|
||||
/**
|
||||
* Copyright (c) 2018-2099, DreamLu 卢春梦 (qq596392912@gmail.com).
|
||||
* <p>
|
||||
* Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* <p>
|
||||
* http://www.gnu.org/licenses/lgpl.html
|
||||
* <p>
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springblade.core.redis.config;
|
||||
|
||||
import org.springframework.beans.factory.ObjectProvider;
|
||||
import org.springframework.boot.autoconfigure.AutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.cache.CacheManagerCustomizer;
|
||||
import org.springframework.boot.autoconfigure.cache.CacheManagerCustomizers;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* CacheManagerCustomizers配置
|
||||
*
|
||||
* @author L.cm
|
||||
*/
|
||||
@AutoConfiguration
|
||||
@ConditionalOnMissingBean(CacheManagerCustomizers.class)
|
||||
public class RedisCacheManagerConfig {
|
||||
|
||||
@Bean
|
||||
public CacheManagerCustomizers cacheManagerCustomizers(
|
||||
ObjectProvider<List<CacheManagerCustomizer<?>>> customizers) {
|
||||
return new CacheManagerCustomizers(customizers.getIfAvailable());
|
||||
}
|
||||
}
|
@ -0,0 +1,61 @@
|
||||
/**
|
||||
* Copyright (c) 2018-2099, DreamLu 卢春梦 (qq596392912@gmail.com).
|
||||
* <p>
|
||||
* Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* <p>
|
||||
* http://www.gnu.org/licenses/lgpl.html
|
||||
* <p>
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springblade.core.redis.config;
|
||||
|
||||
|
||||
import org.springblade.core.redis.cache.BladeRedis;
|
||||
import org.springblade.core.redis.pubsub.RPubSubListenerDetector;
|
||||
import org.springblade.core.redis.pubsub.RPubSubPublisher;
|
||||
import org.springblade.core.redis.pubsub.RedisPubSubPublisher;
|
||||
import org.springframework.boot.autoconfigure.AutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.data.redis.connection.RedisConnectionFactory;
|
||||
import org.springframework.data.redis.listener.RedisMessageListenerContainer;
|
||||
import org.springframework.data.redis.serializer.RedisSerializer;
|
||||
|
||||
/**
|
||||
* Redisson pub/sub 发布配置
|
||||
*
|
||||
* @author L.cm
|
||||
*/
|
||||
@AutoConfiguration
|
||||
public class RedisPubSubConfiguration {
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
public RedisMessageListenerContainer redisMessageListenerContainer(RedisConnectionFactory connectionFactory) {
|
||||
RedisMessageListenerContainer container = new RedisMessageListenerContainer();
|
||||
container.setConnectionFactory(connectionFactory);
|
||||
return container;
|
||||
}
|
||||
|
||||
@Bean
|
||||
public RPubSubPublisher topicEventPublisher(BladeRedis bladeRedis,
|
||||
RedisSerializer<Object> redisSerializer) {
|
||||
return new RedisPubSubPublisher(bladeRedis, redisSerializer);
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnBean(RedisSerializer.class)
|
||||
public RPubSubListenerDetector topicListenerDetector(RedisMessageListenerContainer redisMessageListenerContainer,
|
||||
RedisSerializer<Object> redisSerializer) {
|
||||
return new RPubSubListenerDetector(redisMessageListenerContainer, redisSerializer);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,80 @@
|
||||
/**
|
||||
* Copyright (c) 2018-2099, DreamLu 卢春梦 (qq596392912@gmail.com).
|
||||
* <p>
|
||||
* Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* <p>
|
||||
* http://www.gnu.org/licenses/lgpl.html
|
||||
* <p>
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springblade.core.redis.config;
|
||||
|
||||
import org.springblade.core.redis.cache.BladeRedis;
|
||||
import org.springblade.core.redis.serializer.ProtoStuffSerializer;
|
||||
import org.springblade.core.redis.serializer.RedisKeySerializer;
|
||||
import org.springblade.core.tool.config.RedisConfiguration;
|
||||
import org.springframework.boot.autoconfigure.AutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
||||
import org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration;
|
||||
import org.springframework.cache.annotation.EnableCaching;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.data.redis.connection.RedisConnectionFactory;
|
||||
import org.springframework.data.redis.core.RedisTemplate;
|
||||
import org.springframework.data.redis.core.StringRedisTemplate;
|
||||
import org.springframework.data.redis.core.ValueOperations;
|
||||
import org.springframework.data.redis.serializer.RedisSerializer;
|
||||
|
||||
/**
|
||||
* RedisTemplate 配置
|
||||
*
|
||||
* @author L.cm
|
||||
*/
|
||||
@EnableCaching
|
||||
@AutoConfiguration(before = {RedisConfiguration.class, RedisAutoConfiguration.class})
|
||||
public class RedisTemplateConfiguration {
|
||||
|
||||
/**
|
||||
* value 值 序列化
|
||||
*
|
||||
* @return RedisSerializer
|
||||
*/
|
||||
@Bean
|
||||
@ConditionalOnMissingBean(RedisSerializer.class)
|
||||
public RedisSerializer<Object> redisSerializer() {
|
||||
return new ProtoStuffSerializer();
|
||||
}
|
||||
|
||||
@Bean(name = "redisTemplate")
|
||||
@ConditionalOnMissingBean(name = "redisTemplate")
|
||||
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory, RedisSerializer<Object> redisSerializer) {
|
||||
RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
|
||||
// key 序列化
|
||||
RedisKeySerializer keySerializer = new RedisKeySerializer();
|
||||
redisTemplate.setKeySerializer(keySerializer);
|
||||
redisTemplate.setHashKeySerializer(keySerializer);
|
||||
// value 序列化
|
||||
redisTemplate.setValueSerializer(redisSerializer);
|
||||
redisTemplate.setHashValueSerializer(redisSerializer);
|
||||
redisTemplate.setConnectionFactory(redisConnectionFactory);
|
||||
return redisTemplate;
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean(ValueOperations.class)
|
||||
public ValueOperations valueOperations(RedisTemplate redisTemplate) {
|
||||
return redisTemplate.opsForValue();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public BladeRedis bladeRedis(RedisTemplate<String, Object> redisTemplate, StringRedisTemplate stringRedisTemplate) {
|
||||
return new BladeRedis(redisTemplate, stringRedisTemplate);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,99 @@
|
||||
/**
|
||||
* Copyright (c) 2018-2099, DreamLu 卢春梦 (qq596392912@gmail.com).
|
||||
* <p>
|
||||
* Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* <p>
|
||||
* http://www.gnu.org/licenses/lgpl.html
|
||||
* <p>
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springblade.core.redis.pubsub;
|
||||
|
||||
import lombok.experimental.UtilityClass;
|
||||
import org.springblade.core.tool.utils.CharPool;
|
||||
import org.springframework.data.redis.listener.ChannelTopic;
|
||||
import org.springframework.data.redis.listener.PatternTopic;
|
||||
import org.springframework.data.redis.listener.Topic;
|
||||
|
||||
/**
|
||||
* channel 工具类
|
||||
*
|
||||
* @author L.cm
|
||||
*/
|
||||
@UtilityClass
|
||||
class ChannelUtil {
|
||||
|
||||
/**
|
||||
* 获取 pub sub topic
|
||||
*
|
||||
* @param channel channel
|
||||
* @return Topic
|
||||
*/
|
||||
public static Topic getTopic(String channel) {
|
||||
return isPattern(channel) ? new PatternTopic(channel) : new ChannelTopic(channel);
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断是否为模糊话题,*、? 和 [...]
|
||||
*
|
||||
* @param channel 话题名
|
||||
* @return 是否模糊话题
|
||||
*/
|
||||
public static boolean isPattern(String channel) {
|
||||
int length = channel.length();
|
||||
boolean isRightSqBracket = false;
|
||||
// 倒序,因为表达式一般在尾部
|
||||
for (int i = length - 1; i > 0; i--) {
|
||||
char charAt = channel.charAt(i);
|
||||
switch (charAt) {
|
||||
case CharPool.ASTERISK:
|
||||
case CharPool.QUESTION_MARK:
|
||||
if (isEscapeChars(channel, i)) {
|
||||
break;
|
||||
}
|
||||
return true;
|
||||
case CharPool.RIGHT_SQ_BRACKET:
|
||||
if (isEscapeChars(channel, i)) {
|
||||
break;
|
||||
}
|
||||
isRightSqBracket = true;
|
||||
break;
|
||||
case CharPool.LEFT_SQ_BRACKET:
|
||||
if (isEscapeChars(channel, i)) {
|
||||
break;
|
||||
}
|
||||
if (isRightSqBracket) {
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断是否为转义字符
|
||||
*
|
||||
* @param name 话题名
|
||||
* @param index 索引
|
||||
* @return 是否为转义字符
|
||||
*/
|
||||
private static boolean isEscapeChars(String name, int index) {
|
||||
if (index < 1) {
|
||||
return false;
|
||||
}
|
||||
// 预读一位,判断是否为转义符 “/”
|
||||
char charAt = name.charAt(index - 1);
|
||||
return CharPool.BACK_SLASH == charAt;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,45 @@
|
||||
/**
|
||||
* Copyright (c) 2018-2099, DreamLu 卢春梦 (qq596392912@gmail.com).
|
||||
* <p>
|
||||
* Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* <p>
|
||||
* http://www.gnu.org/licenses/lgpl.html
|
||||
* <p>
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springblade.core.redis.pubsub;
|
||||
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.ToString;
|
||||
|
||||
/**
|
||||
* 基于 redis pub sub 事件对象
|
||||
*
|
||||
* @author L.cm
|
||||
*/
|
||||
@Getter
|
||||
@ToString
|
||||
@RequiredArgsConstructor
|
||||
public class RPubSubEvent<M> {
|
||||
/**
|
||||
* 匹配模式时的正则
|
||||
*/
|
||||
private final CharSequence pattern;
|
||||
/**
|
||||
* channel
|
||||
*/
|
||||
private final CharSequence channel;
|
||||
/**
|
||||
* pub 的消息对象
|
||||
*/
|
||||
private final M msg;
|
||||
}
|
@ -0,0 +1,39 @@
|
||||
/**
|
||||
* Copyright (c) 2018-2099, DreamLu 卢春梦 (qq596392912@gmail.com).
|
||||
* <p>
|
||||
* Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* <p>
|
||||
* http://www.gnu.org/licenses/lgpl.html
|
||||
* <p>
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springblade.core.redis.pubsub;
|
||||
|
||||
import java.lang.annotation.*;
|
||||
|
||||
/**
|
||||
* 基于 Redisson 的消息监听器
|
||||
*
|
||||
* @author L.cm
|
||||
*/
|
||||
@Target(ElementType.METHOD)
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Documented
|
||||
@Inherited
|
||||
public @interface RPubSubListener {
|
||||
|
||||
/**
|
||||
* topic name,支持通配符, 如 *、? 和 [...]
|
||||
*
|
||||
* @return String
|
||||
*/
|
||||
String value();
|
||||
|
||||
}
|
@ -0,0 +1,81 @@
|
||||
/**
|
||||
* Copyright (c) 2018-2099, DreamLu 卢春梦 (qq596392912@gmail.com).
|
||||
* <p>
|
||||
* Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* <p>
|
||||
* http://www.gnu.org/licenses/lgpl.html
|
||||
* <p>
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springblade.core.redis.pubsub;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springblade.core.tool.utils.ReflectUtil;
|
||||
import org.springframework.beans.BeansException;
|
||||
import org.springframework.beans.factory.config.BeanPostProcessor;
|
||||
import org.springframework.core.annotation.AnnotationUtils;
|
||||
import org.springframework.data.redis.listener.RedisMessageListenerContainer;
|
||||
import org.springframework.data.redis.listener.Topic;
|
||||
import org.springframework.data.redis.serializer.RedisSerializer;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ClassUtils;
|
||||
import org.springframework.util.ReflectionUtils;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
/**
|
||||
* Redisson 监听器
|
||||
*
|
||||
* @author L.cm
|
||||
*/
|
||||
@Slf4j
|
||||
@RequiredArgsConstructor
|
||||
public class RPubSubListenerDetector implements BeanPostProcessor {
|
||||
private final RedisMessageListenerContainer redisMessageListenerContainer;
|
||||
private final RedisSerializer<Object> redisSerializer;
|
||||
|
||||
@Override
|
||||
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
|
||||
Class<?> userClass = ClassUtils.getUserClass(bean);
|
||||
ReflectionUtils.doWithMethods(userClass, method -> {
|
||||
RPubSubListener listener = AnnotationUtils.findAnnotation(method, RPubSubListener.class);
|
||||
if (listener != null) {
|
||||
String channel = listener.value();
|
||||
Assert.hasText(channel, "@RPubSubListener value channel must not be empty.");
|
||||
log.info("Found @RPubSubListener on bean:{} method:{}", beanName, method);
|
||||
|
||||
// 校验 method,method 入参数大于等于1
|
||||
int paramCount = method.getParameterCount();
|
||||
if (paramCount > 1) {
|
||||
throw new IllegalArgumentException("@RPubSubListener on method " + method + " parameter count must less or equal to 1.");
|
||||
}
|
||||
// 精准模式和模糊匹配模式
|
||||
Topic topic = ChannelUtil.getTopic(channel);
|
||||
redisMessageListenerContainer.addMessageListener((message, pattern) -> {
|
||||
String messageChannel = new String(message.getChannel());
|
||||
Object body = redisSerializer.deserialize(message.getBody());
|
||||
invokeMethod(bean, method, paramCount, new RPubSubEvent<>(channel, messageChannel, body));
|
||||
}, topic);
|
||||
}
|
||||
}, ReflectionUtils.USER_DECLARED_METHODS);
|
||||
return bean;
|
||||
}
|
||||
|
||||
private static void invokeMethod(Object bean, Method method, int paramCount, RPubSubEvent<Object> topicEvent) {
|
||||
// 支持没有参数的方法
|
||||
if (paramCount == 0) {
|
||||
ReflectUtil.invokeMethod(method, bean);
|
||||
} else {
|
||||
ReflectUtil.invokeMethod(method, bean, topicEvent);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,35 @@
|
||||
/**
|
||||
* Copyright (c) 2018-2099, DreamLu 卢春梦 (qq596392912@gmail.com).
|
||||
* <p>
|
||||
* Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* <p>
|
||||
* http://www.gnu.org/licenses/lgpl.html
|
||||
* <p>
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springblade.core.redis.pubsub;
|
||||
|
||||
/**
|
||||
* 基于 Redisson 的消息发布器
|
||||
*
|
||||
* @author L.cm
|
||||
*/
|
||||
public interface RPubSubPublisher {
|
||||
|
||||
/**
|
||||
* 发布消息
|
||||
*
|
||||
* @param channel 队列名
|
||||
* @param message 消息
|
||||
* @return 收到消息的客户数量
|
||||
*/
|
||||
<T> Long publish(String channel, T message);
|
||||
|
||||
}
|
@ -0,0 +1,45 @@
|
||||
/**
|
||||
* Copyright (c) 2018-2099, DreamLu 卢春梦 (qq596392912@gmail.com).
|
||||
* <p>
|
||||
* Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* <p>
|
||||
* http://www.gnu.org/licenses/lgpl.html
|
||||
* <p>
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springblade.core.redis.pubsub;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springblade.core.redis.cache.BladeRedis;
|
||||
import org.springframework.beans.factory.InitializingBean;
|
||||
import org.springframework.data.redis.serializer.RedisSerializer;
|
||||
|
||||
/**
|
||||
* Redisson pub/sub 发布器
|
||||
*
|
||||
* @author L.cm
|
||||
*/
|
||||
@Slf4j
|
||||
@RequiredArgsConstructor
|
||||
public class RedisPubSubPublisher implements InitializingBean, RPubSubPublisher {
|
||||
private final BladeRedis bladeRedis;
|
||||
private final RedisSerializer<Object> redisSerializer;
|
||||
|
||||
@Override
|
||||
public <T> Long publish(String channel, T message) {
|
||||
return bladeRedis.publish(channel, message, redisSerializer::serialize);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterPropertiesSet() throws Exception {
|
||||
log.info("RPubSubPublisher init success.");
|
||||
}
|
||||
}
|
@ -0,0 +1,51 @@
|
||||
/**
|
||||
* Copyright (c) 2018-2099, DreamLu 卢春梦 (qq596392912@gmail.com).
|
||||
* <p>
|
||||
* Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* <p>
|
||||
* http://www.gnu.org/licenses/lgpl.html
|
||||
* <p>
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springblade.core.redis.serializer;
|
||||
|
||||
/**
|
||||
* redis序列化辅助类.单纯的泛型无法定义通用schema,原因是无法通过泛型T得到Class
|
||||
*
|
||||
* @author L.cm
|
||||
*/
|
||||
public class BytesWrapper<T> implements Cloneable {
|
||||
private T value;
|
||||
|
||||
public BytesWrapper() {
|
||||
}
|
||||
|
||||
public BytesWrapper(T value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
public void setValue(T value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
public T getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public BytesWrapper<T> clone() {
|
||||
try {
|
||||
return (BytesWrapper) super.clone();
|
||||
} catch (CloneNotSupportedException e) {
|
||||
return new BytesWrapper<>();
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,61 @@
|
||||
/**
|
||||
* Copyright (c) 2018-2099, DreamLu 卢春梦 (qq596392912@gmail.com).
|
||||
* <p>
|
||||
* Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* <p>
|
||||
* http://www.gnu.org/licenses/lgpl.html
|
||||
* <p>
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springblade.core.redis.serializer;
|
||||
|
||||
import io.protostuff.LinkedBuffer;
|
||||
import io.protostuff.ProtobufIOUtil;
|
||||
import io.protostuff.Schema;
|
||||
import io.protostuff.runtime.RuntimeSchema;
|
||||
import org.springblade.core.tool.utils.ObjectUtil;
|
||||
import org.springframework.data.redis.serializer.RedisSerializer;
|
||||
import org.springframework.data.redis.serializer.SerializationException;
|
||||
|
||||
/**
|
||||
* ProtoStuff 序列化
|
||||
*
|
||||
* @author L.cm
|
||||
*/
|
||||
public class ProtoStuffSerializer implements RedisSerializer<Object> {
|
||||
private final Schema<BytesWrapper> schema;
|
||||
|
||||
public ProtoStuffSerializer() {
|
||||
this.schema = RuntimeSchema.getSchema(BytesWrapper.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] serialize(Object object) throws SerializationException {
|
||||
if (object == null) {
|
||||
return null;
|
||||
}
|
||||
LinkedBuffer buffer = LinkedBuffer.allocate(LinkedBuffer.DEFAULT_BUFFER_SIZE);
|
||||
try {
|
||||
return ProtobufIOUtil.toByteArray(new BytesWrapper<>(object), schema, buffer);
|
||||
} finally {
|
||||
buffer.clear();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object deserialize(byte[] bytes) throws SerializationException {
|
||||
if (ObjectUtil.isEmpty(bytes)) {
|
||||
return null;
|
||||
}
|
||||
BytesWrapper<Object> wrapper = new BytesWrapper<>();
|
||||
ProtobufIOUtil.mergeFrom(bytes, wrapper, schema);
|
||||
return wrapper.getValue();
|
||||
}
|
||||
}
|
@ -0,0 +1,74 @@
|
||||
/**
|
||||
* Copyright (c) 2018-2099, DreamLu 卢春梦 (qq596392912@gmail.com).
|
||||
* <p>
|
||||
* Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* <p>
|
||||
* http://www.gnu.org/licenses/lgpl.html
|
||||
* <p>
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springblade.core.redis.serializer;
|
||||
|
||||
import org.springframework.cache.interceptor.SimpleKey;
|
||||
import org.springframework.core.convert.ConversionService;
|
||||
import org.springframework.core.convert.support.DefaultConversionService;
|
||||
import org.springframework.data.redis.serializer.RedisSerializer;
|
||||
|
||||
import java.nio.charset.Charset;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* 将redis key序列化为字符串
|
||||
*
|
||||
* <p>
|
||||
* spring cache中的简单基本类型直接使用 StringRedisSerializer 会有问题
|
||||
* </p>
|
||||
*
|
||||
* @author L.cm
|
||||
*/
|
||||
public class RedisKeySerializer implements RedisSerializer<Object> {
|
||||
private final Charset charset;
|
||||
private final ConversionService converter;
|
||||
|
||||
public RedisKeySerializer() {
|
||||
this(StandardCharsets.UTF_8);
|
||||
}
|
||||
|
||||
public RedisKeySerializer(Charset charset) {
|
||||
Objects.requireNonNull(charset, "Charset must not be null");
|
||||
this.charset = charset;
|
||||
this.converter = DefaultConversionService.getSharedInstance();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object deserialize(byte[] bytes) {
|
||||
// redis keys 会用到反序列化
|
||||
if (bytes == null) {
|
||||
return null;
|
||||
}
|
||||
return new String(bytes, charset);
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] serialize(Object object) {
|
||||
Objects.requireNonNull(object, "redis key is null");
|
||||
String key;
|
||||
if (object instanceof SimpleKey) {
|
||||
key = "";
|
||||
} else if (object instanceof String) {
|
||||
key = (String) object;
|
||||
} else {
|
||||
key = converter.convert(object, String.class);
|
||||
}
|
||||
return key.getBytes(this.charset);
|
||||
}
|
||||
|
||||
}
|
16
pom.xml
16
pom.xml
@ -61,8 +61,8 @@
|
||||
<mica.auto.version>3.1.4</mica.auto.version>
|
||||
<druid.version>1.2.23</druid.version>
|
||||
|
||||
<spring.version>6.1.14</spring.version>
|
||||
<spring.boot.version>3.2.10</spring.boot.version>
|
||||
<spring.version>6.1.15</spring.version>
|
||||
<spring.boot.version>3.2.12</spring.boot.version>
|
||||
<spring.boot.admin.version>3.2.3</spring.boot.admin.version>
|
||||
<spring.cloud.version>2023.0.3</spring.cloud.version>
|
||||
|
||||
@ -85,6 +85,7 @@
|
||||
<module>blade-starter-log</module>
|
||||
<module>blade-starter-mybatis</module>
|
||||
<module>blade-starter-oss</module>
|
||||
<module>blade-starter-redis</module>
|
||||
<module>blade-starter-report</module>
|
||||
<module>blade-starter-social</module>
|
||||
<module>blade-starter-swagger</module>
|
||||
@ -197,6 +198,11 @@
|
||||
<artifactId>blade-starter-oss</artifactId>
|
||||
<version>${revision}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springblade</groupId>
|
||||
<artifactId>blade-starter-redis</artifactId>
|
||||
<version>${revision}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springblade</groupId>
|
||||
<artifactId>blade-starter-tenant</artifactId>
|
||||
@ -364,6 +370,12 @@
|
||||
<artifactId>springdoc-openapi-starter-webflux-ui</artifactId>
|
||||
<version>2.3.0</version>
|
||||
</dependency>
|
||||
<!-- redisson -->
|
||||
<dependency>
|
||||
<groupId>org.redisson</groupId>
|
||||
<artifactId>redisson</artifactId>
|
||||
<version>3.17.7</version>
|
||||
</dependency>
|
||||
<!-- protostuff -->
|
||||
<dependency>
|
||||
<groupId>io.protostuff</groupId>
|
||||
|
Loading…
Reference in New Issue
Block a user