初始化仓库
This commit is contained in:
@@ -0,0 +1,28 @@
|
||||
package xtools.boot.cache.redis;
|
||||
|
||||
import org.springframework.context.annotation.Import;
|
||||
import xtools.boot.cache.redis.selector.BootCacheRedisImportSelector;
|
||||
import xtools.boot.core.utils.ModuleLoadUtils;
|
||||
|
||||
/**
|
||||
* <p>Title : BootCacheRedisConfiguration</p>
|
||||
* <p>Description : BootCacheRedisConfiguration</p>
|
||||
* <p>DevelopTools : Idea_x64_v2026.1</p>
|
||||
* <p>DevelopSystem : macOS Sequoia 15.7.5</p>
|
||||
* <p>Company : org.xujun</p>
|
||||
*
|
||||
* @author : XuJun
|
||||
* @version : 5.0.0
|
||||
* @date : 2026/01/01 09:30
|
||||
*/
|
||||
@Import(BootCacheRedisImportSelector.class)
|
||||
public class BootCacheRedisConfiguration {
|
||||
|
||||
/**
|
||||
* 构造方法
|
||||
*/
|
||||
public BootCacheRedisConfiguration() {
|
||||
ModuleLoadUtils.loadSuccess(BootCacheRedisConfiguration.class);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,458 @@
|
||||
package xtools.boot.cache.redis.base;
|
||||
|
||||
import com.alibaba.fastjson2.JSON;
|
||||
import jakarta.annotation.Resource;
|
||||
import org.springframework.data.redis.core.RedisTemplate;
|
||||
import org.springframework.data.redis.core.StringRedisTemplate;
|
||||
import org.springframework.data.redis.core.script.DefaultRedisScript;
|
||||
import org.springframework.stereotype.Component;
|
||||
import xtools.base.config.BaseParams;
|
||||
import xtools.core.ArrUtils;
|
||||
import xtools.core.CollectionUtils;
|
||||
import xtools.core.StringUtils;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.StringJoiner;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* <p>Title : RedisService</p>
|
||||
* <p>Description : RedisService</p>
|
||||
* <p>DevelopTools : Idea_x64_v2026.1</p>
|
||||
* <p>DevelopSystem : macOS Sequoia 15.7.5</p>
|
||||
* <p>Company : org.xujun</p>
|
||||
*
|
||||
* @author : XuJun
|
||||
* @version : 5.0.0
|
||||
* @date : 2026/01/01 09:30
|
||||
*/
|
||||
@Component
|
||||
public class RedisService implements BaseParams {
|
||||
|
||||
/**
|
||||
* RedisTemplate
|
||||
**/
|
||||
@Resource
|
||||
private RedisTemplate<String, Object> redisTemplate;
|
||||
|
||||
/**
|
||||
* StringRedisTemplate
|
||||
**/
|
||||
@Resource
|
||||
private StringRedisTemplate stringRedisTemplate;
|
||||
|
||||
/**
|
||||
* 获取组合 Key
|
||||
*
|
||||
* @param keys 多个 Key
|
||||
* @return 组合 Key
|
||||
*/
|
||||
public String getKey(String... keys) {
|
||||
if (ArrUtils.isEmpty(keys)) {
|
||||
return null;
|
||||
}
|
||||
StringJoiner sj = new StringJoiner(CP_COLON);
|
||||
for (String key : keys) {
|
||||
sj.add(key);
|
||||
}
|
||||
return sj.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* 整数 值递增一
|
||||
*
|
||||
* @param key Key
|
||||
* @return 递增后的值
|
||||
*/
|
||||
public Long incr(String key) {
|
||||
return stringRedisTemplate.opsForValue().increment(key);
|
||||
}
|
||||
|
||||
/**
|
||||
* 保存数据
|
||||
*
|
||||
* @param key Key
|
||||
* @param data 数据
|
||||
*/
|
||||
public void set(String key, Object data) {
|
||||
this.set(key, data, null, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* 保存数据
|
||||
*
|
||||
* @param key Key
|
||||
* @param data 数据
|
||||
* @param timeout 过期时间
|
||||
*/
|
||||
public void set(String key, Object data, Long timeout) {
|
||||
this.set(key, data, timeout, TimeUnit.SECONDS);
|
||||
}
|
||||
|
||||
/**
|
||||
* 保存数据
|
||||
*
|
||||
* @param key Key
|
||||
* @param data 数据
|
||||
* @param timeout 过期时间
|
||||
* @param unit 时间单位
|
||||
*/
|
||||
public void set(String key, Object data, Long timeout, TimeUnit unit) {
|
||||
// 参数校验
|
||||
if (StringUtils.isEmpty(key) || data == null) {
|
||||
return;
|
||||
}
|
||||
// 序列化数据
|
||||
String serializeData;
|
||||
if (data instanceof String) {
|
||||
serializeData = data.toString();
|
||||
} else {
|
||||
serializeData = JSON.toJSONString(data);
|
||||
}
|
||||
if (StringUtils.isEmpty(serializeData)) {
|
||||
return;
|
||||
}
|
||||
if (unit == null) {
|
||||
unit = TimeUnit.SECONDS;
|
||||
}
|
||||
// 判断是否有过期时间
|
||||
if (timeout == null || timeout <= 0) {
|
||||
stringRedisTemplate.opsForValue().set(key, serializeData);
|
||||
} else {
|
||||
stringRedisTemplate.opsForValue().set(key, serializeData, timeout, unit);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 保存数据(如果key存在则保存失败)
|
||||
*
|
||||
* @param key Key
|
||||
* @param data 数据
|
||||
* @return 保存结果
|
||||
*/
|
||||
public Boolean setNx(String key, Object data) {
|
||||
return this.setNx(key, data, null, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* 保存数据(如果key存在则保存失败)
|
||||
*
|
||||
* @param key Key
|
||||
* @param data 数据
|
||||
* @param timeout 有效时间
|
||||
* @return 保存结果
|
||||
*/
|
||||
public Boolean setNx(String key, Object data, Long timeout) {
|
||||
return this.setNx(key, data, timeout, TimeUnit.SECONDS);
|
||||
}
|
||||
|
||||
/**
|
||||
* 保存数据(如果key存在则保存失败)
|
||||
*
|
||||
* @param key Key
|
||||
* @param data 数据
|
||||
* @param timeout 有效时间
|
||||
* @param unit 时间单位
|
||||
* @return 保存结果
|
||||
*/
|
||||
public Boolean setNx(String key, Object data, Long timeout, TimeUnit unit) {
|
||||
// 参数校验
|
||||
if (StringUtils.isEmpty(key) || data == null) {
|
||||
return false;
|
||||
}
|
||||
// 序列化数据
|
||||
String serializeData;
|
||||
if (data instanceof String) {
|
||||
serializeData = data.toString();
|
||||
} else {
|
||||
serializeData = JSON.toJSONString(data);
|
||||
}
|
||||
if (StringUtils.isEmpty(serializeData)) {
|
||||
return false;
|
||||
}
|
||||
if (unit == null) {
|
||||
unit = TimeUnit.SECONDS;
|
||||
}
|
||||
// 判断是否有过期时间
|
||||
if (timeout == null || timeout <= 0) {
|
||||
return stringRedisTemplate.opsForValue().setIfAbsent(key, serializeData);
|
||||
} else {
|
||||
return stringRedisTemplate.opsForValue().setIfAbsent(key, serializeData, timeout, unit);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取数据
|
||||
*
|
||||
* @param <T> Class 类型
|
||||
* @param key Key
|
||||
* @param clazz Class
|
||||
* @return 数据
|
||||
*/
|
||||
public <T> T get(String key, Class<T> clazz) {
|
||||
// 参数校验
|
||||
if (StringUtils.isEmpty(key)) {
|
||||
return null;
|
||||
}
|
||||
// 从缓存获取数据
|
||||
String data = stringRedisTemplate.opsForValue().get(key);
|
||||
if (StringUtils.isEmpty(data)) {
|
||||
return null;
|
||||
}
|
||||
return JSON.to(clazz, data);
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除 key 对应的数据
|
||||
*
|
||||
* @param key Key
|
||||
* @return 删除结果
|
||||
*/
|
||||
public Boolean del(String key) {
|
||||
// 参数校验
|
||||
if (StringUtils.isEmpty(key)) {
|
||||
return false;
|
||||
}
|
||||
return stringRedisTemplate.delete(key);
|
||||
}
|
||||
|
||||
/**
|
||||
* 更改 Key
|
||||
*
|
||||
* @param oldKey 旧Key
|
||||
* @param newKey 新Key
|
||||
*/
|
||||
public void rename(String oldKey, String newKey) {
|
||||
// 参数校验
|
||||
if (StringUtils.isEmpty(oldKey) || StringUtils.isEmpty(newKey)) {
|
||||
return;
|
||||
}
|
||||
stringRedisTemplate.rename(oldKey, newKey);
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置key的过期时间(秒)
|
||||
*
|
||||
* @param key Key
|
||||
* @param timeout 时间(秒)
|
||||
* @return 设置结果
|
||||
*/
|
||||
public Boolean expire(String key, long timeout) {
|
||||
// 参数校验
|
||||
if (StringUtils.isEmpty(key) || timeout <= 0) {
|
||||
return false;
|
||||
}
|
||||
return stringRedisTemplate.expire(key, timeout, TimeUnit.SECONDS);
|
||||
}
|
||||
|
||||
/**
|
||||
* 取消对 Key 过期时间的设置
|
||||
*
|
||||
* @param key Key
|
||||
* @return true or false
|
||||
*/
|
||||
public Boolean persist(String key) {
|
||||
// 参数校验
|
||||
if (StringUtils.isEmpty(key)) {
|
||||
return false;
|
||||
}
|
||||
return stringRedisTemplate.persist(key);
|
||||
}
|
||||
|
||||
/**
|
||||
* 查找所有匹配给定的模式的键
|
||||
*
|
||||
* @param pattern key的表达式,*表示多个,?表示一个
|
||||
* @return 全部符合表达式记录
|
||||
*/
|
||||
public Set<String> getByPattern(String pattern) {
|
||||
// 参数校验
|
||||
if (StringUtils.isEmpty(pattern)) {
|
||||
return null;
|
||||
}
|
||||
return stringRedisTemplate.keys(pattern);
|
||||
}
|
||||
|
||||
/**
|
||||
* 尝试获取锁
|
||||
*
|
||||
* @param key 锁的key
|
||||
* @param value 请求标识(可用UUID)
|
||||
* @param expireTime 过期时间(秒)
|
||||
* @return 是否获取成功
|
||||
*/
|
||||
public boolean tryLock(String key, String value, long expireTime) {
|
||||
Boolean result = redisTemplate.opsForValue().setIfAbsent(key, value, expireTime, TimeUnit.SECONDS);
|
||||
return Boolean.TRUE.equals(result);
|
||||
}
|
||||
|
||||
/**
|
||||
* 释放锁(使用Lua脚本保证原子性)
|
||||
*
|
||||
* @param key 锁的key
|
||||
* @param value 请求标识
|
||||
* @return 是否释放成功
|
||||
*/
|
||||
public boolean releaseLock(String key, String value) {
|
||||
String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
|
||||
|
||||
DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>();
|
||||
redisScript.setScriptText(script);
|
||||
redisScript.setResultType(Long.class);
|
||||
|
||||
Long result = redisTemplate.execute(redisScript, Collections.singletonList(key), value);
|
||||
return Long.valueOf(CP_NUM1).equals(result);
|
||||
}
|
||||
|
||||
/**
|
||||
* 保存 Hash 数据
|
||||
*
|
||||
* @param key Key
|
||||
* @param map Hash 数据
|
||||
* @return 保存结果
|
||||
*/
|
||||
public boolean hashPutAll(String key, Map<Object, String> map) {
|
||||
// 参数校验
|
||||
if (StringUtils.isEmpty(key)) {
|
||||
return false;
|
||||
}
|
||||
if (CollectionUtils.isEmpty(map)) {
|
||||
return false;
|
||||
}
|
||||
stringRedisTemplate.opsForHash().putAll(key, map);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 保存 Hash 数据
|
||||
*
|
||||
* @param key key
|
||||
* @param hashKey hashKey
|
||||
* @param data 数据
|
||||
* @return 保存结果
|
||||
*/
|
||||
public boolean hashPut(String key, Object hashKey, Object data) {
|
||||
// 参数校验
|
||||
if (StringUtils.isEmpty(key) || Objects.isNull(hashKey)) {
|
||||
return false;
|
||||
}
|
||||
// 序列化数据
|
||||
String serializeData;
|
||||
if (data instanceof String) {
|
||||
serializeData = data.toString();
|
||||
} else {
|
||||
serializeData = JSON.toJSONString(data);
|
||||
}
|
||||
if (StringUtils.isEmpty(serializeData)) {
|
||||
return false;
|
||||
}
|
||||
stringRedisTemplate.opsForHash().putIfAbsent(key, hashKey, serializeData);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 保存 Hash 数据
|
||||
*
|
||||
* @param key key
|
||||
* @param hashKey hashKey
|
||||
* @param data 数据
|
||||
* @param expireTime 过期时间(秒)
|
||||
* @return 保存结果
|
||||
*/
|
||||
public boolean hashPut(String key, Object hashKey, Object data, long expireTime) {
|
||||
// 参数校验
|
||||
if (StringUtils.isEmpty(key) || Objects.isNull(hashKey)) {
|
||||
return false;
|
||||
}
|
||||
// 序列化数据
|
||||
String serializeData;
|
||||
if (data instanceof String) {
|
||||
serializeData = data.toString();
|
||||
} else {
|
||||
serializeData = JSON.toJSONString(data);
|
||||
}
|
||||
if (StringUtils.isEmpty(serializeData)) {
|
||||
return false;
|
||||
}
|
||||
String script =
|
||||
"redis.call('HSET', KEYS[1], ARGV[1], ARGV[2]) " +
|
||||
"redis.call('HEXPIRE', KEYS[1], ARGV[3], 'FIELDS', 1, ARGV[1]) " +
|
||||
"return 1";
|
||||
Long result = stringRedisTemplate.execute(
|
||||
new DefaultRedisScript<>(script, Long.class),
|
||||
Collections.singletonList(key),
|
||||
hashKey, serializeData, String.valueOf(expireTime)
|
||||
);
|
||||
return Long.valueOf(CP_NUM1).equals(result);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 Hash 中所有数据
|
||||
*
|
||||
* @param <T> 结果类型
|
||||
* @param key key
|
||||
* @param clazz 结果类型
|
||||
* @return 数据集
|
||||
*/
|
||||
public <T> T hashEntries(String key, Class<T> clazz) {
|
||||
// 参数校验
|
||||
if (StringUtils.isEmpty(key)) {
|
||||
return null;
|
||||
}
|
||||
Map<Object, Object> data = stringRedisTemplate.opsForHash().entries(key);
|
||||
if (CollectionUtils.isEmpty(data)) {
|
||||
return null;
|
||||
}
|
||||
return JSON.to(clazz, data);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 Hash 中指定 Key 的数据
|
||||
*
|
||||
* @param <T> 结果类型
|
||||
* @param key key
|
||||
* @param hashKey hashKey
|
||||
* @param clazz 结果类型
|
||||
* @return 数据
|
||||
*/
|
||||
public <T> T hashGet(String key, Object hashKey, Class<T> clazz) {
|
||||
// 参数校验
|
||||
if (StringUtils.isEmpty(key) || Objects.isNull(hashKey)) {
|
||||
return null;
|
||||
}
|
||||
Object data = stringRedisTemplate.opsForHash().get(key, hashKey);
|
||||
return JSON.to(clazz, data);
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除 Hash 中指定 Key 的数据
|
||||
*
|
||||
* @param key key
|
||||
* @param hashKeys hashKey数组
|
||||
* @return 删除结果
|
||||
*/
|
||||
public Long hashDelete(String key, Object... hashKeys) {
|
||||
if (StringUtils.isBlank(key) || ArrUtils.isEmpty(hashKeys)) {
|
||||
return 0L;
|
||||
}
|
||||
return stringRedisTemplate.opsForHash().delete(key, hashKeys);
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断 Hash 中是否存在指定 Key 的数据
|
||||
*
|
||||
* @param key key
|
||||
* @param hashKey hashKey
|
||||
* @return 存在结果
|
||||
*/
|
||||
public boolean hashExists(String key, Object hashKey) {
|
||||
if (StringUtils.isBlank(key) || Objects.isNull(hashKey)) {
|
||||
return false;
|
||||
}
|
||||
return stringRedisTemplate.opsForHash().hasKey(key, hashKey);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
package xtools.boot.cache.redis.enums;
|
||||
|
||||
/**
|
||||
* <p>Title : BaseCacheEnum</p>
|
||||
* <p>Description : BaseCacheEnum</p>
|
||||
* <p>DevelopTools : Idea_x64_v2026.1</p>
|
||||
* <p>DevelopSystem : macOS Sequoia 15.7.5</p>
|
||||
* <p>Company : org.xujun</p>
|
||||
*
|
||||
* @author : XuJun
|
||||
* @version : 5.0.0
|
||||
* @date : 2026/3/25 14:57
|
||||
*/
|
||||
public interface BaseCacheEnum {
|
||||
|
||||
/**
|
||||
* 获取key
|
||||
*
|
||||
* @return key
|
||||
*/
|
||||
String key();
|
||||
|
||||
/**
|
||||
* 获取超时时间
|
||||
*
|
||||
* @return 超时时间
|
||||
*/
|
||||
Long expireTime();
|
||||
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
package xtools.boot.cache.redis.monitor;
|
||||
|
||||
import com.alibaba.fastjson2.JSONArray;
|
||||
import com.alibaba.fastjson2.JSONObject;
|
||||
import jakarta.annotation.Resource;
|
||||
import org.springframework.data.redis.connection.RedisCommands;
|
||||
import org.springframework.data.redis.connection.RedisConnectionFactory;
|
||||
import org.springframework.data.redis.core.RedisTemplate;
|
||||
import org.springframework.stereotype.Component;
|
||||
import xtools.core.StringUtils;
|
||||
|
||||
import java.util.Objects;
|
||||
import java.util.Properties;
|
||||
|
||||
/**
|
||||
* <p>Title : RedisMonitor</p>
|
||||
* <p>Description : RedisMonitor</p>
|
||||
* <p>DevelopTools : Idea_x64_v2026.1</p>
|
||||
* <p>DevelopSystem : macOS Sequoia 15.7.5</p>
|
||||
* <p>Company : org.xujun</p>
|
||||
*
|
||||
* @author : XuJun
|
||||
* @version : 5.0.0
|
||||
* @date : 2026/01/01 09:30
|
||||
*/
|
||||
@Component
|
||||
public class RedisMonitor {
|
||||
|
||||
@Resource
|
||||
private RedisTemplate<String, String> redisTemplate;
|
||||
|
||||
/**
|
||||
* 状态
|
||||
*
|
||||
* @return 状态数据
|
||||
*/
|
||||
public JSONObject stats() {
|
||||
RedisConnectionFactory factory = redisTemplate.getConnectionFactory();
|
||||
if (Objects.isNull(factory)) {
|
||||
return null;
|
||||
}
|
||||
// 获取redis命令
|
||||
RedisCommands commands = factory.getConnection().commands();
|
||||
// 获取相对应信息
|
||||
Properties info = commands.info();
|
||||
Object dbSize = commands.dbSize();
|
||||
Properties commandStats = commands.info("commandstats");
|
||||
JSONArray cmd = new JSONArray();
|
||||
if (Objects.nonNull(commandStats)) {
|
||||
commandStats.stringPropertyNames().forEach(key -> {
|
||||
String property = commandStats.getProperty(key);
|
||||
JSONObject data = JSONObject.of(
|
||||
"name", StringUtils.removeStart(key, "cmdstat_"),
|
||||
"value", StringUtils.substringBetween(property, "calls=", ",usec")
|
||||
);
|
||||
cmd.add(data);
|
||||
});
|
||||
}
|
||||
// 封装数据
|
||||
return JSONObject.of("info", info, "dbSize", dbSize, "cmd", cmd);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
package xtools.boot.cache.redis.selector;
|
||||
|
||||
import org.jspecify.annotations.NonNull;
|
||||
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
|
||||
import org.springframework.context.annotation.ClassPathBeanDefinitionScanner;
|
||||
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
|
||||
import org.springframework.core.type.AnnotationMetadata;
|
||||
|
||||
/**
|
||||
* <p>Title : BootCacheRedisImportSelector</p>
|
||||
* <p>Description : BootCacheRedisImportSelector</p>
|
||||
* <p>DevelopTools : Idea_x64_v2026.1</p>
|
||||
* <p>DevelopSystem : macOS Sequoia 15.7.5</p>
|
||||
* <p>Company : org.xujun</p>
|
||||
*
|
||||
* @author : XuJun
|
||||
* @version : 5.0.0
|
||||
* @date : 2026/01/01 09:30
|
||||
*/
|
||||
public class BootCacheRedisImportSelector implements ImportBeanDefinitionRegistrar {
|
||||
|
||||
/**
|
||||
* 根据给定的注释元数据,根据需要注册bean
|
||||
*
|
||||
* @param importingClassMetadata AnnotationMetadata
|
||||
* @param registry BeanDefinitionRegistry
|
||||
*/
|
||||
@Override
|
||||
public void registerBeanDefinitions(@NonNull AnnotationMetadata importingClassMetadata, @NonNull BeanDefinitionRegistry registry) {
|
||||
// 构建扫描对象
|
||||
ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner(registry, true);
|
||||
// 扫描包下路径
|
||||
scanner.scan("xtools.boot.cache.redis");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
package xtools.boot.cache.redis.utils;
|
||||
|
||||
import xtools.boot.cache.redis.base.RedisService;
|
||||
import xtools.boot.core.utils.SpringContextUtils;
|
||||
|
||||
/**
|
||||
* <p>Title : RedisUtils</p>
|
||||
* <p>Description : RedisUtils</p>
|
||||
* <p>DevelopTools : Idea_x64_v2026.1</p>
|
||||
* <p>DevelopSystem : macOS Sequoia 15.7.5</p>
|
||||
* <p>Company : org.xujun</p>
|
||||
*
|
||||
* @author : XuJun
|
||||
* @version : 5.0.0
|
||||
* @date : 2026/2/10 09:02
|
||||
*/
|
||||
public class RedisUtils {
|
||||
|
||||
/**
|
||||
* Redis 服务
|
||||
**/
|
||||
private static RedisService redisService;
|
||||
|
||||
/**
|
||||
* 获取 Redis 服务
|
||||
*
|
||||
* @return Redis 服务
|
||||
*/
|
||||
public static RedisService get() {
|
||||
if (redisService != null) {
|
||||
return redisService;
|
||||
}
|
||||
return SpringContextUtils.getBean(RedisService.class);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
xtools.boot.cache.redis.BootCacheRedisConfiguration
|
||||
Reference in New Issue
Block a user