当前位置:   article > 正文

订单重复提交_springboot + aop + redis + 注解 , 实现接口幂等性校验_aop 注解实现幂等性

aop 注解实现幂等性

一、概念

幂等性, 通俗的说就是一个接口, 多次发起同一个请求, 必须保证操作只能执行一次
比如:

  • 订单接口, 不能多次创建订单
  • 支付接口, 重复支付同一笔订单只能扣一次钱
  • 支付宝回调接口, 可能会多次回调, 必须处理重复回调
  • 普通表单提交接口, 因为网络超时等原因多次点击提交, 只能成功一次
    等等

二、常见解决方案

  1. 唯一索引 -- 防止新增脏数据
  2. token机制 -- 防止页面重复提交
  3. 悲观锁 -- 获取数据的时候加锁(锁表或锁行)
  4. 乐观锁 -- 基于版本号version实现, 在更新数据那一刻校验数据
  5. 分布式锁 -- redis(jedis、redisson)或zookeeper实现
  6. 状态机 -- 状态变更, 更新数据时判断状态

三、本文实现

本文采用第2种方式实现, 即通过redis + token机制实现接口幂等性校验

四、实现思路

本文主要处理场景:同一个用户,一个请求,在规定的时间内只能发起1次请求。

这边主要处理的防止页面重复提交,为保证幂等性,请求接口时,后端通过header或者接口请求参数获取登录信息+请求路径判断redis中是否存在此key。

  • 如果不存在, 正常处理业务逻辑, 并把此key存入redis中并设置过期时间, 那么, 如果是重复请求, 由于key已存在于redis, 则不能通过校验, 返回请勿重复操作提示
  • 如果存在, 说明在redis里的key还未过期,当前是重复请求, 返回提示即可

五、项目简介

  • springboot
  • redis 
  • @ReSubmitCheck注解 + AOP切面对请求进行拦截
  • @SharException全局异常处理
  • 压测工具: jmeter

说明:

  • 本文重点介绍幂等性核心实现, 关于springboot如何集成redisServerResponseResponseCode等细枝末节不在本文讨论

六、代码实现

  1. 新增redis 配置 :pom+配置文件
  1. <!-- Redis 依赖 -->
  2. <dependency>
  3. <groupId>org.springframework.boot</groupId>
  4. <artifactId>spring-boot-starter-data-redis</artifactId>
  5. </dependency>
  6. #########################本地开发环境#########################
  7. ##spring boot 配置
  8. server.port=8004
  9. spring.application.name=share
  10. ############################################################
  11. ## MySQL配置
  12. spring.datasource.url=jdbc:mysql://192.168.1.12:3306/share?characterEncoding=utf-8&useSSL=false&serverTimezone=UTC
  13. spring.datasource.username=root
  14. spring.datasource.password=123456
  15. spring.datasource.driverClassName=com.mysql.cj.jdbc.Driver
  16. ############################################################
  17. ## Redis配置
  18. spring.redis.host=192.168.1.12
  19. #spring.redis.password=
  20. spring.redis.database=1
  21. spring.redis.port=6379
  22. ############################################################

2.自定义注解  @ReSubmitCheck

  1. import java.lang.annotation.*;
  2. /**
  3. * 在需要保证接口幂等性的Controller的方法上使用此注解
  4. * 重复提交校验注解
  5. */
  6. @Target({ElementType.PARAMETER, ElementType.METHOD})
  7. @Retention(RetentionPolicy.RUNTIME)
  8. @Documented
  9. public @interface ReSubmitCheck {
  10. //校验几秒内重复提交
  11. int seconds() default 3;
  12. }

 3. 防止重复提交切面处理器 PreventReSummitAspect

  1. import com.city.share.annotation.ReSubmitCheck;
  2. import com.city.share.enums.ResultEnum;
  3. import com.city.share.exception.ShareException;
  4. import lombok.extern.slf4j.Slf4j;
  5. import org.aspectj.lang.JoinPoint;
  6. import org.aspectj.lang.annotation.Aspect;
  7. import org.aspectj.lang.annotation.Before;
  8. import org.springframework.beans.factory.annotation.Autowired;
  9. import org.springframework.data.redis.core.StringRedisTemplate;
  10. import org.springframework.stereotype.Component;
  11. import org.springframework.web.context.request.RequestContextHolder;
  12. import org.springframework.web.context.request.ServletRequestAttributes;
  13. import javax.servlet.http.HttpServletRequest;
  14. import java.util.concurrent.TimeUnit;
  15. /**
  16. * 防重复点击
  17. */
  18. @Aspect
  19. @Component
  20. @Slf4j
  21. public class PreventReSummitAspect {
  22. /**
  23. * redis工具类
  24. */
  25. @Autowired
  26. private StringRedisTemplate redisTemplate;
  27. @Before("@annotation(reSubmitCheck)")
  28. public void preventReSubmit(JoinPoint joinPoint, ReSubmitCheck reSubmitCheck) {
  29. ServletRequestAttributes attributes =
  30. (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
  31. //获取用户登录的accesstoken
  32. HttpServletRequest request = attributes.getRequest();
  33. String token = request.getParameter("accesstoken");
  34. if (token == null) {
  35. throw new ShareException(ResultEnum.ON_LOGIN);
  36. }
  37. String lockKey = "ReSubmit:" + token + "_" + request.getServletPath();
  38. Boolean result = redisTemplate.opsForValue().setIfAbsent(lockKey, lockKey, reSubmitCheck.seconds(), TimeUnit.SECONDS);
  39. if (!result) {
  40. System.out.println("重复请求:"+lockKey);
  41. throw new ShareException(ResultEnum.RESUBMIT_ERROR);
  42. }
  43. }
  44. }

 4.测试controller  HolleContraller

  1. import com.city.share.Dto.Result;
  2. import com.city.share.Utils.ResultUtil;
  3. import com.city.share.annotation.ReSubmitCheck;
  4. import org.springframework.web.bind.annotation.GetMapping;
  5. import org.springframework.web.bind.annotation.RestController;
  6. import java.io.Serializable;
  7. @RestController
  8. public class HolleContraller implements Serializable{
  9. @GetMapping("/holle")
  10. @ReSubmitCheck(seconds=10)//这边设置了10秒内不能重复访问
  11. public Result holleTest(){
  12. System.out.println("hello spring boot");
  13. return ResultUtil.success("hello spring boot");
  14. }
  15. }

OK, 目前为止, 校验代码准备就绪, 接下来测试验证

七、测试验证

访问:127.0.0.1:8004/holle?accesstoken=123456

 查看redis

测试接口安全性: 利用jmeter测试工具模拟10个并发请求

请求结果:因为都是在10秒内,所以只有第一个请求成功

八、总结

其实思路很简单, 就是每次请求保证唯一性, 从而保证幂等性, 通过spring aop+注解, 就不用每次请求都写重复代码, 其实也可以利用拦截器实现。

如果小伙伴有什么疑问或者建议欢迎提出

参考地址:https://www.cnblogs.com/wangzaiplus/p/10931335.html

源码地址:https://download.csdn.net/download/zppiio/85309475icon-default.png?t=M3K6https://download.csdn.net/download/zppiio/85309475

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

闽ICP备14008679号