跳至主要內容

防重复提交

xw大约 2 分钟解决方案

解决方案

  1. 前端JS控制点击次数,屏蔽点击按钮无法点击
  2. 数据库或者其他存储增加唯一索引约束
  3. 服务端token令牌方式。下单前先获取令牌-存储redis 下单时一并把token提交并检验和删除

实践

采用服务端token令牌方式,通过自定注解设置防重提交。

  1. 自定义注解

    import java.lang.annotation.*;
    
    @Documented
    @Target(ElementType.METHOD)
    @Retention(RetentionPolicy.RUNTIME)
    public @interface RepeatSubmit {
    
        /**
         * 加锁过期时间,默认是5秒
         * @return
         */
        long lockTime() default 5;
    
        /**
         * 默认限制类型,是方法参数
         * @return
         */
        Type limitType() default Type.PARAM;
    
        /**
         * 两种类型,token 或者 param
         */
        enum  Type{ PARAM , TOKEN};
    }
    
  2. 配置redis

    spring.redis.client-type=jedis
    spring.redis.host=120.79.150.146
    spring.redis.password=xdclass.net
    spring.redis.port=6379
    spring.redis.jedis.pool.max-active=100
    spring.redis.jedis.pool.max-idle=100
    spring.redis.jedis.pool.min-idle=100
    spring.redis.jedis.pool.max-wait=60000
    
  3. 获取令牌接口

      @Autowired
        private StringRedisTemplate redisTemplate;
    
    
        @GetMapping("token")
        public JsonData getToken(){
            LoginUser loginUser = LoginInterceptor.threadLocal.get();
            String token = CommonUtil.getStringNumRandom(32);
                //"order:submit:%s:%s"
            String key = String.format(RedisKey.SUBMIT_ORDER_TOKEN_KEY, loginUser.getAccountNo(),requestToken);
    
            redisTemplate.opsForValue().set(key, "1", 30, TimeUnit.MINUTES);
    
            return JsonData.buildSuccess(token);
    
    
        }
    
  4. 开发切面类

    /**
         * 定义 @Pointcut注解表达式,
         *  方式一:@annotation:当执行的方法上拥有指定的注解时生效(我们采用这)
         *  方式二:execution:一般用于指定方法的执行
         *
         * @param repeatSubmit
         */
        @Pointcut("@annotation(repeatSubmit)")
        public void pointcutNoRepeatSubmit(RepeatSubmit repeatSubmit) {
    
        }
        
        
        
         /**
         * 环绕通知, 围绕着方法执行
         * @Around 可以用来在调用一个具体方法前和调用后来完成一些具体的任务。
         *
         * 方式一:单用 @Around("execution(* net.xdclass.controller.*.*(..))")可以
         * 方式二:用@Pointcut和@Around联合注解也可以(我们采用这个)
         *
         *
         * 两种方式
         * 方式一:加锁 固定时间内不能重复提交
         * <p>
         * 方式二:先请求获取token,这边再删除token,删除成功则是第一次提交
         *
         * @param joinPoint
         * @param noRepeatSubmit
         * @return
         * @throws Throwable
         */
        @Around("pointcutNoRepeatSubmit(noRepeatSubmit)")
        public Object around(ProceedingJoinPoint joinPoint, RepeatSubmit noRepeatSubmit) throws Throwable {
            HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
    
            boolean res;
    
            String type = noRepeatSubmit.limitType().name();
    
            if (type.equals(RepeatSubmit.Type.PARAM.name())) {
                //方式一方法参数            TODO
    
     
            } else {
                //方式二,令牌形式
                String requestToken = request.getHeader("request-token");
                if (StringUtils.isBlank(requestToken)) {
                    throw new BizException(BizCodeEnum.ORDER_CONFIRM_TOKEN_EQUAL_FAIL);
    
                }
                LoginUser loginUser = LoginInterceptor.threadLocal.get();
                //"order:submit:%s:%s"
                 String key = String.format(RedisKey.SUBMIT_ORDER_TOKEN_KEY, loginUser.getAccountNo(),requestToken);
    
    
                /**
                 * 提交表单的token key
                 * 方式一:不用lua脚本获取再判断,之前是因为 key组成是 order:submit:accountNo, value是对应的token,所以需要先获取值,再判断
                 * 方式二:可以直接key是 order:submit:accountNo:token,然后直接删除成功则完成
                 */
                res = stringRedisTemplate.delete(key);
    
            }
    
            if (!res) {
                throw new BizException(BizCodeEnum.ORDER_CONFIRM_REPEAT);
            }
    
            System.out.println("目标方法执行前");
            Object object = joinPoint.proceed();
            System.out.println("目标方法执行后");
            return object;
        }