This commit is contained in:
clay
2024-03-06 17:44:09 +08:00
commit adaec0eadd
1493 changed files with 219939 additions and 0 deletions

View File

@@ -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 {};
}

View File

@@ -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;
}
}

View File

@@ -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"
*/
}

View File

@@ -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;
}
}

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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);
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -0,0 +1 @@
cn.fateverse.common.redis.configure.RedisConfig