springmvc 防止表单重复提交的两种方法
作者:阿豪_mike 发布时间:2023-03-27 17:57:18
标签:springmvc,表单,重复提交
目录
1、通过session中的token验证
步骤1:创建自定义注解
步骤2:创建自定义 * (@slf4j是lombok的注解)
步骤3:将自定义 * 添加到配置文件
2、通过当前用户上一次请求的url和参数验证重复提交
步骤1:创建自定义注解
步骤2:创建自定义 *
步骤3:将自定义 * 添加到配置文件
最近在本地开发测试的时候,遇到一个表单重复提交的现象。 因为网络延迟的问题,我点击了两次提交按钮,数据库里生成了两条记录。其实这种现象以前也有遇到过,一般都是提交后把按钮置灰,无法再次提交,这是很常见的客户端处理的方式。 但是这不是从根本上解决问题,虽然客户端解决了多次提交的问题,但是接口中依旧存在着问题。假设我们不是从客户端提交,而是被其他的系统调用,当遇到网络延迟,系统补偿的时候,还会遇到这种问题
1、通过session中的token验证
初始化页面时生成一个唯一token,将其放在页面隐藏域和session中
* 拦截请求,校验来自页面请求中的token与session中的token是否一致
判断,如果一致则提交成功并移除session中的token,不一致则说明重复提交并记录日志
步骤1:创建自定义注解
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Token {
boolean save() default false;
boolean remove() default false;
}
步骤2:创建自定义 * (@slf4j是lombok的注解)
@Slf4j
public class RepeatSubmitInterceptor extends HandlerInterceptorAdapter {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
HandlerMethod handlerMethod = null;
try {
handlerMethod = (HandlerMethod)handler;
} catch (Exception e) {
return true;
}
Method method = handlerMethod.getMethod();
Token token = method.getAnnotation(Token.class);
if(token != null ){
boolean saveSession = token.save();
if(saveSession){
request.getSession(true).setAttribute("token", UUID.randomUUID());
}
boolean removeSession = token.remove();
if(removeSession){
if(isRepeatSubmitSession(request)){
log.info("repeat submit session :" + request.getServletPath());
response.sendRedirect("/error/409");
return false;
}
request.getSession(true).removeAttribute("token");
}
}
return true;
}
private boolean isRepeatSubmitSession(HttpServletRequest request){
String sessionToken = String.valueOf(request.getSession(true).getAttribute("token") == null ? "" : request.getSession(true).getAttribute("token"));
String clientToken = String.valueOf(request.getParameter("token") == null ? "" : request.getParameter("token"));
if(sessionToken == null || sessionToken.equals("")){
return true;
}
if(clientToken == null || clientToken.equals("")){
return true;
}
if(!sessionToken.equals(clientToken)){
return true;
}
return false;
}
}
步骤3:将自定义 * 添加到配置文件
<mvc:interceptor>
<mvc:mapping path="/**"/>
<bean class="com.chinagdn.base.common.interceptor.RepeatSubmitInterceptor"/>
</mvc:interceptor>
使用案例
//save = true 用于生成token
@Token(save = true)
@RequestMapping(value = "save", method = RequestMethod.GET)
public String save(LoginUser loginUser, Model model) throws Exception {
return "sys/user/edit";
}
//remove = true 用于验证token
@Token(remove = true)
@RequestMapping(value = "save", method = RequestMethod.POST)
public String save(@Valid LoginUser loginUser, Errors errors, RedirectAttributes redirectAttributes, Model model) throws Exception {
//.....
}
jsp页面隐藏域添加token
<input type="hidden" name="token" value="${sessionScope.token}">
2、通过当前用户上一次请求的url和参数验证重复提交
* 拦截请求,将上一次请求的url和参数和这次的对比
判断,是否一致说明重复提交并记录日志
步骤1:创建自定义注解
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface SameUrlData {
}
步骤2:创建自定义 *
public class SameUrlDataInterceptor extends HandlerInterceptorAdapter {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
if (handler instanceof HandlerMethod) {
HandlerMethod handlerMethod = (HandlerMethod) handler;
Method method = handlerMethod.getMethod();
//是否有 SameUrlData 注解
SameUrlData annotation = method.getAnnotation(SameUrlData.class);
if (annotation != null) {
if (repeatDataValidator(request)) {//如果重复相同数据
response.sendRedirect("/error/409");
return false;
} else {
return true;
}
}
return true;
} else {
return super.preHandle(request, response, handler);
}
}
/**
* 验证同一个url数据是否相同提交 ,相同返回true
* @param httpServletRequest
* @return
*/
private boolean repeatDataValidator(HttpServletRequest httpServletRequest) {
String params = JsonMapper.toJsonString(httpServletRequest.getParameterMap());
String url = httpServletRequest.getRequestURI();
Map<String, String> map = new HashMap<>();
map.put(url, params);
String nowUrlParams = map.toString();//
Object preUrlParams = httpServletRequest.getSession().getAttribute("repeatData");
if (preUrlParams == null) { //如果上一个数据为null,表示还没有访问页面
httpServletRequest.getSession().setAttribute("repeatData", nowUrlParams);
return false;
} else { //否则,已经访问过页面
if (preUrlParams.toString().equals(nowUrlParams)) { //如果上次url+数据和本次url+数据相同,则表示城府添加数据
return true;
} else { //如果上次 url+数据 和本次url加数据不同,则不是重复提交
httpServletRequest.getSession().setAttribute("repeatData", nowUrlParams);
return false;
}
}
}
}
步骤3:将自定义 * 添加到配置文件
<mvc:interceptor>
<mvc:mapping path="/**"/>
<bean class="com.chinagdn.base.common.interceptor.SameUrlDataInterceptor"/>
</mvc:interceptor>
使用案例
//在controller层使用 @SameUrlData 注解即可
@SameUrlData
@RequestMapping(value = "save", method = RequestMethod.POST)
public String save(@Valid LoginUser loginUser, Errors errors, RedirectAttributes redirectAttributes, Model model) throws Exception {
//.....
}
来源:https://juejin.cn/post/6990963223486791688


