forked from dyf/fys-Multi-tenant
fys
This commit is contained in:
@ -0,0 +1,45 @@
|
||||
package com.fuyuanshen.common.redis.config;
|
||||
|
||||
import com.github.benmanes.caffeine.cache.Cache;
|
||||
import com.github.benmanes.caffeine.cache.Caffeine;
|
||||
import com.fuyuanshen.common.redis.manager.PlusSpringCacheManager;
|
||||
import org.springframework.boot.autoconfigure.AutoConfiguration;
|
||||
import org.springframework.cache.CacheManager;
|
||||
import org.springframework.cache.annotation.EnableCaching;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* 缓存配置
|
||||
*
|
||||
* @author Lion Li
|
||||
*/
|
||||
@AutoConfiguration
|
||||
@EnableCaching
|
||||
public class CacheConfig {
|
||||
|
||||
/**
|
||||
* caffeine 本地缓存处理器
|
||||
*/
|
||||
@Bean
|
||||
public Cache<Object, Object> caffeine() {
|
||||
return Caffeine.newBuilder()
|
||||
// 设置最后一次写入或访问后经过固定时间过期
|
||||
.expireAfterWrite(30, TimeUnit.SECONDS)
|
||||
// 初始的缓存空间大小
|
||||
.initialCapacity(100)
|
||||
// 缓存的最大条数
|
||||
.maximumSize(1000)
|
||||
.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* 自定义缓存管理器 整合spring-cache
|
||||
*/
|
||||
@Bean
|
||||
public CacheManager cacheManager() {
|
||||
return new PlusSpringCacheManager();
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,159 @@
|
||||
package com.fuyuanshen.common.redis.config;
|
||||
|
||||
import cn.hutool.core.util.ObjectUtil;
|
||||
import com.fasterxml.jackson.annotation.JsonAutoDetect;
|
||||
import com.fasterxml.jackson.annotation.PropertyAccessor;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.databind.jsontype.impl.LaissezFaireSubTypeValidator;
|
||||
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
|
||||
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer;
|
||||
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import com.fuyuanshen.common.core.utils.SpringUtils;
|
||||
import com.fuyuanshen.common.redis.config.properties.RedissonProperties;
|
||||
import com.fuyuanshen.common.redis.handler.KeyPrefixHandler;
|
||||
import com.fuyuanshen.common.redis.handler.RedisExceptionHandler;
|
||||
import org.redisson.client.codec.StringCodec;
|
||||
import org.redisson.codec.CompositeCodec;
|
||||
import org.redisson.codec.TypedJsonJacksonCodec;
|
||||
import org.redisson.spring.starter.RedissonAutoConfigurationCustomizer;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.autoconfigure.AutoConfiguration;
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.core.task.VirtualThreadTaskExecutor;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.TimeZone;
|
||||
|
||||
/**
|
||||
* redis配置
|
||||
*
|
||||
* @author Lion Li
|
||||
*/
|
||||
@Slf4j
|
||||
@AutoConfiguration
|
||||
@EnableConfigurationProperties(RedissonProperties.class)
|
||||
public class RedisConfig {
|
||||
|
||||
@Autowired
|
||||
private RedissonProperties redissonProperties;
|
||||
|
||||
@Bean
|
||||
public RedissonAutoConfigurationCustomizer redissonCustomizer() {
|
||||
return config -> {
|
||||
JavaTimeModule javaTimeModule = new JavaTimeModule();
|
||||
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
|
||||
javaTimeModule.addSerializer(LocalDateTime.class, new LocalDateTimeSerializer(formatter));
|
||||
javaTimeModule.addDeserializer(LocalDateTime.class, new LocalDateTimeDeserializer(formatter));
|
||||
ObjectMapper om = new ObjectMapper();
|
||||
om.registerModule(javaTimeModule);
|
||||
om.setTimeZone(TimeZone.getDefault());
|
||||
om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
|
||||
// 指定序列化输入的类型,类必须是非final修饰的。序列化时将对象全类名一起保存下来
|
||||
om.activateDefaultTyping(LaissezFaireSubTypeValidator.instance, ObjectMapper.DefaultTyping.NON_FINAL);
|
||||
// LoggerFactory.useSlf4jLogging(true);
|
||||
// FuryCodec furyCodec = new FuryCodec();
|
||||
// CompositeCodec codec = new CompositeCodec(StringCodec.INSTANCE, furyCodec, furyCodec);
|
||||
TypedJsonJacksonCodec jsonCodec = new TypedJsonJacksonCodec(Object.class, om);
|
||||
// 组合序列化 key 使用 String 内容使用通用 json 格式
|
||||
CompositeCodec codec = new CompositeCodec(StringCodec.INSTANCE, jsonCodec, jsonCodec);
|
||||
config.setThreads(redissonProperties.getThreads())
|
||||
.setNettyThreads(redissonProperties.getNettyThreads())
|
||||
// 缓存 Lua 脚本 减少网络传输(redisson 大部分的功能都是基于 Lua 脚本实现)
|
||||
.setUseScriptCache(true)
|
||||
.setCodec(codec);
|
||||
if (SpringUtils.isVirtual()) {
|
||||
config.setNettyExecutor(new VirtualThreadTaskExecutor("redisson-"));
|
||||
}
|
||||
RedissonProperties.SingleServerConfig singleServerConfig = redissonProperties.getSingleServerConfig();
|
||||
if (ObjectUtil.isNotNull(singleServerConfig)) {
|
||||
// 使用单机模式
|
||||
config.useSingleServer()
|
||||
//设置redis key前缀
|
||||
.setNameMapper(new KeyPrefixHandler(redissonProperties.getKeyPrefix()))
|
||||
.setTimeout(singleServerConfig.getTimeout())
|
||||
.setClientName(singleServerConfig.getClientName())
|
||||
.setIdleConnectionTimeout(singleServerConfig.getIdleConnectionTimeout())
|
||||
.setSubscriptionConnectionPoolSize(singleServerConfig.getSubscriptionConnectionPoolSize())
|
||||
.setConnectionMinimumIdleSize(singleServerConfig.getConnectionMinimumIdleSize())
|
||||
.setConnectionPoolSize(singleServerConfig.getConnectionPoolSize());
|
||||
}
|
||||
// 集群配置方式 参考下方注释
|
||||
RedissonProperties.ClusterServersConfig clusterServersConfig = redissonProperties.getClusterServersConfig();
|
||||
if (ObjectUtil.isNotNull(clusterServersConfig)) {
|
||||
config.useClusterServers()
|
||||
//设置redis key前缀
|
||||
.setNameMapper(new KeyPrefixHandler(redissonProperties.getKeyPrefix()))
|
||||
.setTimeout(clusterServersConfig.getTimeout())
|
||||
.setClientName(clusterServersConfig.getClientName())
|
||||
.setIdleConnectionTimeout(clusterServersConfig.getIdleConnectionTimeout())
|
||||
.setSubscriptionConnectionPoolSize(clusterServersConfig.getSubscriptionConnectionPoolSize())
|
||||
.setMasterConnectionMinimumIdleSize(clusterServersConfig.getMasterConnectionMinimumIdleSize())
|
||||
.setMasterConnectionPoolSize(clusterServersConfig.getMasterConnectionPoolSize())
|
||||
.setSlaveConnectionMinimumIdleSize(clusterServersConfig.getSlaveConnectionMinimumIdleSize())
|
||||
.setSlaveConnectionPoolSize(clusterServersConfig.getSlaveConnectionPoolSize())
|
||||
.setReadMode(clusterServersConfig.getReadMode())
|
||||
.setSubscriptionMode(clusterServersConfig.getSubscriptionMode());
|
||||
}
|
||||
log.info("初始化 redis 配置");
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 异常处理器
|
||||
*/
|
||||
@Bean
|
||||
public RedisExceptionHandler redisExceptionHandler() {
|
||||
return new RedisExceptionHandler();
|
||||
}
|
||||
|
||||
/**
|
||||
* redis集群配置 yml
|
||||
*
|
||||
* --- # redis 集群配置(单机与集群只能开启一个另一个需要注释掉)
|
||||
* spring.data:
|
||||
* redis:
|
||||
* cluster:
|
||||
* nodes:
|
||||
* - 192.168.0.100:6379
|
||||
* - 192.168.0.101:6379
|
||||
* - 192.168.0.102:6379
|
||||
* # 密码
|
||||
* password:
|
||||
* # 连接超时时间
|
||||
* timeout: 10s
|
||||
* # 是否开启ssl
|
||||
* ssl.enabled: false
|
||||
*
|
||||
* redisson:
|
||||
* # 线程池数量
|
||||
* threads: 16
|
||||
* # Netty线程池数量
|
||||
* nettyThreads: 32
|
||||
* # 集群配置
|
||||
* clusterServersConfig:
|
||||
* # 客户端名称
|
||||
* clientName: ${fys.name}
|
||||
* # master最小空闲连接数
|
||||
* masterConnectionMinimumIdleSize: 32
|
||||
* # master连接池大小
|
||||
* masterConnectionPoolSize: 64
|
||||
* # slave最小空闲连接数
|
||||
* slaveConnectionMinimumIdleSize: 32
|
||||
* # slave连接池大小
|
||||
* slaveConnectionPoolSize: 64
|
||||
* # 连接空闲超时,单位:毫秒
|
||||
* idleConnectionTimeout: 10000
|
||||
* # 命令等待超时,单位:毫秒
|
||||
* timeout: 3000
|
||||
* # 发布和订阅连接池大小
|
||||
* subscriptionConnectionPoolSize: 50
|
||||
* # 读取模式
|
||||
* readMode: "SLAVE"
|
||||
* # 订阅模式
|
||||
* subscriptionMode: "MASTER"
|
||||
*/
|
||||
|
||||
}
|
@ -0,0 +1,135 @@
|
||||
package com.fuyuanshen.common.redis.config.properties;
|
||||
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
import org.redisson.config.ReadMode;
|
||||
import org.redisson.config.SubscriptionMode;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
|
||||
/**
|
||||
* Redisson 配置属性
|
||||
*
|
||||
* @author Lion Li
|
||||
*/
|
||||
@Data
|
||||
@ConfigurationProperties(prefix = "redisson")
|
||||
public class RedissonProperties {
|
||||
|
||||
/**
|
||||
* redis缓存key前缀
|
||||
*/
|
||||
private String keyPrefix;
|
||||
|
||||
/**
|
||||
* 线程池数量,默认值 = 当前处理核数量 * 2
|
||||
*/
|
||||
private int threads;
|
||||
|
||||
/**
|
||||
* Netty线程池数量,默认值 = 当前处理核数量 * 2
|
||||
*/
|
||||
private int nettyThreads;
|
||||
|
||||
/**
|
||||
* 单机服务配置
|
||||
*/
|
||||
private SingleServerConfig singleServerConfig;
|
||||
|
||||
/**
|
||||
* 集群服务配置
|
||||
*/
|
||||
private ClusterServersConfig clusterServersConfig;
|
||||
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
public static class SingleServerConfig {
|
||||
|
||||
/**
|
||||
* 客户端名称
|
||||
*/
|
||||
private String clientName;
|
||||
|
||||
/**
|
||||
* 最小空闲连接数
|
||||
*/
|
||||
private int connectionMinimumIdleSize;
|
||||
|
||||
/**
|
||||
* 连接池大小
|
||||
*/
|
||||
private int connectionPoolSize;
|
||||
|
||||
/**
|
||||
* 连接空闲超时,单位:毫秒
|
||||
*/
|
||||
private int idleConnectionTimeout;
|
||||
|
||||
/**
|
||||
* 命令等待超时,单位:毫秒
|
||||
*/
|
||||
private int timeout;
|
||||
|
||||
/**
|
||||
* 发布和订阅连接池大小
|
||||
*/
|
||||
private int subscriptionConnectionPoolSize;
|
||||
|
||||
}
|
||||
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
public static class ClusterServersConfig {
|
||||
|
||||
/**
|
||||
* 客户端名称
|
||||
*/
|
||||
private String clientName;
|
||||
|
||||
/**
|
||||
* master最小空闲连接数
|
||||
*/
|
||||
private int masterConnectionMinimumIdleSize;
|
||||
|
||||
/**
|
||||
* master连接池大小
|
||||
*/
|
||||
private int masterConnectionPoolSize;
|
||||
|
||||
/**
|
||||
* slave最小空闲连接数
|
||||
*/
|
||||
private int slaveConnectionMinimumIdleSize;
|
||||
|
||||
/**
|
||||
* slave连接池大小
|
||||
*/
|
||||
private int slaveConnectionPoolSize;
|
||||
|
||||
/**
|
||||
* 连接空闲超时,单位:毫秒
|
||||
*/
|
||||
private int idleConnectionTimeout;
|
||||
|
||||
/**
|
||||
* 命令等待超时,单位:毫秒
|
||||
*/
|
||||
private int timeout;
|
||||
|
||||
/**
|
||||
* 发布和订阅连接池大小
|
||||
*/
|
||||
private int subscriptionConnectionPoolSize;
|
||||
|
||||
/**
|
||||
* 读取模式
|
||||
*/
|
||||
private ReadMode readMode;
|
||||
|
||||
/**
|
||||
* 订阅模式
|
||||
*/
|
||||
private SubscriptionMode subscriptionMode;
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,50 @@
|
||||
package com.fuyuanshen.common.redis.handler;
|
||||
|
||||
import com.fuyuanshen.common.core.utils.StringUtils;
|
||||
import org.redisson.api.NameMapper;
|
||||
|
||||
/**
|
||||
* redis缓存key前缀处理
|
||||
*
|
||||
* @author ye
|
||||
* @date 2022/7/14 17:44
|
||||
* @since 4.3.0
|
||||
*/
|
||||
public class KeyPrefixHandler implements NameMapper {
|
||||
|
||||
private final String keyPrefix;
|
||||
|
||||
public KeyPrefixHandler(String keyPrefix) {
|
||||
//前缀为空 则返回空前缀
|
||||
this.keyPrefix = StringUtils.isBlank(keyPrefix) ? "" : keyPrefix + ":";
|
||||
}
|
||||
|
||||
/**
|
||||
* 增加前缀
|
||||
*/
|
||||
@Override
|
||||
public String map(String name) {
|
||||
if (StringUtils.isBlank(name)) {
|
||||
return null;
|
||||
}
|
||||
if (StringUtils.isNotBlank(keyPrefix) && !name.startsWith(keyPrefix)) {
|
||||
return keyPrefix + name;
|
||||
}
|
||||
return name;
|
||||
}
|
||||
|
||||
/**
|
||||
* 去除前缀
|
||||
*/
|
||||
@Override
|
||||
public String unmap(String name) {
|
||||
if (StringUtils.isBlank(name)) {
|
||||
return null;
|
||||
}
|
||||
if (StringUtils.isNotBlank(keyPrefix) && name.startsWith(keyPrefix)) {
|
||||
return name.substring(keyPrefix.length());
|
||||
}
|
||||
return name;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,30 @@
|
||||
package com.fuyuanshen.common.redis.handler;
|
||||
|
||||
import cn.hutool.http.HttpStatus;
|
||||
import com.baomidou.lock.exception.LockFailureException;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import com.fuyuanshen.common.core.domain.R;
|
||||
import org.springframework.web.bind.annotation.ExceptionHandler;
|
||||
import org.springframework.web.bind.annotation.RestControllerAdvice;
|
||||
|
||||
/**
|
||||
* Redis异常处理器
|
||||
*
|
||||
* @author AprilWind
|
||||
*/
|
||||
@Slf4j
|
||||
@RestControllerAdvice
|
||||
public class RedisExceptionHandler {
|
||||
|
||||
/**
|
||||
* 分布式锁Lock4j异常
|
||||
*/
|
||||
@ExceptionHandler(LockFailureException.class)
|
||||
public R<Void> handleLockFailureException(LockFailureException e, HttpServletRequest request) {
|
||||
String requestURI = request.getRequestURI();
|
||||
log.error("获取锁失败了'{}',发生Lock4j异常.", requestURI, e);
|
||||
return R.fail(HttpStatus.HTTP_UNAVAILABLE, "业务处理中,请稍后再试...");
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,97 @@
|
||||
package com.fuyuanshen.common.redis.manager;
|
||||
|
||||
import com.fuyuanshen.common.core.utils.SpringUtils;
|
||||
import org.springframework.cache.Cache;
|
||||
|
||||
import java.util.concurrent.Callable;
|
||||
|
||||
/**
|
||||
* Cache 装饰器模式(用于扩展 Caffeine 一级缓存)
|
||||
*
|
||||
* @author LionLi
|
||||
*/
|
||||
public class CaffeineCacheDecorator implements Cache {
|
||||
|
||||
private static final com.github.benmanes.caffeine.cache.Cache<Object, Object>
|
||||
CAFFEINE = SpringUtils.getBean("caffeine");
|
||||
|
||||
private final String name;
|
||||
private final Cache cache;
|
||||
|
||||
public CaffeineCacheDecorator(String name, Cache cache) {
|
||||
this.name = name;
|
||||
this.cache = cache;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getNativeCache() {
|
||||
return cache.getNativeCache();
|
||||
}
|
||||
|
||||
public String getUniqueKey(Object key) {
|
||||
return name + ":" + key;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ValueWrapper get(Object key) {
|
||||
Object o = CAFFEINE.get(getUniqueKey(key), k -> cache.get(key));
|
||||
return (ValueWrapper) o;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public <T> T get(Object key, Class<T> type) {
|
||||
Object o = CAFFEINE.get(getUniqueKey(key), k -> cache.get(key, type));
|
||||
return (T) o;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void put(Object key, Object value) {
|
||||
CAFFEINE.invalidate(getUniqueKey(key));
|
||||
cache.put(key, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ValueWrapper putIfAbsent(Object key, Object value) {
|
||||
CAFFEINE.invalidate(getUniqueKey(key));
|
||||
return cache.putIfAbsent(key, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void evict(Object key) {
|
||||
evictIfPresent(key);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean evictIfPresent(Object key) {
|
||||
boolean b = cache.evictIfPresent(key);
|
||||
if (b) {
|
||||
CAFFEINE.invalidate(getUniqueKey(key));
|
||||
}
|
||||
return b;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clear() {
|
||||
CAFFEINE.invalidateAll();
|
||||
cache.clear();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean invalidate() {
|
||||
return cache.invalidate();
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public <T> T get(Object key, Callable<T> valueLoader) {
|
||||
Object o = CAFFEINE.get(getUniqueKey(key), k -> cache.get(key, valueLoader));
|
||||
return (T) o;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,202 @@
|
||||
/**
|
||||
* Copyright (c) 2013-2021 Nikita Koksharov
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* 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 com.fuyuanshen.common.redis.manager;
|
||||
|
||||
import com.fuyuanshen.common.redis.utils.RedisUtils;
|
||||
import org.redisson.api.RMap;
|
||||
import org.redisson.api.RMapCache;
|
||||
import org.redisson.spring.cache.CacheConfig;
|
||||
import org.redisson.spring.cache.RedissonCache;
|
||||
import org.springframework.boot.convert.DurationStyle;
|
||||
import org.springframework.cache.Cache;
|
||||
import org.springframework.cache.CacheManager;
|
||||
import org.springframework.cache.transaction.TransactionAwareCacheDecorator;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ConcurrentMap;
|
||||
|
||||
/**
|
||||
* A {@link org.springframework.cache.CacheManager} implementation
|
||||
* backed by Redisson instance.
|
||||
* <p>
|
||||
* 修改 RedissonSpringCacheManager 源码
|
||||
* 重写 cacheName 处理方法 支持多参数
|
||||
*
|
||||
* @author Nikita Koksharov
|
||||
*
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public class PlusSpringCacheManager implements CacheManager {
|
||||
|
||||
private boolean dynamic = true;
|
||||
|
||||
private boolean allowNullValues = true;
|
||||
|
||||
private boolean transactionAware = true;
|
||||
|
||||
Map<String, CacheConfig> configMap = new ConcurrentHashMap<>();
|
||||
ConcurrentMap<String, Cache> instanceMap = new ConcurrentHashMap<>();
|
||||
|
||||
/**
|
||||
* Creates CacheManager supplied by Redisson instance
|
||||
*/
|
||||
public PlusSpringCacheManager() {
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Defines possibility of storing {@code null} values.
|
||||
* <p>
|
||||
* Default is <code>true</code>
|
||||
*
|
||||
* @param allowNullValues stores if <code>true</code>
|
||||
*/
|
||||
public void setAllowNullValues(boolean allowNullValues) {
|
||||
this.allowNullValues = allowNullValues;
|
||||
}
|
||||
|
||||
/**
|
||||
* Defines if cache aware of Spring-managed transactions.
|
||||
* If {@code true} put/evict operations are executed only for successful transaction in after-commit phase.
|
||||
* <p>
|
||||
* Default is <code>false</code>
|
||||
*
|
||||
* @param transactionAware cache is transaction aware if <code>true</code>
|
||||
*/
|
||||
public void setTransactionAware(boolean transactionAware) {
|
||||
this.transactionAware = transactionAware;
|
||||
}
|
||||
|
||||
/**
|
||||
* Defines 'fixed' cache names.
|
||||
* A new cache instance will not be created in dynamic for non-defined names.
|
||||
* <p>
|
||||
* `null` parameter setups dynamic mode
|
||||
*
|
||||
* @param names of caches
|
||||
*/
|
||||
public void setCacheNames(Collection<String> names) {
|
||||
if (names != null) {
|
||||
for (String name : names) {
|
||||
getCache(name);
|
||||
}
|
||||
dynamic = false;
|
||||
} else {
|
||||
dynamic = true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set cache config mapped by cache name
|
||||
*
|
||||
* @param config object
|
||||
*/
|
||||
public void setConfig(Map<String, ? extends CacheConfig> config) {
|
||||
this.configMap = (Map<String, CacheConfig>) config;
|
||||
}
|
||||
|
||||
protected CacheConfig createDefaultConfig() {
|
||||
return new CacheConfig();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Cache getCache(String name) {
|
||||
// 重写 cacheName 支持多参数
|
||||
String[] array = StringUtils.delimitedListToStringArray(name, "#");
|
||||
name = array[0];
|
||||
|
||||
Cache cache = instanceMap.get(name);
|
||||
if (cache != null) {
|
||||
return cache;
|
||||
}
|
||||
if (!dynamic) {
|
||||
return cache;
|
||||
}
|
||||
|
||||
CacheConfig config = configMap.get(name);
|
||||
if (config == null) {
|
||||
config = createDefaultConfig();
|
||||
configMap.put(name, config);
|
||||
}
|
||||
|
||||
if (array.length > 1) {
|
||||
config.setTTL(DurationStyle.detectAndParse(array[1]).toMillis());
|
||||
}
|
||||
if (array.length > 2) {
|
||||
config.setMaxIdleTime(DurationStyle.detectAndParse(array[2]).toMillis());
|
||||
}
|
||||
if (array.length > 3) {
|
||||
config.setMaxSize(Integer.parseInt(array[3]));
|
||||
}
|
||||
int local = 1;
|
||||
if (array.length > 4) {
|
||||
local = Integer.parseInt(array[4]);
|
||||
}
|
||||
|
||||
if (config.getMaxIdleTime() == 0 && config.getTTL() == 0 && config.getMaxSize() == 0) {
|
||||
return createMap(name, config, local);
|
||||
}
|
||||
|
||||
return createMapCache(name, config, local);
|
||||
}
|
||||
|
||||
private Cache createMap(String name, CacheConfig config, int local) {
|
||||
RMap<Object, Object> map = RedisUtils.getClient().getMap(name);
|
||||
|
||||
Cache cache = new RedissonCache(map, allowNullValues);
|
||||
if (local == 1) {
|
||||
cache = new CaffeineCacheDecorator(name, cache);
|
||||
}
|
||||
if (transactionAware) {
|
||||
cache = new TransactionAwareCacheDecorator(cache);
|
||||
}
|
||||
Cache oldCache = instanceMap.putIfAbsent(name, cache);
|
||||
if (oldCache != null) {
|
||||
cache = oldCache;
|
||||
}
|
||||
return cache;
|
||||
}
|
||||
|
||||
private Cache createMapCache(String name, CacheConfig config, int local) {
|
||||
RMapCache<Object, Object> map = RedisUtils.getClient().getMapCache(name);
|
||||
|
||||
Cache cache = new RedissonCache(map, config, allowNullValues);
|
||||
if (local == 1) {
|
||||
cache = new CaffeineCacheDecorator(name, cache);
|
||||
}
|
||||
if (transactionAware) {
|
||||
cache = new TransactionAwareCacheDecorator(cache);
|
||||
}
|
||||
Cache oldCache = instanceMap.putIfAbsent(name, cache);
|
||||
if (oldCache != null) {
|
||||
cache = oldCache;
|
||||
} else {
|
||||
map.setMaxSize(config.getMaxSize());
|
||||
}
|
||||
return cache;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<String> getCacheNames() {
|
||||
return Collections.unmodifiableSet(configMap.keySet());
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -0,0 +1,61 @@
|
||||
package com.fuyuanshen.common.redis.utils;
|
||||
|
||||
import lombok.AccessLevel;
|
||||
import lombok.NoArgsConstructor;
|
||||
import com.fuyuanshen.common.core.utils.SpringUtils;
|
||||
import org.springframework.cache.Cache;
|
||||
import org.springframework.cache.CacheManager;
|
||||
|
||||
/**
|
||||
* 缓存操作工具类
|
||||
*
|
||||
* @author Michelle.Chung
|
||||
*/
|
||||
@NoArgsConstructor(access = AccessLevel.PRIVATE)
|
||||
@SuppressWarnings(value = {"unchecked"})
|
||||
public class CacheUtils {
|
||||
|
||||
private static final CacheManager CACHE_MANAGER = SpringUtils.getBean(CacheManager.class);
|
||||
|
||||
/**
|
||||
* 获取缓存值
|
||||
*
|
||||
* @param cacheNames 缓存组名称
|
||||
* @param key 缓存key
|
||||
*/
|
||||
public static <T> T get(String cacheNames, Object key) {
|
||||
Cache.ValueWrapper wrapper = CACHE_MANAGER.getCache(cacheNames).get(key);
|
||||
return wrapper != null ? (T) wrapper.get() : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 保存缓存值
|
||||
*
|
||||
* @param cacheNames 缓存组名称
|
||||
* @param key 缓存key
|
||||
* @param value 缓存值
|
||||
*/
|
||||
public static void put(String cacheNames, Object key, Object value) {
|
||||
CACHE_MANAGER.getCache(cacheNames).put(key, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除缓存值
|
||||
*
|
||||
* @param cacheNames 缓存组名称
|
||||
* @param key 缓存key
|
||||
*/
|
||||
public static void evict(String cacheNames, Object key) {
|
||||
CACHE_MANAGER.getCache(cacheNames).evict(key);
|
||||
}
|
||||
|
||||
/**
|
||||
* 清空缓存值
|
||||
*
|
||||
* @param cacheNames 缓存组名称
|
||||
*/
|
||||
public static void clear(String cacheNames) {
|
||||
CACHE_MANAGER.getCache(cacheNames).clear();
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,237 @@
|
||||
package com.fuyuanshen.common.redis.utils;
|
||||
|
||||
import lombok.AccessLevel;
|
||||
import lombok.NoArgsConstructor;
|
||||
import com.fuyuanshen.common.core.utils.SpringUtils;
|
||||
import org.redisson.api.*;
|
||||
|
||||
import java.util.concurrent.CompletionStage;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.function.Function;
|
||||
|
||||
/**
|
||||
* 分布式队列工具
|
||||
* 轻量级队列 重量级数据量 请使用 MQ
|
||||
* 要求 redis 5.X 以上
|
||||
*
|
||||
* @author Lion Li
|
||||
* @version 3.6.0 新增
|
||||
*/
|
||||
@NoArgsConstructor(access = AccessLevel.PRIVATE)
|
||||
public class QueueUtils {
|
||||
|
||||
private static final RedissonClient CLIENT = SpringUtils.getBean(RedissonClient.class);
|
||||
|
||||
|
||||
/**
|
||||
* 获取客户端实例
|
||||
*/
|
||||
public static RedissonClient getClient() {
|
||||
return CLIENT;
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加普通队列数据
|
||||
*
|
||||
* @param queueName 队列名
|
||||
* @param data 数据
|
||||
*/
|
||||
public static <T> boolean addQueueObject(String queueName, T data) {
|
||||
RBlockingQueue<T> queue = CLIENT.getBlockingQueue(queueName);
|
||||
return queue.offer(data);
|
||||
}
|
||||
|
||||
/**
|
||||
* 通用获取一个队列数据 没有数据返回 null(不支持延迟队列)
|
||||
*
|
||||
* @param queueName 队列名
|
||||
*/
|
||||
public static <T> T getQueueObject(String queueName) {
|
||||
RBlockingQueue<T> queue = CLIENT.getBlockingQueue(queueName);
|
||||
return queue.poll();
|
||||
}
|
||||
|
||||
/**
|
||||
* 通用删除队列数据(不支持延迟队列)
|
||||
*/
|
||||
public static <T> boolean removeQueueObject(String queueName, T data) {
|
||||
RBlockingQueue<T> queue = CLIENT.getBlockingQueue(queueName);
|
||||
return queue.remove(data);
|
||||
}
|
||||
|
||||
/**
|
||||
* 通用销毁队列 所有阻塞监听 报错(不支持延迟队列)
|
||||
*/
|
||||
public static <T> boolean destroyQueue(String queueName) {
|
||||
RBlockingQueue<T> queue = CLIENT.getBlockingQueue(queueName);
|
||||
return queue.delete();
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加延迟队列数据 默认毫秒
|
||||
*
|
||||
* @param queueName 队列名
|
||||
* @param data 数据
|
||||
* @param time 延迟时间
|
||||
*/
|
||||
public static <T> void addDelayedQueueObject(String queueName, T data, long time) {
|
||||
addDelayedQueueObject(queueName, data, time, TimeUnit.MILLISECONDS);
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加延迟队列数据
|
||||
*
|
||||
* @param queueName 队列名
|
||||
* @param data 数据
|
||||
* @param time 延迟时间
|
||||
* @param timeUnit 单位
|
||||
*/
|
||||
public static <T> void addDelayedQueueObject(String queueName, T data, long time, TimeUnit timeUnit) {
|
||||
RBlockingQueue<T> queue = CLIENT.getBlockingQueue(queueName);
|
||||
RDelayedQueue<T> delayedQueue = CLIENT.getDelayedQueue(queue);
|
||||
delayedQueue.offer(data, time, timeUnit);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取一个延迟队列数据 没有数据返回 null
|
||||
*
|
||||
* @param queueName 队列名
|
||||
*/
|
||||
public static <T> T getDelayedQueueObject(String queueName) {
|
||||
RBlockingQueue<T> queue = CLIENT.getBlockingQueue(queueName);
|
||||
RDelayedQueue<T> delayedQueue = CLIENT.getDelayedQueue(queue);
|
||||
return delayedQueue.poll();
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除延迟队列数据
|
||||
*/
|
||||
public static <T> boolean removeDelayedQueueObject(String queueName, T data) {
|
||||
RBlockingQueue<T> queue = CLIENT.getBlockingQueue(queueName);
|
||||
RDelayedQueue<T> delayedQueue = CLIENT.getDelayedQueue(queue);
|
||||
return delayedQueue.remove(data);
|
||||
}
|
||||
|
||||
/**
|
||||
* 销毁延迟队列 所有阻塞监听 报错
|
||||
*/
|
||||
public static <T> void destroyDelayedQueue(String queueName) {
|
||||
RBlockingQueue<T> queue = CLIENT.getBlockingQueue(queueName);
|
||||
RDelayedQueue<T> delayedQueue = CLIENT.getDelayedQueue(queue);
|
||||
delayedQueue.destroy();
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加优先队列数据
|
||||
*
|
||||
* @param queueName 队列名
|
||||
* @param data 数据
|
||||
*/
|
||||
public static <T> boolean addPriorityQueueObject(String queueName, T data) {
|
||||
RPriorityBlockingQueue<T> priorityBlockingQueue = CLIENT.getPriorityBlockingQueue(queueName);
|
||||
return priorityBlockingQueue.offer(data);
|
||||
}
|
||||
|
||||
/**
|
||||
* 优先队列获取一个队列数据 没有数据返回 null(不支持延迟队列)
|
||||
*
|
||||
* @param queueName 队列名
|
||||
*/
|
||||
public static <T> T getPriorityQueueObject(String queueName) {
|
||||
RPriorityBlockingQueue<T> queue = CLIENT.getPriorityBlockingQueue(queueName);
|
||||
return queue.poll();
|
||||
}
|
||||
|
||||
/**
|
||||
* 优先队列删除队列数据(不支持延迟队列)
|
||||
*/
|
||||
public static <T> boolean removePriorityQueueObject(String queueName, T data) {
|
||||
RPriorityBlockingQueue<T> queue = CLIENT.getPriorityBlockingQueue(queueName);
|
||||
return queue.remove(data);
|
||||
}
|
||||
|
||||
/**
|
||||
* 优先队列销毁队列 所有阻塞监听 报错(不支持延迟队列)
|
||||
*/
|
||||
public static <T> boolean destroyPriorityQueue(String queueName) {
|
||||
RPriorityBlockingQueue<T> queue = CLIENT.getPriorityBlockingQueue(queueName);
|
||||
return queue.delete();
|
||||
}
|
||||
|
||||
/**
|
||||
* 尝试设置 有界队列 容量 用于限制数量
|
||||
*
|
||||
* @param queueName 队列名
|
||||
* @param capacity 容量
|
||||
*/
|
||||
public static <T> boolean trySetBoundedQueueCapacity(String queueName, int capacity) {
|
||||
RBoundedBlockingQueue<T> boundedBlockingQueue = CLIENT.getBoundedBlockingQueue(queueName);
|
||||
return boundedBlockingQueue.trySetCapacity(capacity);
|
||||
}
|
||||
|
||||
/**
|
||||
* 尝试设置 有界队列 容量 用于限制数量
|
||||
*
|
||||
* @param queueName 队列名
|
||||
* @param capacity 容量
|
||||
* @param destroy 是否销毁
|
||||
*/
|
||||
public static <T> boolean trySetBoundedQueueCapacity(String queueName, int capacity, boolean destroy) {
|
||||
RBoundedBlockingQueue<T> boundedBlockingQueue = CLIENT.getBoundedBlockingQueue(queueName);
|
||||
if (destroy) {
|
||||
boundedBlockingQueue.delete();
|
||||
}
|
||||
return boundedBlockingQueue.trySetCapacity(capacity);
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加有界队列数据
|
||||
*
|
||||
* @param queueName 队列名
|
||||
* @param data 数据
|
||||
* @return 添加成功 true 已达到界限 false
|
||||
*/
|
||||
public static <T> boolean addBoundedQueueObject(String queueName, T data) {
|
||||
RBoundedBlockingQueue<T> boundedBlockingQueue = CLIENT.getBoundedBlockingQueue(queueName);
|
||||
return boundedBlockingQueue.offer(data);
|
||||
}
|
||||
|
||||
/**
|
||||
* 有界队列获取一个队列数据 没有数据返回 null(不支持延迟队列)
|
||||
*
|
||||
* @param queueName 队列名
|
||||
*/
|
||||
public static <T> T getBoundedQueueObject(String queueName) {
|
||||
RBoundedBlockingQueue<T> queue = CLIENT.getBoundedBlockingQueue(queueName);
|
||||
return queue.poll();
|
||||
}
|
||||
|
||||
/**
|
||||
* 有界队列删除队列数据(不支持延迟队列)
|
||||
*/
|
||||
public static <T> boolean removeBoundedQueueObject(String queueName, T data) {
|
||||
RBoundedBlockingQueue<T> queue = CLIENT.getBoundedBlockingQueue(queueName);
|
||||
return queue.remove(data);
|
||||
}
|
||||
|
||||
/**
|
||||
* 有界队列销毁队列 所有阻塞监听 报错(不支持延迟队列)
|
||||
*/
|
||||
public static <T> boolean destroyBoundedQueue(String queueName) {
|
||||
RBoundedBlockingQueue<T> queue = CLIENT.getBoundedBlockingQueue(queueName);
|
||||
return queue.delete();
|
||||
}
|
||||
|
||||
/**
|
||||
* 订阅阻塞队列(可订阅所有实现类 例如: 延迟 优先 有界 等)
|
||||
*/
|
||||
public static <T> void subscribeBlockingQueue(String queueName, Function<T, CompletionStage<Void>> consumer, boolean isDelayed) {
|
||||
RBlockingQueue<T> queue = CLIENT.getBlockingQueue(queueName);
|
||||
if (isDelayed) {
|
||||
// 订阅延迟队列
|
||||
CLIENT.getDelayedQueue(queue);
|
||||
}
|
||||
queue.subscribeOnElements(consumer);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,584 @@
|
||||
package com.fuyuanshen.common.redis.utils;
|
||||
|
||||
import lombok.AccessLevel;
|
||||
import lombok.NoArgsConstructor;
|
||||
import com.fuyuanshen.common.core.utils.SpringUtils;
|
||||
import org.redisson.api.*;
|
||||
import org.redisson.api.options.KeysScanOptions;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
/**
|
||||
* redis 工具类
|
||||
*
|
||||
* @author Lion Li
|
||||
* @version 3.1.0 新增
|
||||
*/
|
||||
@NoArgsConstructor(access = AccessLevel.PRIVATE)
|
||||
@SuppressWarnings(value = {"unchecked", "rawtypes"})
|
||||
public class RedisUtils {
|
||||
|
||||
private static final RedissonClient CLIENT = SpringUtils.getBean(RedissonClient.class);
|
||||
|
||||
/**
|
||||
* 限流
|
||||
*
|
||||
* @param key 限流key
|
||||
* @param rateType 限流类型
|
||||
* @param rate 速率
|
||||
* @param rateInterval 速率间隔
|
||||
* @return -1 表示失败
|
||||
*/
|
||||
public static long rateLimiter(String key, RateType rateType, int rate, int rateInterval) {
|
||||
return rateLimiter(key, rateType, rate, rateInterval, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* 限流
|
||||
*
|
||||
* @param key 限流key
|
||||
* @param rateType 限流类型
|
||||
* @param rate 速率
|
||||
* @param rateInterval 速率间隔
|
||||
* @param timeout 超时时间
|
||||
* @return -1 表示失败
|
||||
*/
|
||||
public static long rateLimiter(String key, RateType rateType, int rate, int rateInterval, int timeout) {
|
||||
RRateLimiter rateLimiter = CLIENT.getRateLimiter(key);
|
||||
rateLimiter.trySetRate(rateType, rate, Duration.ofSeconds(rateInterval), Duration.ofSeconds(timeout));
|
||||
if (rateLimiter.tryAcquire()) {
|
||||
return rateLimiter.availablePermits();
|
||||
} else {
|
||||
return -1L;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取客户端实例
|
||||
*/
|
||||
public static RedissonClient getClient() {
|
||||
return CLIENT;
|
||||
}
|
||||
|
||||
/**
|
||||
* 发布通道消息
|
||||
*
|
||||
* @param channelKey 通道key
|
||||
* @param msg 发送数据
|
||||
* @param consumer 自定义处理
|
||||
*/
|
||||
public static <T> void publish(String channelKey, T msg, Consumer<T> consumer) {
|
||||
RTopic topic = CLIENT.getTopic(channelKey);
|
||||
topic.publish(msg);
|
||||
consumer.accept(msg);
|
||||
}
|
||||
|
||||
/**
|
||||
* 发布消息到指定的频道
|
||||
*
|
||||
* @param channelKey 通道key
|
||||
* @param msg 发送数据
|
||||
*/
|
||||
public static <T> void publish(String channelKey, T msg) {
|
||||
RTopic topic = CLIENT.getTopic(channelKey);
|
||||
topic.publish(msg);
|
||||
}
|
||||
|
||||
/**
|
||||
* 订阅通道接收消息
|
||||
*
|
||||
* @param channelKey 通道key
|
||||
* @param clazz 消息类型
|
||||
* @param consumer 自定义处理
|
||||
*/
|
||||
public static <T> void subscribe(String channelKey, Class<T> clazz, Consumer<T> consumer) {
|
||||
RTopic topic = CLIENT.getTopic(channelKey);
|
||||
topic.addListener(clazz, (channel, msg) -> consumer.accept(msg));
|
||||
}
|
||||
|
||||
/**
|
||||
* 缓存基本的对象,Integer、String、实体类等
|
||||
*
|
||||
* @param key 缓存的键值
|
||||
* @param value 缓存的值
|
||||
*/
|
||||
public static <T> void setCacheObject(final String key, final T value) {
|
||||
setCacheObject(key, value, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* 缓存基本的对象,保留当前对象 TTL 有效期
|
||||
*
|
||||
* @param key 缓存的键值
|
||||
* @param value 缓存的值
|
||||
* @param isSaveTtl 是否保留TTL有效期(例如: set之前ttl剩余90 set之后还是为90)
|
||||
* @since Redis 6.X 以上使用 setAndKeepTTL 兼容 5.X 方案
|
||||
*/
|
||||
public static <T> void setCacheObject(final String key, final T value, final boolean isSaveTtl) {
|
||||
RBucket<T> bucket = CLIENT.getBucket(key);
|
||||
if (isSaveTtl) {
|
||||
try {
|
||||
bucket.setAndKeepTTL(value);
|
||||
} catch (Exception e) {
|
||||
long timeToLive = bucket.remainTimeToLive();
|
||||
if (timeToLive == -1) {
|
||||
setCacheObject(key, value);
|
||||
} else {
|
||||
setCacheObject(key, value, Duration.ofMillis(timeToLive));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
bucket.set(value);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 缓存基本的对象,Integer、String、实体类等
|
||||
*
|
||||
* @param key 缓存的键值
|
||||
* @param value 缓存的值
|
||||
* @param duration 时间
|
||||
*/
|
||||
public static <T> void setCacheObject(final String key, final T value, final Duration duration) {
|
||||
RBatch batch = CLIENT.createBatch();
|
||||
RBucketAsync<T> bucket = batch.getBucket(key);
|
||||
bucket.setAsync(value);
|
||||
bucket.expireAsync(duration);
|
||||
batch.execute();
|
||||
}
|
||||
|
||||
/**
|
||||
* 如果不存在则设置 并返回 true 如果存在则返回 false
|
||||
*
|
||||
* @param key 缓存的键值
|
||||
* @param value 缓存的值
|
||||
* @return set成功或失败
|
||||
*/
|
||||
public static <T> boolean setObjectIfAbsent(final String key, final T value, final Duration duration) {
|
||||
RBucket<T> bucket = CLIENT.getBucket(key);
|
||||
return bucket.setIfAbsent(value, duration);
|
||||
}
|
||||
|
||||
/**
|
||||
* 如果存在则设置 并返回 true 如果存在则返回 false
|
||||
*
|
||||
* @param key 缓存的键值
|
||||
* @param value 缓存的值
|
||||
* @return set成功或失败
|
||||
*/
|
||||
public static <T> boolean setObjectIfExists(final String key, final T value, final Duration duration) {
|
||||
RBucket<T> bucket = CLIENT.getBucket(key);
|
||||
return bucket.setIfExists(value, duration);
|
||||
}
|
||||
|
||||
/**
|
||||
* 注册对象监听器
|
||||
* <p>
|
||||
* key 监听器需开启 `notify-keyspace-events` 等 redis 相关配置
|
||||
*
|
||||
* @param key 缓存的键值
|
||||
* @param listener 监听器配置
|
||||
*/
|
||||
public static <T> void addObjectListener(final String key, final ObjectListener listener) {
|
||||
RBucket<T> result = CLIENT.getBucket(key);
|
||||
result.addListener(listener);
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置有效时间
|
||||
*
|
||||
* @param key Redis键
|
||||
* @param timeout 超时时间
|
||||
* @return true=设置成功;false=设置失败
|
||||
*/
|
||||
public static boolean expire(final String key, final long timeout) {
|
||||
return expire(key, Duration.ofSeconds(timeout));
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置有效时间
|
||||
*
|
||||
* @param key Redis键
|
||||
* @param duration 超时时间
|
||||
* @return true=设置成功;false=设置失败
|
||||
*/
|
||||
public static boolean expire(final String key, final Duration duration) {
|
||||
RBucket rBucket = CLIENT.getBucket(key);
|
||||
return rBucket.expire(duration);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获得缓存的基本对象。
|
||||
*
|
||||
* @param key 缓存键值
|
||||
* @return 缓存键值对应的数据
|
||||
*/
|
||||
public static <T> T getCacheObject(final String key) {
|
||||
RBucket<T> rBucket = CLIENT.getBucket(key);
|
||||
return rBucket.get();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获得key剩余存活时间
|
||||
*
|
||||
* @param key 缓存键值
|
||||
* @return 剩余存活时间
|
||||
*/
|
||||
public static <T> long getTimeToLive(final String key) {
|
||||
RBucket<T> rBucket = CLIENT.getBucket(key);
|
||||
return rBucket.remainTimeToLive();
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除单个对象
|
||||
*
|
||||
* @param key 缓存的键值
|
||||
*/
|
||||
public static boolean deleteObject(final String key) {
|
||||
return CLIENT.getBucket(key).delete();
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除集合对象
|
||||
*
|
||||
* @param collection 多个对象
|
||||
*/
|
||||
public static void deleteObject(final Collection collection) {
|
||||
RBatch batch = CLIENT.createBatch();
|
||||
collection.forEach(t -> {
|
||||
batch.getBucket(t.toString()).deleteAsync();
|
||||
});
|
||||
batch.execute();
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查缓存对象是否存在
|
||||
*
|
||||
* @param key 缓存的键值
|
||||
*/
|
||||
public static boolean isExistsObject(final String key) {
|
||||
return CLIENT.getBucket(key).isExists();
|
||||
}
|
||||
|
||||
/**
|
||||
* 缓存List数据
|
||||
*
|
||||
* @param key 缓存的键值
|
||||
* @param dataList 待缓存的List数据
|
||||
* @return 缓存的对象
|
||||
*/
|
||||
public static <T> boolean setCacheList(final String key, final List<T> dataList) {
|
||||
RList<T> rList = CLIENT.getList(key);
|
||||
return rList.addAll(dataList);
|
||||
}
|
||||
|
||||
/**
|
||||
* 追加缓存List数据
|
||||
*
|
||||
* @param key 缓存的键值
|
||||
* @param data 待缓存的数据
|
||||
* @return 缓存的对象
|
||||
*/
|
||||
public static <T> boolean addCacheList(final String key, final T data) {
|
||||
RList<T> rList = CLIENT.getList(key);
|
||||
return rList.add(data);
|
||||
}
|
||||
|
||||
/**
|
||||
* 注册List监听器
|
||||
* <p>
|
||||
* key 监听器需开启 `notify-keyspace-events` 等 redis 相关配置
|
||||
*
|
||||
* @param key 缓存的键值
|
||||
* @param listener 监听器配置
|
||||
*/
|
||||
public static <T> void addListListener(final String key, final ObjectListener listener) {
|
||||
RList<T> rList = CLIENT.getList(key);
|
||||
rList.addListener(listener);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获得缓存的list对象
|
||||
*
|
||||
* @param key 缓存的键值
|
||||
* @return 缓存键值对应的数据
|
||||
*/
|
||||
public static <T> List<T> getCacheList(final String key) {
|
||||
RList<T> rList = CLIENT.getList(key);
|
||||
return rList.readAll();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获得缓存的list对象(范围)
|
||||
*
|
||||
* @param key 缓存的键值
|
||||
* @param form 起始下标
|
||||
* @param to 截止下标
|
||||
* @return 缓存键值对应的数据
|
||||
*/
|
||||
public static <T> List<T> getCacheListRange(final String key, int form, int to) {
|
||||
RList<T> rList = CLIENT.getList(key);
|
||||
return rList.range(form, to);
|
||||
}
|
||||
|
||||
/**
|
||||
* 缓存Set
|
||||
*
|
||||
* @param key 缓存键值
|
||||
* @param dataSet 缓存的数据
|
||||
* @return 缓存数据的对象
|
||||
*/
|
||||
public static <T> boolean setCacheSet(final String key, final Set<T> dataSet) {
|
||||
RSet<T> rSet = CLIENT.getSet(key);
|
||||
return rSet.addAll(dataSet);
|
||||
}
|
||||
|
||||
/**
|
||||
* 追加缓存Set数据
|
||||
*
|
||||
* @param key 缓存的键值
|
||||
* @param data 待缓存的数据
|
||||
* @return 缓存的对象
|
||||
*/
|
||||
public static <T> boolean addCacheSet(final String key, final T data) {
|
||||
RSet<T> rSet = CLIENT.getSet(key);
|
||||
return rSet.add(data);
|
||||
}
|
||||
|
||||
/**
|
||||
* 注册Set监听器
|
||||
* <p>
|
||||
* key 监听器需开启 `notify-keyspace-events` 等 redis 相关配置
|
||||
*
|
||||
* @param key 缓存的键值
|
||||
* @param listener 监听器配置
|
||||
*/
|
||||
public static <T> void addSetListener(final String key, final ObjectListener listener) {
|
||||
RSet<T> rSet = CLIENT.getSet(key);
|
||||
rSet.addListener(listener);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获得缓存的set
|
||||
*
|
||||
* @param key 缓存的key
|
||||
* @return set对象
|
||||
*/
|
||||
public static <T> Set<T> getCacheSet(final String key) {
|
||||
RSet<T> rSet = CLIENT.getSet(key);
|
||||
return rSet.readAll();
|
||||
}
|
||||
|
||||
/**
|
||||
* 缓存Map
|
||||
*
|
||||
* @param key 缓存的键值
|
||||
* @param dataMap 缓存的数据
|
||||
*/
|
||||
public static <T> void setCacheMap(final String key, final Map<String, T> dataMap) {
|
||||
if (dataMap != null) {
|
||||
RMap<String, T> rMap = CLIENT.getMap(key);
|
||||
rMap.putAll(dataMap);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 注册Map监听器
|
||||
* <p>
|
||||
* key 监听器需开启 `notify-keyspace-events` 等 redis 相关配置
|
||||
*
|
||||
* @param key 缓存的键值
|
||||
* @param listener 监听器配置
|
||||
*/
|
||||
public static <T> void addMapListener(final String key, final ObjectListener listener) {
|
||||
RMap<String, T> rMap = CLIENT.getMap(key);
|
||||
rMap.addListener(listener);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获得缓存的Map
|
||||
*
|
||||
* @param key 缓存的键值
|
||||
* @return map对象
|
||||
*/
|
||||
public static <T> Map<String, T> getCacheMap(final String key) {
|
||||
RMap<String, T> rMap = CLIENT.getMap(key);
|
||||
return rMap.getAll(rMap.keySet());
|
||||
}
|
||||
|
||||
/**
|
||||
* 获得缓存Map的key列表
|
||||
*
|
||||
* @param key 缓存的键值
|
||||
* @return key列表
|
||||
*/
|
||||
public static <T> Set<String> getCacheMapKeySet(final String key) {
|
||||
RMap<String, T> rMap = CLIENT.getMap(key);
|
||||
return rMap.keySet();
|
||||
}
|
||||
|
||||
/**
|
||||
* 往Hash中存入数据
|
||||
*
|
||||
* @param key Redis键
|
||||
* @param hKey Hash键
|
||||
* @param value 值
|
||||
*/
|
||||
public static <T> void setCacheMapValue(final String key, final String hKey, final T value) {
|
||||
RMap<String, T> rMap = CLIENT.getMap(key);
|
||||
rMap.put(hKey, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取Hash中的数据
|
||||
*
|
||||
* @param key Redis键
|
||||
* @param hKey Hash键
|
||||
* @return Hash中的对象
|
||||
*/
|
||||
public static <T> T getCacheMapValue(final String key, final String hKey) {
|
||||
RMap<String, T> rMap = CLIENT.getMap(key);
|
||||
return rMap.get(hKey);
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除Hash中的数据
|
||||
*
|
||||
* @param key Redis键
|
||||
* @param hKey Hash键
|
||||
* @return Hash中的对象
|
||||
*/
|
||||
public static <T> T delCacheMapValue(final String key, final String hKey) {
|
||||
RMap<String, T> rMap = CLIENT.getMap(key);
|
||||
return rMap.remove(hKey);
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除Hash中的数据
|
||||
*
|
||||
* @param key Redis键
|
||||
* @param hKeys Hash键
|
||||
*/
|
||||
public static <T> void delMultiCacheMapValue(final String key, final Set<String> hKeys) {
|
||||
RBatch batch = CLIENT.createBatch();
|
||||
RMapAsync<String, T> rMap = batch.getMap(key);
|
||||
for (String hKey : hKeys) {
|
||||
rMap.removeAsync(hKey);
|
||||
}
|
||||
batch.execute();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取多个Hash中的数据
|
||||
*
|
||||
* @param key Redis键
|
||||
* @param hKeys Hash键集合
|
||||
* @return Hash对象集合
|
||||
*/
|
||||
public static <K, V> Map<K, V> getMultiCacheMapValue(final String key, final Set<K> hKeys) {
|
||||
RMap<K, V> rMap = CLIENT.getMap(key);
|
||||
return rMap.getAll(hKeys);
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置原子值
|
||||
*
|
||||
* @param key Redis键
|
||||
* @param value 值
|
||||
*/
|
||||
public static void setAtomicValue(String key, long value) {
|
||||
RAtomicLong atomic = CLIENT.getAtomicLong(key);
|
||||
atomic.set(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取原子值
|
||||
*
|
||||
* @param key Redis键
|
||||
* @return 当前值
|
||||
*/
|
||||
public static long getAtomicValue(String key) {
|
||||
RAtomicLong atomic = CLIENT.getAtomicLong(key);
|
||||
return atomic.get();
|
||||
}
|
||||
|
||||
/**
|
||||
* 递增原子值
|
||||
*
|
||||
* @param key Redis键
|
||||
* @return 当前值
|
||||
*/
|
||||
public static long incrAtomicValue(String key) {
|
||||
RAtomicLong atomic = CLIENT.getAtomicLong(key);
|
||||
return atomic.incrementAndGet();
|
||||
}
|
||||
|
||||
/**
|
||||
* 递减原子值
|
||||
*
|
||||
* @param key Redis键
|
||||
* @return 当前值
|
||||
*/
|
||||
public static long decrAtomicValue(String key) {
|
||||
RAtomicLong atomic = CLIENT.getAtomicLong(key);
|
||||
return atomic.decrementAndGet();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获得缓存的基本对象列表(全局匹配忽略租户 自行拼接租户id)
|
||||
* <P>
|
||||
* limit-设置扫描的限制数量(默认为0,查询全部)
|
||||
* pattern-设置键的匹配模式(默认为null)
|
||||
* chunkSize-设置每次扫描的块大小(默认为0,本方法设置为1000)
|
||||
* type-设置键的类型(默认为null,查询全部类型)
|
||||
* </P>
|
||||
* @see KeysScanOptions
|
||||
* @param pattern 字符串前缀
|
||||
* @return 对象列表
|
||||
*/
|
||||
public static Collection<String> keys(final String pattern) {
|
||||
return keys(KeysScanOptions.defaults().pattern(pattern).chunkSize(1000));
|
||||
}
|
||||
|
||||
/**
|
||||
* 通过扫描参数获取缓存的基本对象列表
|
||||
* @param keysScanOptions 扫描参数
|
||||
* <P>
|
||||
* limit-设置扫描的限制数量(默认为0,查询全部)
|
||||
* pattern-设置键的匹配模式(默认为null)
|
||||
* chunkSize-设置每次扫描的块大小(默认为0)
|
||||
* type-设置键的类型(默认为null,查询全部类型)
|
||||
* </P>
|
||||
* @see KeysScanOptions
|
||||
*/
|
||||
public static Collection<String> keys(final KeysScanOptions keysScanOptions) {
|
||||
Stream<String> keysStream = CLIENT.getKeys().getKeysStream(keysScanOptions);
|
||||
return keysStream.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除缓存的基本对象列表(全局匹配忽略租户 自行拼接租户id)
|
||||
*
|
||||
* @param pattern 字符串前缀
|
||||
*/
|
||||
public static void deleteKeys(final String pattern) {
|
||||
CLIENT.getKeys().deleteByPattern(pattern);
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查redis中是否存在key
|
||||
*
|
||||
* @param key 键
|
||||
*/
|
||||
public static Boolean hasKey(String key) {
|
||||
RKeys rKeys = CLIENT.getKeys();
|
||||
return rKeys.countExists(key) > 0;
|
||||
}
|
||||
}
|
@ -0,0 +1,180 @@
|
||||
package com.fuyuanshen.common.redis.utils;
|
||||
|
||||
import cn.hutool.core.date.DatePattern;
|
||||
import cn.hutool.core.date.DateUtil;
|
||||
import lombok.AccessLevel;
|
||||
import lombok.NoArgsConstructor;
|
||||
import com.fuyuanshen.common.core.utils.SpringUtils;
|
||||
import com.fuyuanshen.common.core.utils.StringUtils;
|
||||
import org.redisson.api.RIdGenerator;
|
||||
import org.redisson.api.RedissonClient;
|
||||
|
||||
import java.time.Duration;
|
||||
|
||||
/**
|
||||
* 发号器工具类
|
||||
*
|
||||
* @author 秋辞未寒
|
||||
* @date 2024-12-10
|
||||
*/
|
||||
@NoArgsConstructor(access = AccessLevel.PRIVATE)
|
||||
public class SequenceUtils {
|
||||
|
||||
/**
|
||||
* 默认初始值
|
||||
*/
|
||||
public static final Long DEFAULT_INIT_VALUE = 1L;
|
||||
|
||||
/**
|
||||
* 默认步长
|
||||
*/
|
||||
public static final Long DEFAULT_STEP_VALUE = 1L;
|
||||
|
||||
/**
|
||||
* 默认过期时间-天
|
||||
*/
|
||||
public static final Duration DEFAULT_EXPIRE_TIME_DAY = Duration.ofDays(1);
|
||||
|
||||
/**
|
||||
* 默认过期时间-分钟
|
||||
*/
|
||||
public static final Duration DEFAULT_EXPIRE_TIME_MINUTE = Duration.ofMinutes(1);
|
||||
|
||||
/**
|
||||
* 获取Redisson客户端实例
|
||||
*/
|
||||
private static final RedissonClient REDISSON_CLIENT = SpringUtils.getBean(RedissonClient.class);
|
||||
|
||||
/**
|
||||
* 获取ID生成器
|
||||
*
|
||||
* @param key 业务key
|
||||
* @param expireTime 过期时间
|
||||
* @param initValue ID初始值
|
||||
* @param stepValue ID步长
|
||||
* @return ID生成器
|
||||
*/
|
||||
private static RIdGenerator getIdGenerator(String key, Duration expireTime, Long initValue, Long stepValue) {
|
||||
if (initValue == null || initValue <= 0) {
|
||||
initValue = DEFAULT_INIT_VALUE;
|
||||
}
|
||||
if (stepValue == null || stepValue <= 0) {
|
||||
stepValue = DEFAULT_STEP_VALUE;
|
||||
}
|
||||
RIdGenerator idGenerator = REDISSON_CLIENT.getIdGenerator(key);
|
||||
// 设置初始值和步长
|
||||
idGenerator.tryInit(initValue, stepValue);
|
||||
// 设置过期时间
|
||||
idGenerator.expire(expireTime);
|
||||
return idGenerator;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取指定业务key的唯一id
|
||||
*
|
||||
* @param key 业务key
|
||||
* @param expireTime 过期时间
|
||||
* @param initValue ID初始值
|
||||
* @param stepValue ID步长
|
||||
* @return 唯一id
|
||||
*/
|
||||
public static long nextId(String key, Duration expireTime, Long initValue, Long stepValue) {
|
||||
return getIdGenerator(key, expireTime, initValue, stepValue).nextId();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取指定业务key的唯一id字符串
|
||||
*
|
||||
* @param key 业务key
|
||||
* @param expireTime 过期时间
|
||||
* @param initValue ID初始值
|
||||
* @param stepValue ID步长
|
||||
* @return 唯一id
|
||||
*/
|
||||
public static String nextIdStr(String key, Duration expireTime, Long initValue, Long stepValue) {
|
||||
return String.valueOf(nextId(key, expireTime, initValue, stepValue));
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取指定业务key的唯一id (ID初始值=1,ID步长=1)
|
||||
*
|
||||
* @param key 业务key
|
||||
* @param expireTime 过期时间
|
||||
* @return 唯一id
|
||||
*/
|
||||
public static long nextId(String key, Duration expireTime) {
|
||||
return getIdGenerator(key, expireTime, DEFAULT_INIT_VALUE, DEFAULT_STEP_VALUE).nextId();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取指定业务key的唯一id字符串 (ID初始值=1,ID步长=1)
|
||||
*
|
||||
* @param key 业务key
|
||||
* @param expireTime 过期时间
|
||||
* @return 唯一id
|
||||
*/
|
||||
public static String nextIdStr(String key, Duration expireTime) {
|
||||
return String.valueOf(nextId(key, expireTime));
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取指定业务key的唯一id字符串 (ID初始值=1,ID步长=1),不足位数自动补零
|
||||
*
|
||||
* @param key 业务key
|
||||
* @param expireTime 过期时间
|
||||
* @param width 位数,不足左补0
|
||||
* @return 补零后的唯一id字符串
|
||||
*/
|
||||
public static String nextPaddedIdStr(String key, Duration expireTime, Integer width) {
|
||||
return StringUtils.leftPad(nextIdStr(key, expireTime), width, '0');
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 yyyyMMdd 开头的唯一id
|
||||
*
|
||||
* @return 唯一id
|
||||
*/
|
||||
public static String nextIdDate() {
|
||||
return nextIdDate("");
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 prefix + yyyyMMdd 开头的唯一id
|
||||
*
|
||||
* @param prefix 业务前缀
|
||||
* @return 唯一id
|
||||
*/
|
||||
public static String nextIdDate(String prefix) {
|
||||
// 前缀+日期 构建 prefixKey
|
||||
String prefixKey = StringUtils.format("{}{}", StringUtils.blankToDefault(prefix, ""), DateUtil.format(DateUtil.date(), DatePattern.PURE_DATE_FORMATTER));
|
||||
// 获取下一个id
|
||||
long nextId = getIdGenerator(prefixKey, DEFAULT_EXPIRE_TIME_DAY, DEFAULT_INIT_VALUE, DEFAULT_STEP_VALUE).nextId();
|
||||
// 返回完整id
|
||||
return StringUtils.format("{}{}", prefixKey, nextId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 yyyyMMddHHmmss 开头的唯一id
|
||||
*
|
||||
* @return 唯一id
|
||||
*/
|
||||
public static String nextIdDateTime() {
|
||||
return nextIdDateTime("");
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 prefix + yyyyMMddHHmmss 开头的唯一id
|
||||
*
|
||||
* @param prefix 业务前缀
|
||||
* @return 唯一id
|
||||
*/
|
||||
public static String nextIdDateTime(String prefix) {
|
||||
// 前缀+日期时间 构建 prefixKey
|
||||
String prefixKey = StringUtils.format("{}{}", StringUtils.blankToDefault(prefix, ""), DateUtil.format(DateUtil.date(), DatePattern.PURE_DATETIME_FORMATTER));
|
||||
// 获取下一个id
|
||||
long nextId = getIdGenerator(prefixKey, DEFAULT_EXPIRE_TIME_MINUTE, DEFAULT_INIT_VALUE, DEFAULT_STEP_VALUE).nextId();
|
||||
// 返回完整id
|
||||
return StringUtils.format("{}{}", prefixKey, nextId);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,2 @@
|
||||
com.fuyuanshen.common.redis.config.RedisConfig
|
||||
com.fuyuanshen.common.redis.config.CacheConfig
|
Reference in New Issue
Block a user