美文网首页SpringBoot - 收录程序员测试
java接口防重提交如何处理

java接口防重提交如何处理

作者: 一枚路过的程序猿 | 来源:发表于2020-08-12 10:17 被阅读0次

    1.什么是接口防重?

    在一定的时间内请求同一接口,同一参数。由于请求是健康请求,会执行正常的业务逻辑,从而产生大量的废数据。

    2.处理方法

    第一种:前台在请求接口的时候,传递一个唯一值,然后在对应接口判断该唯一值,在一定的时间内是否被消费过
    第二种:采用Spring AOP理念,实现请求的切割,在请求执行到某个方法或某层时候,开始拦截进行,获取该请求的参数,用户信息,请求地址,存入redis中并放置过期时间,进行防重(推荐使用)

    3.谈谈以上两种处理方法的利弊

    第一种:局限性太高,前台必须传递一个唯一值,就算请求到达指定后台服务,写一个拦截器,需要配置太多不需要拦截的方法,也许你会说,可以拦截有规则的请求地址,这样真的好吗?
    第二种:作为一名JAVA后台开发,Spring应该是熟悉的不能再熟悉了,Spring核心AOP又用了多少,针对以上请求,只需要写一个注解类,然后切面到该注解上,在需要防重的方法上只需添加注解即可

    4.具体代码(采用第二种)

    注解类

    import java.lang.annotation.*;
    
    /**
     * 防重
     * @author haodongdong
     * @date 2020/8/12
     * @return
     */
    //标识该注解用于方法上
    @Target({ElementType.METHOD})
    //申明该注解为运行时注解,编译后改注解不会被遗弃
    @Retention(RetentionPolicy.RUNTIME)
    //javadoc工具记录
    @Documented
    public @interface PreventSubmit 
    {
    }
    

    切面类

    import com.qianxian.common.exception.AppException;
    import com.qianxian.common.util.TokenUtil;
    import lombok.extern.slf4j.Slf4j;
    import org.apache.commons.lang3.StringUtils;
    import org.aspectj.lang.ProceedingJoinPoint;
    import org.aspectj.lang.annotation.Around;
    import org.aspectj.lang.annotation.Aspect;
    import org.redisson.api.RLock;
    import org.redisson.api.RedissonClient;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.data.redis.core.StringRedisTemplate;
    import org.springframework.stereotype.Component;
    import org.springframework.web.context.request.RequestAttributes;
    import org.springframework.web.context.request.RequestContextHolder;
    import javax.servlet.http.HttpServletRequest;
    import java.util.Arrays;
    import java.util.concurrent.TimeUnit;
    
    /**
     * 防重复提交
     * @author haodongdong
     * @date 2020/8/12
     * @return
     */
    @Component
    @Aspect
    @Slf4j
    public class PreventSubmitAspect {
    
        /**
         * 放重redis前缀
         */
        private static String API_PREVENT_SUBMIT = "api:preventSubmit:";
    
        /**
         * 放重分布式锁前缀
         */
        private static String API_LOCK_PREVENT_SUBMIT = "api:preventSubmit:lock:";
    
        /**
         * 失效时间
         */
        private static Integer INVALID_NUMBER = 3;
    
        /**
         * redis
         */
        @Autowired
        private StringRedisTemplate stringRedisTemplate;
    
        /**
         * 分布式锁
         */
        @Autowired
        private RedissonClient redissonClient;
    
    
        /**
         * 防重
         * @author haodongdong
         * @date 2020/8/12
         * @return
         */
        @Around("@annotation(com.qianxian.user.annotation.PreventSubmit)")
        public Object preventSubmitAspect(ProceedingJoinPoint joinPoint) throws Throwable {
    
            RLock lock = null;
    
            try {
    
                //获取目标方法的参数
                Object[] args = joinPoint.getArgs();
    
                //获取当前request请求
                RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
                HttpServletRequest request = (HttpServletRequest) requestAttributes.resolveReference(RequestAttributes.REFERENCE_REQUEST);
    
                //获取请求地址
                String requestUri = request.getRequestURI();
    
                //获取用户ID
                Long userId = null;
                try {
                    userId = TokenUtil.getUserId(request);
                }catch (Exception e){}
    
                //拼接锁前缀,采用同一方法,同一用户,同一接口
                String temp = requestUri.concat(Arrays.asList(args).toString()) + (userId != null ? userId : "");
                temp = temp.replaceAll("/","");
    
                //拼接rediskey
                String lockPrefix = API_LOCK_PREVENT_SUBMIT.concat(temp);
                String redisPrefix = API_PREVENT_SUBMIT.concat(temp);
    
                /**
                 * 对同一方法同一用户同一参数加锁,即使获取不到用户ID,每个用户请求数据也会不一致,不会造成接口堵塞
                 */
                lock = this.redissonClient.getLock(lockPrefix);
                lock.lock();
    
                String flag = this.stringRedisTemplate.opsForValue().get(redisPrefix);
                if(StringUtils.isNotEmpty(flag)){
                    throw new AppException("您当前的操作太频繁了,请稍后再试!");
                }
    
                //存入redis,设置失效时间
                this.stringRedisTemplate.opsForValue().set(redisPrefix,redisPrefix,INVALID_NUMBER, TimeUnit.SECONDS);
    
                //执行目标方法
                Object result = joinPoint.proceed(args);
                return result;
    
            }finally {
                if(lock != null){
                    lock.unlock();
                }
            }
    
        }
    
    }
    

    相关文章

      网友评论

        本文标题:java接口防重提交如何处理

        本文链接:https://www.haomeiwen.com/subject/vvlkdktx.html