一、前言

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

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

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

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

<!-- ==================  校验表单重复提交所需依赖 ===================== -->
<!-- AOP依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<!-- Redis -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

2、application.yml 中引入Redis配置

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

3、自定义注解 @NoRepeatSubmit

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

4、AOP 拦截处理

注:这里redis存储的key值可由个人具体业务灵活发挥,这里只是示例

ex:单用户登录情况下可以组合 token + url请求路径 , 多个用户可以同时登录的话,可以再加上 ip地址

@Slf4j
@Aspect
@Component
public class NoRepeatSubmitAop { @Autowired
RedisUtil redisUtil; /**
* <p> 【环绕通知】 用于拦截指定方法,判断用户表单保存操作是否属于重复提交 <p>
*
* 定义切入点表达式: execution(public * (…))
* 表达式解释: execution:主体 public:可省略 *:标识方法的任意返回值 任意包+类+方法(…) 任意参数
*
* com.zhengqing.demo.modules.*.api : 标识AOP所切服务的包名,即需要进行横切的业务类
* .*Controller : 标识类名,*即所有类
* .*(..) : 标识任何方法名,括号表示参数,两个点表示任何参数类型
*
* @param pjp:切入点对象
* @param noRepeatSubmit:自定义的注解对象
* @return: java.lang.Object
*/
@Around("execution(* com.zhengqing.demo.modules.*.api.*Controller.*(..)) && @annotation(noRepeatSubmit)")
public Object doAround(ProceedingJoinPoint pjp, NoRepeatSubmit noRepeatSubmit) {
try {
HttpServletRequest request = ((ServletRequestAttributes) Objects.requireNonNull(RequestContextHolder.getRequestAttributes())).getRequest(); // 拿到ip地址、请求路径、token
String ip = IpUtils.getIpAdrress(request);
String url = request.getRequestURL().toString();
String token = request.getHeader(Constants.REQUEST_HEADERS_TOKEN); // 现在时间
long now = System.currentTimeMillis(); // 自定义key值方式
String key = "REQUEST_FORM_" + ip;
if (redisUtil.hasKey(key)) {
// 上次表单提交时间
long lastTime = Long.parseLong(redisUtil.get(key));
// 如果现在距离上次提交时间小于设置的默认时间 则 判断为重复提交 否则 正常提交 -> 进入业务处理
if ((now - lastTime) > noRepeatSubmit.time()) {
// 非重复提交操作 - 重新记录操作时间
redisUtil.set(key, String.valueOf(now));
// 进入处理业务
ApiResult result = (ApiResult) pjp.proceed();
return result;
} else {
return ApiResult.fail("请勿重复提交!");
}
} else {
// 这里是第一次操作
redisUtil.set(key, String.valueOf(now));
ApiResult result = (ApiResult) pjp.proceed();
return result;
}
} catch (Throwable e) {
log.error("校验表单重复提交时异常: {}", e.getMessage());
return ApiResult.fail("校验表单重复提交时异常!");
} } }

5、其中用到的Redis工具类

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

三、测试

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

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

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

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



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

四、总结

实现思路
  1. 首先利用AOP切面在进入方法前拦截 进行表单重复提交校验逻辑处理
  2. 通过 Rediskey-value键值对 存储 需要的逻辑判断数据 【ex:key存储用户提交表单的api请求路径,value存储提交时间】
  3. 逻辑处理

    第一次提交时存入相应数据到redis中

    当再次提交保存时从redis缓存中取出上次提交的时间与当前操作时间做判断,

    如果当前操作时间距离上次操作时间在我们设置的 ‘判断为重复提交的时间(3秒内)’ 则为重复提交 直接 返回重复提交提示语句或其它处理,

    否则为正常提交,进入业务方法处理...
补充

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

本文案例demo源码

https://gitee.com/zhengqingya/java-workspace

