SpringMVC防止表单重复提交(拦截器实现)
之前项目中在表单提交的时候由于网络原因造成响应慢,用户会重复点击,造成新增或修改时数据库产生脏数据,首先想到的解决方式就是在表中加索引,但是由于业务需求,加索引不太合适,便选择了使用SpringMVC的拦截器实现防止表单重复提交的方式解决。
实现思路:
1.自定义注解PreventRepeat,标注表单提交的后台处理方法上,方便拦截器选择性处理
2.自定义拦截器类RepeatUrlDataInterceptor,继承SpringMVC的拦截器HandlerInterceptorAdapter,在preHandle()方法内进行重复性校验,重复性校验大致逻辑:
(1)通过反射拿到请求方法上的注解,判断是否有注解PreventRepeat,有的话进行重复性校验,没有的话将请求放行;
(2)重复性校验时,将request中的请求参数转换为String字符串,并以请求url为key,String参数字符串为value存储在map中,再将map以String的形式存在session中,每次请求时拿到上一次的请求信息与当前的请求信息比较,相同则为重复提交,重复提交抛出自定义的重复异常,交给全局异常处理器处理。
话不多说,这就上代码
1.自定义注解PreventRepeat
package cc.rengu.ecp.platform.mcmp.busidict;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Created by Administrator on 2021/2/2.
* 防止重复提交的注解
*
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface PreventRepeat {
}
2.自定义重复异常
public class RepeatException extends Exception {
private String message;
@Override
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
public RepeatException(String message){
this.message = message;
}
}
3.自定义拦截器类RepeatUrlDataInterceptor
package cc.rengu.ecp.platform.mcmp.common;
import cc.rengu.ecp.platform.mcmp.busidict.PreventRepeat;
import com.alibaba.fastjson.JSONObject;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.PrintWriter;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
/**
* Created by Administrator on 2021/2/2.
*
* 重复数据校验拦截器
*
*/
public class RepeatUrlDataInterceptor extends HandlerInterceptorAdapter{
/**
*
* @param request
* @param response
* @param handler
* @return true:继续往下走;false:中断
* @throws Exception
*/
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
if(handler instanceof HandlerMethod){
HandlerMethod handlerMethod = (HandlerMethod)handler;
Method method = handlerMethod.getMethod();
//判断该方法上是否有防止重复提交的注解
PreventRepeat repeat = method.getAnnotation(PreventRepeat.class);
if(repeat != null){
//有重复判断的注解,进行重复判断
boolean result = repeatDataValidator(request);
if(result){
//重复,给客户端返回重复信息 (可以自定义异常,抛给springmvc的前端控制器,在全局异常处理内处理)
throw new RepeatException("请勿重复提交");
return false;
}else{
return true;
}
}else{
return true;
}
}else{
return super.preHandle(request, response, handler);
}
}
/**
* 重复数据校验
* @param httpServletRequest 请求request
* @return 返回true:重复数据;返回false:不是重复数据
*/
public boolean repeatDataValidator(HttpServletRequest httpServletRequest){
try{
//URL+请求参数转换为json字符串
ObjectMapper mapper = new ObjectMapper();
Map<String,String[]> map1 = httpServletRequest.getParameterMap();
String params = mapper.writeValueAsString(httpServletRequest.getParameterMap());
//获取当前的URL地址,以url为key,请求参数为值,放到map里
String url = httpServletRequest.getRequestURI();
Map<String,String> map = new HashMap<>();
map.put(url,params);
String curUrlData = map.toString();
System.out.println("当前请求数据(URL+参数):" + curUrlData);
//以上代码拿到了当前请求的URL+请求参数,并转换为string,
//从session中获取上一次请求存储的url和请求数据
Object preUrlData = httpServletRequest.getSession().getAttribute("preUrlData");
//如果上次请求的URL+数据为null,则是第一次请求服务器,将当前请求的url+数据存到session 当中(第一次请求的情况)
if(preUrlData == null){
httpServletRequest.getSession().setAttribute("preUrlData",curUrlData);
return false;
}else{
//不为空,则拿上一次请求与当前请求进行比较,相同则是重复提交,不同则不是重复提交,把当前的请求存入session,下一次请求时做判断
if(preUrlData.toString().equals(curUrlData)){
return true;
}else{
httpServletRequest.getSession().setAttribute("preUrlData",curUrlData);
return false;
}
}
}catch (Exception e){
e.printStackTrace();
System.out.println("重复数据校验出现异常");
return false;
}
}
}
将注解@PreventRepeat加到表单的后台处理方法上,经测试,功能实现
防止表单重复提交和保证接口的幂等性是有区别的
防止表单重复提交是防止同一操作发生多次请求,而幂等性是同一操作发生了多次请求后的处理,切勿混淆