最近所做的一個項目需要用到的線上使用者列表,上網搜尋了一下發現現有的解決方案對使用者意外退出的處理均不是太理想。一般來說,使用者離開系統的方式有三種:主動登出、會話逾時、直接關閉瀏覽器,對於前兩種,我們很容易便可將該使用者從線上列表中清除,關鍵是第三種(很多使用者都是直接關閉視窗的~~鬱悶ing),程式無法捕獲視窗關閉的精確時間,只能等到會話逾時後在能將該使用者清除出線上列表,假設我們設定會話逾時時間為60分鐘,而使用者登陸系統隨便瀏覽一個頁面就以關閉瀏覽器的方式退出的話,我們要在將近1小時後才能從線上列表中將該使用者清除出去(想象一下,系統顯示n多人線上,可能除了你之外其他的n-1人都關機走人了,汗一個先```),而本文將嘗試尋找一個解決方案把這種尷尬降至最低。 我的大概思路是,給每線上使用者增加一個RefreshTime屬性,建立一個負責將目前使用者的RefreshTime屬性設定為目前時間的單獨頁面(Refresh.aspx),然後在系統的主要頁面(也可以是所有頁面)中通過xmlhttp不斷地請求Refresh.aspx頁面,一旦使用者關閉了與本系統相關的所有視窗,即以直接關閉瀏覽器的方式退出系統,那麼該使用者的RefreshTime屬性便不會自動更新了,我們再設定一個自動重新整理的逾時時間(這個要比會話逾時短很多_refreshTimeout),當發現某使用者超過_refreshTimeout的時間沒有自動重新整理,就能判定該使用者已經以直接關閉瀏覽器的方式退出了。 假設我們設定會話逾時時間為60分鐘,自動重新整理逾時時間為1分鐘,在用戶端通過xmlhttp每隔25秒(之所以不設1分鐘,是防止網速慢的時候訪問Refresh.aspx逾時,個人感覺,不一定正確)訪問一次Refresh.aspx頁面,在使用者登陸、使用者登出、檢測使用者是否線上的時候都執行清理逾時使用者(包括會話逾時和自動重新整理逾時)操作,這樣一來,線上使用者列表的統計誤差就由60分鐘降至1分鐘了。 ========================================== 具體實現如下: 1、 建立一個名為ActiveUser的類,儲存單個活動使用者資料。
- /// <summary>
- /// 單個線上使用者資料,無法繼承此類。
- /// </summary>
- public sealed class ActiveUser
- {
- private readonly string _ticket; //票據名稱
- private readonly string _username; //登陸使用者名稱
- private readonly string _truename; //登陸使用者名稱
- private readonly string _roleid; //角色
- private readonly DateTime _refreshtime; //最新重新整理時間
- private readonly DateTime _activetime; //最新啟用時間
- private readonly string _clientip; //登陸IP
-
- public ActiveUser(string Ticket,string UserName,string TrueName,string RoleID,string ClientIP) {
- this._ticket=Ticket;
- this._username=UserName;
- this._truename=TrueName;
- this._roleid=RoleID;
- this._refreshtime=DateTime.Now;
- this._activetime=DateTime.Now;
- this._clientip=ClientIP;
- }
- public ActiveUser(string Ticket,string UserName,string TrueName,string RoleID,DateTime RefreshTime,DateTime ActiveTime,string ClientIP) {
- this._ticket=Ticket;
- this._username=UserName;
- this._truename=TrueName;
- this._roleid=RoleID;
- this._refreshtime=RefreshTime;
- this._activetime=ActiveTime;
- this._clientip=ClientIP;
- }
-
- public string Ticket { get{return _ticket;} }
- public string UserName { get{return _username;} }
- public string TrueName { get{return _truename;} }
- public string RoleID { get{return _roleid;} }
- public DateTime RefreshTime { get{return _refreshtime;} }
- public DateTime ActiveTime { get{return _activetime;} }
- public string ClientIP { get{return _clientip;} }//51aspx.com
- }
複製代碼
2、 建立一個名為PassPort的類,儲存線上使用者列表。
- /// <summary>
- /// PassPort 儲存線上使用者列表。
- /// </summary>
- public class PassPort
- {
- private static DataTable _activeusers;
- private int _activeTimeout;
- private int _refreshTimeout;
- /// <summary>
- /// 初始化線上使用者表。
- /// </summary>
- private void userstableFormat()
- {
- if(_activeusers==null) {
- _activeusers = new DataTable("ActiveUsers");
- DataColumn myDataColumn;
- System.Type mystringtype;
- mystringtype = System.Type.GetType("System.String");
- System.Type mytimetype;
- mytimetype = System.Type.GetType("System.DateTime");
- myDataColumn = new DataColumn("Ticket",mystringtype);
- _activeusers.Columns.Add(myDataColumn);
- myDataColumn = new DataColumn("UserName",mystringtype);
- _activeusers.Columns.Add(myDataColumn);
- myDataColumn = new DataColumn("TrueName",mystringtype);
- _activeusers.Columns.Add(myDataColumn);
- myDataColumn = new DataColumn("RoleID",mystringtype);
- _activeusers.Columns.Add(myDataColumn);
- myDataColumn = new DataColumn("RefreshTime",mytimetype);
- _activeusers.Columns.Add(myDataColumn);
- myDataColumn = new DataColumn("ActiveTime",mytimetype);
- _activeusers.Columns.Add(myDataColumn);
- myDataColumn = new DataColumn("ClientIP",mystringtype);
- _activeusers.Columns.Add(myDataColumn);
- }
- }
- public PassPort()
- {
- userstableFormat(); //初始化線上使用者表
- //活動逾時時間初始化 單位:分鐘
- try { _activeTimeout=int.Parse(ConfigurationSettings.AppSettings["ActiveTimeout"]); }
- catch{ _activeTimeout=60; }
- //自動重新整理逾時時間初始化 單位:分鐘
- try { _refreshTimeout=int.Parse(ConfigurationSettings.AppSettings["RefreshTimeout"]); }
- catch{ _refreshTimeout=1; }
- }
- //全部使用者列表
- public DataTable ActiveUsers
- {
- get{return _activeusers.Copy();}
- }
-
- /// <summary>
- /// 新使用者登陸。
- /// </summary>
- public void Login(ActiveUser user,bool SingleLogin)
- {
- DelTimeOut(); //清除逾時使用者
- if(SingleLogin){
- //若是單人登陸則登出原來登陸的使用者
- this.Logout(user.UserName,false);
- }
- DataRow myRow;
- try
- {
- myRow = _activeusers.NewRow();
- myRow["Ticket"] = user.Ticket.Trim();
- myRow["UserName"] = user.UserName.Trim();
- myRow["TrueName"] = ""+user.TrueName.Trim();
- myRow["RoleID"] = ""+user.RoleID.Trim();
- myRow["ActiveTime"] = DateTime.Now;
- myRow["RefreshTime"] = DateTime.Now;
- myRow["ClientIP"] = user.ClientIP.Trim();
- _activeusers.Rows.Add(myRow);
- }
- catch(Exception e)
- {
- throw(new Exception(e.Message));
- }
- _activeusers.AcceptChanges();
-
- }
- /// <summary>
- ///使用者登出,根據Ticket或UserName。
- /// </summary>
- private void Logout(string strUserKey,bool byTicket)
- {
- DelTimeOut(); //清除逾時使用者
- strUserKey=strUserKey.Trim();
- string strExpr;
- strExpr =byTicket ? "Ticket=''" + strUserKey +"''" : "UserName=''" + strUserKey + "''";
- DataRow[] curUser;
- curUser = _activeusers.Select(strExpr);
- if (curUser.Length >0 )
- {
- for(int i = 0; i < curUser.Length; i ++)
- {
- curUser[i].Delete();
- }
- }
- _activeusers.AcceptChanges();
- }
- /// <summary>
- ///使用者登出,根據Ticket。
- /// </summary>
- /// <param name="strTicket">要登出的使用者Ticket</param>
- public void Logout(string strTicket){
- this.Logout(strTicket,true);
- }
- /// <summary>
- ///清除逾時使用者。
- /// </summary>
- private bool DelTimeOut()
- {
- string strExpr;
- strExpr = "ActiveTime < ''" + DateTime.Now.AddMinutes( 0 - _activeTimeout) + "''or RefreshTime < ''"+DateTime.Now.AddMinutes( 0 - _refreshTimeout)+"''";
- DataRow[] curUser;
- curUser = _activeusers.Select(strExpr);
- if (curUser.Length >0 )
- {
- for(int i = 0; i < curUser.Length; i ++)
- {
- curUser[i].Delete();
- }
- }
- _activeusers.AcceptChanges();
- return true;
- }
- /// <summary>
- ///更新使用者啟用時間。
- /// </summary>
- public void ActiveTime(string strTicket)
- {
- DelTimeOut();
- string strExpr;
- strExpr = "Ticket=''" + strTicket + "''";
- DataRow[] curUser;
- curUser = _activeusers.Select(strExpr);
- if (curUser.Length >0 )
- {
- for(int i = 0; i < curUser.Length; i ++)
- {
- curUser[i]["ActiveTime"]=DateTime.Now;
- curUser[i]["RefreshTime"]=DateTime.Now;
- }
- }
- _activeusers.AcceptChanges();
- }
- /// <summary>
- ///更新系統自動重新整理時間。
- /// </summary>
- public void RefreshTime(string strTicket)
- {
- DelTimeOut();
- string strExpr;
- strExpr = "Ticket=''" + strTicket + "''";
- DataRow[] curUser;
- curUser = _activeusers.Select(strExpr);
- if (curUser.Length >0 )
- {
- for(int i = 0; i < curUser.Length; i ++)
- {
- curUser[i]["RefreshTime"]=DateTime.Now;
- }
- }
- _activeusers.AcceptChanges();
- }
- private ActiveUser SingleUser(string strUserKey,bool byTicket)
- {
- strUserKey=strUserKey.Trim();
- string strExpr;
- ActiveUser myuser;
- strExpr =byTicket ? "Ticket=''" + strUserKey +"''" : "UserName=''" + strUserKey + "''";
- DataRow[] curUser;
- curUser = _activeusers.Select(strExpr);
- if (curUser.Length >0 )
- {
- string myTicket=(string)curUser[0]["Ticket"];
- string myUser=(string)curUser[0]["UserName"];
- string myName=(string)curUser[0]["TrueName"];
- string myRoleID=(string)curUser[0]["RoleID"];
- DateTime myActiveTime=(DateTime)curUser[0]["ActiveTime"];
- DateTime myRefreshtime=(DateTime)curUser[0]["RefreshTime"];
- string myClientIP =(string)curUser[0]["ClientIP"];
- myuser=new ActiveUser(myTicket,myUser,myName,myRoleID,myActiveTime,myRefreshtime,myClientIP);
- }
- else
- {
- myuser=new ActiveUser("","","","","");
- }
- return myuser;
- }
- /// <summary>
- ///按Ticket擷取活動使用者。
- /// </summary>
- public ActiveUser SingleUser_byTicket(string strTicket)
- {
- return this.SingleUser(strTicket,true);
- }
- /// <summary>
- ///按UserName擷取活動使用者。
- /// </summary>
- public ActiveUser SingleUser_byUserName(string strUserName)
- {
- return this.SingleUser(strUserName,false);
- }
- /// <summary>
- ///按Ticket判斷使用者是否線上。
- /// </summary>
- public bool IsOnline_byTicket(string strTicket)
- {
- return (bool)(this.SingleUser(strTicket,true).UserName!="");
- }
- /// <summary>
- ///按UserName判斷使用者是否線上。
- /// </summary>
- public bool IsOnline_byUserName(string strUserName)
- {
- return (bool)(this.SingleUser(strUserName,false).UserName!="");
- }
- }
複製代碼
3、 建立一個繼承自PlaceHolder名為Refresh的類,執行更新自動重新整理時間操作。
- /// <summary>
- /// Refresh 執行更新自動重新整理時間操作。
- /// </summary>
- public class Refresh: PlaceHolder
- {
- /// <summary>
- /// 設定儲存Ticket的Session名稱,預設為Ticket。
- /// </summary>
- public virtual string SessionName
- {
- get{
- object obj1 = this.ViewState["SessionName"];
- if (obj1 != null){ return ((string) obj1).Trim(); }
- return "Ticket";
- }
- set{
- this.ViewState["SessionName"] = value;
- }
- }
- protected override void Render(HtmlTextWriter writer)
- {
- string myTicket=(string)this.Page.Session[this.SessionName];
- if(myTicket!=null)
- {
- PassPort myPass = new PassPort();
- myPass.RefreshTime(myTicket);
- writer.Write("OK:"+DateTime.Now.ToString());
- }
- else{
- writer.Write("Sorry:"+DateTime.Now.ToString());
- }
- base.Render(writer);
- }
- }
複製代碼
4、 建立一個繼承自PlaceHolder名為Script的類,產生執行xmlhttp的js指令碼。。
- /// <summary>
- /// Script 產生執行xmlhttp的js指令碼。
- /// </summary>
- public class Script: PlaceHolder
- {
- /// <summary>
- /// 設定js自動重新整理的間隔時間,預設為25秒。
- /// </summary>
- public virtual int RefreshTime
- {
- get
- {
- object obj1 = this.ViewState["RefreshTime"];
- if (obj1 != null){return int.Parse(((string) obj1).Trim());}
- return 25;
- }
- set
- {
- this.ViewState["RefreshTime"] = value;
- }
- }
- protected override void Render(HtmlTextWriter writer)
- {
- //從web.config中讀取xmlhttp的訪問地址
- string refreshUrl=(string)ConfigurationSettings.AppSettings["refreshUrl"];
- string scriptString = @" <script language=""JavaScript"">"+writer.NewLine;
- scriptString += @" window.attachEvent(""onload"", "+this.ClientID+@"_postRefresh);"+writer.NewLine;
- scriptString += @" var "+this.ClientID+@"_xmlhttp=null;"+writer.NewLine;
- scriptString += @" function "+this.ClientID+@"_postRefresh(){"+writer.NewLine;
- scriptString += @" var "+this.ClientID+@"_xmlhttp = new ActiveXObject(""Msxml2.XMLHTTP"");"+writer.NewLine;
- scriptString += @" "+this.ClientID+@"_xmlhttp.Open(""POST"", """+refreshUrl+@""", false);"+writer.NewLine;
- scriptString += @" "+this.ClientID+@"_xmlhttp.Send();"+writer.NewLine;
- scriptString += @" var refreshStr= "+this.ClientID+@"_xmlhttp.responseText;"+writer.NewLine;
-
- scriptString += @" try {"+writer.NewLine;
- scriptString += @" var refreshStr2=refreshStr;"+writer.NewLine;
- //scriptString += @" alert(refreshStr2);"+writer.NewLine;
- scriptString += @" }"+writer.NewLine;
- scriptString += @" catch(e) {}"+writer.NewLine;
- scriptString += @" setTimeout("""+this.ClientID+@"_postRefresh()"","+this.RefreshTime.ToString()+@"000);"+writer.NewLine;
- scriptString += @" }"+writer.NewLine;
- scriptString += @"<";
- scriptString += @"/";
- scriptString += @"script>"+writer.NewLine;
- writer.Write(writer.NewLine);
- writer.Write(scriptString);
- writer.Write(writer.NewLine);
- base.Render(writer);
- }
- }
複製代碼
注意以上四個類同屬於一個名為OnlineUser的工程,他們的命名空間為OnlineUser,編譯產生一個dll。 =============================================== 下面我簡單介紹一下調用方法: 1、 建立一個名為OnlineUserDemo的asp.net web應用程式 2、 在vs的工具箱選項卡上右擊,選擇[添加/移除項],瀏覽定位到OnlineUser.dll,確定即可把Refresh 和Script添加到工具箱。 3、 把自動產生的WebForm1.aspx刪除,並設定web.config
- <appSettings>
- <add key="ActiveTimeout" value="30" />
- <add key="RefreshTimeout" value="1" />
- <add key="refreshUrl" value="refresh.aspx" />
- </appSettings>
複製代碼
4、 添加一個名為Online.aspx的web表單,給該表單添加一個Script控制項,一個DataGrid控制項(id為DataGrid1),兩個HyperLink控制項(分別連結到login.aspx和logout.aspx,text屬性分別設定為“登陸”和“登出”),調整好四個控制項的位置,轉到codebehind,在Page_Load中加入如下代碼:
- string myTicket=(string)this.Page.Session["Ticket"];
- if(myTicket!=null)
- {
- OnlineUser.PassPort myPassPort= new OnlineUser.PassPort();
- if(myPassPort.IsOnline_byTicket(this.Session["Ticket"].ToString()))
- {
- myPassPort.ActiveTime(this.Session["Ticket"].ToString());
- DataGrid1.DataSource=myPassPort.ActiveUsers;
- DataGrid1.DataBind();
- }
- else{
- //若線上使用者列表中找不到目前使用者,則定向到登出頁面
- Response.Redirect("Logout.aspx");
- }
- }
- else{
- Response.Redirect("Login.aspx");
- }
複製代碼
5、 添加一個名為login.aspx的web表單,給該表單添加一個label控制項(id為Label1),設定text屬性為“輸入一個使用者名稱”,再添加一個textbox控制項(id為TextBox1)和一個button控制項(id為Button1),調整好他們的位置,雙擊Button1控制項轉到codebehind,為Button1的Click事件加入如下代碼:
- if(TextBox1.Text.Trim()=="")
- {
- //不可為空
- String scriptString = @"<script language=JavaScript>";
- scriptString += @"alert(""輸入一個使用者名稱/n"");";
- scriptString += @"history.go(-1);";
- scriptString += @"<";
- scriptString += @"/";
- scriptString += @"script>";
- if(!this.Page.IsStartupScriptRegistered("Startup"))
- this.Page.RegisterStartupScript("Startup", scriptString);
- }
- else{
- OnlineUser.PassPort myPassPort= new OnlineUser.PassPort();
- string myTicket=DateTime.Now.ToString("yyyyMMddHHmmss");
- string myUser=TextBox1.Text.Trim();
- string myClintIP=this.Request.UserHostAddress;
- this.Session["Ticket"]=myTicket;
- OnlineUser.ActiveUser myActiveUser=new OnlineUser.ActiveUser(myTicket,myUser,myUser,"test",myClintIP);
- myPassPort.Login(myActiveUser,true);
- Response.Redirect("Online.aspx");
- }
複製代碼
6、 添加一個名為logout.aspx的web表單,給該表單添加一個HyperLink控制項,指向login.aspx,text屬性設定為“重登陸”轉到codebehind,在Page_Load中加入如下代碼:
- OnlineUser.PassPort myPassPort= new OnlineUser.PassPort();
- myPassPort.Logout(this.Session["Ticket"].ToString());
- this.Session["Ticket"]="";
複製代碼
7、 添加一個名為Refresh.txt的文字檔,設定其內容為:
- <%@ Register TagPrefix="cc2" Namespace="OnlineUser" Assembly="OnlineUser" %>
- <%@ Page %>
- <cc2:Refresh id="myRefresh" runat="server"></cc2:Refresh>
- 把Refresh.txt改名為Refresh.aspx
複製代碼
8、 編譯產生工程。 =============================================== 下面進行功能測試: 1、 開啟瀏覽器,在地址欄輸入 http://你機器的IP地址/onlineuserdemo/Login.aspx 2、 輸入一個使用者名稱(假設是test1)登陸,自動轉到online.aspx頁面 3、 找同網段的另外一台機器(設你的機器為a,這台機器為b),重複執行第一步。 4、 輸入一個使用者名稱(假設是test2)登陸,自動轉到online.aspx頁面51aspx 5、 在b機器不斷重新整理online.aspx,若發現test1使用者RefreshTime每過25秒自動更新一次而ActiveTime不變(這個時候a機器不要重新整理頁面啊),則證明a機器的自動重新整理生效。 6、 在a機器不斷重新整理online.aspx,若發現test2使用者RefreshTime每過25秒自動更新一次而ActiveTime不變(這個時候b機器不要重新整理頁面啊),則證明b機器的自動重新整理生效。 7、 直接關閉一台機器(假設是a)上的online.aspx瀏覽視窗,在另一台機器(就是b啦)上重新整理online.aspx,若發現1分鐘後test1掉線線上使用者只剩下test2,證明通過_refreshTimeout清除線上使用者成功。 8、 若5、6、7三步正常,則大功告成,否則就再調試調試~~