Spring Boot (一) 校验表单重复提交的更多相关文章

  1. Spring MVC实现防止表单重复提交(转)

    Spring MVC拦截器+注解方式实现防止表单重复提交  

  2. AOP+Token防止表单重复提交

    表单重复提交: 由于用户误操作,多次点击表单提交按钮 由于网速等原因造成页面卡顿,用户重复刷新提交页面 避免表单重复提交的方式: 1.页面上的按钮做防重复点击操作 2.在数据库中可以做唯一约束 3.利 ...

  3. spring boot 学习(七)小工具篇:表单重复提交

    注解 + 拦截器:解决表单重复提交 前言 学习 Spring Boot 中,我想将我在项目中添加几个我在 SpringMVC 框架中常用的工具类(主要都是涉及到 Spring AOP 部分知识).比如 ...

  4. 一脸懵逼学习Struts数据校验以及数据回显,模型驱动,防止表单重复提交的应用。

    1:Struts2表单数据校验: (1)前台校验,也称之为客户端校验,主要是通过Javascript编程的方式进行数据的验证. (2)后台校验,也称之为服务器校验,这里指的是使用Struts2通过xm ...

  5. JavaWeb -- Struts1 使用示例: 表单校验 防表单重复提交 表单数据封装到实体

    1. struts 工作流程图 超链接 2. 入门案例 struts入门案例: 1.写一个注册页面,把请求交给 struts处理 <form action="${pageContext ...

  6. 大型运输行业实战_day05_1_登录+注销+表单重复提交+登录拦截器

    1.登录 登录实现如下步骤: 1.在首页中添加登录按钮 html代码如下: <%@ page contentType="text/html;charset=UTF-8" la ...

  7. java后端使用token处理表单重复提交

    保证接口幂等性,表单重复提交 前台解决方案:提交后按钮禁用.置灰.页面出现遮罩后台解决方案:   使用token,每个token只能使用一次1.在调用接口之前生成对应的Token,存放至redis 2 ...

  8. 12、Struts2表单重复提交

    什么是表单重复提交 表单的重复提交: 若刷新表单页面, 再提交表单不算重复提交. 在不刷新表单页面的前提下: 多次点击提交按钮 已经提交成功, 按 "回退" 之后, 再点击 &qu ...

  9. Struts2第十三篇【防止表单重复提交】

    回顾防止表单重复提交 当我们学习Session的时候已经通过Session来编写了一个防止表单重复提交的小程序了,我们来回顾一下我们当时是怎么做的: 在Servlet上生成独一无二的token,保存在 ...

随机推荐

  1. 【译】Source Maps浅析

    Time:2019/10/27~2019/10/29 Link: 原文链接 译文开始: 对网站进行性能优化对一个最容易的方法就是把JS和CSS进行打包压缩.但是当你需要调试这些压缩文件中的代码的时候, ...

  2. electron调用c#动态库

    electron调用c#动态库 新建C#动态库 方法要以异步任务的方式,可以直接包装,也可以写成天然异步 代码如下 public class Class1 { public async Task< ...

  3. 算法随笔-二叉树遍历的N种姿势

    最近在练习用Python刷算法,leetcode上刷了快300题.一开始怀疑自己根本不会写代码,现在觉得会写一点点了,痛苦又充实的刷题历程.对我这种半路出家的人而言,收获真的很大. 今天就从二叉树遍历 ...

  4. [.Net Core 3.0从入门到精通]1.笔记简介及.Net Core3.0介绍

    文章目的:.Net Core 3.0学习笔记整理与分享. 面向人群:有一定基础的C#开发人员或学习人员(C#语法一定要掌握). 笔者水平:中级C#开发攻城狮(水平有限,写的不对的地方希望大家指正). ...

  5. 从java到python

    从java到python 从事java开发主要是web方向,这个方向已经相当成熟,基本你想开发什么样的app,总能百度出来相关的项目或者github中搜索到,你需要做的就是掌握调试的技巧,基本你能想到 ...

  6. podman初试-和docker对比

    podman初试-和docker对比 1,什么是docker? Docker 是一个开源的应用容器引擎,属于 Linux 容器的一种封装,Docker 提供简单易用的容器使用接口,让开发者可以打包他们 ...

  7. 第三十九章 POSIX信号量与互斥锁

    POSIX信号量相关函数 sem_open 功能: initialize and open a named semaphore 原型: sem_t *sem_open(const char *name ...

  8. C++学习笔记6_字符串

    1. C语言的字符串,char * s = "aaaa"; #include<string> class Test{ public : Test(int a, char ...

  9. python-->二进制的用法

    1.10进制转换为其他进制 方法一:函数 十进制转二进制:bin(10) --> '0b1010' tpye:是字符串类型 0b:表示2进制 十进制转八进制:oct(10) --> '0o ...

  10. 「考试」小P的生成树

    考场上想到一半正解,没想到随机化,不然也许能够$A$掉. 题目所说的其实就是向量加法,求模长最长的向量生成树. 我们考虑对于两个向量,必然在平行边形对角线方向上,他们的投影和是最大的,长度就是对角线长 ...