使用Session防止表单重复提交

转载自  JavaWeb学习总结(十三)——使用Session防止表单重复提交

 在平时开发中,如果网速比较慢的情况下,用户提交表单后,发现服务器半天都没有响应,那么用户可能会以为是自己没有提交表单,就会再点击提交按钮重复提交表单,我们在开发中必须防止表单重复提交。

一、表单重复提交的常见应用场景

有如下的form.jsp页面

<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%>
<!DOCTYPE HTML>
<html><head><title>Form表单</title></head><body><form action="${pageContext.request.contextPath}/servlet/DoFormServlet" method="post">用户名:<input type="text" name="username"><input type="submit" value="提交" id="submit"></form></body>
</html>

form表单提交到DoFormServlet进行处理

package xdp.gacl.session;import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;public class DoFormServlet extends HttpServlet {public void doGet(HttpServletRequest request, HttpServletResponse response)throws ServletException, IOException {//客户端是以UTF-8编码传输数据到服务器端的,所以需要设置服务器端以UTF-8的编码进行接收,否则对于中文数据就会产生乱码request.setCharacterEncoding("UTF-8");String userName = request.getParameter("username");try {//让当前的线程睡眠3秒钟,模拟网络延迟而导致表单重复提交的现象Thread.sleep(3*1000);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("向数据库中插入数据:"+userName);}public void doPost(HttpServletRequest request, HttpServletResponse response)throws ServletException, IOException {doGet(request, response);}}
如果没有进行form表单重复提交处理,那么在网络延迟的情况下下面的操作将会导致form表单重复提交多次


1.1、场景一:在网络延迟的情况下让用户有时间点击多次submit按钮导致表单重复提交

  演示动画如下所示:



1.2、场景二:表单提交后用户点击【刷新】按钮导致表单重复提交

演示动画如下所示:


点击浏览器的刷新按钮,就是把浏览器上次做的事情再做一次,因为这样也会导致表单重复提交。


1.3、场景三:用户提交表单后,点击浏览器的【后退】按钮回退到表单页面后进行再次提交

演示动画如下所示:


二、利用JavaScript防止表单重复提交

  既然存在上述所说的表单重复提交问题,那么我们就要想办法解决,比较常用的方法是采用JavaScript来防止表单重复提交,具体做法如下:

修改form.jsp页面,添加如下的JavaScript代码来防止表单重复提交

<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%>
<!DOCTYPE HTML>
<html><head><title>Form表单</title><script type="text/javascript">var isCommitted = false;//表单是否已经提交标识,默认为falsefunction dosubmit(){if(isCommitted==false){isCommitted = true;//提交表单后,将表单是否已经提交标识设置为truereturn true;//返回true让表单正常提交}else{return false;//返回false那么表单将不提交}}</script></head><body><form action="${pageContext.request.contextPath}/servlet/DoFormServlet" οnsubmit="return dosubmit()" method="post">用户名:<input type="text" name="username"><input type="submit" value="提交" id="submit"></form></body>
</html>

 我们看看使用了JavaScript来防止表单提交重复是否可以成功,运行效果如下:


可以看到,针对"在网络延迟的情况下让用户有时间点击多次submit按钮导致表单重复提交"这个应用场景,使用JavaScript是可以解决这个问题的,解决的做法就是"用JavaScript控制Form表单只能提交一次"。

  除了用这种方式之外,经常见的另一种方式就是表单提交之后,将提交按钮设置为不可用,让用户没有机会点击第二次提交按钮,代码如下:

function dosubmit(){//获取表单提交按钮var btnSubmit = document.getElementById("submit");//将表单提交按钮设置为不可用,这样就可以避免用户再次点击提交按钮btnSubmit.disabled= "disabled";//返回true让表单可以正常提交return true;
}

运行效果如下:


另外还有一种做法就是提交表单后,将提交按钮隐藏起来,这种做法和将提交按钮设置为不可用是差不多的,个人觉得将提交按钮隐藏影响到页面布局的美观,并且可能会让用户误以为是bug(怎么我一点击按钮,按钮就不见了呢?用户可能会有这样的疑问),我个人在开发中用得比较多的是表单提交后,将提交按钮设置为不可用,反正使用JavaScript防止表单重复提交的做法都是差不多的,目的都是让表单只能提交一次,这样就可以做到表单不重复提交了。

  使用JavaScript防止表单重复提交的做法只对上述提交到导致表单重复提交的三种场景中的【场景一】有效,而对于【场景二】和【场景三】是没有用,依然无法解决表单重复提交问题。

三、利用Session防止表单重复提交

  对于【场景二】和【场景三】导致表单重复提交的问题,既然客户端无法解决,那么就在服务器端解决,在服务器端解决就需要用到session了。

  具体的做法:在服务器端生成一个唯一的随机标识号,专业术语称为Token(令牌),同时在当前用户的Session域中保存这个Token。然后将Token发送到客户端的Form表单中,在Form表单中使用隐藏域来存储这个Token,表单提交的时候连同这个Token一起提交到服务器端,然后在服务器端判断客户端提交上来的Token与服务器端生成的Token是否一致,如果不一致,那就是重复提交了,此时服务器端就可以不处理重复提交的表单。如果相同则处理表单提交,处理完后清除当前用户的Session域中存储的标识号。
  在下列情况下,服务器程序将拒绝处理用户提交的表单请求:

  1. 存储Session域中的Token(令牌)与表单提交的Token(令牌)不同。
  2. 当前用户的Session中不存在Token(令牌)
  3. 用户提交的表单数据中没有Token(令牌)

看具体的范例:

  1.创建FormServlet,用于生成Token(令牌)和跳转到form.jsp页面

package xdp.gacl.session;import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;public class FormServlet extends HttpServlet {private static final long serialVersionUID = -884689940866074733L;public void doGet(HttpServletRequest request, HttpServletResponse response)throws ServletException, IOException {String token = TokenProccessor.getInstance().makeToken();//创建令牌System.out.println("在FormServlet中生成的token:"+token);request.getSession().setAttribute("token", token);  //在服务器使用session保存token(令牌)request.getRequestDispatcher("/form.jsp").forward(request, response);//跳转到form.jsp页面}public void doPost(HttpServletRequest request, HttpServletResponse response)throws ServletException, IOException {doGet(request, response);}}

2.在form.jsp中使用隐藏域来存储Token(令牌)

<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<title>form表单</title>
</head><body><form action="${pageContext.request.contextPath}/servlet/DoFormServlet" method="post"><%--使用隐藏域存储生成的token--%><%--<input type="hidden" name="token" value="<%=session.getAttribute("token") %>">--%><%--使用EL表达式取出存储在session中的token--%><input type="hidden" name="token" value="${token}"/> 用户名:<input type="text" name="username"> <input type="submit" value="提交"></form>
</body>
</html>

3.DoFormServlet处理表单提交

package xdp.gacl.session;import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;public class DoFormServlet extends HttpServlet {public void doGet(HttpServletRequest request, HttpServletResponse response)throws ServletException, IOException {boolean b = isRepeatSubmit(request);//判断用户是否是重复提交if(b==true){System.out.println("请不要重复提交");return;}request.getSession().removeAttribute("token");//移除session中的tokenSystem.out.println("处理用户提交请求!!");}/*** 判断客户端提交上来的令牌和服务器端生成的令牌是否一致* @param request* @return *         true 用户重复提交了表单 *         false 用户没有重复提交表单*/private boolean isRepeatSubmit(HttpServletRequest request) {String client_token = request.getParameter("token");//1、如果用户提交的表单数据中没有token,则用户是重复提交了表单if(client_token==null){return true;}//取出存储在Session中的tokenString server_token = (String) request.getSession().getAttribute("token");//2、如果当前用户的Session中不存在Token(令牌),则用户是重复提交了表单if(server_token==null){return true;}//3、存储在Session中的Token(令牌)与表单提交的Token(令牌)不同,则用户是重复提交了表单if(!client_token.equals(server_token)){return true;}return false;}public void doPost(HttpServletRequest request, HttpServletResponse response)throws ServletException, IOException {doGet(request, response);}}

 生成Token的工具类TokenProccessor

package xdp.gacl.session;import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Random;
import sun.misc.BASE64Encoder;public class TokenProccessor {/**单例设计模式(保证类的对象在内存中只有一个)*1、把类的构造函数私有*2、自己创建一个类的对象*3、对外提供一个公共的方法,返回类的对象*/private TokenProccessor(){}private static final TokenProccessor instance = new TokenProccessor();/*** 返回类的对象* @return*/public static TokenProccessor getInstance(){return instance;}/*** 生成Token* Token:Nv6RRuGEVvmGjB+jimI/gw==* @return*/public String makeToken(){  //checkException//  7346734837483  834u938493493849384  43434384String token = (System.currentTimeMillis() + new Random().nextInt(999999999)) + "";//数据指纹   128位长   16个字节  md5try {MessageDigest md = MessageDigest.getInstance("md5");byte md5[] =  md.digest(token.getBytes());//base64编码--任意二进制编码明文字符   adfsdfsdfsfBASE64Encoder encoder = new BASE64Encoder();return encoder.encode(md5);} catch (NoSuchAlgorithmException e) {throw new RuntimeException(e);}}
}

 首先访问FormServlet,在FormServlet中生成Token之后再重定向到form.jsp页面,这次是在服务器端处理表单重复提交的,运行效果如下:


从运行效果中可以看到,通过这种方式处理表单重复提交,可以解决上述的场景二和场景三中出现的表单重复提交问题。


本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/328566.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

如果你也会C#,那不妨了解下F#(5):模块、与C#互相调用

F# 项目 在之前的几篇文章介绍的代码都在交互窗口&#xff08;fsi.exe&#xff09;里运行&#xff0c;但平常开发的软件程序可能含有大类类型和函数定义&#xff0c;代码不可能都在一个文件里。下面我们来看VS里提供的F#项目模板。 F#项目模板有以下几种类型&#xff08;以VS20…

用户模块开发 分类模块 商品模块 购物车模块

分类表 https://openhome.alipay.com/platform/appDaily.htm?tabaccount 沙箱 沙箱环境使用说明 https://docs.open.alipay.com/200/105311 https://docs.open.alipay.com/200/105311 当面付 app都有文档在最下面 https://docs.open.alipay.com/204/105297 app集成文…

hbase 单机连接hadoop_Hadoop、Hbase单机环境安装

1. Hadoop安装1.1 HDFS配置fs.defaultFShdfs://localhost:9000hadoop.tmp.dirfile:/home/local/data/hadoop/tmpdfs.replication1dfs.namenode.name.dirfile:/home/local/data/hadoop/tmp/dfs/namedfs.datanode.data.dirfile:/home/local/data/hadoop/tmp/dfs/data编辑Hadoop下…

高效的SQLSERVER分页查询

Sqlserver数据库分页查询一直是Sqlserver的短板&#xff0c;闲来无事&#xff0c;想出几种方法&#xff0c;假设有表ARTICLE,字段ID、YEAR...(其他省略)&#xff0c;数据53210条(客户真实数据&#xff0c;量不大)&#xff0c;分页查询每页30条&#xff0c;查询第1500页&#xf…

Java中classLoader浅析

转载自 Java中classLoader浅析本文为在公司内部TD上写的一篇小文, 主要讲解java中classLoader基础知识, 现在拿来这里分享一下. 一、问题 请在Eclipse中新建如下类&#xff0c;并运行它&#xff1a; package java.lang;public class Long {public static void main(String[] …

微软BUG Bounty悬赏项目扩展至.NET Core和ASP.NET Core

微软宣布自 2016 年 9 月 1 日开始将 .NET Core 和 ASP.NET 纳入到 BUG Bounty 悬赏项目范围内&#xff0c;微软将会为 Windows 和 Linux 平台上的两个编程代码提供漏洞悬赏。 微软公布的细节中写道&#xff1a; 微软将会为、最新微软 .NET Core 和 ASP.NET Core 的最新 RTM 版…

计算字典的个数_[LeetCode] 440. 字典序的第K小数字

题目链接&#xff1a; https://leetcode-cn.com/problems/k-th-smallest-in-lexicographical-order难度&#xff1a;困难通过率&#xff1a;28.4%题目描述:给定整数 n 和 k&#xff0c;找到 1 到 n 中字典序第 k 小的数字。注意&#xff1a;1 ≤ k ≤ n ≤ 109。示例:**输入:**…

ASP.NET Core开发-Docker部署运行

ASP.NET Core开发Docker部署&#xff0c;.NET Core支持Docker 部署运行。我们将ASP.NET Core 部署在Docker 上运行。 大家可能都见识过Docker &#xff0c;今天我们就详细了解一下Docker的用途&#xff0c;以及真实的应用场景。 Docker源于PaaS&#xff0c;PaaS的应用场景即是D…

java异常捕获的一点感悟

转载自 java异常捕获的一点感悟 class Annoyance extends Exception {} class Sneeze extends Annoyance {} class Human { public static void main(String[] args) throws Exception { try { try { throw new Sneeze(); } catch ( Annoyance a ) { System.out…

android修改机型cpu,mac,androidid....

https://blog.csdn.net/yyy_bbb_lll/article/details/80734881 android 改机&#xff0c;抹机工具开发 一 2018年06月19日 16:43:40 阅读数&#xff1a;865 本工具以Xposed框架为基础&#xff0c;实现了改机软件所需的大部分功能。先贴界面图&#xff1a; 设备基本信息的显示…

data layui table 排序_浅谈layui中table的sort排序

table模块是layui框架最核心的组成之一&#xff0c;它用于对表格进行一些列功能和动态化数据操作&#xff0c;本文介绍了layui中table的sort排序&#xff0c;解决了在我们使用sort排序时可能遇到的一些问题。今天来谈谈table sort的那点事。预告一下&#xff0c;目的是做到前台…

gRPC C#学习

前些天gRPC 发布1.0 版本&#xff0c;代表着gRPC 已经正式进入稳定阶段。 今天我们就来学习gRPC C# 。而且目前也已经支持.NET Core 可以实现完美跨平台。 传统的.NET 可以通过Mono 来实现跨平台调用。 GitHub&#xff1a; https://github.com/grpc/grpc gRPC 简单介绍&#x…

Java日期及时间库插件 -- Joda Time.

转载自 Java日期及时间库插件 -- Joda Time.来到新公司工作也有一个多月了, 陆陆续续做了一些简单的项目. 今天做一个新东西的时候发现了 Joda Time的这个东西, 因为以前用的都是JDK原生的时间处理API, 大家都知道Java原生的时间处理的API一直都是不太好用, 所以这个有必要去…

电商校招指导

https://www.imooc.com/article/19998 https://www.imooc.com/article/18998 鼓励大家多总结 有道笔记可以记录下来 多年总结的 平时要多总结 https://www.imooc.com/article/19094 温馨tips&#xff1a; 校招的要求会比社招低很多&#xff0c;大家要把握好机会。因为校招…

大咖开讲:一小时学会.NET MVC开发的那些事儿

许多ASP.NET开发人员开始接触MVC认为MVC与ASP.NET完全没有关系&#xff0c;是一个全新的Web开发&#xff0c;事实上ASP.NET是创建WEB应用的框架&#xff0c;而MVC是能够用更好的方法来组织并管理代码的一种更高级架构体系。我们可将原来的ASP.NET称为ASP.NET Webform&#xff0…

HashMap的实现原理及其特点

HashMap的实现原理及其特点 2018年03月15日 20:43:08 阅读数&#xff1a;11045更多 个人分类&#xff1a; Java基础知识点 版权声明&#xff1a;本文为博主原创文章&#xff0c;未经博主允许不得转载。 https://blog.csdn.net/lovewebeye/article/details/79573702 1) Hash…

重写equals就必须重写hashCode的原理分析

转载自 重写equals就必须重写hashCode的原理分析因为最近在整理Java集合的源码&#xff0c; 所以今天再来谈谈这个古老的话题&#xff0c;因为后面讲HashMap会用到这个知识点&#xff0c; 所以重新梳理下。如果不被重写&#xff08;原生Object&#xff09;的hashCode和equals是…

mysql unique count_MySQL - Count Number of Unique Values

问题If I have three columns:orderNumber, name, emailand I would like to count how many unique emails are in the table how would I go about doing so?A statement like:SELECT count(email) FROM ordersgives me the total count.I tried SELECT DISTINCT count(emai…

如何在 ASP.NET MVC 中集成 AngularJS(2)

在如何在 ASP.NET MVC 中集成 AngularJS(1)中&#xff0c;我们介绍了 ASP.NET MVC 捆绑和压缩、应用程序版本自动刷新和工程构建等内容。 下面介绍如何在 ASP.NET MVC 中集成 AngularJS 的第二部分。 ASP.NET 捆绑和压缩 CSS 和 JavaScript 的捆绑与压缩功能是 ASP.NET MVC 最流…

bean交个spring和new比较区别

在spring的配置文件中我们要使用DataSource这个对吧 这个bean将会被多个bean引用 通过ref"dateSource"这个引用 当我们要修改这个只要修改引用就好了 不需要修改很多了 主要是解耦 比如你有一个A类 在好几个类里边要调用到A的方法 new的话就要在每个类里都new…