一、常见幂等方案

接口幂等方案_Express


二、幂等方案实现


1、创建幂等注解

@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface Idempotent {

    /**
     * 幂等KEY的超时时间,默认为1秒
     */
    int timeout() default 1;

    /**
     * 时间单位,默认为秒
     */
    TimeUnit timeUnit() default TimeUnit.SECONDS;

    /**
     * 使用的 Key 解析器(可自行扩展)
     * @see RequestIdempotentKeyResolver
     * @see ExpressionIdempotentKeyResolver
     * @see DefaultIdempotentKeyResolver
     */
    Class<? extends IdempotentKeyResolver> keyResolver() default DefaultIdempotentKeyResolver.class;

    /**
     * HTTP请求头中幂等KEY字段名,默认为:IDEMPOTENT_KEY
     */
    @AliasFor("express")
    String keyParameter() default "";

    /**
     * Spring SpEL表达式。
     * 注意:可用数据为方法的参数
     */
    @AliasFor("keyParameter")
    String express() default "" ;
}


2、创建幂等切面

@Aspect
@Slf4j
public class IdempotentAspect {

    private final Map<Class<? extends IdempotentKeyResolver>, IdempotentKeyResolver> keyResolversMap = new HashMap<>();

    private final IdempotentHandler idempotentHandler;

    public IdempotentAspect(List<IdempotentKeyResolver> keyResolvers, IdempotentHandler idempotentHandler) {
        if(keyResolvers != null){
            for(IdempotentKeyResolver resolver : keyResolvers) {
                if(resolver == null){
                    continue;
                }
                keyResolversMap.put(resolver.getClass(),resolver) ;
            }
        }
        this.idempotentHandler = idempotentHandler;
    }

    @Before("@annotation(idempotent)")
    public void beforePointCut(JoinPoint joinPoint, Idempotent idempotent) {
        IdempotentKeyResolver keyResolver = keyResolversMap.get(idempotent.keyResolver());
        if(keyResolver == null) {
            throw new IdempotentException(ResultCode.IDEMPOTENT_KEY_RESOLVER_NOT_FOUND);
        }
        String key = keyResolver.resolver(joinPoint, idempotent);
        if(StringUtils.isBlank(key)) {
            throw new IdempotentException(ResultCode.IDEMPOTENT_KEY_NOT_FOUND);
        }
        log.info("获取到的幂等KEY:{}",key);
        // 进行幂等判断,是否是第一次被执行
        boolean success = idempotentHandler.isFirstRun(key, idempotent.timeout(), idempotent.timeUnit());
        if (!success) {
            throw new IdempotentException(ResultCode.IDEMPOTENT_REPEAT);
        }
    }
}


3、定义幂等KEY解析器

public interface IdempotentKeyResolver {
    /**
     * 解析幂等使用的KEY
     * @param joinPoint     AOP切面
     * @param idempotent    幂等注解
     *
     * @return java.lang.String
     */
    String resolver(JoinPoint joinPoint, Idempotent idempotent);
}


4、定义幂等处理器

public interface IdempotentHandler {

    /**
     * 幂等KEY的奇前缀
     */
    static final String IDEMPOTENT_KEY_PREFIX = "IDEMPOTENT:" ;

    /**
     * 判断是否第一次执行
     * @param key       处理幂等操作的唯一键
     * @param timeout   幂等操作的过期时间
     * @param timeUnit  幂等操作过期时间单位
     *
     * @return boolean
     */
    boolean isFirstRun(String key, long timeout, TimeUnit timeUnit) ;
}


5、创建基于方法签名的幂等KEY解析器

public class DefaultIdempotentKeyResolver implements IdempotentKeyResolver {

    @Override
    public String resolver(JoinPoint joinPoint,Idempotent idempotent) {
        String methodName = joinPoint.getSignature().toString();
        String argsStr = JsonUtils.toJsonString(joinPoint.getArgs());
        return DigestUtils.md5Hex(methodName + argsStr);
    }
}


6、创建基于请求参数的幂等KEY解析器

public class RequestIdempotentKeyResolver implements IdempotentKeyResolver {

    /**
     * 请求头的默认参数名
     */
    public static final String HEADER_NAME = "IDEMPOTENT_KEY" ;

    @Override
    public String resolver(JoinPoint joinPoint, Idempotent idempotent) {
        ServletRequestAttributes requestAttributes = (ServletRequestAttributes)RequestContextHolder.getRequestAttributes() ;
        if(requestAttributes == null){
            return null ;
        }
        HttpServletRequest request = requestAttributes.getRequest();

        String key = HEADER_NAME ;
        if(StringUtils.isNotBlank(idempotent.keyParameter())){
            key = idempotent.keyParameter() ;
        }
        String value = request.getHeader(key) ;
        if(StringUtils.isNotBlank(value)) {
            return value ;
        }
        return request.getParameter(key) ;
    }
}

