springmvc 防止表单重复提交的两种方法

 更新时间:2021年08月03日 10:59:56   作者:阿豪_mike  
最近在本地开发测试的时候,遇到一个表单重复提交的现象。本文主要介绍了springmvc 防止表单重复提交的两种方法,感兴趣的可以了解一下

最近在本地开发测试的时候,遇到一个表单重复提交的现象。 因为网络延迟的问题,我点击了两次提交按钮,数据库里生成了两条记录。其实这种现象以前也有遇到过,一般都是提交后把按钮置灰,无法再次提交,这是很常见的客户端处理的方式。 但是这不是从根本上解决问题,虽然客户端解决了多次提交的问题,但是接口中依旧存在着问题。假设我们不是从客户端提交,而是被其他的系统调用,当遇到网络延迟,系统补偿的时候,还会遇到这种问题

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 {
     //.....
    }

到此这篇关于springmvc 防止表单重复提交的两种方法的文章就介绍到这了,更多相关springmvc 防止表单重复提交内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • Java实现支付对接常用加密方式的示例代码

    Java实现支付对接常用加密方式的示例代码

    这篇文章主要为大家详细介绍了Java如何实现支付对接时常用加密方式,文中的示例代码讲解详细,对我们学习Java有一点帮助,需要的可以参考一下
    2023-02-02
  • Java中StringBuilder类的用法解析

    Java中StringBuilder类的用法解析

    StringBuilder是一个可变的字符序列,这个类提供了一个与StringBuffer兼容的API。本文主要为大家介绍了StringBuilder类的常用用法,需要的可以参考一下
    2023-05-05
  • Spring配置使用之Bean生命周期详解

    Spring配置使用之Bean生命周期详解

    这篇文章主要介绍了Spring配置使用之Bean生命周期详解,具有一定参考价值,需要的朋友可以了解下。
    2017-10-10
  • logback日志输出格式设置方式

    logback日志输出格式设置方式

    这篇文章主要介绍了logback日志输出格式设置方式,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2023-04-04
  • mybatis相同的sql查询第二次查不出结果问题

    mybatis相同的sql查询第二次查不出结果问题

    这篇文章主要介绍了mybatis相同的sql查询第二次查不出结果问题,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2022-01-01
  • 详解Spring Bean的集合注入和自动装配

    详解Spring Bean的集合注入和自动装配

    这篇文章主要为大家详细介绍了Spring Bean中集合注入和自动装配的方法,文中的示例代码讲解详细,对我们学习有一定的帮助,需要的可以参考一下
    2022-06-06
  • SpringBoot中发送QQ邮件功能的实现代码

    SpringBoot中发送QQ邮件功能的实现代码

    这篇文章主要介绍了SpringBoot中发送QQ邮件功能的实现代码,非常不错,具有参考借鉴价值,需要的朋友可以参考下
    2018-02-02
  • springboot整合websocket后启动报错(javax.websocket.server.ServerContainer not available)

    springboot整合websocket后启动报错(javax.websocket.server.ServerCont

    这篇文章主要介绍了springboot整合websocket后启动报错(javax.websocket.server.ServerContainer not available),通过分析错误信息、排查代码和配置,找出问题的根源,并给出相应的解决方案,感兴趣的可以了解一下
    2024-01-01
  • 关于SpringSecurity配置403权限访问页面的完整代码

    关于SpringSecurity配置403权限访问页面的完整代码

    本文给大家分享SpringSecurity配置403权限访问页面的完整代码,配置之前和配置之后的详细介绍,代码简单易懂,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友参考下吧
    2021-06-06
  • SpringBoot定时任务多线程实现示例

    SpringBoot定时任务多线程实现示例

    在真实的Java开发环境中,我们经常会需要用到定时任务来帮助我们完成一些特殊的任务,本文主要介绍了SpringBoot定时任务多线程实现示例,文中通过示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2021-12-12

最新评论