防止表单重复提交

防止表单重复提交的方案:

一. 通过js限制表单是否可以提交的方式
PS:用户刷新页面或使用postman等工具绕过前段页面仍能重复提交表单。

1.form表单提交(第一种)

<form action="/acceptFormRequest/formRequest" onsubmit="return formSubmit()" method="post" target="nm_iframe">
    <input type="hidden" id="formToken" name="formToken" >
    用户名:<input type="text" name="submitInfo">
    <input type="submit" value="提交" id="submit">
</form>
<iframe id="id_iframe" name="nm_iframe" style="display:none;"></iframe>
<script type="text/javascript">

    //默认提交状态为false
    var commitStatus = true;
    function formSubmit(){
        if(commitStatus){
            //提交表单后,把提交状态改为false
            commitStatus = false;
            return true;//允许表单提交
        }
        return false;//阻止表单提交
    }
</script>

在这里插入图片描述
(1.1)图中标红处,一定要加return ,主要是对form表单提交事件进行处理
(1.2)加iframe为了防止表单提交后跳转
2.form表单提交(第二种使用原生的ajax发送请求)

<form id="registSubmit" action="" method="post" >
    <input type="hidden" name="formToken" value="${sessionScope.formToken}">
    用户名:<input type="text" name="submitInfo" id="submitInfo">
    <input type="submit" value="提交" id="submit">
</form>

<script type="text/javascript">
    //默认提交状态为false
    var commitStatus = true;

    var registSubmitId = document.getElementById("registSubmit");
    registSubmitId.onsubmit=function (ev) {
        if(commitStatus) {
            ajaxRequest();
            commitStatus=false;
        }

        //一直防止表单提交,然后进行页面跳转  下边两种效果一样
        //return false;
        ev.preventDefault();
    }


    //ajax请求
    function ajaxRequest() {
        //1.创建XMLHTTPRequest对象,对于低版本的IE,需要换一个ActiveXObject对象
        var  xmlhttp;
        if (window.XMLHttpRequest) {
            xmlhttp = new XMLHttpRequest();
        } else {
            xmlhttp = new ActiveXObject('Microsoft.XMLHTTP');
        }

        //2.使用open方法设置和服务器的交互信息:
        //设置请求的url参数,参数一是请求的类型,参数二是请求的url,参数三指定是否使用异步,默认是true
        xmlhttp.open("post", "/acceptFormRequest/formRequest", true);

        /*
       1. post请求一定要添加请求头才行不然会报错
        表单上传编码格式为application/x-www-form-urlencoded,参数的格式为key=value&key=value。
        application/x-www-form-urlencoded是浏览器默认的编码格式。对于Get请求,
        是将参数转换?key=value&key=value格式,连接到url后
        服务器知道参数用符号&间隔,如果参数值中需要&,则必须对其进行编码。
        编码格式就是application/x-www-form-urlencoded
        (将键值对的参数用&连接起来,如果有空格,将空格转换为+加号;有特殊符号,
        将特殊符号转换为ASCII HEX值)。

        2.上传文件也要指定编码格式为multipart/form-data。
        在form标签加属性 enctype="multipart/form-data"


        注意:一般情况下使用“application/x-www-form-urlencoded”会比较快捷
              大数据传输时一般选择“multipart/form-data”。
        (PS:application/x-www-form-urlencoded和multipart/form-data两种在后台接收参数的时候有差异,
        servlet,springMVC,springBoot接收需要注意配置问题下次再分享)
         */
        xmlhttp.setRequestHeader("Content-type", "application/x-www-form-urlencoded");

        //3.发送请求 GET请求不需要参数,POST请求需要把body部分以字符串或者FormData对象传进去。
        var submitInfoId = document.getElementById("submitInfo");
        //和axios的格式一样
        var params = "submitInfo="+submitInfoId.value;
        xmlhttp.send(params);


        //4.注册事件 onreadystatechange 状态改变就会调用
        xmlhttp.onreadystatechange = function () {
            if (xmlhttp.readyState === 4) { // 成功完成
                // 判断响应结果:
                if (xmlhttp.status === 200) {
                    // 成功,通过responseText拿到响应的文本:
                    console.log(xmlhttp.responseText);
                } else {
                    // 失败,根据响应码判断失败原因:
                    console.log(xmlhttp.status);
                }
            } else {
                // HTTP请求还在继续...
            }
        }
    }
</script>

2.form表单提交(第三种使用jquery.js的ajax或者jquery.form.min.js的ajaxForm或ajaxSubmit发送请求)

<form id="registSubmit" action="" method="post" >
    <input type="hidden"  name="formToken" value="${sessionScope.formToken}">
    用户名:<input type="text" name="submitInfo" id="submitInfo">
    <input type="submit" value="提交">
</form>
    <script>
