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;
+ }
+
}
diff --git a/blade-core-tool/src/main/java/org/springblade/core/tool/utils/RedisUtil.java b/blade-core-tool/src/main/java/org/springblade/core/tool/utils/RedisUtil.java
index ea095f2..b319a01 100644
--- a/blade-core-tool/src/main/java/org/springblade/core/tool/utils/RedisUtil.java
+++ b/blade-core-tool/src/main/java/org/springblade/core/tool/utils/RedisUtil.java
@@ -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 {
diff --git a/blade-starter-redis/pom.xml b/blade-starter-redis/pom.xml
new file mode 100644
index 0000000..118fab3
--- /dev/null
+++ b/blade-starter-redis/pom.xml
@@ -0,0 +1,46 @@
+
+
+
+ blade-tool
+ org.springblade
+ ${revision}
+
+ 4.0.0
+
+ blade-starter-redis
+ ${project.artifactId}
+ jar
+
+
+ org.springblade.blade.starter.redis
+
+
+
+
+
+ org.springblade
+ blade-core-tool
+
+
+
+ org.springframework.boot
+ spring-boot-starter-data-redis
+
+
+ org.redisson
+ redisson
+
+
+
+ io.protostuff
+ protostuff-core
+
+
+ io.protostuff
+ protostuff-runtime
+
+
+
+
diff --git a/blade-starter-redis/src/main/java/org/springblade/core/redis/cache/BladeRedis.java b/blade-starter-redis/src/main/java/org/springblade/core/redis/cache/BladeRedis.java
new file mode 100644
index 0000000..98cff4e
--- /dev/null
+++ b/blade-starter-redis/src/main/java/org/springblade/core/redis/cache/BladeRedis.java
@@ -0,0 +1,876 @@
+/**
+ * Copyright (c) 2018-2099, DreamLu 卢春梦 (qq596392912@gmail.com).
+ *
+ * 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
+ *
+ * http://www.gnu.org/licenses/lgpl.html
+ *
+ * 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 redisTemplate;
+ private final StringRedisTemplate stringRedisTemplate;
+ private final ValueOperations valueOps;
+ private final HashOperations hashOps;
+ private final ListOperations listOps;
+ private final SetOperations setOps;
+ private final ZSetOperations zSetOps;
+
+ public BladeRedis(RedisTemplate 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 get(String key) {
+ return (T) valueOps.get(key);
+ }
+
+ /**
+ * 获取cache 为 null 时使用加载器,然后设置缓存
+ *
+ * @param key cacheKey
+ * @param loader cache loader
+ * @param 泛型
+ * @return 结果
+ */
+ @Nullable
+ public T get(String key, Supplier 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 get(CacheKey cacheKey) {
+ return (T) valueOps.get(cacheKey.getKey());
+ }
+
+ /**
+ * 获取cache 为 null 时使用加载器,然后设置缓存
+ *
+ * @param cacheKey cacheKey
+ * @param loader cache loader
+ * @param 泛型
+ * @return 结果
+ */
+ @Nullable
+ public T get(CacheKey cacheKey, Supplier 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 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 keys(String pattern) {
+ return redisTemplate.keys(pattern);
+ }
+
+ /**
+ * 同时设置一个或多个 key-value 对。
+ * 如果某个给定 key 已经存在,那么 MSET 会用新值覆盖原来的旧值,如果这不是你所希望的效果,请考虑使用 MSETNX 命令:它只会在所有给定 key 都不存在的情况下进行设置操作。
+ * MSET 是一个原子性(atomic)操作,所有给定 key 都会在同一时间内被设置,某些给定 key 被更新而另一些给定 key 没有改变的情况,不可能发生。
+ *
+ * 例子:
+ * Cache cache = RedisKit.use(); // 使用 Redis 的 cache
+ * cache.mset("k1", "v1", "k2", "v2"); // 放入多个 key value 键值对
+ * List list = cache.mget("k1", "k2"); // 利用多个键值得到上面代码放入的值
+ *
+ */
+ public void mSet(Object... keysValues) {
+ valueOps.multiSet(CollectionUtil.toMap(keysValues));
+ }
+
+ /**
+ * 返回所有(一个或多个)给定 key 的值。
+ * 如果给定的 key 里面,有某个 key 不存在,那么这个 key 返回特殊值 nil 。因此,该命令永不失败。
+ */
+ public List mGet(String... keys) {
+ return mGet(Arrays.asList(keys));
+ }
+
+ /**
+ * 返回所有(一个或多个)给定 key 的值。
+ * 如果给定的 key 里面,有某个 key 不存在,那么这个 key 返回特殊值 nil 。因此,该命令永不失败。
+ */
+ public List mGet(Collection 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 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 hash) {
+ hashOps.putAll(key, hash);
+ }
+
+ /**
+ * 返回哈希表 key 中给定域 field 的值。
+ */
+ public 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 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 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 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 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 表示列表的倒数第二个元素,以此类推。
+ *
+ * 例子:
+ * 获取 list 中所有数据:cache.lrange(listKey, 0, -1);
+ * 获取 list 中下标 1 到 3 的数据: cache.lrange(listKey, 1, 3);
+ *
+ */
+ 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 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 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 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 otherKeys) {
+ return setOps.intersect(key, otherKeys);
+ }
+
+ /**
+ * 返回集合中的一个随机元素。
+ */
+ public 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 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 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 scoreMembers) {
+ Set> 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 泛型标记
+ * @return Long
+ */
+ @Nullable
+ public Long publish(String channel, T message, Function mapper) {
+ return redisTemplate.execute((RedisCallback) 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 keySerializer = (RedisSerializer) this.redisTemplate.getKeySerializer();
+ return Objects.requireNonNull(keySerializer.serialize(redisKey), "Redis key is null.");
+ }
+
+}
diff --git a/blade-starter-redis/src/main/java/org/springblade/core/redis/cache/CacheKey.java b/blade-starter-redis/src/main/java/org/springblade/core/redis/cache/CacheKey.java
new file mode 100644
index 0000000..91aa3c4
--- /dev/null
+++ b/blade-starter-redis/src/main/java/org/springblade/core/redis/cache/CacheKey.java
@@ -0,0 +1,49 @@
+/**
+ * Copyright (c) 2018-2099, DreamLu 卢春梦 (qq596392912@gmail.com).
+ *
+ * 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
+ *
+ * http://www.gnu.org/licenses/lgpl.html
+ *
+ * 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;
+ }
+
+}
diff --git a/blade-starter-redis/src/main/java/org/springblade/core/redis/cache/ICacheKey.java b/blade-starter-redis/src/main/java/org/springblade/core/redis/cache/ICacheKey.java
new file mode 100644
index 0000000..ff08ef1
--- /dev/null
+++ b/blade-starter-redis/src/main/java/org/springblade/core/redis/cache/ICacheKey.java
@@ -0,0 +1,70 @@
+/**
+ * Copyright (c) 2018-2099, DreamLu 卢春梦 (qq596392912@gmail.com).
+ *
+ * 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
+ *
+ * http://www.gnu.org/licenses/lgpl.html
+ *
+ * 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);
+ }
+
+}
diff --git a/blade-starter-redis/src/main/java/org/springblade/core/redis/config/BladeRedisCacheAutoConfiguration.java b/blade-starter-redis/src/main/java/org/springblade/core/redis/config/BladeRedisCacheAutoConfiguration.java
new file mode 100644
index 0000000..6dd1acf
--- /dev/null
+++ b/blade-starter-redis/src/main/java/org/springblade/core/redis/config/BladeRedisCacheAutoConfiguration.java
@@ -0,0 +1,113 @@
+/**
+ * Copyright (c) 2018-2099, DreamLu 卢春梦 (qq596392912@gmail.com).
+ *
+ * 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
+ *
+ * http://www.gnu.org/licenses/lgpl.html
+ *
+ * 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添加超时时间
+ *
+ *
+ * @author L.cm
+ */
+@AutoConfiguration(before = RedisConfiguration.class)
+@EnableConfigurationProperties(CacheProperties.class)
+public class BladeRedisCacheAutoConfiguration {
+
+ /**
+ * 序列化方式
+ */
+ private final RedisSerializer redisSerializer;
+ private final CacheProperties cacheProperties;
+ private final CacheManagerCustomizers customizerInvoker;
+ @Nullable
+ private final RedisCacheConfiguration redisCacheConfiguration;
+
+ BladeRedisCacheAutoConfiguration(RedisSerializer redisSerializer,
+ CacheProperties cacheProperties,
+ CacheManagerCustomizers customizerInvoker,
+ ObjectProvider 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 cacheNames = this.cacheProperties.getCacheNames();
+ Map initialCaches = new LinkedHashMap<>();
+ if (!cacheNames.isEmpty()) {
+ Map 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;
+ }
+ }
+}
diff --git a/blade-starter-redis/src/main/java/org/springblade/core/redis/config/ProtoStuffSerializerConfiguration.java b/blade-starter-redis/src/main/java/org/springblade/core/redis/config/ProtoStuffSerializerConfiguration.java
new file mode 100644
index 0000000..fed52a8
--- /dev/null
+++ b/blade-starter-redis/src/main/java/org/springblade/core/redis/config/ProtoStuffSerializerConfiguration.java
@@ -0,0 +1,41 @@
+/**
+ * Copyright (c) 2018-2099, DreamLu 卢春梦 (qq596392912@gmail.com).
+ *
+ * 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
+ *
+ * http://www.gnu.org/licenses/lgpl.html
+ *
+ * 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 redisSerializer() {
+ return new ProtoStuffSerializer();
+ }
+
+}
diff --git a/blade-starter-redis/src/main/java/org/springblade/core/redis/config/RedisAutoCacheManager.java b/blade-starter-redis/src/main/java/org/springblade/core/redis/config/RedisAutoCacheManager.java
new file mode 100644
index 0000000..dd3ccce
--- /dev/null
+++ b/blade-starter-redis/src/main/java/org/springblade/core/redis/config/RedisAutoCacheManager.java
@@ -0,0 +1,63 @@
+/**
+ * Copyright (c) 2018-2099, DreamLu 卢春梦 (qq596392912@gmail.com).
+ *
+ * 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
+ *
+ * http://www.gnu.org/licenses/lgpl.html
+ *
+ * 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 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);
+ }
+
+}
diff --git a/blade-starter-redis/src/main/java/org/springblade/core/redis/config/RedisCacheManagerConfig.java b/blade-starter-redis/src/main/java/org/springblade/core/redis/config/RedisCacheManagerConfig.java
new file mode 100644
index 0000000..e71d3c2
--- /dev/null
+++ b/blade-starter-redis/src/main/java/org/springblade/core/redis/config/RedisCacheManagerConfig.java
@@ -0,0 +1,42 @@
+/**
+ * Copyright (c) 2018-2099, DreamLu 卢春梦 (qq596392912@gmail.com).
+ *
+ * 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
+ *
+ * http://www.gnu.org/licenses/lgpl.html
+ *
+ * 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>> customizers) {
+ return new CacheManagerCustomizers(customizers.getIfAvailable());
+ }
+}
diff --git a/blade-starter-redis/src/main/java/org/springblade/core/redis/config/RedisPubSubConfiguration.java b/blade-starter-redis/src/main/java/org/springblade/core/redis/config/RedisPubSubConfiguration.java
new file mode 100644
index 0000000..c08ed0d
--- /dev/null
+++ b/blade-starter-redis/src/main/java/org/springblade/core/redis/config/RedisPubSubConfiguration.java
@@ -0,0 +1,61 @@
+/**
+ * Copyright (c) 2018-2099, DreamLu 卢春梦 (qq596392912@gmail.com).
+ *
+ * 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
+ *
+ * http://www.gnu.org/licenses/lgpl.html
+ *
+ * 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 redisSerializer) {
+ return new RedisPubSubPublisher(bladeRedis, redisSerializer);
+ }
+
+ @Bean
+ @ConditionalOnBean(RedisSerializer.class)
+ public RPubSubListenerDetector topicListenerDetector(RedisMessageListenerContainer redisMessageListenerContainer,
+ RedisSerializer redisSerializer) {
+ return new RPubSubListenerDetector(redisMessageListenerContainer, redisSerializer);
+ }
+
+}
diff --git a/blade-starter-redis/src/main/java/org/springblade/core/redis/config/RedisTemplateConfiguration.java b/blade-starter-redis/src/main/java/org/springblade/core/redis/config/RedisTemplateConfiguration.java
new file mode 100644
index 0000000..e4c5caa
--- /dev/null
+++ b/blade-starter-redis/src/main/java/org/springblade/core/redis/config/RedisTemplateConfiguration.java
@@ -0,0 +1,80 @@
+/**
+ * Copyright (c) 2018-2099, DreamLu 卢春梦 (qq596392912@gmail.com).
+ *
+ * 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
+ *
+ * http://www.gnu.org/licenses/lgpl.html
+ *
+ * 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 redisSerializer() {
+ return new ProtoStuffSerializer();
+ }
+
+ @Bean(name = "redisTemplate")
+ @ConditionalOnMissingBean(name = "redisTemplate")
+ public RedisTemplate redisTemplate(RedisConnectionFactory redisConnectionFactory, RedisSerializer redisSerializer) {
+ RedisTemplate 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 redisTemplate, StringRedisTemplate stringRedisTemplate) {
+ return new BladeRedis(redisTemplate, stringRedisTemplate);
+ }
+
+}
diff --git a/blade-starter-redis/src/main/java/org/springblade/core/redis/pubsub/ChannelUtil.java b/blade-starter-redis/src/main/java/org/springblade/core/redis/pubsub/ChannelUtil.java
new file mode 100644
index 0000000..1bd7371
--- /dev/null
+++ b/blade-starter-redis/src/main/java/org/springblade/core/redis/pubsub/ChannelUtil.java
@@ -0,0 +1,99 @@
+/**
+ * Copyright (c) 2018-2099, DreamLu 卢春梦 (qq596392912@gmail.com).
+ *
+ * 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
+ *
+ * http://www.gnu.org/licenses/lgpl.html
+ *
+ * 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;
+ }
+
+}
diff --git a/blade-starter-redis/src/main/java/org/springblade/core/redis/pubsub/RPubSubEvent.java b/blade-starter-redis/src/main/java/org/springblade/core/redis/pubsub/RPubSubEvent.java
new file mode 100644
index 0000000..fb96bb0
--- /dev/null
+++ b/blade-starter-redis/src/main/java/org/springblade/core/redis/pubsub/RPubSubEvent.java
@@ -0,0 +1,45 @@
+/**
+ * Copyright (c) 2018-2099, DreamLu 卢春梦 (qq596392912@gmail.com).
+ *
+ * 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
+ *
+ * http://www.gnu.org/licenses/lgpl.html
+ *
+ * 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 {
+ /**
+ * 匹配模式时的正则
+ */
+ private final CharSequence pattern;
+ /**
+ * channel
+ */
+ private final CharSequence channel;
+ /**
+ * pub 的消息对象
+ */
+ private final M msg;
+}
diff --git a/blade-starter-redis/src/main/java/org/springblade/core/redis/pubsub/RPubSubListener.java b/blade-starter-redis/src/main/java/org/springblade/core/redis/pubsub/RPubSubListener.java
new file mode 100644
index 0000000..5cd2c56
--- /dev/null
+++ b/blade-starter-redis/src/main/java/org/springblade/core/redis/pubsub/RPubSubListener.java
@@ -0,0 +1,39 @@
+/**
+ * Copyright (c) 2018-2099, DreamLu 卢春梦 (qq596392912@gmail.com).
+ *
+ * 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
+ *
+ * http://www.gnu.org/licenses/lgpl.html
+ *
+ * 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();
+
+}
diff --git a/blade-starter-redis/src/main/java/org/springblade/core/redis/pubsub/RPubSubListenerDetector.java b/blade-starter-redis/src/main/java/org/springblade/core/redis/pubsub/RPubSubListenerDetector.java
new file mode 100644
index 0000000..4fbc0d1
--- /dev/null
+++ b/blade-starter-redis/src/main/java/org/springblade/core/redis/pubsub/RPubSubListenerDetector.java
@@ -0,0 +1,81 @@
+/**
+ * Copyright (c) 2018-2099, DreamLu 卢春梦 (qq596392912@gmail.com).
+ *
+ * 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
+ *
+ * http://www.gnu.org/licenses/lgpl.html
+ *
+ * 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 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 topicEvent) {
+ // 支持没有参数的方法
+ if (paramCount == 0) {
+ ReflectUtil.invokeMethod(method, bean);
+ } else {
+ ReflectUtil.invokeMethod(method, bean, topicEvent);
+ }
+ }
+
+}
diff --git a/blade-starter-redis/src/main/java/org/springblade/core/redis/pubsub/RPubSubPublisher.java b/blade-starter-redis/src/main/java/org/springblade/core/redis/pubsub/RPubSubPublisher.java
new file mode 100644
index 0000000..edc05c6
--- /dev/null
+++ b/blade-starter-redis/src/main/java/org/springblade/core/redis/pubsub/RPubSubPublisher.java
@@ -0,0 +1,35 @@
+/**
+ * Copyright (c) 2018-2099, DreamLu 卢春梦 (qq596392912@gmail.com).
+ *
+ * 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
+ *
+ * http://www.gnu.org/licenses/lgpl.html
+ *
+ * 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 收到消息的客户数量
+ */
+ Long publish(String channel, T message);
+
+}
diff --git a/blade-starter-redis/src/main/java/org/springblade/core/redis/pubsub/RedisPubSubPublisher.java b/blade-starter-redis/src/main/java/org/springblade/core/redis/pubsub/RedisPubSubPublisher.java
new file mode 100644
index 0000000..ff7a0e8
--- /dev/null
+++ b/blade-starter-redis/src/main/java/org/springblade/core/redis/pubsub/RedisPubSubPublisher.java
@@ -0,0 +1,45 @@
+/**
+ * Copyright (c) 2018-2099, DreamLu 卢春梦 (qq596392912@gmail.com).
+ *
+ * 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
+ *
+ * http://www.gnu.org/licenses/lgpl.html
+ *
+ * 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 redisSerializer;
+
+ @Override
+ public Long publish(String channel, T message) {
+ return bladeRedis.publish(channel, message, redisSerializer::serialize);
+ }
+
+ @Override
+ public void afterPropertiesSet() throws Exception {
+ log.info("RPubSubPublisher init success.");
+ }
+}
diff --git a/blade-starter-redis/src/main/java/org/springblade/core/redis/serializer/BytesWrapper.java b/blade-starter-redis/src/main/java/org/springblade/core/redis/serializer/BytesWrapper.java
new file mode 100644
index 0000000..9ab4cc2
--- /dev/null
+++ b/blade-starter-redis/src/main/java/org/springblade/core/redis/serializer/BytesWrapper.java
@@ -0,0 +1,51 @@
+/**
+ * Copyright (c) 2018-2099, DreamLu 卢春梦 (qq596392912@gmail.com).
+ *
+ * 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
+ *
+ * http://www.gnu.org/licenses/lgpl.html
+ *
+ * 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 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 clone() {
+ try {
+ return (BytesWrapper) super.clone();
+ } catch (CloneNotSupportedException e) {
+ return new BytesWrapper<>();
+ }
+ }
+}
diff --git a/blade-starter-redis/src/main/java/org/springblade/core/redis/serializer/ProtoStuffSerializer.java b/blade-starter-redis/src/main/java/org/springblade/core/redis/serializer/ProtoStuffSerializer.java
new file mode 100644
index 0000000..bef9ba8
--- /dev/null
+++ b/blade-starter-redis/src/main/java/org/springblade/core/redis/serializer/ProtoStuffSerializer.java
@@ -0,0 +1,61 @@
+/**
+ * Copyright (c) 2018-2099, DreamLu 卢春梦 (qq596392912@gmail.com).
+ *
+ * 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
+ *
+ * http://www.gnu.org/licenses/lgpl.html
+ *
+ * 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 {
+ private final Schema 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 wrapper = new BytesWrapper<>();
+ ProtobufIOUtil.mergeFrom(bytes, wrapper, schema);
+ return wrapper.getValue();
+ }
+}
diff --git a/blade-starter-redis/src/main/java/org/springblade/core/redis/serializer/RedisKeySerializer.java b/blade-starter-redis/src/main/java/org/springblade/core/redis/serializer/RedisKeySerializer.java
new file mode 100644
index 0000000..ba0b7ee
--- /dev/null
+++ b/blade-starter-redis/src/main/java/org/springblade/core/redis/serializer/RedisKeySerializer.java
@@ -0,0 +1,74 @@
+/**
+ * Copyright (c) 2018-2099, DreamLu 卢春梦 (qq596392912@gmail.com).
+ *
+ * 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
+ *
+ * http://www.gnu.org/licenses/lgpl.html
+ *
+ * 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序列化为字符串
+ *
+ *
+ * spring cache中的简单基本类型直接使用 StringRedisSerializer 会有问题
+ *
+ *
+ * @author L.cm
+ */
+public class RedisKeySerializer implements RedisSerializer {
+ 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);
+ }
+
+}
diff --git a/pom.xml b/pom.xml
index fa531f7..5f21dca 100644
--- a/pom.xml
+++ b/pom.xml
@@ -61,8 +61,8 @@
3.1.4
1.2.23
- 6.1.14
- 3.2.10
+ 6.1.15
+ 3.2.12
3.2.3
2023.0.3
@@ -85,6 +85,7 @@
blade-starter-log
blade-starter-mybatis
blade-starter-oss
+ blade-starter-redis
blade-starter-report
blade-starter-social
blade-starter-swagger
@@ -197,6 +198,11 @@
blade-starter-oss
${revision}