7、创建基于EL表达式的幂等KEY解析器

public class ExpressionIdempotentKeyResolver implements IdempotentKeyResolver {

    private final ParameterNameDiscoverer parameterNameDiscoverer = new StandardReflectionParameterNameDiscoverer();

    private final ExpressionParser expressionParser = new SpelExpressionParser();

    @Override
    public String resolver(JoinPoint joinPoint, Idempotent idempotent) {
        if(StringUtils.isBlank(idempotent.express())){
            return null ;
        }
        // 获得被拦截方法参数名列表
        Method method = getMethod(joinPoint);
        Object[] args = joinPoint.getArgs();
        String[] parameterNames = this.parameterNameDiscoverer.getParameterNames(method);

        // 准备 Spring EL 表达式解析的上下文
        StandardEvaluationContext evaluationContext = new StandardEvaluationContext();
        if (ArrayUtil.isNotEmpty(parameterNames)) {
            for (int i = 0; i < parameterNames.length; i++) {
                evaluationContext.setVariable(parameterNames[i], args[i]);
            }
        }

        // 解析表达式
        Expression expression = expressionParser.parseExpression(idempotent.express());
        return expression.getValue(evaluationContext, String.class);
    }


    private static Method getMethod(JoinPoint point) {
        // 处理,声明在类上的情况
        MethodSignature signature = (MethodSignature)point.getSignature();
        Method method = signature.getMethod();
        if (!method.getDeclaringClass().isInterface()) {
            return method;
        }

        // 处理,声明在接口上的情况
        try {
            return point.getTarget().getClass().getDeclaredMethod(point.getSignature().getName(),
                    method.getParameterTypes());
        } catch (NoSuchMethodException e) {
            throw new RuntimeException(e);
        }
    }

}


8、默认的幂等处理器

public class DefaultIdempotentHandler implements IdempotentHandler{
    @Override
    public boolean isFirstRun(String key, long timeout, TimeUnit timeUnit) {
        return false;
    }
}

9、基于缓存的幂等处理器

public class CacheIdempotentHandler implements IdempotentHandler {

    private CacheService cacheService;

    public CacheIdempotentHandler(CacheService cacheService){
        this.cacheService = cacheService ;
    }

    @Override
    public boolean isFirstRun(String key, long timeout, TimeUnit timeUnit) {
        return cacheService.setIfAbsent(formatKey(key),"",timeout,timeUnit) ;
    }

    private String formatKey(String key) {
        return IDEMPOTENT_KEY_PREFIX + key ;
    }

}

10、创建幂等处理器的配置

abstract class HandlerConfiguration {

    @Configuration(proxyBeanMethods = false)
    @ConditionalOnBean(CacheService.class)
    @ConditionalOnMissingBean(IdempotentHandler.class)
    public static class CacheHandlerConfiguration {

        @Bean
        private IdempotentHandler idempotentHandler(CacheService cacheService){
            return new CacheIdempotentHandler(cacheService) ;
        }
    }

    @Configuration(proxyBeanMethods = false)
    @ConditionalOnMissingBean(IdempotentHandler.class)
    public static class DefaultHandlerConfiguration {

        @Bean
        private IdempotentHandler idempotentHandler(){
            return new DefaultIdempotentHandler() ;
        }
    }
}

11、创建幂等的主配置

@Configuration
public class IdempotentConfiguration {


    @Configuration(proxyBeanMethods = false)
    @Import({HandlerConfiguration.CacheHandlerConfiguration.class, HandlerConfiguration.DefaultHandlerConfiguration.class})
    public class DefaultConfiguration {


        @Bean
        @ConditionalOnMissingBean(IdempotentAspect.class)
        @ConditionalOnBean(IdempotentHandler.class)
        public IdempotentAspect idempotentAspect(@Autowired(required = false) List<IdempotentKeyResolver> keyResolvers,
                                                 IdempotentHandler idempotentHandler) {
            List<IdempotentKeyResolver> keyResolverList = new ArrayList<>();
            if (keyResolvers != null && keyResolvers.size() > 0) {
                for (IdempotentKeyResolver keyResolver : keyResolvers) {
                    if (keyResolver == null) {
                        continue;
                    }
                    keyResolverList.add(keyResolver);
                }
            }
            // 添加默认的
            keyResolverList.add(new ExpressionIdempotentKeyResolver());
            keyResolverList.add(new RequestIdempotentKeyResolver());
            keyResolverList.add(new DefaultIdempotentKeyResolver());
            return new IdempotentAspect(keyResolverList, idempotentHandler);
        }
    }
}