当前位置:   article > 正文

Spring Boot (一) 校验表单重复提交(自定义注解-幂等性)_springboot防止重复提交注解

springboot防止重复提交注解

一、前言

在某些情况下,由于网速慢,用户操作有误(连续点击两下提交按钮),页面卡顿等原因,可能会出现表单数据重复提交造成数据库保存多条重复数据。

存在如上问题可以交给前端解决,判断多长时间内不能再次点击保存按钮,当然,如果存在聪明的用户能够绕过前端验证,后端更应该去进行拦截处理,下面小编将基于 SpringBoot 2.1.8.RELEASE 环境通过 AOP切面 + 自定义校验注解 + Redis缓存 来解决这一问题。

二、Spring Boot 校验表单重复提交操作

1、pom.xml 中引入所需依赖

  1. <!-- ================== 校验表单重复提交所需依赖 ===================== -->
  2. <!-- AOP依赖 -->
  3. <dependency>
  4. <groupId>org.springframework.boot</groupId>
  5. <artifactId>spring-boot-starter-aop</artifactId>
  6. </dependency>
  7. <!-- Redis -->
  8. <dependency>
  9. <groupId>org.springframework.boot</groupId>
  10. <artifactId>spring-boot-starter-data-redis</artifactId>
  11. </dependency>

2、application.yml 中引入Redis配置

  1. spring:
  2. redis:
  3. # Redis数据库索引(默认为0)
  4. database: 0
  5. # Redis服务器地址
  6. host: 127.0.0.1
  7. # Redis服务器连接端口
  8. port: 6379
  9. timeout: 6000
  10. # Redis服务器连接密码(默认为空)
  11. # password:
  12. jedis:
  13. pool:
  14. max-active: 1000 # 连接池最大连接数(使用负值表示没有限制)
  15. max-wait: -1 # 连接池最大阻塞等待时间(使用负值表示没有限制)
  16. max-idle: 10 # 连接池中的最大空闲连接
  17. min-idle: 5 # 连接池中的最小空闲连接

3、自定义注解 @NoRepeatSubmit

  1. // 作用到方法上
  2. @Target(ElementType.METHOD)
  3. // 运行时有效
  4. @Retention(RetentionPolicy.RUNTIME)
  5. public @interface NoRepeatSubmit {
  6. /**
  7. * 默认时间3秒
  8. */
  9. int time() default 3 * 1000;
  10. }

4、AOP 拦截处理

注:这里redis存储的key值可由个人具体业务灵活发挥,这里只是示例 ex:单用户登录情况下可以组合 token + url请求路径 , 多个用户可以同时登录的话,可以再加上 ip地址

