软件编程
位置:首页>> 软件编程>> java编程>> springmvc 防止表单重复提交的两种方法

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

0
投稿

猜你喜欢

手机版 软件编程 asp之家 www.aspxhome.com