当前位置:   article > 正文

用AOP切片+Redis防接口幂等性重复提交_aop 加 redis 防止重复请求

aop 加 redis 防止重复请求

        如何防止接口被重复提交?

        接口幂等性就是用户对同一操作发起的一次请求或多次请求的结果是一致的,不会因为对此提交而重复执行或出现其他问题。         例:在支付的时候,如果用户以为没支付成功(实际成功了),再次点击按钮导致被扣了两次钱,这是无法接收的问题,所以这个给问题是一定要解决的。本文使用的是AOP切片来解决这个问题。

        

        再后端通过自定义注解,将这个注解作为切点,再在需要防幂等接口上添加注解,在执行方法之前在切片当中进行判断是否是重复提交。这样减少了和业务的耦合。具体的是在切点当中获取用户的token、user_id、url用于作为redis的唯一key,因为redis有自动过期机制,所以只要给这个key设置过期时间,就可以让这个用户在这个key还没过期之前无法重复调用这个接口。

具体实现:

  • 导入这个aop依赖和Redis依赖,注意还要有一些springboot基础依赖
  1. <dependency>
  2. <groupId>org.springframework.boot</groupId>
  3. <artifactId>spring-boot-starter-aop</artifactId>
  4. </dependency>
  5. <dependency>
  6. <groupId>org.springframework.boot</groupId>
  7. <artifactId>spring-boot-starter-data-redis</artifactId>
  8. </dependency>
  • 编写注解。
  1. @Target(ElementType.METHOD) // 注解只能用于方法
  2. @Retention(RetentionPolicy.RUNTIME) // 修饰注解的生命周期
  3. @Documented
  4. public @interface RepeatSubmit {
  5. /**
  6. * 防重复操作过期时间,默认1s
  7. */
  8. long expireTime() default 1;
  9. }
  • 编写切片,将那个注解变为切点
  1. @Slf4j
  2. @Component
  3. @Aspect
  4. public class RepeatSubmitAspect {
  5. @Autowired
  6. private RedisTemplate redisTemplate;
  7. /**
  8. * 定义切点
  9. */
  10. @Pointcut("@annotation(com.example.demo.apo.RepeatSubmit)")//这里填写自己那个注解的路径
  11. public void repeatSubmit() {}
  12. @Around("repeatSubmit()")
  13. public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
  14. ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder
  15. .getRequestAttributes();
  16. HttpServletRequest request = attributes.getRequest();
  17. Method method = ((MethodSignature) joinPoint.getSignature()).getMethod();
  18. // 获取防重复提交注解
  19. RepeatSubmit annotation = method.getAnnotation(RepeatSubmit.class);
  20. // 获取token当做key,这里是新后端项目获取不到哈,先写死
  21. // String token = request.getHeader("Authorization");
  22. String tokenKey = "hhhhhhh,nihao";
  23. if (StringUtils.isBlank(tokenKey)) {
  24. throw new RuntimeException("token不存在,请登录!");
  25. }
  26. String url = request.getRequestURI();
  27. /**
  28. * 通过前缀 + url + token 来生成redis上的 key
  29. * 可以在加上用户id,这里没办法获取,大家可以在项目中加上
  30. */
  31. String redisKey = "repeat_submit_key:"
  32. .concat(url)
  33. .concat(tokenKey);
  34. log.info("==========redisKey ====== {}",redisKey);
  35. if (!redisTemplate.hasKey(redisKey)) {
  36. redisTemplate.opsForValue().set(redisKey, redisKey, annotation.expireTime(), TimeUnit.SECONDS);
  37. try {
  38. //正常执行方法并返回
  39. return joinPoint.proceed();
  40. } catch (Throwable throwable) {
  41. redisTemplate.delete(redisKey);
  42. throw new Throwable(throwable);
  43. }
  44. } else {
  45. // 抛出异常
  46. throw new Throwable("请勿重复提交");
  47. }
  48. }
  49. }
  •  编写接口
    • 这一步自己可以随便写一个接口,只要给接口加上切点(也就是这里的@RepeatSubmit注解),别用我这里的代码,
  1. @RepeatSubmit(expireTime = 10)//为了方便展示不能重复提交的功能这里将过期时间设置为10s
  2. @GetMapping("/open/logins")
  3. public ResultUtil logins(UserParam userParam){
  4. String login = iUserService.login(userParam);
  5. return ResultUtil.success(login);
  6. }

 测试:

  第一次:

第二次 

10s后:

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/你好赵伟/article/detail/550753
推荐阅读
相关标签
  

闽ICP备14008679号