注意:如果不能进入拦截应该是拦截扫描包路径不对,这样拦截的是lz子包的类文件 @Around("execution(* com.lz..*..*Controller.*(..)) && @annotation(noRepeatSubmit)")

  1. @Slf4j
  2. @Aspect
  3. @Component
  4. public class NoRepeatSubmitAop {
  5. @Autowired
  6. RedisUtil redisUtil;
  7. /**
  8. * <p> 【环绕通知】 用于拦截指定方法,判断用户表单保存操作是否属于重复提交 <p>
  9. *
  10. * 定义切入点表达式: execution(public * (…))
  11. * 表达式解释: execution:主体 public:可省略 *:标识方法的任意返回值 任意包+类+方法(…) 任意参数
  12. *
  13. * com.zhengqing.demo.modules.*.api : 标识AOP所切服务的包名,即需要进行横切的业务类
  14. * .*Controller : 标识类名,*即所有类
  15. * .*(..) : 标识任何方法名,括号表示参数,两个点表示任何参数类型
  16. *
  17. * @param pjp:切入点对象
  18. * @param noRepeatSubmit:自定义的注解对象
  19. * @return: java.lang.Object
  20. */
  21. @Around("execution(* com.lz..*..*Controller.*(..)) && @annotation(noRepeatSubmit)")
  22. public Object doAround(ProceedingJoinPoint pjp, NoRepeatSubmit noRepeatSubmit) {
  23. try {
  24. HttpServletRequest request = ((ServletRequestAttributes) Objects.requireNonNull(RequestContextHolder.getRequestAttributes())).getRequest();
  25. // 拿到ip地址、请求路径、token
  26. String ip = IpUtils.getIpAdrress(request);
  27. String url = request.getRequestURL().toString();
  28. String token = request.getHeader(Constants.REQUEST_HEADERS_TOKEN);
  29. // 现在时间
  30. long now = System.currentTimeMillis();
  31. // 自定义key值方式
  32. String key = "REQUEST_FORM_" + ip;
  33. if (redisUtil.hasKey(key)) {
  34. // 上次表单提交时间
  35. long lastTime = Long.parseLong(redisUtil.get(key));
  36. // 如果现在距离上次提交时间小于设置的默认时间 则 判断为重复提交 否则 正常提交 -> 进入业务处理
  37. if ((now - lastTime) > noRepeatSubmit.time()) {
  38. // 非重复提交操作 - 重新记录操作时间
  39. redisUtil.set(key, String.valueOf(now));
  40. // 进入处理业务
  41. ApiResult result = (ApiResult) pjp.proceed();
  42. return result;
  43. } else {
  44. return ApiResult.fail("请勿重复提交!");
  45. }
  46. } else {
  47. // 这里是第一次操作
  48. redisUtil.set(key, String.valueOf(now));
  49. ApiResult result = (ApiResult) pjp.proceed();
  50. return result;
  51. }
  52. } catch (Throwable e) {
  53. log.error("校验表单重复提交时异常: {}", e.getMessage());
  54. return ApiResult.fail("校验表单重复提交时异常!");
  55. }
  56. }
  57. // 获取请求头token信息
  58. // HttpServletRequest request = ((ServletRequestAttributes) Objects.requireNonNull(RequestContextHolder.getRequestAttributes())).getRequest();
  59. // String tokens=request.getParameter("token");
  60. }

5、其中用到的Redis工具类

由于太多,这里就不直接贴出来了,可参考文末给出的案例demo源码

三、测试

在需要校验的方法上加上自定义的校验注解 @NoRepeatSubmit 即可

  1. @RestController
  2. public class IndexController extends BaseController {
  3. @NoRepeatSubmit
  4. @GetMapping(value = "/index", produces = "application/json;charset=utf-8")
  5. public ApiResult index() {
  6. return ApiResult.ok("Hello World ~ ");
  7. }
  8. }

这里重复访问此 index api请求以模拟提交表单测试

第一次访问 http://127.0.0.1:8080/index

 在这里插入图片描述 

多次刷新此请求,则提示请勿重复提交!

 在这里插入图片描述

四、总结

实现思路

  1. 首先利用AOP切面在进入方法前拦截 进行表单重复提交校验逻辑处理
  2. 通过 Redis 的 key-value键值对 存储 需要的逻辑判断数据 【ex:key存储用户提交表单的api请求路径,value存储提交时间】
  3. 逻辑处理 : 第一次提交时存入相应数据到redis中 当再次提交保存时从redis缓存中取出上次提交的时间与当前操作时间做判断, 如果当前操作时间距离上次操作时间在我们设置的 ‘判断为重复提交的时间(3秒内)’ 则为重复提交 直接 返回重复提交提示语句或其它处理, 否则为正常提交,进入业务方法处理...

补充

如果api遵从的是严格的Restful风格 即 @PostMapping 用于表单提交操作,则可不用自定义注解方式去判断需要校验重复提交的路径,直接在aop切面拦截该请求路径后,通过反射拿到该方法上的注解是否存在 @PostMapping 如果存在则是提交表单的api,即进行校验处理,如果不存在即是其它的 @GetMapping 、 @PutMapping 、@DeleteMapping 操作 ...

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

闽ICP备14008679号