init
This commit is contained in:
@@ -0,0 +1,69 @@
|
||||
package cn.fateverse.common.redis.annotation;
|
||||
|
||||
|
||||
import cn.fateverse.common.redis.enums.RedisCacheType;
|
||||
|
||||
import java.lang.annotation.*;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* @author Clay
|
||||
* @date 2023-06-05
|
||||
*/
|
||||
@Target(ElementType.METHOD)
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Documented
|
||||
public @interface RedisCache {
|
||||
|
||||
/**
|
||||
* 前缀
|
||||
*/
|
||||
String prefix() default "";
|
||||
|
||||
/**
|
||||
* 主键的el表达式,用于修改和更新的时候删除指定的缓存
|
||||
* 参数Spring EL表达式例如 #{param.name},通过表达式获取到id数据
|
||||
*/
|
||||
String primaryKey() default "";
|
||||
|
||||
/**
|
||||
* 缓存过期时间,timeout为 0 则表示不设置过期时间
|
||||
*/
|
||||
long timeout() default 0;
|
||||
|
||||
/**
|
||||
* 缓存过期时间校验的时间间隔的单位
|
||||
*/
|
||||
TimeUnit timeUnit() default TimeUnit.SECONDS;
|
||||
|
||||
/**
|
||||
* 操作类型
|
||||
*/
|
||||
RedisCacheType type() default RedisCacheType.GET;
|
||||
|
||||
/**
|
||||
* keys和ignoreKeys不能同时使用
|
||||
* 参数Spring EL表达式例如 #{param.name},表达式的值作为防重复校验key的一部分
|
||||
*/
|
||||
String[] keys() default {};
|
||||
|
||||
/**
|
||||
* keys和ignoreKeys不能同时使用
|
||||
* ignoreKeys不区分入参,所有入参拥有相同的字段时,都将过滤掉
|
||||
*/
|
||||
String[] ignoreKeys() default {};
|
||||
|
||||
/**
|
||||
* Spring EL表达式,决定是否进行重复提交校验,多个条件之间为且的关系,默认是进行校验
|
||||
*/
|
||||
String[] conditions() default {"true"};
|
||||
|
||||
/**
|
||||
* 当未配置key时,设置哪几个参数作,默认取所有参数
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
int[] argsIndex() default {};
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,240 @@
|
||||
package cn.fateverse.common.redis.aspect;
|
||||
|
||||
import cn.fateverse.common.redis.enums.RedisCacheType;
|
||||
import cn.fateverse.common.redis.utils.ExpressionUtils;
|
||||
import cn.fateverse.common.redis.utils.KeyUtils;
|
||||
import cn.fateverse.common.redis.constant.RedisConstant;
|
||||
import cn.fateverse.common.redis.exception.RedisCacheException;
|
||||
import cn.fateverse.common.redis.annotation.RedisCache;
|
||||
import com.alibaba.nacos.common.utils.ArrayUtils;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.aspectj.lang.ProceedingJoinPoint;
|
||||
import org.aspectj.lang.annotation.Around;
|
||||
import org.aspectj.lang.annotation.Aspect;
|
||||
import org.aspectj.lang.reflect.MethodSignature;
|
||||
import org.redisson.api.RLock;
|
||||
import org.redisson.api.RedissonClient;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.data.redis.core.RedisTemplate;
|
||||
import org.springframework.data.redis.core.ScanOptions;
|
||||
import org.springframework.expression.EvaluationContext;
|
||||
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
|
||||
import org.springframework.util.ObjectUtils;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ThreadPoolExecutor;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* redis 缓存切面类
|
||||
* <p>
|
||||
* redis中缓存key说明:
|
||||
* 1. 通用数据类型
|
||||
* 全局缓存前缀 + 自定义缓存前缀 + 调用类类路径 + other + 调用函数名 + 参数组合形成的md5key
|
||||
* 2. 主键数据类型
|
||||
* 全局缓存前缀 + 自定义缓存前缀 + 调用类类路径 + primary_key + 调用函数名 + 主键数据id
|
||||
*
|
||||
* @author Clay
|
||||
* @date 2023-06-05
|
||||
*/
|
||||
@Slf4j
|
||||
@Aspect
|
||||
@ConditionalOnProperty(name = "enabled", prefix = "redis.cache", havingValue = "true", matchIfMissing = true)
|
||||
public class RedisCacheAspect {
|
||||
//redis使用分隔符
|
||||
private static final String REDIS_SEPARATOR = RedisConstant.REDIS_SEPARATOR;
|
||||
//redis缓存全局前缀
|
||||
private static final String REDIS_CACHE_KEY_PREFIX = "redis:cache" + REDIS_SEPARATOR;
|
||||
//主键标识前缀
|
||||
private static final String PRIMARY_KEY = "primary:key" + REDIS_SEPARATOR;
|
||||
//其他数据类型标识前缀
|
||||
private static final String OTHER = "other" + REDIS_SEPARATOR;
|
||||
//分布式锁标记
|
||||
public static final String LOCK = "lock";
|
||||
|
||||
@Resource
|
||||
private ThreadPoolTaskExecutor executor;
|
||||
|
||||
@Resource
|
||||
private RedisTemplate<String, Object> restTemplate;
|
||||
|
||||
|
||||
@Resource
|
||||
private RedissonClient redissonClient;
|
||||
|
||||
@Around("@within(redisCache) || @annotation(redisCache)")
|
||||
public Object redisCacheCheck(ProceedingJoinPoint point, RedisCache redisCache) throws Throwable {
|
||||
final Object[] args = point.getArgs();
|
||||
// 获取方法签名
|
||||
MethodSignature methodSignature = (MethodSignature) point.getSignature();
|
||||
// 获取参数名称
|
||||
String[] parameterNames = methodSignature.getParameterNames();
|
||||
final String[] conditions = redisCache.conditions();
|
||||
//根据条件判断是否需要进行防重复提交检查
|
||||
EvaluationContext context = ExpressionUtils.getEvaluationContext(args, parameterNames);
|
||||
if (!ExpressionUtils.getConditionValue(context, conditions) || ArrayUtils.isEmpty(args)) {
|
||||
return point.proceed();
|
||||
}
|
||||
if (ObjectUtils.isEmpty(redisCache.prefix())) {
|
||||
throw new RedisCacheException("redis cache prefix can't be null...");
|
||||
}
|
||||
RedisCacheType type = redisCache.type();
|
||||
switch (type) {
|
||||
case GET:
|
||||
case GET_BY_PRIMARY_KEY:
|
||||
return getCache(point, redisCache, context);
|
||||
case INSERT:
|
||||
return insertCache(point, redisCache);
|
||||
case UPDATE:
|
||||
case DELETE:
|
||||
return updateCache(point, redisCache, context);
|
||||
default:
|
||||
throw new RedisCacheException("redis cache type is not exist!");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新缓存,更新主键标识缓存,和非主键标识缓存
|
||||
*
|
||||
* @param point 切点
|
||||
* @param redisCache 注解信息
|
||||
* @return 数据
|
||||
* @throws Throwable 报错信息
|
||||
*/
|
||||
private Object updateCache(ProceedingJoinPoint point, RedisCache redisCache, EvaluationContext context) throws Throwable {
|
||||
//获取到键信息
|
||||
StringBuilder redisCacheBuffer = getRedisCacheClassKey(point, redisCache);
|
||||
//当前为更新方法,所以当前的键定义为other为后续删除
|
||||
StringBuilder other = new StringBuilder(redisCacheBuffer).append(OTHER).append("*");
|
||||
//构建主键key
|
||||
StringBuilder primaryKey = new StringBuilder(redisCacheBuffer);
|
||||
//判断当前主键key是否为空,如果不为空则删除该数据信息
|
||||
if (!ObjectUtils.isEmpty(redisCache.primaryKey())) {
|
||||
Object parametersKey = ExpressionUtils.getExpressionValue(context, redisCache.primaryKey());
|
||||
primaryKey.append(PRIMARY_KEY).append("*").append(parametersKey);
|
||||
deleteObject(primaryKey);
|
||||
}
|
||||
//第一次删除缓存数据
|
||||
deleteObject(other);
|
||||
//执行目标方法
|
||||
Object proceed = point.proceed();
|
||||
//执行方法完成后异步再次删除
|
||||
asyncDeleteObject(other);
|
||||
//进行主键的异步删除
|
||||
if (!ObjectUtils.isEmpty(redisCache.primaryKey())) {
|
||||
asyncDeleteObject(primaryKey);
|
||||
}
|
||||
return proceed;
|
||||
}
|
||||
|
||||
private void asyncDeleteObject(StringBuilder primaryKey) {
|
||||
executor.execute(() -> {
|
||||
deleteObject(primaryKey);
|
||||
});
|
||||
}
|
||||
|
||||
private void deleteObject(StringBuilder key) {
|
||||
Set<String> keys = restTemplate.scan(ScanOptions.scanOptions().match(key.toString()).build())
|
||||
.stream().collect(Collectors.toSet());
|
||||
restTemplate.delete(keys);
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新缓存,主要更新非组件标识的缓存数据
|
||||
*
|
||||
* @param point 切点
|
||||
* @param redisCache 注解信息
|
||||
* @return 数据
|
||||
* @throws Throwable 报错信息
|
||||
*/
|
||||
private Object insertCache(ProceedingJoinPoint point, RedisCache redisCache) throws Throwable {
|
||||
StringBuilder key = getRedisCacheClassKey(point, redisCache);
|
||||
key.append(OTHER).append("*");
|
||||
//修改数据库之前先同步删除
|
||||
deleteObject(key);
|
||||
Object proceed = point.proceed();
|
||||
//数据修改完成后异步再次删除
|
||||
asyncDeleteObject(key);
|
||||
return proceed;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取到数据
|
||||
*
|
||||
* @param point 切点
|
||||
* @param redisCache 注解信息
|
||||
* @return 数据
|
||||
* @throws Throwable 报错信息
|
||||
*/
|
||||
private Object getCache(ProceedingJoinPoint point, RedisCache redisCache, EvaluationContext context) throws Throwable {
|
||||
final Object[] args = point.getArgs();
|
||||
StringBuilder redisCacheBuffer = getRedisCacheClassKey(point, redisCache);
|
||||
String parametersKey;
|
||||
//判断当前方法的类型是否为主键获取,并且指定组件方法
|
||||
if (redisCache.type().equals(RedisCacheType.GET_BY_PRIMARY_KEY) && !ObjectUtils.isEmpty(redisCache.primaryKey())) {
|
||||
redisCacheBuffer.append(PRIMARY_KEY);
|
||||
parametersKey = Objects.requireNonNull(ExpressionUtils.getExpressionValue(context, redisCache.primaryKey())).toString();
|
||||
} else {
|
||||
redisCacheBuffer.append(OTHER);
|
||||
parametersKey = KeyUtils.getParametersKey(args, redisCache.keys(), redisCache.ignoreKeys(), redisCache.argsIndex(), REDIS_SEPARATOR, context);
|
||||
}
|
||||
String methodName = point.getSignature().getName();
|
||||
//天剑方法
|
||||
redisCacheBuffer.append(methodName);
|
||||
//添加参数key
|
||||
redisCacheBuffer.append(REDIS_SEPARATOR).append(parametersKey);
|
||||
//转换为redis的键
|
||||
String redisKey = redisCacheBuffer.toString();
|
||||
//从redis中获取到value
|
||||
Object cacheValue = restTemplate.opsForValue().get(redisKey);
|
||||
if (null == cacheValue) {
|
||||
//此处使用分布式锁
|
||||
String lockKey = redisCacheBuffer.append(RedisConstant.REDIS_SEPARATOR)
|
||||
.append(LOCK).toString();
|
||||
RLock lock = redissonClient.getFairLock(lockKey);
|
||||
try {
|
||||
if (lock.tryLock(5, TimeUnit.SECONDS)) {
|
||||
//尝试再次获取
|
||||
cacheValue = restTemplate.opsForValue().get(redisKey);
|
||||
//如果还是未获取到,则执行方法生成
|
||||
if (null == cacheValue) {
|
||||
cacheValue = point.proceed();
|
||||
//如果注解未设置过期时间
|
||||
if (redisCache.timeout() == 0) {
|
||||
restTemplate.opsForValue().set(redisKey, cacheValue);
|
||||
} else {
|
||||
restTemplate.opsForValue().set(redisKey, cacheValue, redisCache.timeout(), redisCache.timeUnit());
|
||||
}
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
if (lock.isLocked() && lock.isHeldByCurrentThread()) {
|
||||
lock.unlock();
|
||||
}
|
||||
}
|
||||
}
|
||||
return cacheValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取到redis cache key 到 class 的关键字
|
||||
*
|
||||
* @param point 切点
|
||||
* @param redisCache 注解信息
|
||||
* @return cache key
|
||||
*/
|
||||
private StringBuilder getRedisCacheClassKey(ProceedingJoinPoint point, RedisCache redisCache) {
|
||||
//获取到redis key的的关键词
|
||||
StringBuilder redisCacheBuffer = new StringBuilder(REDIS_CACHE_KEY_PREFIX);
|
||||
redisCacheBuffer.append(redisCache.prefix()).append(REDIS_SEPARATOR);
|
||||
// 获取到报名 类名 函数名
|
||||
String className = point.getTarget().getClass().getName();
|
||||
redisCacheBuffer.append(className).append(REDIS_SEPARATOR);
|
||||
return redisCacheBuffer;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,173 @@
|
||||
package cn.fateverse.common.redis.configure;
|
||||
|
||||
import cn.fateverse.common.redis.configure.properties.RedissonProperties;
|
||||
import com.fasterxml.jackson.annotation.JsonAutoDetect;
|
||||
import com.fasterxml.jackson.annotation.JsonTypeInfo;
|
||||
import com.fasterxml.jackson.annotation.PropertyAccessor;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.databind.jsontype.impl.LaissezFaireSubTypeValidator;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.redisson.codec.JsonJacksonCodec;
|
||||
import org.redisson.config.ClusterServersConfig;
|
||||
import org.redisson.config.SingleServerConfig;
|
||||
import org.redisson.spring.data.connection.RedissonConnectionFactory;
|
||||
import org.redisson.spring.starter.RedissonAutoConfigurationCustomizer;
|
||||
import org.springframework.beans.BeansException;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.config.BeanPostProcessor;
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.cache.annotation.EnableCaching;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.boot.autoconfigure.data.redis.RedisProperties;
|
||||
import org.springframework.data.redis.core.RedisTemplate;
|
||||
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
|
||||
import org.springframework.data.redis.serializer.StringRedisSerializer;
|
||||
import org.springframework.util.ObjectUtils;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import java.util.Optional;
|
||||
|
||||
|
||||
@Slf4j
|
||||
@EnableCaching
|
||||
@EnableConfigurationProperties({RedissonProperties.class})
|
||||
public class RedisConfig {
|
||||
|
||||
@Resource
|
||||
private RedisProperties properties;
|
||||
|
||||
@Resource
|
||||
private RedissonProperties redissonProperties;
|
||||
|
||||
@Resource
|
||||
private ObjectMapper objectMapper;
|
||||
|
||||
|
||||
@Bean
|
||||
public BeanPostProcessor redisTemplatePostProcessor(RedissonConnectionFactory redissonConnectionFactory) {
|
||||
return new BeanPostProcessor() {
|
||||
@Override
|
||||
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
|
||||
if ("redisTemplate".equals(beanName) && bean instanceof RedisTemplate) {
|
||||
RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
|
||||
redisTemplate.setConnectionFactory(redissonConnectionFactory);
|
||||
|
||||
Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<>(Object.class);
|
||||
ObjectMapper objectMapper = new ObjectMapper();
|
||||
objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
|
||||
objectMapper.activateDefaultTyping(LaissezFaireSubTypeValidator.instance, ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.PROPERTY);
|
||||
jackson2JsonRedisSerializer.setObjectMapper(objectMapper);
|
||||
|
||||
StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
|
||||
|
||||
//设置key序列化方式string
|
||||
redisTemplate.setKeySerializer(stringRedisSerializer);
|
||||
//设置value的序列化方式json
|
||||
redisTemplate.setValueSerializer(jackson2JsonRedisSerializer);
|
||||
redisTemplate.setHashKeySerializer(stringRedisSerializer);
|
||||
redisTemplate.setHashValueSerializer(jackson2JsonRedisSerializer);
|
||||
redisTemplate.afterPropertiesSet();
|
||||
return redisTemplate;
|
||||
}
|
||||
return bean;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@Bean
|
||||
public RedissonAutoConfigurationCustomizer redissonCustomizer() {
|
||||
return config -> {
|
||||
config.setThreads(redissonProperties.getThreads())
|
||||
.setNettyThreads(redissonProperties.getNettyThreads())
|
||||
.setCodec(new JsonJacksonCodec(objectMapper));
|
||||
RedissonProperties.SingleServerConfig singleServerConfig = redissonProperties.getSingleServerConfig();
|
||||
if (!ObjectUtils.isEmpty(singleServerConfig)) {
|
||||
// 使用单机模式
|
||||
SingleServerConfig singleServer = config.useSingleServer();
|
||||
singleServer
|
||||
.setDatabase(properties.getDatabase())
|
||||
//设置redis key前缀
|
||||
.setTimeout(singleServerConfig.getTimeout())
|
||||
.setClientName(singleServerConfig.getClientName())
|
||||
.setIdleConnectionTimeout(singleServerConfig.getIdleConnectionTimeout())
|
||||
.setSubscriptionConnectionPoolSize(singleServerConfig.getSubscriptionConnectionPoolSize())
|
||||
.setConnectionMinimumIdleSize(singleServerConfig.getConnectionMinimumIdleSize())
|
||||
.setConnectionPoolSize(singleServerConfig.getConnectionPoolSize())
|
||||
.setPingConnectionInterval(Optional.ofNullable(singleServerConfig.getPingConnectionInterval()).orElse(3000));
|
||||
if (!ObjectUtils.isEmpty(properties.getPassword())) {
|
||||
singleServer.setPassword(properties.getPassword());
|
||||
}
|
||||
}
|
||||
// 集群配置方式 参考下方注释
|
||||
RedissonProperties.ClusterServersConfig clusterServersConfig = redissonProperties.getClusterServersConfig();
|
||||
if (!ObjectUtils.isEmpty(clusterServersConfig)) {
|
||||
ClusterServersConfig clusterServers = config.useClusterServers();
|
||||
clusterServers
|
||||
//设置redis key前缀
|
||||
.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())
|
||||
.setPingConnectionInterval(Optional.ofNullable(singleServerConfig.getPingConnectionInterval()).orElse(3000));
|
||||
if (!ObjectUtils.isEmpty(properties.getPassword())) {
|
||||
clusterServers.setPassword(properties.getPassword());
|
||||
}
|
||||
}
|
||||
log.info("初始化 redis 配置");
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* redis集群配置 yml
|
||||
*
|
||||
* --- # redis 集群配置(单机与集群只能开启一个另一个需要注释掉)
|
||||
* spring:
|
||||
* redis:
|
||||
* cluster:
|
||||
* nodes:
|
||||
* - 192.168.0.100:6379
|
||||
* - 192.168.0.101:6379
|
||||
* - 192.168.0.102:6379
|
||||
* # 密码
|
||||
* password:
|
||||
* # 连接超时时间
|
||||
* timeout: 10s
|
||||
* # 是否开启ssl
|
||||
* ssl: false
|
||||
*
|
||||
* redisson:
|
||||
* # 线程池数量
|
||||
* threads: 16
|
||||
* # Netty线程池数量
|
||||
* nettyThreads: 32
|
||||
* # 集群配置
|
||||
* clusterServersConfig:
|
||||
* # 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 cn.fateverse.common.redis.configure.properties;
|
||||
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
import org.redisson.config.ReadMode;
|
||||
import org.redisson.config.SubscriptionMode;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* Redisson 配置属性
|
||||
*
|
||||
* @author Lion Li
|
||||
*/
|
||||
@Data
|
||||
@Component
|
||||
@ConfigurationProperties(prefix = "redisson")
|
||||
public class RedissonProperties {
|
||||
|
||||
/**
|
||||
* 线程池数量,默认值 = 当前处理核数量 * 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;
|
||||
|
||||
private Integer pingConnectionInterval;
|
||||
|
||||
}
|
||||
|
||||
@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;
|
||||
private int pingConnectionInterval;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package cn.fateverse.common.redis.constant;
|
||||
|
||||
/**
|
||||
* @author Clay
|
||||
* @date 2023-06-05
|
||||
*/
|
||||
public class RedisConstant {
|
||||
|
||||
public static final String REDIS_SEPARATOR = ":";
|
||||
public static final long REDIS_EXPIRE = 60;
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package cn.fateverse.common.redis.enums;
|
||||
|
||||
/**
|
||||
* @author Clay
|
||||
* @date 2023-06-05
|
||||
*/
|
||||
public enum RedisCacheType {
|
||||
|
||||
GET,
|
||||
GET_BY_PRIMARY_KEY,
|
||||
INSERT,
|
||||
UPDATE,
|
||||
DELETE;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package cn.fateverse.common.redis.exception;
|
||||
|
||||
/**
|
||||
* @author Clay
|
||||
* @date 2023-10-15
|
||||
*/
|
||||
public class RedisCacheException extends RuntimeException {
|
||||
|
||||
public RedisCacheException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
|
||||
public RedisCacheException(Throwable cause) {
|
||||
super(cause);
|
||||
}
|
||||
|
||||
public RedisCacheException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
|
||||
super(message, cause, enableSuppression, writableStackTrace);
|
||||
}
|
||||
|
||||
public RedisCacheException(String message) {
|
||||
super(message);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,181 @@
|
||||
package cn.fateverse.common.redis.utils;
|
||||
|
||||
import org.springframework.expression.EvaluationContext;
|
||||
import org.springframework.expression.Expression;
|
||||
import org.springframework.expression.ParserContext;
|
||||
import org.springframework.expression.common.TemplateParserContext;
|
||||
import org.springframework.expression.spel.standard.SpelExpressionParser;
|
||||
import org.springframework.expression.spel.support.StandardEvaluationContext;
|
||||
import org.springframework.lang.NonNull;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.ObjectUtils;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
/**
|
||||
* Spring Expression 工具类
|
||||
*
|
||||
* @author Clay
|
||||
* @date 2023-05-10
|
||||
*/
|
||||
public class ExpressionUtils {
|
||||
//el表达式缓存
|
||||
private static final Map<String, Expression> EXPRESSION_CACHE = new ConcurrentHashMap<>(64);
|
||||
//上下文模板
|
||||
private static final ParserContext parserContext = new TemplateParserContext("#{", "}");
|
||||
|
||||
/**
|
||||
* 获取Expression对象
|
||||
*
|
||||
* @param expressionString Spring EL 表达式字符串 例如 param.id
|
||||
* @return Expression
|
||||
*/
|
||||
public static Expression getExpression(@Nullable String expressionString) {
|
||||
if (ObjectUtils.isEmpty(expressionString)) {
|
||||
return null;
|
||||
}
|
||||
//双重锁检查机制实现表达式的校验
|
||||
Expression expression = EXPRESSION_CACHE.get(expressionString);
|
||||
if (null == expression) {
|
||||
synchronized (EXPRESSION_CACHE) {
|
||||
expression = EXPRESSION_CACHE.get(expressionString);
|
||||
if (null == expression) {
|
||||
expression = new SpelExpressionParser().parseExpression(expressionString, parserContext);
|
||||
EXPRESSION_CACHE.put(expressionString, expression);
|
||||
}
|
||||
}
|
||||
}
|
||||
return expression;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取到上下文表达式
|
||||
*
|
||||
* @param args 参数
|
||||
* @return 上下文表达式
|
||||
*/
|
||||
public static EvaluationContext getEvaluationContext(Object[] args, String[] parameterNames) {
|
||||
EvaluationContext evaluationContext;
|
||||
if (null == args || args.length == 0) {
|
||||
return null;
|
||||
}
|
||||
//如果参数只有一个作为root对象
|
||||
evaluationContext = new StandardEvaluationContext();
|
||||
for (int i = 0; i < args.length; i++) {
|
||||
evaluationContext.setVariable(parameterNames[i], args[i]);
|
||||
}
|
||||
return evaluationContext;
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据Spring EL表达式字符串从上下文对象中求值
|
||||
*
|
||||
* @param context 上下文对象
|
||||
* @param expressionString Spring EL表达式
|
||||
* @param clazz 值得类型
|
||||
* @param <T> 泛型
|
||||
* @return 返回值
|
||||
*/
|
||||
public static <T> T getExpressionValue(@Nullable EvaluationContext context, @Nullable String expressionString, @NonNull Class<? extends T> clazz) {
|
||||
if (context == null) {
|
||||
return null;
|
||||
}
|
||||
Expression expression = getExpression(expressionString);
|
||||
if (expression == null) {
|
||||
return null;
|
||||
}
|
||||
return expression.getValue(context, clazz);
|
||||
}
|
||||
|
||||
/**
|
||||
* 通过表达式获取到值,不使用泛型
|
||||
*
|
||||
* @param context 上下文对象
|
||||
* @param expressionString Spring EL表达式
|
||||
* @param <T> 泛型
|
||||
* @return 返回值
|
||||
*/
|
||||
public static <T> T getExpressionValue(@Nullable EvaluationContext context, @Nullable String expressionString) {
|
||||
Expression expression = getExpression(expressionString);
|
||||
if (expression == null) {
|
||||
return null;
|
||||
}
|
||||
//noinspection unchecked
|
||||
return (T) expression.getValue(context);
|
||||
}
|
||||
|
||||
/**
|
||||
* 求值
|
||||
*
|
||||
* @param context 上下文对象
|
||||
* @param expressionStrings Spring EL表达式
|
||||
* @param <T> 泛型 这里的泛型要慎用,大多数情况下要使用Object接收避免出现转换异常
|
||||
* @return 结果集
|
||||
*/
|
||||
public static <T> T[] getExpressionValue(@Nullable EvaluationContext context, @Nullable String... expressionStrings) {
|
||||
if (context == null) {
|
||||
return null;
|
||||
}
|
||||
if (ObjectUtils.isEmpty(expressionStrings)) {
|
||||
return null;
|
||||
}
|
||||
//noinspection ConstantConditions
|
||||
Object[] values = new Object[expressionStrings.length];
|
||||
for (int i = 0; i < expressionStrings.length; i++) {
|
||||
//noinspection unchecked
|
||||
values[i] = (T) getExpressionValue(context, expressionStrings[i]);
|
||||
}
|
||||
//noinspection unchecked
|
||||
return (T[]) values;
|
||||
}
|
||||
|
||||
/**
|
||||
* 表达式条件求值
|
||||
* 如果为值为null则返回false,
|
||||
* 如果为布尔类型直接返回,
|
||||
* 如果为数字类型则判断是否大于0
|
||||
*
|
||||
* @param context 上下文对象
|
||||
* @param expressionString Spring EL表达式
|
||||
* @return 返回值
|
||||
*/
|
||||
@Nullable
|
||||
public static boolean getConditionValue(@Nullable EvaluationContext context, @Nullable String expressionString) {
|
||||
Object value = getExpressionValue(context, expressionString);
|
||||
if (value == null) {
|
||||
return false;
|
||||
}
|
||||
if (value instanceof Boolean) {
|
||||
return (boolean) value;
|
||||
}
|
||||
if (value instanceof Number) {
|
||||
return ((Number) value).longValue() > 0;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 表达式条件求值
|
||||
*
|
||||
* @param context 上下文对象
|
||||
* @param expressionStrings Spring EL表达式数组
|
||||
* @return 返回值
|
||||
*/
|
||||
@Nullable
|
||||
public static boolean getConditionValue(@Nullable EvaluationContext context, @Nullable String... expressionStrings) {
|
||||
if (context == null) {
|
||||
return false;
|
||||
}
|
||||
if (ObjectUtils.isEmpty(expressionStrings)) {
|
||||
return false;
|
||||
}
|
||||
//noinspection ConstantConditions
|
||||
for (String expressionString : expressionStrings) {
|
||||
if (!getConditionValue(context, expressionString)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
package cn.fateverse.common.redis.utils;
|
||||
|
||||
import com.alibaba.fastjson2.JSON;
|
||||
import org.springframework.expression.EvaluationContext;
|
||||
import org.springframework.util.DigestUtils;
|
||||
import org.springframework.util.ObjectUtils;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* @author Clay
|
||||
* @date 2023-06-05
|
||||
*/
|
||||
public class KeyUtils {
|
||||
|
||||
|
||||
public static String getParametersKey(Object[] args, String[] keys, String[] ignoreKeys, int[] argsIndex, String separator, EvaluationContext context) {
|
||||
StringBuilder parametersBuffer = new StringBuilder();
|
||||
// 优先判断是否设置防重字段,因keys试数组,取值时是按照顺序排列的,这里不需要重新排序
|
||||
if (!ObjectUtils.isEmpty(keys)) {
|
||||
Object[] argsForKey = ExpressionUtils.getExpressionValue(context, keys);
|
||||
for (Object arg : argsForKey) {
|
||||
parametersBuffer.append(separator).append(String.valueOf(arg));
|
||||
}
|
||||
}
|
||||
// 如果没有设置防重的字段,那么需要把所有的字段和值作为key,因通过反射获取字段时,顺序时不确定的,这里取出来之后需要进行排序
|
||||
else {
|
||||
// 只有当keys为空时,ignoreKeys和argsIndex生效
|
||||
if (!ObjectUtils.isEmpty(argsIndex)) {
|
||||
for (int index : argsIndex) {
|
||||
parametersBuffer.append(separator).append(getKeyAndValueJsonStr(args[index], ignoreKeys));
|
||||
}
|
||||
} else {
|
||||
for (Object obj : args) {
|
||||
parametersBuffer.append(separator).append(getKeyAndValueJsonStr(obj, ignoreKeys));
|
||||
}
|
||||
}
|
||||
}
|
||||
// 将请求参数取md5值作为key的一部分
|
||||
return DigestUtils.md5DigestAsHex(parametersBuffer.toString().getBytes());
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 将字段转换为json字符串
|
||||
*
|
||||
* @param target
|
||||
* @return
|
||||
*/
|
||||
public static String getKeyAndValueJsonStr(Object target, String[] ignoreKeys) {
|
||||
//返回对象
|
||||
Map<String, Object> map = new HashMap<>(4);
|
||||
//需要忽略的Set集合
|
||||
Set<String> ignoreKeysSet = new HashSet<>();
|
||||
if (null != ignoreKeys) {
|
||||
ignoreKeysSet = new HashSet<>(Arrays.asList(ignoreKeys));
|
||||
}
|
||||
//获取对象的class
|
||||
Class<?> targetClass = target.getClass();
|
||||
//遍历所有的字段
|
||||
for (Field field : targetClass.getDeclaredFields()) {
|
||||
//获取字段名称
|
||||
String fieldName = field.getName();
|
||||
//判断当前字段是否在忽略字段集合中,如果不在则添加到result中
|
||||
if (!ignoreKeysSet.contains(fieldName)) {
|
||||
//设置当前字段属性是可以访问的
|
||||
field.setAccessible(true);
|
||||
Object val;
|
||||
try {
|
||||
//获取到数据
|
||||
val = field.get(target);
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new RuntimeException("您的操作太频繁,请稍后再试", e);
|
||||
}
|
||||
//添加到返回类型中
|
||||
map.put(fieldName, val);
|
||||
}
|
||||
}
|
||||
Map<String, Object> sortMap = sortMapByKey(map);
|
||||
return JSON.toJSONString(sortMap);
|
||||
}
|
||||
|
||||
|
||||
private static Map<String, Object> sortMapByKey(Map<String, Object> map) {
|
||||
if (map == null || map.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
Map<String, Object> sortMap = new TreeMap<>(String::compareTo);
|
||||
sortMap.putAll(map);
|
||||
return sortMap;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
cn.fateverse.common.redis.configure.RedisConfig
|
||||
Reference in New Issue
Block a user