在接收消息的时候,消息推送重复。如果处理消息的接口无法保证幂等,那么重复消费消息产生的影响可能会非常大
需求描述:
请求同一业务接口若传参一样,则返回数据相同并且后台业务逻辑只执行一次
链接:https://pan.baidu.com/s/1gKS490Igb4yG7CvxMyt9Wg
提取码:favg
配置yml文件 编写execution表达式即可拦截
配置yml文件 编写execution表达式即可拦截
注解 > 配置 > 默认
使用者需要添加
默认策略
可配置为:unique, idempotent,normal
策略返回数据缓存时间
如果有防重,幂等策略使用该配置
只有正常策略的情况,可忽略该配置项
唯一标识表名称,取唯一标识符值时使用
该配置项必须和参数名称一致,否则取不到唯一标识,可以理解为某个请求在一定时间段内的唯一标识。
AOP(execution表达式),用于匹配方法执行的连接点
数组格式,可配置多项,可以时普通类方法,Colletorller方法,Service方法,Dao方法
map集合
对executions表达式的一个补充,具体到方法使用那种策略(不支持重载),若要重载则需要重载的方法使用注解方式解决@ZkModel,具体使用查看该注解详情
Key: 方法全路径 value:方法使用那种策略
每个方法对应的过期时间
Key:方法全路径 value:返回值过期时间
获取锁等待时间,超出时间抛出异常:获取锁超时
锁过期时间,超出时间自动释放锁
用于缓存返回值以及锁,与项目的redis相互独立
database: 1 使用几号库
host: ${REDIS_IP} redis服务器IP
port: ${REDIS_PORT} 端口
password: ${REDIS_PASSWORD} 密码
maxIdle: 20 连接池最大等待连接数
minIdle: 0 连接池最小等待连接数
maxActive: 50 最大连接数
maxWait: 10000 最大等待时间
testOnBorrow: true 开启校验连接是否可用
timeout: 100000 客户端闲置多长时间进行关闭
具体配置项参考redis配置文件介绍
具体需要什么操作都可在这里配置 非yml配置方式 > yml配置
遵循约定规则
该注解可解决同一个连接点下不同切点(方法名相同),
使用方式:在需要切点的方法上加上该注解,值为三种策略:unique, idempotent,normal
如
PolicyConst
开关注解,该注解加到入口类
背景: 由于针对不同类型的请求和调用,paramName值存在不同的位置,为了适配不同环境下都能取到对应的值,因此提出过滤链式取值,Filter1没有取到值则继续执行Filter2,如果Filter2拿到值则直接返回,否则继续执行Filter3一次类推,如果执行完都没拿到则抛出异常,提示没有可用唯一标识
Filter过滤器代码中提供三种:
请求:
HeadFilter:从请求头中获取
RequestPaRamFilter: 从请求参数中获取
非请求方式:
MethodParamFilter: 从方法参数中获取(直接调用方法非请求场景)
另外:用户可继承Filter自定义实现逻辑,然后加入过滤链中
具体需要那几种过滤器根据用户所需自行加入
package com.zk.annotation;
import com.zk.configuration.UniqueIdempotentAspectConfig;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.context.annotation.Import;
/**
* @author: Lei Guo
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import({UniqueIdempotentAspectConfig.class})
//@Deprecated
public @interface EnableUniqueIdempotentAspect {
}
package com.zk.annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* @author: Lei Guo
* @use:
* 1. 解决重载配置文件过长,可用注解的方式简化配置、
* 2. 使用注解方式可解决重载问题
* 注解优先级>配置优先级>默认值
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@Documented
public @interface ZkModel {
/**
* 策略类型,如果为空取配置文件默认值
* 注解的优先级大于yml文件maps属性配置的内容
* @return
*/
String value() default "";
/**
* 返回值过期时间
* @return
*/
long expireMillions() default 0L;
}
package com.zk.aop;
import com.zk.model.PolicyCommonResult;
import org.aopalliance.intercept.MethodInvocation;
/**
* @author: Lei Guo
*/
public interface BaseAspect {
/**
* 执行前
*
* @param invocation
* @return
*/
String before(MethodInvocation invocation);
/**
* 执行中
* @param requestId
* @param invocation
* @return
* @throws Throwable
*/
PolicyCommonResult running(String requestId, MethodInvocation invocation) throws Throwable;
/**
* 执行后
* @param policyCommonResult
* @param invocation
* @return
*/
Object after(PolicyCommonResult policyCommonResult, MethodInvocation invocation);
/**
* 抛出异常
*
* @param invocation
* @param e
*/
void afterThrowing(MethodInvocation invocation, Throwable e);
/**
* 一定执行
*
* @param invocation
*/
void afterReturning(MethodInvocation invocation);
}
package com.zk.aop;
import java.util.Map;
import com.zk.filter.FilterManager;
import com.zk.cache.CacheLock;
import com.zk.enums.MethodReturnValueTypeEnum;
import com.zk.filter.HeadFilter;
import com.zk.filter.MethodParamFilter;
import com.zk.model.FilterParams;
import com.zk.policy.PolicyManager;
import com.zk.component.UniqueIdempotenConfig;
import com.zk.cache.CustomCache;
import com.zk.enums.PolicyTypeEnum;
import com.zk.interfaces.GetAndSetCacheTask;
import com.zk.interfaces.GetCacheTask;
import com.zk.model.ImitateMethodReturnValue;
import com.zk.model.PolicyCommonResult;
import com.zk.utils.request.RequestMethodUtils;
import com.zk.utils.cacheTask.TaskUtils;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.springframework.aop.framework.ReflectiveMethodInvocation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
/**
* @author Lei Guo
*/
@Component
public final class UniqueIdempotentAspect implements MethodInterceptor, BaseAspect {
@Autowired
private UniqueIdempotenConfig uniqueIdempotenConfig;
@Autowired
private CustomCache customCache;
@Autowired
private CacheLock cacheLock;
@Autowired
private FilterManager filterManager;
@Override
public Object invoke(MethodInvocation invocation) throws Throwable {
//防止不同的aop拦截重复调用invoke,防重幂等逻辑,不放在try里面:不影响别的aspect类的的异常捕获执行对应的方法
ReflectiveMethodInvocation cglibMethodInvocation = (ReflectiveMethodInvocation) invocation;
if(!ObjectUtils.isEmpty(cglibMethodInvocation.getUserAttributes())){
return invocation.proceed();
}
//防重幂等逻辑
PolicyCommonResult result;
try {
//执行前
String requestId = before(invocation);
//执行中
result = running(requestId, invocation);
//执行后
return after(result, invocation);
} catch (Throwable e) {
afterThrowing(invocation, e);
throw new RuntimeException(e);
} finally {
afterReturning(invocation);
}
}
@Override
public String before(MethodInvocation invocation) {
//获取唯一标识参数名称,用于防重或者幂等使用
FilterParams filterParams = new FilterParams();
filterParams
.setHttpServletRequest(((ServletRequestAttributes)RequestContextHolder.getRequestAttributes()).getRequest())
.setInvocation(invocation)
.setParamName(uniqueIdempotenConfig.getParamName());
return filterManager.run(filterParams);
}
/**
* 将执行返回数据进行一层封装
* @param requestId
* @param invocation
* @return
* @throws Throwable
*/
@Override
public PolicyCommonResult running(String requestId, MethodInvocation invocation)
throws Throwable {
if (StringUtils.isEmpty(requestId)) {
throw new IllegalArgumentException("requestId cant not empty");
}
//缓存获取
GetCacheTask getCacheTask = TaskUtils.getCacheTask(customCache);
//缓存存储操作
GetAndSetCacheTask getAndSetCacheTask = TaskUtils.getAndSetCacheTask(cacheLock);
//获取策略结果
return new PolicyManager.Builder()
.setDefaultModel(uniqueIdempotenConfig.getDefaultModel())
.setMaps(uniqueIdempotenConfig.getMaps())
.setMethodFullPath(RequestMethodUtils.getMethodFullPath(invocation))
.setAnnotationValue(RequestMethodUtils.getModelAnnotationValue(invocation))
.build()
.execute(
requestId,
invocation,
RequestMethodUtils.getModelExpireMillions(invocation, uniqueIdempotenConfig),
customCache,
getCacheTask,
getAndSetCacheTask);
}
@Override
public Object after(PolicyCommonResult policyCommonResult, MethodInvocation invocation) {
//获取响应类型 正常 防重 幂等
PolicyTypeEnum policyTypeEnum = policyCommonResult.getType();
//获取响应值
ImitateMethodReturnValue data = (ImitateMethodReturnValue) policyCommonResult.getData();
//获取响应值的类型 正常类型, 异常类型
MethodReturnValueTypeEnum methodReturnTypeEnum = data.getMethodReturnValueTypeEnum();
//判断响应值的类型是否是异常数据
if(MethodReturnValueTypeEnum.ERROR.equals(methodReturnTypeEnum)){
throw new IllegalArgumentException(String.valueOf(data.getData()));
}
if(MethodReturnValueTypeEnum.REPEATABLE.equals(methodReturnTypeEnum)){
throw new RuntimeException("The request cannot be repeated:" + RequestMethodUtils
.getMethodName(invocation));
}
if(MethodReturnValueTypeEnum.LOCK_OVERTIME.equals(methodReturnTypeEnum)){
throw new RuntimeException(String.valueOf(data.getData()));
}
switch (policyTypeEnum) {
case NORMAL:
case IDEMPOTENT:
case UNIQUE:
return data.getData();
default:
throw new IllegalArgumentException(
"non-existen return type:" + policyTypeEnum);
}
}
@Override
public void afterThrowing(MethodInvocation invocation, Throwable e) {
// nothing to do...
}
@Override
public void afterReturning(MethodInvocation invocation) {
// nothing to do...
}
}
package com.zk.cache;
import com.zk.component.UniqueIdempotenConfig;
import com.zk.component.ZkRedisConfig;
import javax.annotation.PostConstruct;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import java.time.Duration;
import java.util.Objects;
import java.util.concurrent.ThreadLocalRandom;
import java.util.concurrent.TimeUnit;
@Slf4j
@Component
@SuppressWarnings(value = {"unused", "WeakerAccess"})
public class CacheLock {
@Autowired
private UniqueIdempotenConfig uniqueIdempotenConfig;
/**
* 锁永不过期
*/
public final static int EXPIRE_TIME_MILLISECONDS_NEVER = -1;
/**
* 锁默认等待时间.
*/
private final static int WAIT_TIME_MILLISECONDS_DEFAULT = 0;
/**
* 锁默认过期时间. 2000 ms
*/
private final static int EXPIRE_TIME_MILLISECONDS_DEFAULT = 10*60*1000;
private final static String REDIS_LOCK_KEY_PREFIX = "zk-common-uniqueIdempotent:lock:";
private final static String LOCK_HOLD_TAG = "1";
@Autowired
private ZkRedisConfig zkRedisConfig;
private RedisTemplate redisTemplate;
@PostConstruct
private void init(){
this.redisTemplate = zkRedisConfig.getRedisTemplate();
}
/**
* redis 加锁,使用默认的等待和过期时间
* @param key redis lock key
* @return true or false
*/
public boolean lock(String key) {
return lock(key, uniqueIdempotenConfig.getLockWaitMillions(), uniqueIdempotenConfig.getLockExpireMillions());
}
/**
* 拿到锁返回true,拿不到锁就返回false
* @param key redis lock key
* @param waitMilliseconds 锁获取等待毫秒数. <=0表示不进行等待
* @param expireMilliseconds 锁过期毫秒数, -1表示永不过期
* @return true or false
*/
public boolean lock(String key, long waitMilliseconds, long expireMilliseconds) {
/*
todo: 待完善点,需要支持锁可重入
*/
Objects.requireNonNull(key);
if (waitMilliseconds <= 0) {
return lockNoWait(key, expireMilliseconds);
}
long now = System.nanoTime();
do {
if (lockNoWait(key, expireMilliseconds)) {
return true;
}
long sleepTime = 50L + ThreadLocalRandom.current().nextLong(100);
try {
TimeUnit.MILLISECONDS.sleep(sleepTime);
} catch (InterruptedException e) {
log.info("redis加锁休眠被中断" + e.getMessage());
}
} while ((System.nanoTime() - now) < TimeUnit.NANOSECONDS.convert(waitMilliseconds, TimeUnit.MILLISECONDS));
return false;
}
/**
* 加锁
* @param key redis lock key
* @param expireMilliseconds 过期毫秒数
* @return true or false
*/
public boolean lockNoWait(String key, long expireMilliseconds) {
key = buildKey(Objects.requireNonNull(key));
if (Boolean.TRUE.equals(redisTemplate.opsForValue().setIfAbsent(key, LOCK_HOLD_TAG, Duration.ofMillis(expireMilliseconds)))) {
if (log.isDebugEnabled()) {
log.debug("redis lock success. key: " + key + ", expireMilliseconds: " + expireMilliseconds);
}
return true;
}
return false;
}
/**
* 解锁
*
* @param key redis unlock key
* @return 是否上锁成功
*/
public Boolean unLock(String key) {
key = buildKey(Objects.requireNonNull(key));
if (isHold(key)) {
Boolean result = redisTemplate.delete(key);
if (log.isDebugEnabled()) {
log.debug("redis unlock. key: " + key + ", result: " + result);
}
return result;
} else {
// TODO exception un hold lock
throw new RuntimeException("当前线程未持有锁,不允许释放. key:" + key);
}
}
private String buildKey(String key) {
return REDIS_LOCK_KEY_PREFIX + key;
}
private boolean isHold(String key) {
// TODO 完善是否持有锁的校验
return true;
}
}
package com.zk.cache;
import com.zk.component.ZkRedisConfig;
import com.zk.utils.redis.RedisKeyUtils;
import com.zk.utils.redis.RedisKeyUtils.Prefix;
import java.util.concurrent.TimeUnit;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
/**
* @author: Lei Guo
*/
@Component
public class CustomCache {
@Autowired
private ZkRedisConfig zkRedisConfig;
public Object get(String requestId) {
return zkRedisConfig.getRedisTemplate().opsForValue().get(Prefix.ZK_REQUEST_ID.getKey(requestId));
}
public void set(String requestId, Object value, Long expireMillions, TimeUnit timeUnit) {
zkRedisConfig.getRedisTemplate().opsForValue().set(Prefix.ZK_REQUEST_ID.getKey(requestId), value, expireMillions, timeUnit);
}
}
package com.zk.common;
/**
* @author: Lei Guo
* @use:仅供注解Model使用 如:@Model(value = PolicyConst.NORMAL)
*/
public interface PolicyConst {
String NORMAL = "normal";
String UNIQUE = "unique";
String IDEMPOTENT = "idempotent";
}
package com.zk.component;
import java.util.Map;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
@Component
//@PropertySource(value = {"classpath:/application.yml"}, encoding = "utf-8")
@ConfigurationProperties("zk.unique-idempotent.kk-config")
public class UniqueIdempotenConfig {
/**
* 默认所有接口模式 Unique:防重 Idempoten: 幂等
*/
private String defaultModel;
/**
* 默认过期时间 毫秒
*/
private Long expireMillions;
/**
*获取唯一标识参数名称 requestId
*/
private String paramName;
/**
* 需要拦截的控制器所在包
*/
private String[] executions;
/**
* 对应接口设置的模型
* @return
*/
private Map maps;
/**
* 每个方法返回值过期时间
*/
private Map expireMap;
/**
* 锁的等待时间
*/
private Long lockWaitMillions;
/**
* 锁的过期时间
*/
private Long lockExpireMillions;
public String getDefaultModel() {
return defaultModel;
}
public void setDefaultModel(String defaultModel) {
this.defaultModel = defaultModel;
}
public Long getExpireMillions() {
return expireMillions;
}
public void setExpireMillions(Long expireMillions) {
this.expireMillions = expireMillions;
}
public String[] getExecutions() {
return executions;
}
public void setExecutions(String[] executions) {
this.executions = executions;
}
public Map getMaps() {
return maps;
}
public void setMaps(
Map maps) {
this.maps = maps;
}
public String getParamName() {
return paramName;
}
public void setParamName(String paramName) {
this.paramName = paramName;
}
public Map getExpireMap() {
return expireMap;
}
public void setExpireMap(Map expireMap) {
this.expireMap = expireMap;
}
public Long getLockWaitMillions() {
return lockWaitMillions;
}
public void setLockWaitMillions(Long lockWaitMillions) {
this.lockWaitMillions = lockWaitMillions;
}
public Long getLockExpireMillions() {
return lockExpireMillions;
}
public void setLockExpireMillions(Long lockExpireMillions) {
this.lockExpireMillions = lockExpireMillions;
}
}
package com.zk.component;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import javax.annotation.PostConstruct;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.connection.RedisPassword;
import org.springframework.data.redis.connection.RedisStandaloneConfiguration;
import org.springframework.data.redis.connection.jedis.JedisClientConfiguration;
import org.springframework.data.redis.connection.jedis.JedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import org.springframework.stereotype.Component;
import redis.clients.jedis.JedisPoolConfig;
/**
*@author: Lei Guo
*/
@Component
public class ZkRedisConfig {
@Value("${zk.unique-idempotent.redis.host}")
private String hostName;
@Value("${zk.unique-idempotent.redis.port}")
private int port;
@Value("${zk.unique-idempotent.redis.password}")
private String passWord;
@Value("${zk.unique-idempotent.redis.maxIdle}")
private int maxIdl;
@Value("${zk.unique-idempotent.redis.minIdle}")
private int minIdl;
@Value("${zk.unique-idempotent.redis.timeout}")
private int timeout;
@Value("${zk.unique-idempotent.redis.database}")
private int database;
private RedisTemplate redisTemplate;
public RedisTemplate getRedisTemplate() {
return redisTemplate;
}
@PostConstruct
public void initRedisTemp() throws Exception{
System.out.println("###### START 初始化 Redis 连接池 START ######");
redisTemplate = redisTemplateObject(database);
// redisTemplateMap.put(bicycleInfoDb,redisTemplateObject(bicycleInfoDb));
// redisTemplateMap.put(currentLocationDb,redisTemplateObject(currentLocationDb));
// redisTemplateMap.put(lockDb,redisTemplateObject(lockDb));
// redisTemplateMap.put(ebikeNoDb,redisTemplateObject(ebikeNoDb));
// redisTemplateMap.put(bikeNoDb,redisTemplateObject(bikeNoDb));
System.out.println("###### END 初始化 Redis 连接池 END ######");
}
public RedisTemplate redisTemplateObject(Integer dbIndex) throws Exception {
RedisTemplate redisTemplateObject = new RedisTemplate();
redisTemplateObject.setConnectionFactory(redisConnectionFactory(jedisPoolConfig(),dbIndex));
setSerializer(redisTemplateObject);
redisTemplateObject.afterPropertiesSet();
return redisTemplateObject;
}
/**
* 连接池配置信息
* @return
*/
public JedisPoolConfig jedisPoolConfig() {
JedisPoolConfig poolConfig=new JedisPoolConfig();
//最大连接数
poolConfig.setMaxIdle(maxIdl);
//最小空闲连接数
poolConfig.setMinIdle(minIdl);
poolConfig.setTestOnBorrow(true);
poolConfig.setTestOnReturn(true);
poolConfig.setTestWhileIdle(true);
poolConfig.setNumTestsPerEvictionRun(10);
poolConfig.setTimeBetweenEvictionRunsMillis(60000);
//当池内没有可用的连接时,最大等待时间
poolConfig.setMaxWaitMillis(10000);
//------其他属性根据需要自行添加-------------
return poolConfig;
}
/**
* jedis连接工厂
* @param jedisPoolConfig
* @return
*/
public RedisConnectionFactory redisConnectionFactory(JedisPoolConfig jedisPoolConfig,int db) {
//单机版jedis
RedisStandaloneConfiguration redisStandaloneConfiguration =
new RedisStandaloneConfiguration();
//设置redis服务器的host或者ip地址
redisStandaloneConfiguration.setHostName(hostName);
//设置默认使用的数据库
redisStandaloneConfiguration.setDatabase(db);
//设置密码
redisStandaloneConfiguration.setPassword(RedisPassword.of(passWord));
//设置redis的服务的端口号
redisStandaloneConfiguration.setPort(port);
//获得默认的连接池构造器(怎么设计的,为什么不抽象出单独类,供用户使用呢)
JedisClientConfiguration.JedisPoolingClientConfigurationBuilder jpcb =
(JedisClientConfiguration.JedisPoolingClientConfigurationBuilder) JedisClientConfiguration.builder();
//指定jedisPoolConifig来修改默认的连接池构造器(真麻烦,滥用设计模式!)
jpcb.poolConfig(jedisPoolConfig);
//通过构造器来构造jedis客户端配置
JedisClientConfiguration jedisClientConfiguration = jpcb.build();
//单机配置 + 客户端配置 = jedis连接工厂
return new JedisConnectionFactory(redisStandaloneConfiguration, jedisClientConfiguration);
}
private void setSerializer(RedisTemplate template) {
Jackson2JsonRedisSerializer
package com.zk.configuration;
import com.zk.aop.UniqueIdempotentAspect;
import com.zk.component.UniqueIdempotenConfig;
import org.springframework.aop.Advisor;
import org.springframework.aop.aspectj.AspectJExpressionPointcut;
import org.springframework.aop.support.DefaultPointcutAdvisor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* @author: Lei Guuo
*/
@Configuration
public class ConfigurableAdvisorConfig {
@Autowired
private UniqueIdempotenConfig uniqueIdempotenConfig;
@Autowired
private UniqueIdempotentAspect uniqueIdempotentAspect;
//
// @Autowired
// private CustomCache customCache;
//
// @Autowired
// private CacheLock cacheLock;
@Bean
public Advisor configurabledvisor() {
AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut();
StringBuilder stringBuilder = new StringBuilder();
for (String execution : uniqueIdempotenConfig.getExecutions()) {
stringBuilder.append("||");
stringBuilder.append(execution);
}
pointcut.setExpression(stringBuilder.substring(stringBuilder.indexOf("||") + 2));
// 配置增强类advisor
DefaultPointcutAdvisor advisor = new DefaultPointcutAdvisor();
advisor.setOrder(1);
advisor.setPointcut(pointcut);
advisor.setAdvice(uniqueIdempotentAspect);
return advisor;
}
}
package com.zk.configuration;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
/**
* @author: Lei Guo
*/
@Configuration
@ComponentScan(basePackages = {"com.zk.component", "com.zk.cache", "com.zk.aop", "com.zk.filter"})
//@Deprecated
public class UniqueIdempotentAspectConfig {
}
package com.zk.enums;
/**
* @author: Lei Guo
*/
public enum MethodReturnTypeEnum {
VOID("void", "返回类型void");
private String type;
private String message;
MethodReturnTypeEnum(String type, String message){
this.type = type;
this.message = message;
}
public String getType() {
return type;
}
public String getMessage() {
return message;
}
}
package com.zk.enums;
/**
* @author: Lei Guo
* @use: 值类型: 正常, 异常
*/
public enum MethodReturnValueTypeEnum {
NORMAL("normal", "正常返回"),
ERROR("error", "异常数据"),
LOCK_OVERTIME("lock_overtime", "锁请求超时"),
REPEATABLE("repeatable", "重复请求");
private String type;
private String desc;
MethodReturnValueTypeEnum(String type, String desc){
this.type = type;
this.desc = desc;
}
public String getType() {
return type;
}
public String getDesc() {
return desc;
}
}
package com.zk.enums;
import java.util.Arrays;
/**
* @Author: Lei Guo
* @use: 类型
*/
public enum ModelTypeEnum {
UNIQUE("unique", "防重"),
IDEMPOTENT("idempotent", "幂等"),
NORMAL("normal", "正常");
private String code;
private String desc;
ModelTypeEnum(String code, String desc){
this.code = code;
this.desc = desc;
}
public String getCode() {
return code;
}
public void setCode(String code) {
this.code = code;
}
public String getDesc() {
return desc;
}
public static ModelTypeEnum getInstanceByCode(String code){
return Arrays
.asList(ModelTypeEnum.values())
.parallelStream()
.filter( modelTypeEnum -> modelTypeEnum.getCode().equals(code))
.findFirst()
.get();
}
}
package com.zk.enums;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
/**
* @author: Lei Guo
*/
public enum PolicyTypeEnum {
NORMAL("normal", "","正常返回"),
UNIQUE("unique", "重复提交", "防重返回"),
IDEMPOTENT("idempotent", "","幂等返回");
private String type;
private String message;
private String desc;
PolicyTypeEnum(String type, String message, String desc){
this.type = type;
this.message = message;
this.desc = desc;
}
public String getCode() {
return type;
}
public String getMessage() {
return message;
}
public String getDesc() {
return desc;
}
public static List getAllPolicyResultTypes(){
List types = new ArrayList<>();
Arrays
.asList(PolicyTypeEnum.values())
.parallelStream()
.forEach( policyResultTypeEnum -> types.add(policyResultTypeEnum.type));
return types;
}
@Override
public String toString() {
return "PolicyResultTypeEnum{" +
"type='" + type + '\'' +
", message='" + message + '\'' +
", desc='" + desc + '\'' +
'}';
}
}
package com.zk.filter;
import com.zk.model.FilterParams;
import java.util.LinkedList;
import java.util.List;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;
/**
* @author Lei Guo 骨架抽象类,减少实体类的接口以及类的继承,使用骨架类代替做这些事情
*/
public abstract class AbstractExecutor implements Executor {
private static final int FIRST_INDEX = 0;
protected List filterList = new LinkedList<>();
/**
* 执行
*
* @param filterParams
* @return
*/
@Override
abstract public String execute(FilterParams filterParams);
/**
* 递归执行
*
* @param filterParams
* @return
*/
protected String recursionExecute(FilterParams filterParams) {
if (!CollectionUtils.isEmpty(this.filterList)) {
String requestId = this.filterList
.remove(FIRST_INDEX)
.chain(filterParams);
if (StringUtils.isEmpty(requestId)) {
return recursionExecute(filterParams);
}
return requestId;
}
return "";
}
}
package com.zk.filter;
import com.zk.model.FilterParams;
/**
* @author: Lei Guo
*/
public interface Executor {
/**
* 执行
* @param filterParams
* @return
*/
String execute(FilterParams filterParams);
}
package com.zk.filter;
import com.zk.model.FilterParams;
/**
* @author: Lei Guo
* 用于过滤不同类型的请求
*/
public interface Filter {
/**
* 过滤链执行
* @param filterParams
* @return
*/
String chain(FilterParams filterParams);
}
package com.zk.filter;
import com.zk.model.FilterParams;
import java.util.List;
import org.springframework.util.StringUtils;
/**
* @author: Lei Guo
* @use: 执行器, 执行过滤链
*/
public class FilterExecutor extends AbstractExecutor {
public FilterExecutor(List filterList){
this.filterList = filterList;
}
/**
* 执行
* @param filterParams
* @return
*/
@Override
public String execute(FilterParams filterParams){
String requestId = this.recursionExecute(filterParams);
if(StringUtils.isEmpty(requestId)){
throw new RuntimeException("无可用唯一标识");
}
return requestId;
}
}
package com.zk.filter;
import com.zk.model.FilterParams;
import java.util.LinkedList;
import java.util.List;
import org.springframework.stereotype.Component;
/**
* @author: Lei Guo
*/
@Component
public class FilterManager {
/**
*过滤链
*/
private final List filterList = new LinkedList<>();
/**
* 添加过滤器
* @param filter
*/
public FilterManager add(Filter filter){
this.filterList.add(filter);
return this;
}
/**
* 获取唯一标识RequestId
* @param filterParams
* @return
*/
public String run(FilterParams filterParams){
return new FilterExecutor(this.filterList)
.execute(filterParams);
}
}
package com.zk.filter;
import com.zk.model.FilterParams;
import javax.servlet.http.HttpServletRequest;
import org.springframework.util.ObjectUtils;
/**
* @author: Lei Guo
* @use: 用于Http协议获取唯一 requestId
*/
public class HeadFilter implements Filter{
@Override
public String chain(FilterParams filterParams) {
if(ObjectUtils.isEmpty(filterParams)){
return "";
}
HttpServletRequest request = filterParams.getHttpServletRequest();
if(ObjectUtils.isEmpty(request)){
return "";
}
return request.getHeader(filterParams.getParamName());
}
}
package com.zk.filter;
import com.zk.model.FilterParams;
import com.zk.utils.request.RequestMethodUtils;
import java.lang.reflect.Method;
import org.aopalliance.intercept.MethodInvocation;
import org.springframework.util.ObjectUtils;
/**
* @author: Lei Guo
* @author: 用于直接调用方法获取唯一 requestId
*/
public class MethodParamFilter implements Filter{
@Override
public String chain(FilterParams filterParams) {
if(ObjectUtils.isEmpty(filterParams)){
return "";
}
MethodInvocation methodInvocation = filterParams.getInvocation();
if(ObjectUtils.isEmpty(methodInvocation)){
return "";
}
Method method = methodInvocation.getMethod();
Object[] arguments = methodInvocation.getArguments();
Object requestId = RequestMethodUtils.getArgumentByIndex(
arguments,
RequestMethodUtils.getParamIndex(
method,
filterParams.getParamName()));
return requestId== null ? "" : String.valueOf(requestId);
}
}
package com.zk.filter;
import com.zk.model.FilterParams;
import java.nio.charset.StandardCharsets;
import javax.servlet.http.HttpServletRequest;
import lombok.extern.slf4j.Slf4j;
import org.springframework.util.ObjectUtils;
/**
* @author: Lei Guo
* @use: 请求参数
*/
@Slf4j
public class RequestParamFilter implements Filter{
@Override
public String chain(FilterParams filterParams) {
if(ObjectUtils.isEmpty(filterParams)){
return "";
}
HttpServletRequest request = filterParams.getHttpServletRequest();
if(ObjectUtils.isEmpty(request)){
return "";
}
try{
request.setCharacterEncoding(StandardCharsets.UTF_8.name());
return request.getParameter(filterParams.getParamName());
}catch (Throwable e){
log.error("RequestParamFilter chain throw an exception:{}", e.getMessage());
}
return "";
}
}
package com.zk.interfaces;
import com.zk.cache.CustomCache;
import com.zk.enums.PolicyTypeEnum;
import org.aopalliance.intercept.MethodInvocation;
/**
* @author: Lei Guo
*/
@FunctionalInterface
public interface GetAndSetCacheTask {
/**
* 执行
* @param policyTypeEnum
* @param requestId
* @param expireMillions
* @param invocation
* @param customCache
* @param getCacheTask
* @return
* @throws Throwable
*/
T apply(
PolicyTypeEnum policyTypeEnum,
String requestId,
Long expireMillions,
MethodInvocation invocation,
CustomCache customCache,
GetCacheTask getCacheTask)
throws Throwable;
}
package com.zk.interfaces;
/**
* @author: Lei Guo
*/
@FunctionalInterface
public interface GetCacheTask {
/**
* 执行
* @param requestId
* @return
* @throws Throwable
*/
T apply(String requestId);
}
package com.zk.interfaces;
import com.zk.cache.CustomCache;
import com.zk.enums.PolicyTypeEnum;
import org.aopalliance.intercept.MethodInvocation;
/**
* @author: Lei Guo
*/
@FunctionalInterface
public interface PolicyAgainExecuteTask {
/**
* 执行
* @param policyTypeEnum
* @param requestId
* @param invocation
* @param expireMillions
* @param customCache
* @param getCacheTask
* @param getAndSetCacheTask
* @return
* @throws Throwable
*/
public T apply(
PolicyTypeEnum policyTypeEnum,
String requestId,
MethodInvocation invocation,
Long expireMillions,
CustomCache customCache,
GetCacheTask getCacheTask,
GetAndSetCacheTask getAndSetCacheTask) throws Throwable;
}
package com.zk.model;
import javax.servlet.http.HttpServletRequest;
import org.aopalliance.intercept.MethodInvocation;
/**
* @author: Lei Guo
* @use: 过滤不同请求所需参数
*/
public class FilterParams {
private String paramName;
private HttpServletRequest httpServletRequest;
private MethodInvocation invocation;
public String getParamName() {
return paramName;
}
public FilterParams setParamName(String paramName) {
this.paramName = paramName;
return this;
}
public HttpServletRequest getHttpServletRequest() {
return httpServletRequest;
}
public FilterParams setHttpServletRequest(HttpServletRequest httpServletRequest) {
this.httpServletRequest = httpServletRequest;
return this;
}
public MethodInvocation getInvocation() {
return invocation;
}
public FilterParams setInvocation(MethodInvocation invocation) {
this.invocation = invocation;
return this;
}
}
package com.zk.model;
import com.zk.enums.MethodReturnValueTypeEnum;
import java.io.Serializable;
/**
* @author: Lei Guo
* 不能用于返回前端使用,仅供后端内部使用
*/
public class ImitateMethodReturnValue implements Serializable, Cloneable {
private MethodReturnValueTypeEnum methodReturnValueTypeEnum;
private Object data;
/**
* 反序列化需要无参构造
*/
public ImitateMethodReturnValue(){}
public ImitateMethodReturnValue(MethodReturnValueTypeEnum methodReturnValueTypeEnum,
Object data) {
this.methodReturnValueTypeEnum = methodReturnValueTypeEnum;
this.data = data;
}
public MethodReturnValueTypeEnum getMethodReturnValueTypeEnum() {
return methodReturnValueTypeEnum;
}
public Object getData() {
return data;
}
public void setMethodReturnValueTypeEnum(
MethodReturnValueTypeEnum methodReturnValueTypeEnum) {
this.methodReturnValueTypeEnum = methodReturnValueTypeEnum;
}
}
package com.zk.model;
import com.zk.enums.PolicyTypeEnum;
import java.io.Serializable;
/**
* @author: Lei Guo
*/
public class PolicyCommonResult implements Serializable, Cloneable {
private PolicyTypeEnum type;
private Object data;
public PolicyCommonResult(PolicyTypeEnum type, Object data) {
this.type = type;
this.data = data;
}
public PolicyTypeEnum getType() {
return type;
}
public void setType(PolicyTypeEnum type) {
this.type = type;
}
public Object getData() {
return data;
}
public void setData(Object data) {
this.data = data;
}
}
package com.zk.model;
import java.time.Duration;
import org.springframework.boot.context.properties.ConfigurationProperties;
/**
* @author: Lei Guo
*/
@ConfigurationProperties(prefix = "zk.unique-idempotent.redis")
public class RedisProperties {
/**
* Database index used by the connection factory.
*/
private int database = 0;
/**
* Connection URL. Overrides host, port, and password. User is ignored. Example:
* redis://user:[email protected]:6379
*/
private String url;
/**
* Redis server host.
*/
private String host = "localhost";
/**
* Login password of the redis server.
*/
private String password;
/**
* Redis server port.
*/
private int port = 6379;
public int getDatabase() {
return database;
}
public void setDatabase(int database) {
this.database = database;
}
public String getUrl() {
return url;
}
public void setUrl(String url) {
this.url = url;
}
public String getHost() {
return host;
}
public void setHost(String host) {
this.host = host;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public int getPort() {
return port;
}
public void setPort(int port) {
this.port = port;
}
public Duration getTimeout() {
return timeout;
}
public void setTimeout(Duration timeout) {
this.timeout = timeout;
}
/**
* Connection timeout.
*/
private Duration timeout;
/**
* Pool properties.
*/
public static class Pool {
/**
* Maximum number of "idle" connections in the pool. Use a negative value to
* indicate an unlimited number of idle connections.
*/
private int maxIdle = 8;
/**
* Target for the minimum number of idle connections to maintain in the pool. This
* setting only has an effect if it is positive.
*/
private int minIdle = 0;
/**
* Maximum number of connections that can be allocated by the pool at a given
* time. Use a negative value for no limit.
*/
private int maxActive = 8;
/**
* Maximum amount of time a connection allocation should block before throwing an
* exception when the pool is exhausted. Use a negative value to block
* indefinitely.
*/
private Duration maxWait = Duration.ofMillis(-1);
public int getMaxIdle() {
return this.maxIdle;
}
public void setMaxIdle(int maxIdle) {
this.maxIdle = maxIdle;
}
public int getMinIdle() {
return this.minIdle;
}
public void setMinIdle(int minIdle) {
this.minIdle = minIdle;
}
public int getMaxActive() {
return this.maxActive;
}
public void setMaxActive(int maxActive) {
this.maxActive = maxActive;
}
public Duration getMaxWait() {
return this.maxWait;
}
public void setMaxWait(Duration maxWait) {
this.maxWait = maxWait;
}
}
}
package com.zk.policy;
import com.zk.cache.CustomCache;
import com.zk.enums.PolicyTypeEnum;
import com.zk.interfaces.GetAndSetCacheTask;
import com.zk.interfaces.GetCacheTask;
import com.zk.model.PolicyCommonResult;
import com.zk.utils.policyTask.PolicyUtils;
import org.aopalliance.intercept.MethodInvocation;
/**
* @author: Lei Guo
* 幂等策略
*/
public class IdempotentPolicy implements Policy {
@Override
public PolicyCommonResult execute(
String requestId,
MethodInvocation invocation,
Long expireMillions,
CustomCache customCache,
GetCacheTask getCacheTask,
GetAndSetCacheTask getAndSetCacheTask) throws Throwable {
return (PolicyCommonResult) PolicyUtils.getPolicyAgainExecuteTask().apply(
PolicyTypeEnum.IDEMPOTENT,
requestId,
invocation,
expireMillions,
customCache,
getCacheTask,
getAndSetCacheTask);
}
}
package com.zk.policy;
import com.zk.cache.CustomCache;
import com.zk.enums.PolicyTypeEnum;
import com.zk.interfaces.GetAndSetCacheTask;
import com.zk.interfaces.GetCacheTask;
import com.zk.model.PolicyCommonResult;
import com.zk.utils.policyTask.PolicyUtils;
import org.aopalliance.intercept.MethodInvocation;
/**
* @author: Lei Guo
* @use: 正常请求策略
*/
public class NormalPolicy implements Policy {
@Override
public PolicyCommonResult execute(
String requestId,
MethodInvocation invocation,
Long expireMillions,
CustomCache customCache,
GetCacheTask getCacheTask,
GetAndSetCacheTask getAndSetCacheTask) throws Throwable {
return (PolicyCommonResult) PolicyUtils.getPolicyAgainExecuteTask().apply(
PolicyTypeEnum.NORMAL,
requestId,
invocation,
expireMillions,
customCache,
getCacheTask,
getAndSetCacheTask
);
}
}
package com.zk.policy;
import com.zk.cache.CustomCache;
import com.zk.interfaces.GetAndSetCacheTask;
import com.zk.interfaces.GetCacheTask;
import com.zk.model.PolicyCommonResult;
import org.aopalliance.intercept.MethodInvocation;
/**
* @author: Lei Guo
*/
public interface Policy{
/**
* 执行
* @param requestId
* @param invocation
* @param expireMillions
* @param customCache
* @param getCacheTask
* @param getAndSetCacheTask
* @return
* @throws Throwable
*/
PolicyCommonResult execute(
String requestId,
MethodInvocation invocation,
Long expireMillions,
CustomCache customCache,
GetCacheTask getCacheTask,
GetAndSetCacheTask getAndSetCacheTask) throws Throwable;
}
package com.zk.policy;
import com.zk.enums.ModelTypeEnum;
import java.util.Map;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;
/**
* @author: Lei Guo
*/
public class PolicyManager {
public static class Builder{
private Map maps;
//方法全路径用于查找方法策略
private String methodFullPath;
private String defaultModel;
//策略注解值
private String annotationValue;
public Builder setDefaultModel(String defaultModel) {
this.defaultModel = defaultModel;
return this;
}
public Builder setMaps(Map maps) {
this.maps = maps;
return this;
}
public Builder setMethodFullPath(String methodFullPath) {
this.methodFullPath = methodFullPath;
return this;
}
public Builder setAnnotationValue(String annotationValue) {
this.annotationValue = annotationValue;
return this;
}
public Policy build(){
//注解 > 配置 > 默认
String methodModel;
//默认值
methodModel = this.defaultModel;
//配置值
String modelTemp = this.maps.get(this.methodFullPath);
if (!ObjectUtils.isEmpty(this.maps) && !StringUtils.isEmpty(modelTemp)) {
methodModel = modelTemp;
}
//注解值
if(!StringUtils.isEmpty(annotationValue)){
methodModel = annotationValue;
}
switch (ModelTypeEnum.getInstanceByCode(methodModel)){
case IDEMPOTENT: return new IdempotentPolicy();
case UNIQUE: return new UniquePolicy();
case NORMAL: return new NormalPolicy();
default: throw new IllegalArgumentException("不存在该类型的策略:" + methodModel);
}
}
}
}
package com.zk.policy;
import com.zk.cache.CustomCache;
import com.zk.enums.PolicyTypeEnum;
import com.zk.interfaces.GetAndSetCacheTask;
import com.zk.interfaces.GetCacheTask;
import com.zk.interfaces.PolicyAgainExecuteTask;
import com.zk.model.PolicyCommonResult;
import com.zk.utils.policyTask.PolicyUtils;
import org.aopalliance.intercept.MethodInvocation;
/**
* @author: Lei Guo 防重策略
*/
public class UniquePolicy implements Policy {
@Override
public PolicyCommonResult execute(
String requestId,
MethodInvocation invocation,
Long expireMillions,
CustomCache customCache,
GetCacheTask getCacheTask,
GetAndSetCacheTask getAndSetCacheTask) throws Throwable {
return (PolicyCommonResult) PolicyUtils.getPolicyAgainExecuteTask().apply(
PolicyTypeEnum.UNIQUE,
requestId,
invocation,
expireMillions,
customCache,
getCacheTask,
getAndSetCacheTask
);
}
}
cacheTask
package com.zk.utils.cacheTask;
import com.zk.cache.CacheLock;
import com.zk.cache.CustomCache;
import com.zk.enums.PolicyTypeEnum;
import com.zk.enums.MethodReturnValueTypeEnum;
import com.zk.interfaces.GetAndSetCacheTask;
import com.zk.interfaces.GetCacheTask;
import com.zk.model.ImitateMethodReturnValue;
import java.util.concurrent.TimeUnit;
import org.springframework.util.ObjectUtils;
/**
* @author: Lei Guo
*/
public class TaskUtils {
/**
* 是否已存在
*/
private static final Byte IS_EXIST = 1;
/**
* 从缓存中获取值
* @param customCache
* @return
*/
public static GetCacheTask getCacheTask(CustomCache customCache){
return requestId -> customCache.get(requestId);
}
/**
* 获取值并且缓存
* 封装返回值
* @param cacheLock
* @return
*/
public static GetAndSetCacheTask getAndSetCacheTask(CacheLock cacheLock){
return (
policyType,
requestId,
expireMillions,
invocation,
customCache,
getCacheTaskTemp) -> {
GetCacheTask getCacheTask = TaskUtils.getCacheTask(customCache);
//正常策略
if(PolicyTypeEnum.NORMAL.equals(policyType)){
return new ImitateMethodReturnValue(MethodReturnValueTypeEnum.NORMAL, invocation.proceed());
}
//防重和幂等策略
if (cacheLock.lock(requestId)) {
ImitateMethodReturnValue imitateMethodReturnValue = (ImitateMethodReturnValue) getCacheTask.apply(requestId);
//如果缓存不为空则执行幂等和防重返回值策略
if (!ObjectUtils.isEmpty(imitateMethodReturnValue)) {
if(PolicyTypeEnum.IDEMPOTENT.equals(policyType)){
return imitateMethodReturnValue;
}
return new ImitateMethodReturnValue(MethodReturnValueTypeEnum.REPEATABLE, imitateMethodReturnValue.getData());
}
//不为空执行缓存逻辑
try {
//缓存值的设置
Object resultCache, resultValue;
resultCache = resultValue = invocation.proceed();
if(PolicyTypeEnum.UNIQUE.equals(policyType)){
resultCache = IS_EXIST;
}
//缓存数据
customCache
.set(requestId, new ImitateMethodReturnValue(
MethodReturnValueTypeEnum.NORMAL, resultCache), expireMillions,
TimeUnit.MILLISECONDS);
return new ImitateMethodReturnValue(
MethodReturnValueTypeEnum.NORMAL, resultValue);
}catch (Throwable e){
return new ImitateMethodReturnValue(MethodReturnValueTypeEnum.ERROR, e.getMessage());
} finally {
cacheLock.unLock(requestId);
}
}
return new ImitateMethodReturnValue(MethodReturnValueTypeEnum.LOCK_OVERTIME,
MethodReturnValueTypeEnum.LOCK_OVERTIME.getDesc() + "方法名称:" + invocation.getMethod().getName());
};
}
}
policyTask
package com.zk.utils.policyTask;
import com.zk.cache.CustomCache;
import com.zk.enums.MethodReturnValueTypeEnum;
import com.zk.enums.PolicyTypeEnum;
import com.zk.interfaces.GetAndSetCacheTask;
import com.zk.interfaces.GetCacheTask;
import com.zk.interfaces.PolicyAgainExecuteTask;
import com.zk.model.ImitateMethodReturnValue;
import com.zk.model.PolicyCommonResult;
import org.aopalliance.intercept.MethodInvocation;
import org.springframework.util.ObjectUtils;
/**
* @author: Lei Guo
*/
public class PolicyUtils {
public static PolicyAgainExecuteTask getPolicyAgainExecuteTask(){
return (PolicyTypeEnum policyTypeEnum,
String requestId,
MethodInvocation invocation,
Long expireMillions,
CustomCache customCache,
GetCacheTask getCacheTask,
GetAndSetCacheTask getAndSetCacheTask) -> {
switch (policyTypeEnum){
case NORMAL: return new PolicyCommonResult(
policyTypeEnum,
getAndSetCacheTask.apply(
policyTypeEnum,
requestId,
expireMillions,
invocation,
customCache,
getCacheTask));
case UNIQUE:
{
Object result = getCacheTask.apply(requestId);
if (!ObjectUtils.isEmpty(result)) {
ImitateMethodReturnValue imitateMethodReturnValue = (ImitateMethodReturnValue) result;
imitateMethodReturnValue.setMethodReturnValueTypeEnum(
MethodReturnValueTypeEnum.REPEATABLE);
return new PolicyCommonResult(policyTypeEnum, imitateMethodReturnValue);
} else {
return new PolicyCommonResult(
policyTypeEnum,
getAndSetCacheTask.apply(
policyTypeEnum,
requestId,
expireMillions,
invocation,
customCache,
getCacheTask));
}
}
case IDEMPOTENT:
{
Object result = getCacheTask.apply(requestId);
if (!ObjectUtils.isEmpty(result)) {
return new PolicyCommonResult(policyTypeEnum, result);
} else {
return new PolicyCommonResult(
policyTypeEnum,
getAndSetCacheTask.apply(
policyTypeEnum,
requestId,
expireMillions,
invocation,
customCache,
getCacheTask));
}
}
default: throw new IllegalArgumentException("不存在的策略类型:" + policyTypeEnum);
}
};
}
}
redis
package com.zk.utils.redis;
/**
* @author: Lei Guo
*/
public interface RedisKeyUtils {
/**
* key后缀
*/
enum Suffix{
EMPTY("","");
String suffix;
String use;
Suffix(String suffix, String use){
this.suffix = suffix;
this.use = use;
}
public String getKey(String str){
return str + this.suffix;
}
}
/**
* key前置
*/
enum Prefix{
EMPTY("",""),
ZK_REQUEST_ID("zk_request_id:", "缓存请求结果,用于防重和幂等进行逻辑判断");
String prefix;
String use;
Prefix(String prefix, String use){
this.prefix = prefix;
this.use = use;
}
public String getKey(String str){
return this.prefix + str;
}
}
/**
* 完整key
*/
enum Key{
EMPTY("","");
String key;
String use;
Key(String key, String use){
this.key = key;
this.use = use;
}
public String getKey(){
return this.key;
}
}
}
request
package com.zk.utils.request;
import com.zk.annotation.ZkModel;
import com.zk.component.UniqueIdempotenConfig;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.Arrays;
import java.util.Map;
import org.aopalliance.intercept.MethodInvocation;
import org.springframework.util.CollectionUtils;
import org.springframework.util.ObjectUtils;
/**
* @author: Lei Guo
*/
public class RequestMethodUtils {
/**
* 根据参数名称获取参数索引
* @param method 方法对象
* @param paramName 参数名称
* @return
*/
public static int getParamIndex(Method method, String paramName) {
int recordIndex = -1;
if(ObjectUtils.isEmpty(method)){
return recordIndex;
}
Parameter[] params = method.getParameters();
if(CollectionUtils.isEmpty(Arrays.asList(params))){
return recordIndex;
}
for(int index = 0; index < params.length; index ++){
if(params[index].getName().equals(paramName)){
recordIndex = index;
break;
}
}
return recordIndex;
}
/**
* 根据参数索引获取参数值
* @param arguments 参数集合
* @param paramIndex 需要查询参数的索引
* @return
*/
public static Object getArgumentByIndex(Object[] arguments, int paramIndex) {
if(-1 == paramIndex){
return null;
}
if(CollectionUtils.isEmpty(Arrays.asList(arguments))){
return null;
}
return arguments[paramIndex];
}
/**
* 获取方法全路径
* @param invocation
* @return
*/
public static String getMethodFullPath(MethodInvocation invocation) {
if(ObjectUtils.isEmpty(invocation)){
return null;
}
Method method = invocation.getMethod();
return method.getDeclaringClass().getName() + "." + method.getName();
}
/**
* 获取方法名称
* @param invocation
* @return
*/
public static String getMethodName(MethodInvocation invocation){
return invocation.getMethod().getName();
}
/**
* 获取注解策略
* @param invocation
* @return
*/
public static String getModelAnnotationValue(MethodInvocation invocation){
Method method = invocation.getMethod();
ZkModel zkModel = method.getAnnotation(ZkModel.class);
return zkModel == null ? "" : zkModel.value();
}
public static Long getModelExpireMillions(MethodInvocation invocation, UniqueIdempotenConfig uniqueIdempotenConfig){
//默认过期时间
Long expireMillions = uniqueIdempotenConfig.getExpireMillions();
//配置过期时间
Map expireMap= uniqueIdempotenConfig.getExpireMap();
Long expire = expireMap.get(RequestMethodUtils.getMethodFullPath(invocation));
if(!ObjectUtils.isEmpty(expireMap) && !ObjectUtils.isEmpty(expire)){
expireMillions = expire;
}
//注解过期时间
Method method = invocation.getMethod();
ZkModel zkModel = method.getAnnotation(ZkModel.class);
if (ObjectUtils.isEmpty(zkModel)){
return expireMillions;
}
return zkModel.expireMillions();
}
}
yml
package com.zk.utils.yml;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;
import org.yaml.snakeyaml.Yaml;
/**
* @author: Lei Guo
*/
public class YmlUtil {
private static Map ymls = new HashMap<>();
private static ThreadLocal nowFileName = new ThreadLocal<>();
static{
loadYml("application.yml");
}
public static void loadYml(String fileName) {
nowFileName.set(fileName);
if (!ymls.containsKey(fileName)) {
ymls.put(fileName, new Yaml().loadAs(YmlUtil.class.getResourceAsStream("/" + fileName), LinkedHashMap.class));
}
}
public static Object getValue(String key) {
String[] keys = key.split("[.]");
Map ymlInfo = (Map) ymls.get(nowFileName.get()).clone();
for (int i = 0; i < keys.length; i++) {
Object value = ymlInfo.get(keys[i]);
if (i < keys.length - 1) {
ymlInfo = (Map) value;
} else if (value == null) {
System.out.println("key不存在");
} else {
return value;
}
}
return null;
}
public static Object value(String key) {
//loadYml("application.yml");
return getValue(key);
}
}
不积硅步无以至千里,不积小流无以成江海