如何避免表单的重复提交
当网络比较阻塞或者访问量比较多的情况下,用户就会不停地点击表单的提交按钮,造成表单的重复提交。我们在开发web应用时,如何来避免这种情况的出现呢?下面提供三种方法:
1.在客户端用Javascript脚本语言,当用户再次点击提交按钮时,提示“已提交,请稍等”。
核心代码如下:
script type="text/javascript">
var checkSubmitFlag = true;
function checkSubmit(){
if(checkSubmitFlag ==true){
document.theForm.submit();
checkSubmitFlag = false;
}
else{
alert("你已经提交了表单,请稍等!");
}
}
</script>
其中 “theForm”为表单的name属性。
2.同样在客户端使用js脚本语言,当用户点击了提交按钮时,按钮就变灰,不能再次点击。
script type="text/javascript">
var checkSubmitFlag = true;
function checkSubmit(){
if(checkSubmitFlag ==true){
document.theForm.btnSubmit.disabled = true; //提交按钮变灰
document.theForm.submit();
checkSubmitFlag = false;
}
}
</script>
其中 "btnSubmit" 为提交按钮的name属性
3.服务器端避免表单重复提交
ps:在客户端通过javascript脚本控制表单的重复提交时,存在不足之处。即当显示提交成功后,浏览器如果单击“刷新按钮”,将导致表单再次提交;或者浏览器单击“后退”按钮后,也会导致表单的重复提交。为了解决上述的不足,可以在服务器端编写相应的程序避免表单重复提交,用servlet技术。
基本原理:
jsp页面部分内容是由服务器端程序TokenProcessor动态产生,具体过程就是 TokenProcessor 会为每次产生的包含Form表单页面,产生一个唯一的随机标识号,该标识号不仅要设置成隐蔽域中的值,同时海保存到Session域。
当浏览者提交Form表单时,HandlerServlet程序会比较隐藏域中的值与Session域中的值,如果相同就处理表单数据,处理后就清除当前Session域中存储的标识号,如果不相同就忽略表单请求。当重复提交相同Form表单页面时,当前Session域中不存在相应的表单标识号。
(1)首先创建一个产生令牌的服务器端程序 TokenProcessor.java 。
package com.ldm.servlet;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
public class TokenProcessor {
static final String TOKEN_KEY = "org.sunxin.token";
private static TokenProcessor instance = new TokenProcessor();
// 获得该类的实例
public static TokenProcessor getInstance() {
return instance;
}
private long previous; // 最近一次生成令牌值的时间戳
// 判断请求参数中的令牌值是否有效
public synchronized boolean isTokenValid(HttpServletRequest request) {
HttpSession session = request.getSession(false);
if (session == null) {
return false;
}
// 从session中取出保存的令牌值
String saved = (String) session.getAttribute(TOKEN_KEY);
System.out.println("session中取出的令牌值:" + saved);
if (saved == null) {
return false;
}
// resetToken(request);//清除session中的令牌值
String token = (String) request.getParameter(TOKEN_KEY);
System.out.println("request中取出的令牌值:" + token);
if (token == null) {
return false;
}
return saved.equals(token);
}
// 清除session中的令牌值
public synchronized void resetToken(HttpServletRequest request) {
HttpSession session = request.getSession();
if (session == null) {
return;
}
session.removeAttribute(TOKEN_KEY);
}
// 产生一个新的令牌值,保存到session中
public synchronized void saveToken(HttpServletRequest request) {
HttpSession session = request.getSession();
String token = generateToken(request);
if (token != null) {
session.setAttribute(TOKEN_KEY, token);
}
}
// 根据用户回话ID和当前的系统时间生成一个唯一的令牌
public synchronized String generateToken(HttpServletRequest request) {
HttpSession session = request.getSession();
try {
byte id[] = session.getId().getBytes();
long current = System.currentTimeMillis();
if (current == previous) {
current++;
}
previous = current;
byte now[] = new Long(current).toString().getBytes();
MessageDigest md;
md = MessageDigest.getInstance("MD5");
md.update(id);
md.update(now);
return toHex(md.digest());
} catch (NoSuchAlgorithmException e) {
// TODO Auto-generated catch block
e.printStackTrace();
return null;
}
}
// 将一个字节数组转换为一个十六进制数字的字符串
private String toHex(byte buffer[]) {
StringBuffer sb = new StringBuffer(buffer.length * 2);
for (int i = 0; i < buffer.length; i++) {
sb.append(Character.forDigit((buffer[i] & 0xf0) >> 4, 16));
sb.append(Character.forDigit((buffer[i] & 0x0f), 16));
}
return sb.toString();
}
// 从session中得到令牌值,如果session中没有令牌值,则生成一个新的令牌值
public synchronized String getToken(HttpServletRequest request) {
HttpSession session = request.getSession();
if (session == null) {
return null;
}
String token = (String) session.getAttribute(TOKEN_KEY);
if (token == null) {
token = generateToken(request);
}
session.setAttribute(TOKEN_KEY, token);
return token;
}
}
(2)创建一个jsp页面,提交表单--login.jsp
<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%>
<%@ include file="header.jsp" %> //封装了request.getContextPath
<%@ page import="com.ldm.servlet.TokenProcessor" %>
<%
String path = request.getContextPath();
String basePath = request.getScheme()+"://"+request.getServerName()+":"+request.getServerPort()+path+"/";
%>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<base href="<%=basePath%>">
<title>My JSP 'login.jsp' starting page</title>
<meta http-equiv="pragma" content="no-cache">
<meta http-equiv="cache-control" content="no-cache">
<meta http-equiv="expires" content="0">
<meta http-equiv="keywords" content="keyword1,keyword2,keyword3">
<meta http-equiv="description" content="This is my page">
</head>
<body>
<%
//获取令牌类实例
TokenProcessor processor = TokenProcessor.getInstance();
//获取令牌值
String token = processor.getToken(request);
%>
<form action="${ctx}/servlet/handle" name="theForm" method="post">
<table>
<tr>
<td>用户名:</td>
<td><input type="text" name="username"/></td>
</tr>
<tr>
<td>密码:</td>
<td>
<input type="password" name="password"/>
<%--设置隐藏域,其值为令牌值--%>
<input type="hidden" name="org.sunxin.token" value="<%=token%>"/>
</td>
</tr>
<tr>
<td>
<input type="reset" value="重设">
</td>
<td>
<input type="submit" value="提交" name="btnSubmit" >
</td>
</tr>
</table>
</form>
</body>
</html>
(3)创建一个处理提交表单的servlet程序,HandlerServlet.java 。
package com.ldm.servlet;
import java.io.IOException;
import java.io.PrintWriter;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
public class HandleServlet extends HttpServlet {
public HandleServlet() {
super();
}
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
doPost(request, response);
}
public void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
response.setContentType("text/html;charset=utf-8");
// HttpSession session = request.getSession();
// System.out.println(session.getAttribute("org.sunxin.token"));
System.out.println("````````````````````````````");
PrintWriter out = response.getWriter(); // 获取输出流
TokenProcessor processor = TokenProcessor.getInstance();
System.out.println("processor.isTokenValid(request):" + processor.isTokenValid(request));
if(!processor.isTokenValid(request)){
processor.saveToken(request);
out.println("你已经提交了表单,同一表单不能提交两次哦,亲~~");
}else{
out.println("success");
processor.resetToken(request);
}
}//http://wjheye.iteye.com/blog/1035964
}
(4)web.xml servlet配置如下:
<?xml version="1.0" encoding="UTF-8"?>
<web-app version="2.5"
xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">
<servlet>
<description>This is the description of my J2EE component</description>
<display-name>This is the display name of my J2EE component</display-name>
<servlet-name>HandleServlet</servlet-name>
<servlet-class>com.ldm.servlet.HandleServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>HandleServlet</servlet-name>
<url-pattern>/servlet/handle</url-pattern>
</servlet-mapping>
<welcome-file-list>
<welcome-file>index.jsp</welcome-file>
</welcome-file-list>
</web-app>