/**************************第一种使用ajax进行发送请求*****************************************/
        //默认提交状态为true
        var commitStatus = true;
        $("#registSubmit").submit(function (e) {
            if(commitStatus) {
                ajaxRequest();
                commitStatus=false;
            }
            //防止表单提交,然后进行页面跳转  下边两种效果一样
            //return false;
            e.preventDefault();
        });

         //发送ajax请求
        function ajaxRequest() {
           //1.
            $.ajax({
                url: "/acceptFormRequest/formRequest",
                data: {submitInfo: $("#submitInfo").val()},
                type: "POST",
                dataType: "json",
                success: function(data) {
                    // data = jQuery.parseJSON(data);
                    //dataType指明了返回数据为json类型,故不需要再反序列化
                    console.log(data);
                }
            });

           /*
           2.
           $.post("/formSubmit", {submitInfo: $("#submitInfo").val()}, function(data) {
                    console.log(data);
                },
                "json"
            );
            */

          /*
          3.
          $.get("/formSubmit", function(data) {
                    console.log(data);
                },
                'json'
            );
           */


        }

/********************************第二种使用ajaxForm发送form表单**************************************************/
   /*     //表单默认可以提交
        var commitStatus = true;
        //返回false表单不会提交,可进行表单验证操作
        function showRequest(formData, jqForm, options) {
            //formData: 数组对象,提交表单时,Form插件会以Ajax方式自动提交这些数据,格式如:[{name:user,value:val },{name:pwd,value:pwd}]
            //jqForm:   jQuery对象,封装了表单的元素
            //options:  options对象
            console.log(jqForm);

            var queryString = $.param(formData);   //name=1&address=2
            var formElement = jqForm[0];           //将jqForm转换为DOM对象
            console.log(queryString);
            console.log(formElement);
            //var address = formElement.submitInfo.value;  //访问jqForm的DOM元素

            if(commitStatus) {
                commitStatus=false;
                return true;
            }
            return false;
        }

        function showResponse(response, statusText) {
            console.log(response);
        }
        var options = {
            url:"/acceptFormRequest/formRequest",
            type:'post',
            target:'#registSubmit',//返回的内容更换指定的页面元素的内容 缺省值: null
            beforeSerialize:function () {},//序列化提交数据之前的回调函数
            beforeSubmit : showRequest,//提交之前执行的函数
            uploadProgress:function(event, position, total, percentComplete){},//可以对文件上传进度监听
            success :showResponse ,// 成功后执行的函数
            error:function(){},             //提交失败执行的函数
            dataType:'json',  //服务器返回数据类型
            clearForm:true,  //提交成功后是否清空表单中的字段值
            resetForm:true,   //提交成功后是否重置表单中的字段值,即恢复到页面加载时的状态
            timeout:3000,  //设置请求时间,超过该时间后,自动退出请求,单位(毫秒)。 
            contentType: false, //不设置内容类型  如果上传文件下边的都不要设置
            processData: false, //不处理数据
//  
        };

        /!*
        利用ajaxForm提交
        options:也可以为一个回调函数
         *!/
          $("#registSubmit").ajaxForm(options).submit(function (ev) {
              ev.preventDefault();//阻止表单的提交事件
          });

          /!*
           ajaxForm() 不能主动提交 form(最后还是调用ajaxSubmit),函数只是为提交表单做准备需要以 submit 来触发提交;而 ajaxSubmit() 会主动提交表单,同时可以在点击其他按钮时也可以触发提交,不一定是submit按钮
           *!/
        $("#registSubmit").submit(function (e) {
                $("#registSubmit").ajaxSubmit({
                    type: '', // 提交方式 get/post
                    url: '', // 需要提交的 url
                    data: {
                        'param': ""
                    },
                    success: function(data) {
                        // data 保存提交后返回的数据,一般为 json 数据
                        // 此处可对 data 作相关处理
                        alert('提交成功!' + data);
                    }
                });
            $(this).resetForm(); // 提交后重置表单

            //阻止表单的提交事件,两种方式任选其一
            e.preventDefault();
           // return false;
        });
*/


    </script>

二. 给数据库增加唯一键约束(简单粗暴)
alter table table_name add constraint constraint_name unique(column1, column2,…,column_n);

三、通过后台验证session中的token的方式进行防止重复提交
1.定义FormToken自定义注解类。

import java.lang.annotation.*;

@Inherited //如果一个使用了@Inherited修饰的annotation类型被用于一个class,则这个annotation将被用于该class的子类。
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface FormToken {

    /**
     * 如果该页面需要重复验证产生token,加上该标记 置值为true
     * @return
     */
    boolean save() default false;

    /**
     * 如果需要验证是否是重复提交的请求,加上该标记 置值为true
     * @return
     */
    boolean remove() default false;
}

2.产生token页面的加载

/**
 * @ClassName: RouteFowardController
 * @Author: Clement
 * @Date: 2019/8/24 22:30
 * @Version: 1.0
 * @Description:  路由转发的控制器
 */
@Controller
@RequestMapping("/routeForward")
public class RouteFowardController {
    private final String ONE_PAGE="one";
    private final String TWO_PAGE="two";
    private final String THREE_PAGE="three";

