幂等防重JAVA

需求来源

需求背景

在接收消息的时候,消息推送重复。如果处理消息的接口无法保证幂等,那么重复消费消息产生的影响可能会非常大

需求描述:

请求同一业务接口若传参一样,则返回数据相同并且后台业务逻辑只执行一次

百度云盘

链接:https://pan.baidu.com/s/1gKS490Igb4yG7CvxMyt9Wg 
提取码:favg

使用场景

接口调用

配置yml文件 编写execution表达式即可拦截

方法调用

配置yml文件 编写execution表达式即可拦截

约定

注解 > 配置 > 默认

基本配置文件

使用者需要添加

 幂等防重JAVA_第1张图片

幂等防重JAVA_第2张图片

参数详解

defaultModel

默认策略

可配置为:unique, idempotent,normal

  1. unique: 防重策略,第一次请求正常返回数据,第二次请求如果在缓存有效期内则抛出请求重复的异常
  2. Idempotent: 幂等策略, 第一次请求正常返回数据并缓存,第二次请求如果在缓存有效期内则返回缓存数据
  3. normal: 正常策略,正常请求

expireMillios

策略返回数据缓存时间

如果有防重,幂等策略使用该配置

只有正常策略的情况,可忽略该配置项

paramName

唯一标识表名称,取唯一标识符值时使用

该配置项必须和参数名称一致,否则取不到唯一标识,可以理解为某个请求在一定时间段内的唯一标识。

executions

AOP(execution表达式),用于匹配方法执行的连接点

数组格式,可配置多项,可以时普通类方法,Colletorller方法,Service方法,Dao方法

maps

map集合

对executions表达式的一个补充,具体到方法使用那种策略(不支持重载),若要重载则需要重载的方法使用注解方式解决@ZkModel,具体使用查看该注解详情

Key: 方法全路径 value:方法使用那种策略

expireMap

每个方法对应的过期时间

Key:方法全路径 value:返回值过期时间

lockWaitMillions

获取锁等待时间,超出时间抛出异常:获取锁超时

lockExpireMillions

锁过期时间,超出时间自动释放锁

redis

用于缓存返回值以及锁,与项目的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配置方式 >  yml配置

注解

ZkModel

遵循约定规则

该注解可解决同一个连接点下不同切点(方法名相同),

使用方式:在需要切点的方法上加上该注解,值为三种策略:unique, idempotent,normal

 PolicyConst

幂等防重JAVA_第3张图片

EnableUniqueIdempotentAspect

开关注解,该注解加到入口类

使用者基本配置

Filter

背景: 由于针对不同类型的请求和调用,paramName值存在不同的位置,为了适配不同环境下都能取到对应的值,因此提出过滤链式取值,Filter1没有取到值则继续执行Filter2,如果Filter2拿到值则直接返回,否则继续执行Filter3一次类推,如果执行完都没拿到则抛出异常,提示没有可用唯一标识

Filter过滤器代码中提供三种:

请求:

HeadFilter:从请求头中获取

RequestPaRamFilter: 从请求参数中获取

非请求方式:

MethodParamFilter: 从方法参数中获取(直接调用方法非请求场景)

另外:用户可继承Filter自定义实现逻辑,然后加入过滤链中

获取参数规则过滤链

幂等防重JAVA_第4张图片

具体需要那几种过滤器根据用户所需自行加入

启动注解

幂等防重JAVA_第5张图片

源码

插拔式的使用,以下代码单独放到一个项目里面,以maven依赖方式提供给使用者,使用者只需配置yml以及配合使用注解即可使用

幂等防重JAVA_第6张图片

annotation

幂等防重JAVA_第7张图片

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

aop

幂等防重JAVA_第8张图片

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

cache

幂等防重JAVA_第9张图片

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

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

幂等防重JAVA_第10张图片

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 jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(
        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 stringSerializer = new StringRedisSerializer();
    template.setKeySerializer(stringSerializer );
    template.setValueSerializer(jackson2JsonRedisSerializer);
  }


}
 
  

configuration

幂等防重JAVA_第11张图片

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 {

}

enums

幂等防重JAVA_第12张图片

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 + '\'' +
        '}';
  }
}

filter

幂等防重JAVA_第13张图片

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

interfaces

幂等防重JAVA_第14张图片

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

model

幂等防重JAVA_第15张图片

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

幂等防重JAVA_第16张图片

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


}

utils

幂等防重JAVA_第17张图片

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

不积硅步无以至千里,不积小流无以成江海

你可能感兴趣的:(幂等,防重,分布式)