ToB企服应用市场:ToB评测及商务社交产业平台

标题: 切面实现下单请求防重提交功能(自定义注释@repeatSubmit) [打印本页]

作者: 汕尾海湾    时间: 2023-8-9 00:41
标题: 切面实现下单请求防重提交功能(自定义注释@repeatSubmit)
该切面功能适用场景

解决方案

  1. /**
  2. * 自定义防重提交
  3. */
  4. @Documented
  5. @Target(ElementType.METHOD)
  6. @Retention(RetentionPolicy.RUNTIME)
  7. public @interface RepeatSubmit {
  8.     /**
  9.      * 防重提交类型。  方法、令牌
  10.      */
  11.     enum Type {PARAM, TOKEN}
  12.     /**
  13.      * 默认防重提交,是方法参数
  14.      * @return
  15.      */
  16.     Type limitType() default Type.PARAM;
  17.     /**
  18.      * 加锁过期时间,默认5秒
  19.      * @return
  20.      */
  21.     long lockTime() default 5;
  22. }
复制代码
  1. /**
  2. * 定义一个切面类
  3. */
  4. @Aspect
  5. @Component
  6. @Slf4j
  7. public class RepeatSubmitAspect {
  8.     @Autowired
  9.     private StringRedisTemplate redisTemplate;
  10.     @Autowired
  11.     private RedissonClient redissonClient;
  12.     /**
  13.      * 定义 @Pointcut注解表达式,
  14.      * 方式一:@annotation:当执行的方法上拥有指定的注解时生效(我们采用这)
  15.      */
  16.     @Pointcut("@annotation(repeatSubmit)")
  17.     public void pointCutNoRepeatSubmit(RepeatSubmit repeatSubmit) {
  18.     }
  19.     /**
  20.      * 环绕通知, 围绕着方法执行
  21.      *
  22.      * @param joinPoint
  23.      * @param repeatSubmit
  24.      * @return
  25.      * @throws Throwable
  26.      * @Around 可以用来在调用一个具体方法前和调用后来完成一些具体的任务。
  27.      */
  28.     @Around("pointCutNoRepeatSubmit(repeatSubmit)")
  29.     public Object around(ProceedingJoinPoint joinPoint, RepeatSubmit repeatSubmit) throws Throwable {
  30.         HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
  31.         Long accountNo = LoginInterceptor.threadLocal.get().getAccountNo();
  32.         // 记录成功或者失败
  33.         Boolean res = false;
  34.         // 防重提交类型
  35.         String type = repeatSubmit.limitType().name();
  36.         if (type.equalsIgnoreCase(RepeatSubmit.Type.PARAM.name())) {
  37.             //方式一,参数形式防重提交
  38.             long lockTime = repeatSubmit.lockTime();
  39.             String ipAddr = CommonUtil.getIpAddr(request);
  40.             MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
  41.             Method method = methodSignature.getMethod();
  42.             String className = method.getDeclaringClass().getName();
  43.             String key = "order-server:repeat_submit"+CommonUtil.MD5(String.format("%s-%s-%s-%s", ipAddr, className, method, accountNo));
  44.             // 加锁
  45.             //res = redisTemplate.opsForValue().setIfAbsent(key,"1",lockTime, TimeUnit.SECONDS);
  46.             RLock lock = redissonClient.getLock(key);
  47.             // 尝试加锁,最多等待2秒,上锁以后5秒自动解锁 [lockTime默认为5s, 可以自定义]
  48.             res = lock.tryLock(2, lockTime, TimeUnit.SECONDS);
  49.         } else if (type.equalsIgnoreCase(RepeatSubmit.Type.TOKEN.name())) {
  50.             //方式二,令牌形式防重提交
  51.             String requestToken = request.getHeader("request-token");
  52.             if (StringUtils.isBlank(requestToken)) {
  53.                 throw new BizException(BizCodeEnum.ORDER_CONFIRM_TOKEN_EQUAL_FAIL);
  54.             }
  55.             String key = String.format(RedisKey.SUBMIT_ORDER_TOKEN_KEY, accountNo, requestToken);
  56.             /**
  57.              * 提交表单的token key
  58.              * key是 order:submit:accountNo:token,然后直接删除成功则完成
  59.              */
  60.             res = redisTemplate.delete(key);
  61.         }
  62.         if (!res) {
  63.             log.error("订单请求重复提交");
  64.             return null;
  65.         }
  66.         log.info("环绕通知执行前");
  67.         Object obj = joinPoint.proceed();
  68.         log.info("环绕通知执行后");
  69.         return obj;
  70.     }
  71. }
复制代码
  1. @Configuration
  2. public class RedissionConfiguration {
  3.     @Value("${spring.redis.host}")
  4.     private String redisHost;
  5.     @Value("${spring.redis.port}")
  6.     private String redisPort;
  7.     @Value("${spring.redis.password}")
  8.     private String redisPwd;
  9.     /**
  10.      * 配置分布式锁的redisson
  11.      * @return
  12.      */
  13.     @Bean
  14.     public RedissonClient redissonClient(){
  15.         Config config = new Config();
  16.         //单机方式
  17.         config.useSingleServer().setPassword(redisPwd).setAddress("redis://"+redisHost+":"+redisPort);
  18.         //集群
  19.         //config.useClusterServers().addNodeAddress("redis://192.31.21.1:6379","redis://192.31.21.2:6379")
  20.         RedissonClient redissonClient = Redisson.create(config);
  21.         return redissonClient;
  22.     }
  23.     /**
  24.      * 集群模式
  25.      * 备注:可以用"rediss://"来启用SSL连接
  26.      */
  27.     /*@Bean
  28.     public RedissonClient redissonClusterClient() {
  29.         Config config = new Config();
  30.         config.useClusterServers().setScanInterval(2000) // 集群状态扫描间隔时间,单位是毫秒
  31.               .addNodeAddress("redis://127.0.0.1:7000")
  32.               .addNodeAddress("redis://127.0.0.1:7002");
  33.         RedissonClient redisson = Redisson.create(config);
  34.         return redisson;
  35.     }*/
  36. }
复制代码
  1.     /**
  2.      * 下单前获取令牌,用于防重提交
  3.      * @return
  4.      */
  5.     @GetMapping("token")
  6.     public JsonData getOrderToken() {
  7.         Long accountNo = LoginInterceptor.threadLocal.get().getAccountNo();
  8.         String token = CommonUtil.getStringNumRandom(32);
  9.         String key = String.format(RedisKey.SUBMIT_ORDER_TOKEN_KEY, accountNo, token);
  10.         // token 过期时间30分钟
  11.         redisTemplate.opsForValue().set(key, String.valueOf(Thread.currentThread().getId()), 30, TimeUnit.MINUTES);
  12.         return JsonData.buildSuccess(token);
  13.     }        
  14.         @PostMapping("confirm")
  15.     @RepeatSubmit(limitType = RepeatSubmit.Type.TOKEN)
  16.     public void confirmOrder(@RequestBody ConfirmOrderRequest orderRequest, HttpServletResponse response) {
  17.         // TODO 下单业务
  18.     }
复制代码
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!




欢迎光临 ToB企服应用市场:ToB评测及商务社交产业平台 (https://dis.qidao123.com/) Powered by Discuz! X3.4