前段時間要做一個捕捉使用者登入和登出時間的功能,查了很多資料,做了很多測試
,總結出兩套方案,其中對session有了進一步的認識。
使用者的登入時間很好做了,在使用者驗證成功通過後,得到當前系統時間記錄就行;如果系統用的是Acegi的話,可以寫一個類,繼承Acegi中的 AuthenticationProcessingFilter.java,並覆蓋其onSuccessfulAuthentication方法,故名思 意,這個方法就是acegi在對使用者成功通過時進行的一個附加操作的方法,在這個方法裡你可以記錄目前使用者id、時間、ip等等日誌
資訊到資料庫
中。
但要捕捉使用者的登出時間確實是很麻煩,首先要知道是http是無狀態連線協定,當使用者不通過登出方法登出而直接關閉瀏覽器或者結束進程時,伺服器根本無法 捕捉這個事件,不管用戶端發生了什麼伺服器也不知道,使用者僅僅從伺服器那得到了一個sessionid儲存在瀏覽器進程的記憶體中或者cookie中,網上 很多說用window.onunload( )等js方式來捕捉使用者登出事件其實也其不通,因為使用者可以直接殺死進程或者在新視窗頁中開啟連結,然後把原視窗關閉,這時通過js捕捉到關閉瀏覽器事件 並發送請求告訴伺服器,但其實使用者還並沒有離開系統,只不過換了個瀏覽器瀏覽頁面。還有就是通過session到期來捕捉,但在系統正常運行下 session死亡只有兩種可能:
1.session的持有人(即用戶端瀏覽器)在最大無活動等待時間(MaxInactiveInterval)內無任何響應或請求
2.session 被調用invalidate()方法強制弊而當使用者關閉了瀏覽器後標誌著session將不再發送請求到伺服器,伺服器也不會無緣無故調用它的 invalidate()方法,這樣session只能等到time-out時才會銷毀,如果系統設定了session的 MaxInactiveInterval為-1的話,那這個session將永遠存活到伺服器關閉為止。
所以session不能反應出使用者的活動狀態,瀏覽器關閉事件也不能完全得出使用者一定是退出了系統。
那這個登出時間到底怎樣才能得到呢?事實如此,只能降低期望了,和老大討論了許久,最後只有兩套方案行得通,如下。
一, 以效能換精度,也是我最終採用的方案,我們可以精確地得到使用者登入時間,而登出時間我們得到個大概的時間就行,不需要太精確,畢竟系統不是什麼很機密性的 東西。採用session監聽器來實現,寫一類,繼承HttpSessionListener,實現其public void sessionDestroyed(HttpSessionEvent arg0) 和public void sessionCreated(HttpSessionEvent arg0) 方法,當使用者session失效時會走sessionDestroyed方法,在其裡面得到的目前時間就是使用者的大概登出時間,前提是系統session 到期時間不能設定太長,要不誤差就太大了,半小時就差不多。
二, 以精度換效能,b/s系統的頁面一般都會帶有頭部、腳部頁面或者是架構型的,這樣我們就可以在公用的頭腳或主架構頁中插入一個隱藏的iframe或者代碼 塊。裡頭寫一段js方法,window.setInterval()之類的,每隔一小段時間(假設為2分鐘)向伺服器發送一個空請求,目的僅僅是讓伺服器 知道用戶端還開著我的頁面,不管是以哪種方式。伺服器接受這個請求後,建立一個Map對象(建立一javabean類也行,裡面有使用者id和請求時間兩個 屬性),put兩個元素,目前使用者id為value,一約定代號為key,如“userid”,另一元素請求時間為value,同樣一約定代號為key, 如“requesttime”;然後再以這個對象為value,sessionid為key ,set一個attribute到session當中,當下次再收到一個請求時,再重複上部操作,這樣請求時間會不斷更新。做完這些之後,還需要添加一個 定時調度的job,每隔一小段時間(假設為3分鐘),遍曆session對象,從其中的map或者javabean中取出userid 和 requesttime,判斷這個請求時間和目前時間的時差是否大於三分鐘,如果是則說明使用者已經沒有再向伺服器發送請求,即沒有頁面再使用者端開啟著,用 戶離開了,記錄最後一次請求時間,也就是使用者的登出時間。
目前還沒有想到其它
更好的方法,現在這種機制下好像也只能這樣了,還有一點需要注意,如果採用在使用者登入
成 功時記錄登入時間的方式,在要求並發數較大的時候不可行,今天對我們項目組做的一個系統進行登入過程50、100使用者並發測試時,失敗率極高,而問題就出 在登入時向資料庫記錄登入時間和更新最高訪問人數時出現的sql事務死結情況,所以後來更換了方案,在登入時將目前時間為value,sessionid 為key,set到session當中,當這個session到期時,通過httpsessionlistener捕捉到該事件,從session中取出 該使用者的登入時間和目前時間一併存入資料庫,後者作為登出時間;最高訪問人數也放在了application裡,這樣效能上提高了不少。
如果要得到使用者的線上時間的話,需要注意的是,由於一個瀏覽器進程共用一個session,一個瀏覽器中的所有標籤也是共用一個session,在使用者登 錄成功時需要通過當前sessionid判斷使用者是否重複登入了,如果是則在此時就不應該將目前時間set到session中,而應該保持上次儲存的登入 時間。
/*********** 下面再引入一下網上比較經典的描述,更有利於理解session和cookie ************/
讓我們用幾個例子來描述一下cookie和session機制之間的區別與聯絡。筆者曾經常去的一家咖啡店有喝5杯咖啡免費贈一杯咖啡的優惠,然而一次性消費5杯咖啡的機會微乎其微,這時就需要某種方式來紀錄某位顧客的消費數量。想象一下其實也無外乎下面的幾種方案:
1、該店的店員很厲害,能記住每位顧客的消費數量,只要顧客一走進咖啡店,店員就知道該怎麼對待了。這種做法就是協議本身支援狀態。
2、發給顧客一張卡片,上面記錄著消費的數量,一般還有個有效期間限。每次消費時,如果顧客出示這張卡片,則此次消費就會與以前或以後的消費相聯絡起來。這種做法就是在用戶端保持狀態。
3、 發給顧客一張會員卡,除了卡號之外什麼資訊也不紀錄,每次消費時,如果顧客出示該卡片,則店員在店裡的紀錄本上找到這個卡號對應的紀錄添加一些消費資訊。 這種做法就是在伺服器端保持狀態。由於HTTP協議是無狀態的,而出於種種考慮也不希望使之成為有狀態的,因此,後面兩種方案就成為現實的選擇。具體來說 cookie機制採用的是在用戶端保持狀態的方案,而session機制採用的是在伺服器端
保持狀態的方案。同時我們也看到,由於採用伺服器端保持狀態的方案在用戶端也需要儲存一個標識,所以session機制可能需要藉助於cookie機制來達到儲存標識的目的,但實際上它還有其他
選擇。