    /**
     * 加入需要生成token标示的注解
     * @param flag
     * @return
     */
    @RequestMapping("/formPageGoto")
    @FormToken(save = true)
    public String formPageGoto(String flag) {
        if (flag.equals(ONE_PAGE)) {
             return "/formSubmitTest1.jsp";
        }else if (flag.equals(TWO_PAGE)) {
            return "/formSubmitTest2.jsp";
        }else if(flag.equals(THREE_PAGE)){
            return "/formSubmitTest3.jsp";
        }else {
            return "/index.jsp";
        }
    }
}

3.验证重复提交的请求的加载

/**
 * @ClassName: AcceptFormRequestController
 * @Author: Clement
 * @Date: 2019/8/24 22:37
 * @Version: 1.0
 * @Description: 接收form表单的控制器
 */
@RestController
@RequestMapping("/acceptFormRequest")
public class AcceptFormRequestController {


    @RequestMapping("/formRequest")
    @FormToken(remove=true)
    public Map processFormRequest(Map params) {
        HashMap<String, Object> map = new HashMap<>();
        System.out.println("接收到的提交数据:"+params);
        map.put("code",200);
        map.put("msg","已经接收到请求");
        return map;
    }
}

4.拦截器的配置类

/**
 * @ClassName: AvoidRepeatableCommitInterceptor
 * @Author: Clement
 * @Date: 2019/8/24 22:01
 * @Version: 1.0
 * @Description: 避免重复提交拦截器
 */
public  class AvoidRepeatCommitInterceptor extends HandlerInterceptorAdapter {

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        if (handler instanceof HandlerMethod) {
            /*
            HandlerMethod
            封装了很多属性,在访问请求方法的时候可以方便的访问到方法、
            方法参数、方法上的注解、所属类等并且对方法参数封装处理,
            也可以方便的访问到方法参数的注解等信息
             */
            HandlerMethod handlerMethod = (HandlerMethod) handler;
            Method method = handlerMethod.getMethod();
            FormToken annotation = method.getAnnotation(FormToken.class);
            if (annotation != null) {
                boolean needSaveSession = annotation.save();
                System.out.println("needSaveSession:"+needSaveSession);

                if (needSaveSession) {
                    request.getSession(false).setAttribute("formToken", UUID.randomUUID().toString());
                }

                boolean needRemoveSession = annotation.remove();
                if (needRemoveSession) {
                    /*
                    调用验证是否是重复提交的方法进行验证
                    重复提交则返回true
                     */
                    if (isRepeatSubmit(request)) {
                        //对拦截的请求作响应
                        response.setCharacterEncoding("UTF-8");
                        response.setContentType("application/json;charset=UTF-8");
                        PrintWriter out = response.getWriter();
                        HashMap<String, Object> map = new HashMap<>();
                        map.put("code",200);
                        map.put("msg","请求为重复请求已经被拦截");
                        JSONObject jsonObject = new JSONObject(map);
                        out.print(jsonObject);
                        return false;
                    }
                    //执行业务操作方法之前,进行session中的token删除,防止页面前进后退的时候进行重复提交和session中的token对比
                    request.getSession(false).removeAttribute("formToken");
                }
            }
            return true;
        }else {
            return super.preHandle(request, response, handler);
        }
    }


    /**
     * 是否是重复提交判断
     * @param request
     * @return
     */
    public boolean isRepeatSubmit(HttpServletRequest request){
        String serverToken = (String) request.getSession(false).getAttribute("formToken");
        //如果第一次提交,则session中的token不为空
        if(serverToken==null) {
            return true;
        }

        String clientToken = request.getParameter("formToken");

        if (clientToken == null) {
            return true;
        }

        //返回给用户的token和session中的token进行值对比,不同则重复提交
        if (!serverToken.equals(clientToken)) {
            return true;
        }
        return false;
    }
}

5.application中记得配置拦截

<!-- 拦截器 -->
	<mvc:interceptors>
		<mvc:interceptor>
			<!-- 拦截器需要拦截的路径  MVC中
            /,所以访问的地址都由DispatcherServlet进行解析(但是经测试,jsp资源不会被这种方式拦截)
            /* 拦截所有的url  不包括.jsp
            /**拦截所有的URL和子路径 (包括静态资源)  spring mvc   重点:但是可以拦截html   html页面发出的请求会被dispatcher处理
                jsp如果不放在WEB-INF文件下,spring mvc是无法拦截的    这种请情况下需要用最原始的servlet的Filter接口,具体可以参照
                http://blog.csdn.net/lsx991947534/article/details/45499205
              注意:jsp放在WEB-INF下才能不被用户访问到  拦截jsp指的是不能  请求  .do  /   -->
			<mvc:mapping path="/**"/>

			<!-- 放行的路径 -->
			<mvc:exclude-mapping path="/js/**"/>  <!-- 放行js文件夹路径下所有路径 -->
			<!-- 配置拦截器的类 -->
			<bean class="com.web.interceptor.AvoidRepeatCommitInterceptor"></bean>
		</mvc:interceptor>
	</mvc:interceptors>

注:使用到的jar和js
在这里插入图片描述

当然还有AOP的方式进行处理。。。。。此处省略一万代码

年轻就要醒着拼!!!

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值