zoukankan      html  css  js  c++  java
  • 防止Web表单重复提交的方法总结

    在Web开发中,对于处理表单重复提交是经常要面对的事情。那么,存在哪些场景会导致表单重复提交呢?表单重复提交会带来什么问题?有哪些方法可以避免表单重复提交?
    Web表单重复提交

    表单重复提交的场景

    1.场景一:服务端未能及时响应结果(网络延迟,并发排队等因素),导致前端页面没有及时刷新,用户有机会多次提交表单
    用户重复提交表单

    2.场景二:提交表单成功之后用户再次点击刷新按钮导致表单重复提交
    提交成功之后刷新重复提交

    3.场景三:提交表单成功之后点击后退按钮回退到表单页面再次提交
    回退到表单页面再次提交

    表单重复提交的弊端

    下面通过一个简单的示例进行说明。

    • 表单页面: test-form-submit-repeat.jsp
    <%@ page contentType="text/html;charset=UTF-8" language="java" %>
    <html>
    <head>
        <title>处理表单重复提交</title>
    </head>
    <body>
    
        <form action="<%=request.getContextPath()%>/formServlet.do" method="post">
            姓名:<input type="text" name="username" />
            <input type="submit" value="提交">
        </form>
    </body>
    </html>
    
    • 后台Serlvet:FormServlet.java
    public class FormServlet extends HttpServlet{
        @Override
        protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
            this.doPost(req, resp);
        }
    
        @Override
        protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
            req.setCharacterEncoding("UTF-8");
            String userName = req.getParameter("username");
    
            try {
                // 模拟服务端处理效率慢
                Thread.sleep(3 * 1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
    
            System.out.println("插入数据:" + userName);
        }
    }
    

    实验表单重复提交结果:
    实验表单重提交

    显然,从演示结果来看,如果出现表单重复提交,将会导致相同的数据被重复插入到数据库中。实际上,这是不应该发生的。

    如何避免重复提交表单

    关于解决表单重复提交,分为在前端拦截和服务端拦截2种方式。

    1.在前端对表单重复提交进行拦截

    在前端拦截表单重复提交可以通过多种方式实现:
    (1)通过设置变量标志位进行拦截

    <%@ page contentType="text/html;charset=UTF-8" language="java" %>
    <html>
    <head>
        <title>处理表单重复提交</title>
    </head>
    <body>
        <form action="<%=request.getContextPath()%>/formServlet.do" method="post" onsubmit="return checkSubmit();">
            姓名:<input type="text" name="username" />
            <input type="submit" value="提交">
        </form>
    </body>
    <script type="text/javascript">
        // 设置表单提交标志
        var submit = false;
        function checkSubmit() {
            if(!submit) {
                // 表单提交后设置标志位
                submit = true;
                return true;
            }
            // 表单已经提交,不允许再次提交
            console.log("请不要重复提交表单!");
            return false;
        }
    </script>
    </html>
    

    在前端设置变量标志拦截表单重复提交

    (2)通过禁用按钮进行拦截
    除了在前端通过设置标志位进行拦截之外,还可以在表单提交之后将按钮disabled掉,这样就彻底阻止了表单被重复提交的可能。

    <%@ page contentType="text/html;charset=UTF-8" language="java" %>
    <html>
    <head>
        <title>处理表单重复提交</title>
    </head>
    <body>
        <form action="<%=request.getContextPath()%>/formServlet.do" method="post" onsubmit="return disabledSubmit();">
            姓名:<input type="text" name="username" />
            <input id="submitBtn" type="submit" value="提交">
        </form>
    </body>
    <script type="text/javascript">
        function disabledSubmit() {
            // 在提交按钮第一次执行之后就disabled掉,避免重复提交
            document.getElementById("submitBtn").disabled= "disabled";
            return true;
        }
    </script>
    </html>
    

    提交表单之后disabled掉按钮

    当然,还可以直接在提一次提交之后将按钮隐藏掉。但是,是否需要这样做,需要考虑用户的操作体验是不是可以接受。

    在前端拦截虽然可以解决场景一的表单重复提交问题,但是针对场景二(刷新)和场景三(后退重新提交)的表单重复提交是无能为力的。
    前端拦截只对场景一有效

    2.在服务器端对表单重复提交进行拦截
    在服务器端拦截表单重复提交的请求,实际上是通过在服务端保存一个token来实现的,而且这个在服务端保存的token需要通过前端传递,分三步走:

    第一步:访问页面时在服务端保存一个随机token

    public class FormServlet extends HttpServlet{
        @Override
        protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
            UUID uuid = UUID.randomUUID();
            String token = uuid.toString().replaceAll("-", "");
            // 访问页面时随机生成一个token保存在服务端session中
            req.getSession().setAttribute("token", token);
            req.getRequestDispatcher("/test-form-submit-repeat.jsp").forward(req, resp);
        }
    
        @Override
        protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
           doGet(req, resp);
        }
    }
    

    随机token的产生可以使用任何恰当的方式,在这里通过UUID产生。

    第二步:将服务端端保存的随机token通过前端页面传递

    <%@ page contentType="text/html;charset=UTF-8" language="java" %>
    <html>
    <head>
        <title>处理表单重复提交</title>
    </head>
    <body>
        <form action="<%=request.getContextPath()%>/doFormServlet.do" method="post">
            <!-- 隐藏域保存服务端token -->
            <input type="hidden" name="token" value="<%=session.getAttribute("token")%>" />
            姓名:<input type="text" name="username" />
            <input id="submitBtn" type="submit" value="提交">
        </form>
    </body>
    </html>
    

    第三步:提交表单时在服务端通过检查token来判断是否为重复提交的表单请求

    public class DoFormServlet extends HttpServlet{
        @Override
        protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
            doPost(req, resp);
        }
    
        @Override
        protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
            req.setCharacterEncoding("UTF-8");
    
            if(checkRepeatSubmit(req)) {
                System.out.println("请不要重复提交!");
                return;
            }
            
            // 在第一次处理表单之后需要清空token,这一步非常关键
            req.getSession().removeAttribute("token");
    
            String userName = req.getParameter("username");
            try {
                // 模拟服务端处理效率慢
                Thread.sleep(3 * 1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
    
            System.out.println("插入数据:" + userName);
        }
    
        // 检查表单是否为重复提交
        private boolean checkRepeatSubmit(HttpServletRequest req) {
            Object sessionTokenObj = req.getSession().getAttribute("token");
            if(sessionTokenObj == null) {
                // 表单重复提交
                System.out.println("Session token is NULL!");
                return true;
            }
    
            String paramToken = req.getParameter("token");
            if(paramToken == null) {
                // 非法请求
                System.out.println("Parameter token is NULL!");
                return true;
            }
    
            if(!paramToken.equals(sessionTokenObj.toString())) {
                // 非法请求
                System.out.println("Token not same");
                return true;
            }
            return false;
        }
    }
    

    在服务端拦截表单重复提交

    显然,通过在服务端保存token的方式拦截场景二和场景三的表单重复提交是非常有效的。而且,这种方式同样可以拦截场景一的表单重复提交。
    通过服务器端拦截的方式解决场景一的表单重复提交

    也就是说,对于拦截表单重复提交的终极解决方案是在服务器端进行拦截!不过,考虑到用户操作体验的问题,可能需要同时在前端进行拦截,这可以根据具体的产品设计而定。
    在服务端拦截表单重复提交的原理

    另外,有意思的是:在最新的Firefox浏览版本(Firefox Quantum 59.0.1 64位)中,浏览器自己就能处理场景一的表单重复提交(但是不能处理场景二和场景三的表单重复提交)。
    火狐浏览器处理表单重复提交
    经过验证,在最新版的Chrome(Chrome 65.0.3325.181)浏览器中还不具备这个功能。

  • 相关阅读:
    配置管理puppet
    ruby安装
    angularjs 安装篇
    idea 快捷键
    rabbitmq java queue
    spring cloud bus rabbitmq
    rabbitmq 安装篇
    spring cloud eureka
    spring cloud config
    postgre 导入sql文件
  • 原文地址:https://www.cnblogs.com/nuccch/p/8622022.html
Copyright © 2011-2022 走看看