當使用者在表單中填寫完資訊,單擊“送出”按鈕後,可能會因為沒有看到成功資訊而再次單擊“送出”按鈕,進而導緻在服務端接收到兩條同樣的資訊,如果這個資訊是要儲存到資料庫裡的,那麼就會出現兩條相同的資訊,而這往往往會引起資料庫異常,對整個系統的穩定運作會産生緻命的危害。在實際應用中,由于使用者沒有及時看到響應資訊而導緻的重複送出時有發生。響應不及時有可能是因為這個時段伺服器的負載較大,又或者這個處理本身就是比較耗時的操作。
有時候,即使響應及時,也有可能會出現重複送出的情況。伺服器端的程式在處理完使用者送出的資訊後,調用了RequestDispatcher.forward()方法将使用者的請求轉發給成功頁面,使用者看到成功資訊後,單了浏覽器的“重新整理”按鈕,此時浏覽器會再次送出使用者先前輸入的資料,這是因為調用了RequestDispatcher.forward()方法,浏覽器所保留的URL是先前表單送出的URL,如果是采用了RequestDispatcher.sendRedircert()方法将用戶端重定向到成功頁面,就不會出現重複送出的問題了。
下面用用戶端與伺服器端令牌相結合的方式,防止使用者重複送出表單。
廢話少說,出代碼
本示例項目檔案結構如下圖:

login.jsp頁面代碼如下:
<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%>
<%@ page import="com.test.TokenProcessor" %>
<%@ page contentType="text/html; charset=UTF-8"%>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<title>防止表單重複送出</title>
<script type="text/javascript">
<!--
var checkSubmitFlg=true;
function checkSubmit(){
if(true==checkSubmitFlg){
document.theForm.btnSubmit.disable=true;
document.theForm.submit();
checkSubmitFlg=false;
}else{
alert("你已經送出 了表單,請不要重複送出!");
}
}
//-->
</script>
</head>
<body>
<%
TokenProcessor processor=TokenProcessor.getInstance();
String token=processor.getToken(request);
%>
<form action="handler" name="theForm" method="post">
<table>
<tr>
<td>使用者名:</td>
<td><input type="text" name="username"/></td>
</tr>
<tr>
<td>郵件位址:</td>
<td>
<input type="text" name="email"/>
<input type="hidden" name="ltai701" value="<%=token %>"/>
</td>
</tr>
<tr>
<td><input type="reset" value="重填"/></td>
<td><input type="button" value="送出" name="btnSubmit" onclick="checkSubmit()"/></td>
</tr>
</table>
</form>
</body>
</html>
HandlerServlet代碼如下:
package com.test;
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;
public class HandlerServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
int count=0;
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
doPost(req, resp);
}
protected void doPost(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
resp.setContentType("text/html;charset=UTF-8");
PrintWriter out=resp.getWriter();
TokenProcessor processor=TokenProcessor.getInstance();
if(processor.isTokenValid(req)){
System.out.println("submit:"+count);
if(count%2==1) count=0;
else count++;
out.println("success");
}else{
processor.saveToken(req);
out.println("你已經送出了表單,同一表單不能兩次送出");
}
out.close();
}
}
TokenProcessor代碼如下:
package com.test;
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="ltai701";
private static TokenProcessor instance=new TokenProcessor();
public static TokenProcessor getInstance(){
return instance;
}
private long previous;
public synchronized boolean isTokenValid(HttpServletRequest request){
//得到請求的目前session對象
HttpSession session=request.getSession(false);
if(session==null){
return false;
}
//從session中取出儲存的令牌值
String saved=(String)session.getAttribute(TOKEN_KEY);
if(saved==null){
return false;
}
//清除session中的令牌值
resetToken(request);
//得到請求參數中的令牌值
String token=request.getParameter(TOKEN_KEY);
if(token==null){
return false;
}
return saved.equals(token);
}
public synchronized void resetToken(HttpServletRequest request){
HttpSession session=request.getSession(false);
if(session==null){
return;
}
session.removeAttribute(TOKEN_KEY);
}
public synchronized void saveToken(HttpServletRequest request){
HttpSession session=request.getSession(false);
String token=generateToken(request);
if(token!=null){
session.setAttribute(TOKEN_KEY, token);
}
}
public synchronized String generateToken(HttpServletRequest request){
HttpSession session =request.getSession(false);
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=MessageDigest.getInstance("MD5");
md.update(id);
md.update(now);
return toHex(md.digest());
}catch (NoSuchAlgorithmException e) {
// TODO: handle exception
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();
}
public synchronized String getToken(HttpServletRequest request){
HttpSession session=request.getSession(false);
if(null==session)
return null;
String token=(String)session.getAttribute(TOKEN_KEY);
if(null==token){
token=generateToken(request);
if(token!=null){
session.setAttribute(TOKEN_KEY,token);
return token;
}else
return null;
}else
return token;
}
}
web.xml配置檔案如下:
<?xml version="1.0" encoding="UTF-8"?>
<web-app version="2.4"
xmlns="http://java.sun.com/xml/ns/j2ee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee
http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd">
<servlet>
<servlet-name>HanderServlet</servlet-name>
<servlet-class>com.test.HandlerServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>HanderServlet</servlet-name>
<url-pattern>/handler</url-pattern>
</servlet-mapping>
<welcome-file-list>
<welcome-file>index.jsp</welcome-file>
<welcome-file>login.jsp</welcome-file>
</welcome-file-list>
</web-app>
運作效果圖如下:
等待伺服器端響應時重複按“送出”按鈕
送出完成後再重新整理浏覽器或者按回退鍵再按前進鍵,則有
希望此文章能幫助到有需要的人,多謝。