幂等防止重复提交

Idempotent.java

package com.xxxx.idempotent;

import java.lang.annotation.*;

/**
 * 幂等注解
 */
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Idempotent {
    /**
     * 幂等名称,作为redis缓存Key的一部分。
     */
    String value();

    /**
     * 幂等过期时间,即:在此时间段内,对API进行幂等处理。
     */
    long expireMillis();
}

IdempotentAspect.java

package com.xxxx.idempotent;
import java.lang.reflect.Method;
import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.lang.ArrayUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.stereotype.Component;

import com.alibaba.fastjson.JSON;
import com.arvato.auth.utils.BaseException;
import com.arvato.common.util.SpringLocaleUtil;

@Aspect
@Component
@ConditionalOnClass(RedisTemplate.class)
public class IdempotentAspect {

    private static final String KEY_TEMPLATE = "idempotent_auth_%s";

    @Resource
    private RedisTemplate<String,String> redisTemplate;

    @Pointcut("@annotation(com.xxxx.idempotent.Idempotent)")
    public void executeIdempotent() {
    }

    @Around("executeIdempotent()")
    public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
        ValueOperations<String, String> opsValue = redisTemplate.opsForValue();
          //获取方法
        Method method = ((MethodSignature) joinPoint.getSignature()).getMethod();
        //获取幂等注解
        Idempotent idempotent = method.getAnnotation(Idempotent.class);

        //生成key
        String key = String.format(KEY_TEMPLATE, idempotent.value() + "_" + this.generate(method.toString(), joinPoint.getArgs()));
        //通过加锁确保只有一个接口能够正常访问
        String s = opsValue.get(key);
        if (s==null) {
            //缓存请求的key
            opsValue.set(key,"false",idempotent.expireMillis(),TimeUnit.MILLISECONDS);
            return joinPoint.proceed();
        } else {
            throw new BaseException(SpringLocaleUtil.getI18NValue("IdempotentMsg"));
        }
    }

    private String generate(String method,Object[] args) {
        if(null==args) {
            return new SimpleDateFormat("yyyyMMddHHmm").format(new Date());
        }

            // 获取方法传入参数
           if (ArrayUtils.isNotEmpty(args)) {
               List<Object> logArgs = Arrays.stream(args).filter(arg -> (!(arg instanceof HttpServletRequest) && !(arg instanceof HttpServletResponse)))
                       .collect(Collectors.toList());
               return DigestUtils.md5Hex(method+JSON.toJSONString(logArgs));
           } else {
              return method;
           }
    }



}

使用

 @Idempotent(value = "/login/send", expireMillis = 3000L)
评论