Idempotent anti-weight advanced development supports distributed

Source of demand

Demand background

When receiving a message, the message push is repeated. If the interface for processing messages cannot be guaranteed to be idempotent, the impact of repeated consumption of messages may be very large

Description of Requirement:

Request the same business interface if the parameters are the same, the returned data is the same and the background business logic is only executed once

scenes to be used

Interface call

Configure yml file to write execution expression to intercept

Method call

Configure yml file to write execution expression to intercept

Agreement

Annotation> Configuration> Default

Basic configuration file

Detailed parameter

defaultModel

Default strategy

Can be configured as: unique, idempotent, normal

  1. unique: Anti-weighting strategy, the first request will return data normally, and the second request will throw a repeated request exception if the cache is valid
  2. Idempotent: Idempotent strategy, the first request returns the data normally and caches it, the second request returns the cached data if the cache is valid
  3. normal: normal policy, normal request

expireMillios

Strategy return data cache time

If there is anti-weight, the idempotent strategy uses this configuration

Only in the case of a normal strategy, this configuration item can be ignored

paramName

Uniquely identifies the table name, used when taking the unique identifier value

The configuration item must be consistent with the parameter name, otherwise the unique identifier cannot be obtained, which can be understood as the unique identifier of a certain request within a certain period of time.

executions

AOP (execution expression), used to match the connection point of method execution

Array format, multiple configurable, common class method, Colletorller method, Service method, Dao method

maps

map collection

A supplement to the executions expression, specific to the strategy used by the method ( overloading is not supported), if you want to overload, you need to use the annotation method to solve @ZkModel, see the annotation details for specific use

Key: the full path of the method value: which strategy the method uses

expireMap

Expiration time corresponding to each method

Key: method full path value: return value expiration time

lockWaitMillions

The waiting time for acquiring the lock, an exception is thrown if the time is exceeded: acquiring the lock timed

lockExpireMillions

Lock expiration time, the lock will be automatically released after the time expires

redis

Used to cache return values ​​and locks, independent of the project's redis

database: 1
Which library number is used host: $(REDIS_IP) redis server IP
port: $(REDIS_PORT) port
password: $(REDIS_PASSWORD) password
maxIdle: 20 connection pool maximum number of waiting connections
minIdle: 0 connection pool minimum number of waiting connections
maxActive: 50 Maximum number of connections
maxWait: 10000 Maximum waiting time
testOnBorrow: true Turn on to verify whether the connection is available
timeout: 100000 How long does the client idle for shutdown

For specific configuration items, refer to the redis configuration file introduction

Non-yml configuration method

You can configure non-yml configuration methods here> yml configuration

annotation

ZkModel

Follow the agreed rules

This annotation can solve different tangent points under the same connection point (the method name is the same),

Usage: add this annotation to the method that needs to be cut, the value is three strategies: unique, idempotent, normal

Such as

PolicyConst

EnableUniqueIdempotentAspect

Open the attention annotation, the annotation is added to the entry class

Basic user configuration

Filter

Background: Due to different types of requests and calls, the paramName value has different positions. In order to adapt to different environments, the corresponding value can be obtained. Therefore, a filter chain value is proposed. If Filter1 does not obtain a value, continue to execute Filter2. If Filter2 gets the value, it will return directly, otherwise it will continue to execute Filter3 once and so on. If it is not obtained after the execution, an exception will be thrown, indicating that there is no unique identifier available.

Three types are provided in the Filter code:

request:

HeadFilter: get from the request header

RequestPaRamFilter: Obtained from request parameters

Non-requested method:

MethodParamFilter: Obtained from method parameters (direct method call non-request scenario)

In addition: users can inherit Filter custom implementation logic, and then join the filter chain

Get parameter rule filter chain

Which kinds of filters are required to be added according to the needs of users

Start annotation

Source code

For pluggable use, the following code is placed in a project separately and provided to users in a maven dependency mode. Users only need to configure yml and use annotations to use it.

annotation

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)@[email protected]({UniqueIdempotentAspectConfig.class})//@Deprecatedpublic @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)@Documentedpublic @interface ZkModel {     /**     * 策略类型,如果为空取配置文件默认值     * 注解的优先级大于yml文件maps属性配置的内容     * @return     */    String value() default "";     /**     * 返回值过期时间     * @return     */    long expireMillions() default 0L;}

aop

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 */@Componentpublic 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<ImitateMethodReturnValue> getCacheTask = TaskUtils.getCacheTask(customCache);        //缓存存储操作        GetAndSetCacheTask<ImitateMethodReturnValue> 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...    }}

cache

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; @[email protected]@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<String, Object> 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 */@Componentpublic 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);  }}

common

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

component

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<String, String> maps;   /**   * 每个方法返回值过期时间   */  private Map<String, Long> 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<String, String> getMaps() {    return maps;  }   public void setMaps(      Map<String, String> maps) {    this.maps = maps;  }   public String getParamName() {    return paramName;  }   public void setParamName(String paramName) {    this.paramName = paramName;  }   public Map<String, Long> getExpireMap() {    return expireMap;  }   public void setExpireMap(Map<String, Long> 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 */@Componentpublic 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<String, Object> redisTemplate;   public RedisTemplate<String, Object> 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<String, Object> redisTemplateObject(Integer dbIndex) throws Exception {    RedisTemplate<String, Object> redisTemplateObject = new RedisTemplate<String, Object>();    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<String, Object> template) {    Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<Object>(        Object.class);    ObjectMapper om = new ObjectMapper();//    om.enable(SerializationFeature.INDENT_OUTPUT);    om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);    om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);    jackson2JsonRedisSerializer.setObjectMapper(om);     RedisSerializer<String> stringSerializer = new StringRedisSerializer();    template.setKeySerializer(stringSerializer );    template.setValueSerializer(jackson2JsonRedisSerializer);  }  }

configuration

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 */@Configurationpublic 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 */@[email protected](basePackages = {"com.zk.component", "com.zk.cache", "com.zk.aop", "com.zk.filter"})//@Deprecatedpublic class UniqueIdempotentAspectConfig { }

enums

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<String> getAllPolicyResultTypes(){    List<String> 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 + '\'' +        '}';  }}

filter

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<Filter> 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<Filter> 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 */@Componentpublic class FilterManager {     /**     *过滤链     */    private final List<Filter> 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: 请求参数 */@Slf4jpublic 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 "";    }}

interfaces

package com.zk.interfaces; import com.zk.cache.CustomCache;import com.zk.enums.PolicyTypeEnum;import org.aopalliance.intercept.MethodInvocation; /** * @author: Lei Guo */@FunctionalInterfacepublic interface GetAndSetCacheTask<T> {   /**   * 执行   * @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 */@FunctionalInterfacepublic interface GetCacheTask<T> {   /**   * 执行   * @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 */@FunctionalInterfacepublic interface PolicyAgainExecuteTask<T> {     /**     * 执行     * @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;}

model

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

policy

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<String, String> maps;    //方法全路径用于查找方法策略    private String methodFullPath;    private String defaultModel;    //策略注解值    private String annotationValue;     public Builder setDefaultModel(String defaultModel) {      this.defaultModel = defaultModel;      return this;    }     public Builder setMaps(Map<String, String> 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        );    }  }

utils

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<String, Long> 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<String, LinkedHashMap> ymls = new HashMap<>();   private static ThreadLocal<String> 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);  }}

If you don’t accumulate silicon, you can’t reach a thousand miles. If you don’t accumulate small currents, you can’t become a sea