一种接口幂等性实现方案

一种接口幂等性实现方案,第1张

接口幂等性就是用户对同一 *** 作发起了一次或多次请求的对数据的影响是一致不变的,不会因为多次的请求而产生副作用。网上有很多资料对幂等性接口及其实现方案进行了介绍,其中应用比较广泛的是token+redis。其实这种思想的本质就是给一个请求分配一个有关联的唯一键,请求时根据这个唯一键是否存在来判断是否是重复请求。

本文也基于这种思路,通过AOP的方式设计了一种接口幂等性实现方案,通过自定义注解来控制接口幂等性,能够细化到哪些接口要满足幂等性,并且提出了同步幂等和异步幂等的概念。这种方案目前主要是解决用户在上一次请求还没结束的情况下,多次点击重复请求的问题,比如下载导出请求,这种请求一般都比较消耗资源,因此应该避免多次请求(当前可以在前端控制,但对于异步场景,这种方案也支持)。

此方案主要由两个注解和一个切面实现,具体代码如下:

//此注解用来表示哪些参数要用于幂等性校验中
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.PARAMETER)
public @interface IdempotentParam {
}
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface IdempotentRequest {

    /**
     * 自定义子健,最终幂等性键是由用户+子健+幂等入参组成的。
     * 注意:在自定义subKey时,要考虑唯一性,因为幂等入参和用户有可能会相同,此时就需要保证subKey是唯一的。
     */
    String subKey();

    /**
     * 是否为同步幂等,同步幂等会在aop的after中自动清除缓存key重置状态,异步幂等需要在代码中手动清除缓存。
     */
    boolean syncIdempotent() default false;
}
/**
 * 幂等性接口
 * 实现逻辑:
 * 以当前用户[email protected]+@IdempotentParam幂等入参为唯一性校验的值进行幂等性拦截,如果缓存中没有对应的键,则放入缓存并放行请求;
 * 如果缓存中有对应的键,则说明是重复请求,拦截返回;
 * 此外,如果是异步幂等,对应的业务在处理完后需要清除缓存中的键
 **/
@Aspect
@Component
@Lazy(false)
public class IdempotentAspect {
    private static Logger logger = LoggerFactory.getLogger(IdempotentAspect.class);

    /**
     * 存放每个请求线程的幂等性校验键,用于在请求结束后自动删除缓存,实现同步幂等
     */
    private static ThreadLocal<String> keyThreadLocal = new ThreadLocal<>();

    @Pointcut("@annotation(com.hxyy.exam.aop.IdempotentRequest)")
    private void cutMethod() {}

    @Around("cutMethod()")
    public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
        String key = this.generateKey(joinPoint);
        if (CacheUtil.hasKey(key)){
            CommonResponse<Object> response = new CommonResponse<>();
            response.setCode(ResponseEnum.DUPOLICATE_REQUEST.getEnumCode());
            response.setMsg(ResponseEnum.DUPOLICATE_REQUEST.getEnumMsg());
            return response;
        }else {
            CacheUtil.set(key,"");
            return joinPoint.proceed(joinPoint.getArgs());
        }
    }

    @After("cutMethod()")
    public void after(){
        String key = keyThreadLocal.get();
        /**
         * 如果ThreadLocal中存在key,则说明是同步幂等,因此需要在after中清除缓存
         */
        if (StringUtils.isNotEmpty(key)){
            CacheUtil.del(key);
            keyThreadLocal.remove();
        }
    }

    private String generateKey(ProceedingJoinPoint joinPoint) throws NoSuchMethodException {
        String user = JwtUtil.getUserNameFromToken();
        Object[] params = joinPoint.getArgs();
        Method targetMethod = getTargetMethod(joinPoint);
        IdempotentRequest declaredAnnotation = targetMethod.getDeclaredAnnotation(IdempotentRequest.class);
        String subKey = declaredAnnotation.subKey();
        String methodName = targetMethod.getName();
        Annotation[][] parameterAnnotations = targetMethod.getParameterAnnotations();
        StringBuilder paramStr = new StringBuilder();
        for (int i = 0; i < params.length; i++) {
            for (int j = 0; j < parameterAnnotations[i].length; j++) {
                if ("IdempotentParam".equals(parameterAnnotations[i][j].annotationType().getSimpleName())){
                    paramStr.append(JSONObject.toJSONString(params[i]));
                }
            }
        }
        String key = user+subKey+ paramStr;
        /**
         * 如果是同步幂等,则使用ThreadLocal存储key,方便线程后续获取
         */
        if (declaredAnnotation.syncIdempotent()){
            keyThreadLocal.set(key);
        }
        return key;
    }

    private Method getTargetMethod(ProceedingJoinPoint joinPoint) throws NoSuchMethodException {
        String methodName = joinPoint.getSignature().getName();
        Class<?> targetClass = joinPoint.getTarget().getClass();
        Class<?>[] parameterTypes = ((MethodSignature) joinPoint.getSignature()).getParameterTypes();
        Method objMethod = targetClass.getMethod(methodName, parameterTypes);
        return objMethod;
    }
}

使用方式:

@PostMapping("/syncIdempotent")
@IdempotentRequest(subKey = "syncIdempotent",syncIdempotent = true)
public String syncIdempotent(@IdempotentParam @RequestBody Conditions conditions,@RequestParam(value = "param1") String param1, @IdempotentParam @RequestParam(value = "param") String param){
    return "同步幂等接口测试";
}
@PostMapping("/asyncIdempotent")
@IdempotentRequest(subKey = "asyncIdempotent",syncIdempotent = false)
public String asyncIdempotent(@IdempotentParam @RequestBody Conditions conditions){
    new Thread(()->{
        try{
            //业务逻辑处理
        }catch (RuntimeException e){

        }finally {
            //清除缓存
            String user = "xxx";
            String key = user+"asyncIdempotent"+ JSONObject.toJSONString(conditions);//key由用户+subKey+conditions组成
            CacheUtil.del(key);
        }
    }).run();
    return "异步幂等接口测试";
}

欢迎分享,转载请注明来源:内存溢出

原文地址: https://outofmemory.cn/langs/730454.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-04-27
下一篇 2022-04-27

发表评论

登录后才能评论

评论列表(0条)

保存