在平时开发中,如果网速比较慢的情况下,用户提交表单后,发现服务器半天都没有响应,那么用户可能会以为是自己没有提交表单,就会再点击提交按钮重复提交表单,我们在开发中必须防止表单重复提交。
1.什么是表单重复提交
> 在不刷新表单页面的前提下: >> 多次点击提交按钮 >> 已经提交成功, 按 "回退" 之后, 再点击 "提交按钮". >> 在控制器响应页面的形式为转发情况下,若已经提交成功, 然后点击 "刷新(F5)"
> 注意: >> 若刷新表单页面, 再提交表单不算重复提交 >> 若使用的是 redirect 的响应类型(地址栏发生变化), 已经提交成功后, 再点击 "刷新", 不是表单的重复提交
2、客户端利用JavaScript防止表单重复提交
既然存在上述所说的表单重复提交问题,那么我们就要想办法解决,比较常用的方法是采用JavaScript来防止表单重复提交,具体做法如下:
修改form.jsp页面,添加如下的JavaScript代码来防止表单重复提交
<%@ page language="java" contentType="text/html; charset=utf-8" pageEncoding="utf-8"%>首页
我们看看使用了JavaScript来防止表单提交重复是否可以成功,运行效果如下:
后退之后按钮不能点击
另外还有一种做法就是提交表单后,将提交按钮隐藏起来,这种做法和将提交按钮设置为不可用是差不多的,个人觉得将提交按钮隐藏影响到页面布局的美观,并且 可能会让用户误以为是bug(怎么我一点击按钮,按钮就不见了呢?用户可能会有这样的疑问),我个人在开发中用得比较多的是表单提交后,将提交按钮设置为 不可用,反正使用JavaScript防止表单重复提交的做法都是差不多的,目的都是让表单只能提交一次,这样就可以做到表单不重复提交了
三、利用Session防止表单重复提交(常用)
在服务器端解决,在服务器端解决就需要用到session了。
具体的做法:在服务器端生成一个唯一的随机标识号,专业术语称为 Token(令牌),同时在当前用户的Session域中保存这个Token。然后将Token发送到客户端的Form表单中,在Form表单中使用隐藏 域来存储这个Token,表单提交的时候连同这个Token一起提交到服务器端,然后在服务器端判断客户端提交上来的Token与服务器端生成的 Token是否一致,如果不一致,那就是重复提交了,此时服务器端就可以不处理重复提交的表单。如果相同则处理表单提交,处理完后清除当前用户的 Session域中存储的标识号。 在下列情况下,服务器程序将拒绝处理用户提交的表单请求:
-
- 存储Session域中的Token(令牌)与表单提交的Token(令牌)不同。
- 当前用户的Session中不存在Token(令牌)。
- 用户提交的表单数据中没有Token(令牌)。
1.产生随机数(令牌)跳转到表单页面的Java
package Session;import java.io.IOException;import java.security.MessageDigest;import java.security.NoSuchAlgorithmException;import java.util.Random;import javax.servlet.ServletException;import javax.servlet.annotation.WebServlet;import javax.servlet.http.HttpServlet;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;import javax.servlet.http.HttpSession;import sun.misc.BASE64Encoder;@WebServlet("/ServletForm_2")public class ServletForm_2 extends HttpServlet { private static final long serialVersionUID = 1L; public ServletForm_2() { super(); } protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { TokenProcessor tp=TokenProcessor.getTp(); String rand=tp.getToken(); HttpSession session=request.getSession(); session.setAttribute("rand", rand); //用session将数据带过去 request.getRequestDispatcher("/SecondForm.jsp").forward(request,response);; } protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { doGet(request, response); }}//令牌处理器,产生一个随机数,单利模式,一个对象产生class TokenProcessor{ //单利模式 /*** * 1.构造方法私有 * 2.创建一个对象 3.公开一份方法暴露对象 */ private TokenProcessor() { } private static TokenProcessor tp=new TokenProcessor(); public static TokenProcessor getTp(){ return tp; } public String getToken(){ //token是系统当前时间毫秒数+随机数变为的字符串。长度不同 String token=System.currentTimeMillis()+new Random().nextInt()+""; //利用MD5摘要算法得到固定长度的字符串 try { MessageDigest md=MessageDigest.getInstance("md5"); //根据MD5算法得到数据的指纹 byte[] md5=md.digest(token.getBytes()); //BASE64编码,3BYTE变为4byte(每6位前面加2零) BASE64Encoder encoder=new BASE64Encoder(); String ss=encoder.encode(md5); return ss; } catch (NoSuchAlgorithmException e) { // TODO Auto-generated catch block throw new RuntimeException(e); } } }
表单JSP:
<%@ page language="java" contentType="text/html; charset=utf-8" pageEncoding="utf-8"%>Insert title here
处理表单的Servlet
package Session;import java.io.IOException;import java.io.PrintWriter;import javax.servlet.ServletException;import javax.servlet.annotation.WebServlet;import javax.servlet.http.HttpServlet;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;import javax.servlet.http.HttpSession;/** * Servlet implementation class lOGIN2 */@WebServlet("/lOGIN2")public class lOGIN2 extends HttpServlet { private static final long serialVersionUID = 1L; /** * @see HttpServlet#HttpServlet() */ public lOGIN2() { super(); // TODO Auto-generated constructor stub } /** * @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse response) */ protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { //判断带过的随机数是否有效 boolean isValied=isTokenValid(request); if(!isValied){System.out.println("无效,请不要重复提交!"); return ;} request.getSession().removeAttribute("rand"); System.out.println("正在提交。。。。。。。。。"); } private boolean isTokenValid(HttpServletRequest request) { //服务器端带随机数 String ser_hid=(String) request.getSession().getAttribute("rand"); //客户端带过来带随机数 String cli_hid=request.getParameter("hid"); System.out.println(ser_hid+" "+cli_hid+" 5"); if(cli_hid==null){ return false;} if(ser_hid==null){ return false;} if(!cli_hid.equals(ser_hid)){ return false;} return true; } /** * @see HttpServlet#doPost(HttpServletRequest request, HttpServletResponse response) */ protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // TODO Auto-generated method stub doGet(request, response); }}
结果: http://localhost:8080/Servlet/ServletForm_2
查看页面源码:
第一次提交:
刷新页面:
后退后提交:
********: struts的防止表单重复提交比较简单,参考: