一。前言 你在任何一個比較專業的BBS都會看到這樣的問題,即使你Google一下,也會發現有很多的人在關注和詢問,但大家給出的解決方法卻都是千差萬別,(有的人主張采用腳本來解決;有的則想重定向到別的頁面;有的則將此問題提升到Token的角度)為什麼會有如此大的差異呢?
二。問題場景 首先,我們應該先了解為什麼要處理這樣的問題?或者專業一點就是它適合的場景是什麼?(似乎只有人來問沒有人來解釋)
1。重復提交、重復刷新的場景
重復提交、重復刷新都是來解決系統重復記錄的問題。也就是說某個人在多次的提交某條記錄(為什麼?也許是閒了沒有事情干的;最有可能是用戶根本就不知道自己的提交結果是否已經執行了?!)。
但出現了這樣的問題並不見得就必須處理,要看你所開發的系統的類別而定。比如你接手的是某個資源管理系統,系統本身從需求的角度根本就不允許出現"重復"的記錄,在這樣需求的約束條件下,去執行重復的提交動作只會引發“業務級異常”的產生,根本就不可能執行成功也就無所謂避免不避免的問題了。
2。防止後退的場景
了解了重復刷新、重復提交的場景,我們來了解一下"防止後退"操作的原因是什麼?比如你在開發某個投票系統,它有很多的步驟,並且這些步驟之間是有聯系的,比如第一步會將某些信息發送給第二步,第二步緩存了這些信息,同時將自身的信息發送給了第三步。。。。。等等,如果此時用戶處在第三步驟下,我們想象一下某個淘氣用戶的用戶點擊了後退按鈕,此時屏幕出現了第二步驟的頁面,他再次的修改或者再次的提交,進入到下一個步驟(也就是第三步驟),錯誤就會在此產生?!什麼錯誤呢?最為典型的就是這樣的操作直接導致了對於第一個步驟信息的丟失!(如果這樣的信息是依靠Request存放的話,當然你可以存放在Session或者更大的上下文環境中,但這不是個好主意!關於信息存放的問題,下次在就這個問題詳細的討論)
三。如何處理的問題
當然很多的系統(比如訂票系統從需求上本身是允許個人重復訂票的)是必須要避免重復刷新、重復提交、以及防止後退的問題的,但即使是這樣的問題,也要區分如何處理以及在哪裡處理的(網上只是告訴你如何處理,但很少去區分在哪裡處理的),顯然處理的方式無非是客戶端或者服務器端兩種,而面對不同的位置處理的方式也是不同的,但有一點要事先聲明:任何客戶端(尤其是B/S端)的處理都是不可信任的,最好的也是最應該的是服務器端的處理方法。
客戶端處理:
面對客戶端我們可以使用Javascript腳本來解決,如下
1。重復刷新、重復提交
Ways One:設置一個變量,只允許提交一次。
復制代碼代碼如下:
<script language="javascript">
var checkSubmitFlg = false;
function checkSubmit() {
if (checkSubmitFlg == true) {
return false;
}
checkSubmitFlg = true;
return true;
}
document.ondblclick = function docondblclick() {
window.event.returnValue = false;
}
document.onclick = function doc {
if (checkSubmitFlg) {
window.event.returnValue = false;
}
}
</script>
<html:form action="myAction.do" method="post" onsubmit="return checkSubmit();">
Way Two : 將提交按鈕或者image置為disable
<html:form action="myAction.do" method="post"
onsubmit=" getElById_r('submitInput').disabled = true; return true;">
<html:image styleId="submitInput" src="images/ok_b.gif" border="0" />
</html:form>
2。防止用戶後退
這裡的方法是千姿百態,有的是更改浏覽器的歷史紀錄的,比如使用window.history.forward()方法;有的是“用新頁面的URL替換當前的歷史紀錄,這樣浏覽歷史記錄中就只有一個頁面,後退按鈕永遠不會變為可用。”比如使用javascript:location.replace(this.href); event.returnValue=false;
2.服務器端的處理(這裡只說Struts框架的處理)
利用同步令牌(Token)機制來解決Web應用中重復提交的問題,Struts也給出了一個參考實現。
基本原理:
服務器端在處理到達的請求之前,會將請求中包含的令牌值與保存在當前用戶會話中的令牌值進行比較,
看是否匹配。在處理完該請求後,且在答復發送給客戶端之前,將會產生一個新的令牌,該令牌除傳給
客戶端以外,也會將用戶會話中保存的舊的令牌進行替換。這樣如果用戶回退到剛才的提交頁面並再次
提交的話,客戶端傳過來的令牌就和服務器端的令牌不一致,從而有效地防止了重復提交的發生。
if (isTokenValid(request, true)) {
// your code here
return mapping.findForward("success");
} else {
saveToken(request);
return mapping.findForward("submitagain");
}
Struts根據用戶會話ID和當前系統時間來生成一個唯一(對於每個會話)令牌的,具體實現可以參考
TokenProcessor類中的generateToken()方法。
1. //驗證事務控制令牌,<html:form >會自動根據session中標識生成一個隱含input代表令牌,防止兩次提交
2. 在action中:
//<input type="hidden" name="org.apache.struts.taglib.html.TOKEN"
// value="6aa35341f25184fd996c4c918255c3ae">
if (!isTokenValid(request))
errors.add(ActionErrors.GLOBAL_ERROR,
new ActionError("error.transaction.token"));
resetToken(request); //刪除session中的令牌
3. action有這樣的一個方法生成令牌
protected String generateToken(HttpServletRequest request) {
HttpSession session = request. getSession_r();
try {
byte id[] = session. getId_r(). getBytes_r();
byte now[] =
new Long(System.currentTimeMillis()).toString(). getBytes_r();
MessageDigest md = MessageDigest. getInstance_r("MD5");
md.update(id);
md.update(now);
return (toHex(md.digest()));
} catch (IllegalStateException e) {
return (null);
} catch (NoSuchAlgorithmException e) {
return (null);
}
}
總結
對於重復提交、重復刷新、防止後退等等都是屬於系統為避免重復記錄而需要解決的問題,在客戶端去處理需要針對每一種的可能提出相應的解決方案,然而在服務器端看來只不過是對於數據真實性的檢驗問題,基於令牌的處理就是一勞永逸的方法。
同時我們也看到,從不同的角度去看待問題,其解決的方法也是不同的。客戶端更追求的是用戶的操作,而服務端則將注意力放在了數據的處理上,所以在某個對於服務器端看似容易的問題上,用客戶端來解決卻麻煩了很多!反之依然。所以在某些問題的處理上我們需要綜合考慮和平衡,是用客戶端來解決?還是用服務器端來處理?
網頁防刷新重復提交、防後退解決方法
提交後禁用提交按鈕(大部分人都是這樣做的)
如果客戶提交後,按F5刷新怎麼辦?
使用Session
在提交的頁面也就是數據庫處理之前:
if session("ok")=true then
response.write "錯誤,正在提交"
response.end
end if
數據處理完後,修改session("ok")=false。
數據處理成功馬上Redirect到另外一個頁面
操作後刷新的確是個問題,你可以使用跳轉頁面、關閉本頁面,如果是有參數據條件來控制的,那就應該好做了,可以直接修改window.location的值,把參數全部改掉,這樣就差不多了。
缺點:簡單地運用Response.Redirect將不再有效,因為用戶從一個頁面轉到另一個頁面,我們都必須用客戶端代碼清除location.history。注意,這種方法清除的是最後一個訪問歷史記錄,而不是全部的訪問記錄。點擊後退按鈕,再點擊後退按鈕,你可以看到這時打開的是本頁面之前的頁面!(當然,這是在你的客戶端啟用了JavaScript功能的條件下。)
如果客戶按後退,怎麼辦?
防止網頁後退--禁止緩存
我們在進行數據庫添加操作的時候,如果允許後退,而正巧有刷新了頁面,就會再次執行添加操作,無疑這不是我們需要的,像一般網上很多禁止緩存的代碼,有時並不可靠,這時你只要在操作的頁面加上就可以了,在網頁的裡指定要定向的新頁,再點後退,看是不是不會再退到剛才的操作頁面了,實際上已經把這個歷史給刪除了
ASP:
Response.Buffer = True
Response.ExpiresAbsolute = Now() - 1
Response.Expires = 0
Response.CacheControl = "no-cache"
ASP.NET:
Response.Buffer=true;
Response.ExpiresAbsolute=DateTime.Now.AddSeconds(-1);
Response.Expires=0;
Response.CacheControl="no-cache";
究竟怎樣才能"禁用"浏覽器的後退按鈕?或者“怎樣才能防止用戶點擊後退按鈕返回以前浏覽過的頁面?”
遺憾的是,我們無法禁用浏覽器的後退按鈕。
防止網頁後退--新開窗口
用window.open彈出表單頁面,點提交後關閉該頁;處理提交的ASP頁也是用彈出,設定表單的target,點提交時window.open("XXX.asp","_blank"),然後用JS來提交表單,完成後window.close();
簡單的說,就是提交表單的時候彈出新窗口,關閉本窗口。對於window.open()打開的窗口怎麼後退?能後退到哪裡去?
呵呵,羅嗦了一堆廢話,知道怎麼處理了麼?混合運用客戶端腳本和服務器端腳本。
jsp重復提交問題
看了網上的,有幾種方法:
1 在你的表單頁裡HEAD區加入這段代碼:
<META HTTP-EQUIV="pragma" CONTENT="no-cache">
<META HTTP-EQUIV="Cache-Control" CONTENT="no-cache, must-revalidate">
<META HTTP-EQUIV="expires" CONTENT="Wed, 26 Feb 1997 08:21:57 GMT">
2
生成一個令牌保存在用戶session中,在form中加一個hidden域,顯示該令
牌的值,form提交後重新生成一個新的令牌,將用戶提交的令牌和session
中的令牌比較,如相同則是重復提交
3
在你的服務器端控件的代碼中使用Response.Redirect("selfPage")語句。但是大多的數都不使用這種方法。
方法還有很多。。。
4
<input type="button" value="提交" onclick="this.disabled=true;this.form.submit()">
5
在JSP頁面的FORM表單中添加一個hidden域
<input type="hidden" name="url"value=<%=request. getRequestURL_r()%>>
在你的serverlet中添加如下語句
String url=request. getParameter_r("url");
response.sendRedirect(url);
我一般都是采用這樣的方法返回JSP頁面的,不太明白你說的重復刷新是什麼概念
6 ajax 無刷新提交
7 Web開發中防止浏覽器的刷新鍵引起系統操作重復提交
怎麼解決呢?重定向可以解決頁面刷新帶來的數據的重復提交的問題,我們自然可以利用重定向的方式來解決這個問題。但是struts的action裡面mapping.findword();跳轉的話,默認的是在工程文件夾裡面找要跳轉的頁面。這種情況,怎麼解決呢?
修改struts-config.xml 文件,在action裡面有一個redirect重新定向的屬性,struts中默認的是false,添加這個屬性,改成true,在forword中寫上要跳轉頁面的絕對或者相對地址就行了
修改如下:
<action-mappings>
<action attribute="newsActionForm" name="newsActionForm"
input="/addnews.jsp" path="/newsAction" parameter="method"
scope="request" type="com.yongtree.news.action.NewsAction">
<forward name="list" path="/listnews.jsp" redirect="true"></forward>
<forward name="error" path="/addnews.jsp"></forward>
</action>
</action-mappings>
浏覽器相關難處理的問題
浏覽器的後退按鈕使得我們能夠方便地返回以前訪問過的頁面,它無疑非常有用。但有時候我們不得不關閉這個功能,以防止用戶打亂預定的頁面訪問次序。本文介紹網絡上可找到的各種禁用浏覽器後退按鈕方案,分析它們各自的優缺點和適用場合。
一、概述
曾經有許多人問起,“怎樣才能‘禁用’浏覽器的後退按鈕?”,或者“怎樣才能防止用戶點擊後退按鈕返回以前浏覽過的頁面?”在ASP論壇上,這個問題也是問得最多的問題之一。遺憾的是,答案非常簡單:我們無法禁用浏覽器的後退按鈕。
起先我對於居然有人想要禁用浏覽器的後退按鈕感到不可思議。後來,看到竟然有那麼多的人想要禁用這個後退按鈕,我也就釋然(想要禁用的只有後退按鈕,不包括浏覽器的前進按鈕)。因為在默認情況下,用戶提交表單之後可以通過後退按鈕返回表單頁面(而不是使用“編輯”按鈕!),然後再次編輯並提交表單向數據庫插入新的記錄。這是我們不願看到的。
因此我就決定要找出避免出現這種情況的方法。我訪問了許多網站,參考了這些網站所介紹的各種實現方法。如果你經常訪問ASP編程網站,本文所介紹的部分內容你可能已經見到過。本文的任務是把各種可能的方法都介紹給大家,然後找出最好的方法!
二、禁止緩存
在我找到的許多方案中,其中有一種建議禁止頁面緩存。具體是使用服務器端腳本,如下所示:
<%
Response.Buffer = True
Response.ExpiresAbsolute = Now() - 1
Response.Expires = 0
Response.CacheControl = "no-cache"
%>
這種方法非常有效!它強制浏覽器重新訪問服務器下載頁面,而不是從緩存讀取頁面。使用這種方法時,編程者的主要任務是創建一個會話級的變量,通過這個變量確定用戶是否仍舊可以查看那個不適合通過後退按鈕訪問的頁面。由於浏覽器不再緩存這個頁面,當用戶點擊後退按鈕時浏覽器將重新下載該頁面,此時程序就可以檢查那個會話變量,看看是否應該允許用戶打開這個頁面。
例如,假設我們有如下表單:
<%
Response.Buffer = True
Response.ExpiresAbsolute = Now() - 1
Response.Expires = 0
Response.CacheControl = "no-cache"
If Len(Session("FirstTimeToPage")) > 0 then
&single; 用戶已經訪問過當前頁面,現在是再次返回訪問。
&single; 清除會話變量,將用戶重定向到登錄頁面。
Session("FirstTimeToPage") = ""
Response.Redirect "/Bar.asp"
Response.End
End If
&single; 如果程序運行到這裡,說明用戶能夠查看當前頁面
&single; 以下開始創建表單
%>
<form method=post action="SomePage.asp">
<input type=submit>
</form>
我們借助會話變量FirstTimeToPage檢查用戶是否是第一次訪問當前頁面。如果不是第一次(即Session("FirstTimeToPage")包含某個值),那麼我們就清除會話變量的值,然後把用戶重新定向到一個開始頁面。這樣,當表單提交時(此時SompePage.asp被打開),我們必須賦予FirstTimeToPage一個值。即,在SomePage.asp中我們需要加上下面的代碼:
Session("FirstTimeToPage") = "NO"
這樣,已經打開SomePage.asp的用戶如果點擊後退按鈕,浏覽器將重新請求服務器下載頁面,服務器檢查到Session("FirstTimeToPage")包含了一個值,於是就清除Session("FirstTimeToPage"),並把用戶重定向到其他頁面。當然,所有這一切都需要用戶啟用了Cookie,否則會話變量將是無效的。(有關該問題的更多說明,請參見For session ariables to work, must the Web visitor have cookies enabled?)
另外,我們也可以用客戶端代碼使浏覽器不再緩存Web頁面:
<html>
<head>
<meta http-equiv="Expires" CONTENT="0">
<meta http-equiv="Cache-Control" CONTENT="no-cache">
<meta http-equiv="Pragma" CONTENT="no-cache">
</head>
如果使用上面的方法強制浏覽器不再緩存Web頁面,必須注意以下幾點:
只有在使用安全連接時“Pragma: no-cache”才防止浏覽器緩存頁面。對於不受安全保護的頁面,“Pragma: no-cache”被視為與“Expires: -1”相同,此時浏覽器仍舊緩存頁面,但把頁面標記為立即過期。在IE 4或5中,“Cache-Control”META HTTP-EQUIV標記將被忽略,不起作用。
在實際應用中我們可以加上所有這些代碼。然而,由於這種方法不能適用於所有的浏覽器,所以是不推薦使用的。但如果是在Intranet環境下,管理員可以控制用戶使用哪種浏覽器,我想還是有人會使用這種方法。
三、其他方法
接下來我們要討論的方法以後退按鈕本身為中心,而不是浏覽器緩存。這兒有一篇文章Rewiring the Back Button很值得參考。不過我注意到,如果使用這種方法,雖然用戶點擊一下後退按鈕時他不會看到以前輸入數據的頁面,但只要點擊兩次就可以,這可不是我們希望的效果,因為很多時候,固執的用戶總是能夠找到繞過預防措施的辦法。
另外一種禁用後退按鈕的辦法是用客戶端JavaScript打開一個沒有工具條的窗口,這使得用戶很難返回前一頁面,但不是不可能。一種更安全但相當惱人的方法是,當表單提交時打開一個新的窗口,與此同時關閉表單所在的窗口。但我覺得這種方法不值得認真考慮,因為我們總不能讓用戶每提交一個表單就打開一個新窗口。
那麼,在那個我們不想讓用戶返回的頁面是否也可以加入JavaScript代碼呢?在這個頁面中加入的JavaScript代碼可用來產生點擊前進按鈕的效果,這樣也就抵消了用戶點擊後退按鈕所產生的動作。用於實現該功能的JavaScript代碼如下
所示:
<script language="JavaScript">
<!--
javascript:window.history.forward(1);
//-->
</script>
同樣地,這種方法雖然有效,但距離“最好的方法”還差得很遠。後來我又看到有人建議用location.replace從一個頁面轉到另一個頁面。這種方法的原理是,用新頁面的URL替換當前的歷史紀錄,這樣浏覽歷史記錄中就只有一個頁面,後退按鈕永遠不會變為可用。我想這可能正是許多人所尋求的方法,但這種方法仍舊不是任何情況下的最好方法。使用這種方法的實例如下所示:
<A HREF="PageName.htm" onclick="javascript:location.replace(this.href);
event.returnValue=false;">禁止後退到本頁面的鏈接</A>
禁止後退到本頁面的鏈接!
這種方法的缺點在於:簡單地運用Response.Redirect將不再有效,這是因為每次用戶從一個頁面轉到另一個頁面,我們都必須用客戶端代碼清除location.history。另外還要注意,這種方法清除的是最後一個訪問歷史記錄,而不是全部的訪問記錄。
點擊上面的鏈接,你將打開一個簡單的HTML頁面。再點擊後退按鈕,你可以看到這時打開的不是本頁面,而是本頁面之前的頁面!(當然,你必須在浏覽器中啟用了客戶端JavaScript代碼。)
經過一番仔細的尋尋覓覓之後,我發現仍舊無法找出真正能夠完全禁用浏覽器後退按鈕的辦法。所有這裡介紹的方法都能夠在不同程度上、以不同的方式禁止用戶返回前一頁面,但它們都有各自的局限。由於不存在能夠完全禁用後退按鈕的方法,所以最好的方案應該是:混合運用客戶端腳本和服務器端腳本。
<html>
<head>
<meta http-equiv="Expires" CONTENT="0">
<meta http-equiv="Cache-Control" CONTENT="no-cache">
<meta http-equiv="Pragma" CONTENT="no-cache">
</head>
<script language="JavaScript">
<!--
javascript:window.history.forward(1);
//-->
</script>
</html>
Asp.net中防刷新重復提交、防後退方法
簡單操作方法防後退和刷新
Page_Load中加入
Response.Cache.SetNoStore();
//Session中存儲的變量“IsSubmit”是標記是否提交成功的
if (!IsPostBack)
if (Session["IsSubmit"]==null)
Session.Add("IsSubmit",false);
if ((bool)Session["IsSubmit"])
{
//如果表單數據提交成功,就設“Session["IsSubmit"]”為false
Session["IsSubmit"] = false;
//顯示提交成功信息
TextBox1.Text = " * 提交成功!";
}
else
{//否則的話(沒有提交,或者是頁面刷新),不顯示任何信息
TextBox1.Text = "";
Response.End();
}
提交按鈕中加入
Session["IsSubmit"] = true;
Response.Redirect ("本頁");
另外:
1、通常應該在業務層進行判斷(唯一性)解決這種問題
2、要在頁面裝載事件寫上 Response.CacheControl = "no-cache" 清除緩存
3、也有人這樣說:我以前也碰到過這樣的問題,是在分步提交中一個人的簡歷,在寫完第一個頁面後跳到第二個頁面,為了防止用戶用後退返回到第一個頁面,再重新提交第一個頁面,我是當用戶提交第一次提交第一個頁面時,把插入數據庫中的記錄的自增長id號放到session裡,當用戶從第二個頁面返回到第一個頁面再一次提交該頁面時,我就用session裡的值去數據庫查,如果有這個id就用update語句把第一個頁面的數據寫進數據庫,如果沒有查到這個id,就用insert語句。