web--防止表单数据重复提交方法大全

防止重复提交

对于重复提交的问题,也称为“幂等”问题
幂等:F(F(X)) = F(X)多次运算结果一致;简单点说就是对于完全相同的操作,操作一次与操作多次的结果是一样的。

表单重复提交的场景

1.场景一:服务端未能及时响应结果(网络延迟,并发排队等因素),导致前端页面没有及时刷新,用户有机会多次提交表单
2.场景二:提交表单成功之后用户再次点击刷新按钮导致表单重复提交
3.场景三:提交表单成功之后点击后退按钮回退到表单页面再次提交
4.场景四:使用浏览器历史记录重复提交表单;
5.场景五:浏览器重复的HTTP请;
6.场景六:nginx重发等情况;
7.场景七:分布式RPC的try重发等点击提交按钮两次;

如何避免重复提交表单

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

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

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

 <script type="text/javascript">
    // 设置表单提交标志
    var submit = false;
    function checkSubmit() {
        if(!submit) {
            // 表单提交后设置标志位
            submit = true;
            return true;
        }
        // 表单已经提交,不允许再次提交
        console.log("请不要重复提交表单!");
        return false;
    }
</script>

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

 <script type="text/javascript">
    function disabledSubmit() {
        // 在提交按钮第一次执行之后就disabled掉,避免重复提交
        document.getElementById("submitBtn").disabled= "disabled";
        return true;
    }
</script>

当然,还可以直接在提一次提交之后将按钮隐藏掉。但是,是否需要这样做,需要考虑用户的操作体验是不是可以接受。
在前端拦截虽然可以解决场景一的表单重复提交问题,但是针对场景二(刷新)和场景三(后退重新提交)的表单重复提交是无能为力的。

2.在服务器端对表单重复提交进行拦截

1、在服务器端拦截表单重复提交的请求,实际上是通过在服务端保存一个token来实现的,而且这个在服务端保存的token需要通过前端传递,基本流程是在表单、session、token 放入唯一标识符(如:UUID),每次操作时,保存标识一定时间后移除,保存期间有相同的标识就不处理或提示。
分三步走:
第一步:访问页面时在服务端保存一个随机token,这里保存在session

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;
    }
}

也就是说,对于拦截表单重复提交的终极解决方案是在服务器端进行拦截!不过,考虑到用户操作体验的问题,可能需要同时在前端进行拦截,这可以根据具体的产品设计而定。
2、使用Post/Redirect/Get模式。
在提交后执行页面重定向,这就是所谓的 Post-Redirect-Get (PRG)模式。简言之,当用户提交了表单后,你去执行一个客户端的重定向,转到提交成功信息页面。
这能避免用户按F5导致的重复提交,而其也不会出现浏览器表单重复提交的警告,也能消除按浏览器前进和后退按导致的同样问题。
缺点:体验不好,适用部分场景,若是遇到网络问题,还是会出现重复提交问题
3、表单过期的处理
在开发过程中,经常会出现表单出错而返回页面的时候填写的信息全部丢失的情况,为了支持页面回跳,可以通过以下两种方法实现。
1.使用header头设置缓存控制头Cache-control。

header('Cache-control: private, must-revalidate'); //支持页面回跳

2.使用session_cache_limiter方法。
session_cache_limiter(‘private, must-revalidate’); //要写在session_start方法之前
下面的代码片断可以防止用户填写表单的时候,单击“提交”按钮返回时,刚刚在表单上填写的内容不会被清除:

session_cache_limiter('nocache');
session_cache_limiter('private');
session_cache_limiter('public');
session_start();
  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值