猜你喜欢
- 前言在Java开发者中,字符串的拼接占用资源高往往是热议的话题.让我们深入讨论一下为什么会占用高资源。在Java中,字符串对象是不可变的,意
- 我们有时候会遇到这样的情况,需要获取某些中文的拼音、中文首字母缩写和中文首字母,下面我将为大家介绍一下如何获取中文拼音的缩写。1、项目建立和
- 注解作用注解是JDK1.5版本开始引入的一个特性,用于对代码进行说明,可以对包、类、接口、字段、方法参数、局部变量等进行注解。它主要的作用有
- 导入依赖(pom.xml) <!--整合Shiro安全框架--> <dependency>
- 健康检查是Spring Boot Actuator中重要端点之一,可以非常容易查看应用运行至状态。本文在前文的基础上介绍如何自定义健康检查。
- 一、原因:forceclose,意为强行关闭,当前应用程序发生了冲突。NullPointExection(空指针),IndexOutOfBo
- 本文实例为大家分享了Unity实现文本转贴图的具体代码,供大家参考,具体内容如下导入字体导入ttf字体,修改Character为Custom
- HashMap相同key累加valueimport java.util.HashMap;import java.util.Map;publi
- SpringMVC的几个模型对象模型对象的作用主要是保存数据,可以借助它们将数据带到前端。常用的模型对象有以下几个:1.ModelAndVi
- socketpair()函数的声明:#include <sys/types.h>#include <sys/socket.
- 在项目开发中经常会遇到调用第三方接口的情况,比如说调用第三方的天气预报接口。使用流程【1】准备工作:在项目的工具包下导入HttpClient
- 简介Springboot Admin是一个管理和监控Springboot项目的组件,分为服务端和客户端,两端通过http进行通信。由于其轻量
- 由于项目需要,需要用vs窗体程序实现播放视频的窗口的全屏和取消全屏。具体实现界面如图:这是初始状态,视频框的右上角就是控制全屏的按钮这是全屏
- 前言两个数据结构:顺序表和链表数据结构是一门学科,和语言无关。数据 + 结构:一种描述和组织数据的方式。1. 顺序表顺序表是用一段物理地址连
- PathVariable 映射 URL 绑定的占位符带占位符的 URL 是 Spring3.0 新增的功能,该功能在SpringMVC 向
- 目录Retrofit介绍Retrofit优点Retrofit的使用1.引入依赖项2.添加网络权限3.编写Retrofit辅助类4.定义Api
- 前言内存泄漏简单地说就是申请了一块内存空间,使用完毕后没有释放掉。它的一般表现方式是程序运行时间越长,占用内存越多,最终用尽全部内存,整个系
- 本文实例讲述了java实现ArrayList根据存储对象排序功能。分享给大家供大家参考,具体如下:与c++中的qsort的实现极为相似,构建
- 服务提供者@GetMapping("/{id}") public void queryJobInfoLogD
- 本文实例讲述了C#通过创建Windows服务启动程序的方法。分享给大家供大家参考,具体如下:1. 新建一个Windows服务应用程序创建项目