解决用户意外退出在线列表无法及时更新问题2(转载)

1 一般来说,用户离开系统的方式有三种:主动注销、会话超时、直接关闭浏览器,对于前两种,我们很容易便可将该用户从在线列表中清除,关键是第三种(很多用户都是直接关闭窗口的~~郁闷ing),程序无法捕获窗口关闭的精确时间,只能等到会话超时后在能将该用户清除出在线列表,假设我们设置会话超时时间为60分钟,而用户登陆系统随便浏览一个页面就以关闭浏览器的方式退出的话,我们要在将近1小时后才能从在线列表中将该用户清除出去(想象一下,系统显示n多人在线,可能除了你之外其他的n-1人都关机走人了,汗一个先```),而本文将尝试寻找一个解决方案把这种尴尬降至最低。
  2     我的大概思路是,给每在线用户增加一个RefreshTime属性,建立一个负责将当前用户的RefreshTime属性设置为当前时间的单独页面(Refresh.aspx),然后在系统的主要页面(也可以是所有页面)中通过xmlhttp不断地请求Refresh.aspx页面,一旦用户关闭了与本系统相关的所有窗口,即以直接关闭浏览器的方式退出系统,那么该用户的RefreshTime属性便不会自动更新了,我们再设置一个自动刷新的超时时间(这个要比会话超时短很多_refreshTimeout),当发现某用户超过_refreshTimeout的时间没有自动刷新,就能判定该用户已经以直接关闭浏览器的方式退出了。
  3     假设我们设置会话超时时间为60分钟,自动刷新超时时间为1分钟,在客户端通过xmlhttp每隔25秒(之所以不设1分钟,是防止网速慢的时候访问 Refresh.aspx超时,个人感觉,不一定正确)访问一次Refresh.aspx页面,在用户登陆、用户注销、检测用户是否在线的时候都执行清理超时用户(包括会话超时和自动刷新超时)操作,这样一来,在线用户列表的统计误差就由60分钟降至1分钟了。
  4 
  5 ==========================================
  6 
  7  
  8 
  9 具体实现如下:
 10 
 11 
 12 1、 新建一个名为ActiveUser的类,存储单个活动用户数据。
 13 
 14 /// <summary>
 15  /// 单个在线用户数据,无法继承此类。
 16  /// </summary>
 17  public sealed class ActiveUser
 18  { 
 19   private readonly string _ticket;    //票据名称
 20   private readonly string _username;   //登陆用户名
 21   private readonly string _truename;   //登陆用户名
 22   private readonly string _roleid;    //角色
 23   private readonly DateTime _refreshtime;  //最新刷新时间
 24   private readonly DateTime _activetime;  //最新活动时间
 25   private readonly string _clientip;   //登陆IP
 26  
 27   public ActiveUser(string Ticket,string UserName,string TrueName,string RoleID,string ClientIP) {
 28    this._ticket=Ticket;
 29    this._username=UserName;
 30    this._truename=TrueName;
 31    this._roleid=RoleID;
 32    this._refreshtime=DateTime.Now;
 33    this._activetime=DateTime.Now;
 34    this._clientip=ClientIP;
 35   }
 36 
 37   public ActiveUser(string Ticket,string UserName,string TrueName,string RoleID,DateTime RefreshTime,DateTime ActiveTime,string ClientIP)  {
 38    this._ticket=Ticket;
 39    this._username=UserName;
 40    this._truename=TrueName;
 41    this._roleid=RoleID;
 42    this._refreshtime=RefreshTime;
 43    this._activetime=ActiveTime;
 44    this._clientip=ClientIP;
 45   }
 46  
 47   public string Ticket  { get{return _ticket;}  }
 48   public string UserName  { get{return _username;}  }
 49   public string TrueName  { get{return _truename;}  }
 50   public string RoleID  { get{return _roleid;}  }
 51   public DateTime RefreshTime { get{return _refreshtime;} }
 52   public DateTime ActiveTime { get{return _activetime;} }
 53   public string ClientIP  { get{return _clientip;}  }
 54 
 55  }
 56 
 57  
 58 
 59 
 60 2、 新建一个名为PassPort的类,存储在线用户列表。
 61 
 62 /// <summary>
 63  /// PassPort 存储在线用户列表。
 64  /// </summary>
 65  public class PassPort
 66  {
 67   private  static  DataTable  _activeusers;
 68   private  int  _activeTimeout;
 69   private  int  _refreshTimeout;
 70 
 71   /// <summary>
 72   /// 初始化在线用户表。
 73   /// </summary>
 74   private void userstableFormat()
 75   {
 76    if(_activeusers==null) {
 77     _activeusers  =  new  DataTable("ActiveUsers");
 78     DataColumn  myDataColumn;
 79     System.Type mystringtype;
 80     mystringtype = System.Type.GetType("System.String");
 81     System.Type mytimetype;
 82     mytimetype = System.Type.GetType("System.DateTime");
 83     myDataColumn  =  new  DataColumn("Ticket",mystringtype);
 84     _activeusers.Columns.Add(myDataColumn);
 85     myDataColumn  =  new  DataColumn("UserName",mystringtype);
 86     _activeusers.Columns.Add(myDataColumn);
 87     myDataColumn  =  new  DataColumn("TrueName",mystringtype);
 88     _activeusers.Columns.Add(myDataColumn);
 89     myDataColumn  =  new  DataColumn("RoleID",mystringtype);
 90     _activeusers.Columns.Add(myDataColumn);   
 91     myDataColumn  =  new  DataColumn("RefreshTime",mytimetype);
 92     _activeusers.Columns.Add(myDataColumn);
 93     myDataColumn  =  new  DataColumn("ActiveTime",mytimetype);
 94     _activeusers.Columns.Add(myDataColumn);
 95     myDataColumn  =  new  DataColumn("ClientIP",mystringtype);
 96     _activeusers.Columns.Add(myDataColumn);  
 97    }
 98   }
 99 
100   public PassPort()
101   {
102    userstableFormat(); //初始化在线用户表
103    //活动超时时间初始化 单位:分钟
104    try { _activeTimeout=int.Parse(ConfigurationSettings.AppSettings["ActiveTimeout"]); }
105    catch{ _activeTimeout=60; }  
106    //自动刷新超时时间初始化 单位:分钟
107    try { _refreshTimeout=int.Parse(ConfigurationSettings.AppSettings["RefreshTimeout"]); }
108    catch{ _refreshTimeout=1; }  
109   }
110 
111   //全部用户列表
112   public  DataTable  ActiveUsers
113   {
114    get{return  _activeusers.Copy();}
115   }
116  
117   /// <summary>
118   /// 新用户登陆。
119   /// </summary>
120   public void Login(ActiveUser user,bool SingleLogin)
121   {
122    DelTimeOut();  //清除超时用户
123    if(SingleLogin){
124     //若是单人登陆则注销原来登陆的用户
125     this.Logout(user.UserName,false);
126    }
127    DataRow myRow;
128    try
129    {
130     myRow  =  _activeusers.NewRow();   
131     myRow["Ticket"]  =  user.Ticket.Trim();
132     myRow["UserName"]  =  user.UserName.Trim();
133     myRow["TrueName"]  =  ""+user.TrueName.Trim();
134     myRow["RoleID"]  =  ""+user.RoleID.Trim();
135     myRow["ActiveTime"]  =  DateTime.Now;
136     myRow["RefreshTime"]  =  DateTime.Now;
137     myRow["ClientIP"]  =  user.ClientIP.Trim();
138     _activeusers.Rows.Add(myRow);
139    }
140    catch(Exception  e)
141    {
142     throw(new  Exception(e.Message));
143    } 
144    _activeusers.AcceptChanges();
145   
146   }
147 
148   /// <summary>
149   ///用户注销,根据Ticket或UserName。
150   /// </summary>
151   private void Logout(string strUserKey,bool byTicket)
152   {
153    DelTimeOut();  //清除超时用户
154    strUserKey=strUserKey.Trim();
155    string  strExpr;  
156    strExpr =byTicket ? "Ticket='" + strUserKey +"'" : "UserName='" + strUserKey + "'";
157    DataRow[]  curUser;
158    curUser  =  _activeusers.Select(strExpr);
159    if  (curUser.Length  >0  )
160    {
161     for(int  i  =  0;  i  <  curUser.Length;  i  ++)
162     {
163      curUser[i].Delete();
164     }
165    }
166    _activeusers.AcceptChanges();  
167   }
168 
169   /// <summary>
170   ///用户注销,根据Ticket。
171   /// </summary>
172   /// <param name="strTicket">要注销的用户Ticket</param>
173   public void Logout(string strTicket){
174    this.Logout(strTicket,true);
175   }
176 
177   /// <summary>
178   ///清除超时用户。
179   /// </summary>
180   private  bool DelTimeOut()
181   {  
182    string  strExpr;  
183    strExpr = "ActiveTime < '" + DateTime.Now.AddMinutes( 0 - _activeTimeout) + "'or RefreshTime < '"+DateTime.Now.AddMinutes( 0 - _refreshTimeout)+"'";  
184    DataRow[]  curUser;
185    curUser  =  _activeusers.Select(strExpr);
186    if  (curUser.Length  >0  )
187    {
188     for(int  i  =  0;  i  <  curUser.Length;  i  ++)
189     {
190      curUser[i].Delete();    
191     }
192    }
193    _activeusers.AcceptChanges();
194    return  true;
195   }
196 
197   /// <summary>
198   ///更新用户活动时间。
199   /// </summary>
200   public  void  ActiveTime(string  strTicket)
201   {
202    DelTimeOut();
203    string  strExpr;
204    strExpr  =  "Ticket='"  +  strTicket  +  "'"
205    DataRow[]  curUser;
206    curUser  =  _activeusers.Select(strExpr);
207    if  (curUser.Length  >0  )
208    {
209     for(int  i  =  0;  i  <  curUser.Length;  i  ++)
210     {
211      curUser[i]["ActiveTime"]=DateTime.Now;
212      curUser[i]["RefreshTime"]=DateTime.Now;
213     }
214    }
215    _activeusers.AcceptChanges();
216   }
217 
218   /// <summary>
219   ///更新系统自动刷新时间。
220   /// </summary>
221   public  void  RefreshTime(string  strTicket)
222   {
223    DelTimeOut();
224    string  strExpr;
225    strExpr  =  "Ticket='"  +  strTicket  +  "'"
226    DataRow[]  curUser;
227    curUser  =  _activeusers.Select(strExpr);
228    if  (curUser.Length  >0  )
229    {
230     for(int  i  =  0;  i  <  curUser.Length;  i  ++)
231     {
232      curUser[i]["RefreshTime"]=DateTime.Now;
233     }
234    }
235    _activeusers.AcceptChanges();
236   }
237 
238   private ActiveUser SingleUser(string strUserKey,bool byTicket)
239   {
240    strUserKey=strUserKey.Trim();
241    string  strExpr;
242    ActiveUser myuser;
243    strExpr =byTicket ? "Ticket='" + strUserKey +"'" : "UserName='" + strUserKey + "'";
244    DataRow[]  curUser;
245    curUser  =  _activeusers.Select(strExpr);
246    if  (curUser.Length  >0  )
247 {
248     string myTicket=(string)curUser[0]["Ticket"];
249     string myUser=(string)curUser[0]["UserName"];
250     string myName=(string)curUser[0]["TrueName"];
251     string myRoleID=(string)curUser[0]["RoleID"];   
252     DateTime myActiveTime=(DateTime)curUser[0]["ActiveTime"];
253     DateTime myRefreshtime=(DateTime)curUser[0]["RefreshTime"];
254     string myClientIP =(string)curUser[0]["ClientIP"];
255     myuser=new ActiveUser(myTicket,myUser,myName,myRoleID,myActiveTime,myRefreshtime,myClientIP); 
256    }
257    else
258    {
259     myuser=new ActiveUser("","","","","");   
260    }
261    return  myuser;
262   }
263 
264   /// <summary>
265   ///按Ticket获取活动用户。
266   /// </summary>
267   public ActiveUser SingleUser_byTicket(string strTicket)
268   {
269    return this.SingleUser(strTicket,true);
270   }
271 
272   /// <summary>
273   ///按UserName获取活动用户。
274   /// </summary>
275   public ActiveUser SingleUser_byUserName(string strUserName)
276   {
277    return this.SingleUser(strUserName,false);
278   }
279 
280   /// <summary>
281   ///按Ticket判断用户是否在线。
282   /// </summary>
283   public bool IsOnline_byTicket(string strTicket)
284   {
285    return (bool)(this.SingleUser(strTicket,true).UserName!="");
286   }
287 
288   /// <summary>
289   ///按UserName判断用户是否在线。
290   /// </summary>
291   public bool IsOnline_byUserName(string strUserName)
292   {
293    return (bool)(this.SingleUser(strUserName,false).UserName!="");
294   }
295 }
296 
297 
298 3、 新建一个继承自PlaceHolder名为Refresh的类,执行更新自动刷新时间操作。
299  
300 
301 /// <summary>
302  /// Refresh 执行更新自动刷新时间操作。
303  /// </summary>
304  public class Refresh: PlaceHolder
305  {
306   /// <summary>
307   /// 设置存储Ticket的Session名称,默认为Ticket。
308   /// </summary>
309   public virtual string SessionName
310   {
311    get{
312     object obj1 = this.ViewState["SessionName"];
313     if (obj1 != null){ return ((string) obj1).Trim(); }
314     return "Ticket";
315    }
316    set{
317     this.ViewState["SessionName"= value;
318    }
319   }
320 
321   protected override void Render(HtmlTextWriter writer)
322   {
323    string myTicket=(string)this.Page.Session[this.SessionName];
324    if(myTicket!=null)
325    {  
326     PassPort myPass = new PassPort();
327     myPass.RefreshTime(myTicket);
328     writer.Write("OK:"+DateTime.Now.ToString());
329    }
330    else{
331     writer.Write("Sorry:"+DateTime.Now.ToString());
332    }
333    base.Render(writer);
334  }
335 }
336 
337 4、 新建一个继承自PlaceHolder名为Script的类,生成执行xmlhttp的js脚本。。
338 
339 
340 /// <summary>
341  /// Script 生成执行xmlhttp的js脚本。
342  /// </summary>
343  public class Script: PlaceHolder
344  {
345   /// <summary>
346   /// 设置js自动刷新的间隔时间,默认为25秒。
347   /// </summary>
348   public virtual int RefreshTime
349   {
350    get
351    {
352     object obj1 = this.ViewState["RefreshTime"];
353     if (obj1 != null){return int.Parse(((string) obj1).Trim());}
354     return 25;
355    }
356    set
357    {   
358     this.ViewState["RefreshTime"= value;
359    }
360   }
361 
362   protected override void Render(HtmlTextWriter writer)
363   {
364    //从web.config中读取xmlhttp的访问地址
365    string refreshUrl=(string)ConfigurationSettings.AppSettings["refreshUrl"];
366    string scriptString = @" <script language=""JavaScript"">"+writer.NewLine;
367    scriptString += @"  window.attachEvent(""onload"", "+this.ClientID+@"_postRefresh);"+writer.NewLine;
368    scriptString += @"  var "+this.ClientID+@"_xmlhttp=null;"+writer.NewLine;
369    scriptString += @"  function "+this.ClientID+@"_postRefresh(){"+writer.NewLine;
370    scriptString += @"   var "+this.ClientID+@"_xmlhttp = new ActiveXObject(""Msxml2.XMLHTTP"");"+writer.NewLine;
371    scriptString += @"   "+this.ClientID+@"_xmlhttp.Open(""POST"", """+refreshUrl+@""", false);"+writer.NewLine;
372    scriptString += @"   "+this.ClientID+@"_xmlhttp.Send();"+writer.NewLine;
373    scriptString += @"   var refreshStr= "+this.ClientID+@"_xmlhttp.responseText;"+writer.NewLine;
374    
375    scriptString += @"   try {"+writer.NewLine;
376    scriptString += @"    var refreshStr2=refreshStr;"+writer.NewLine;
377    //scriptString += @"    alert(refreshStr2);"+writer.NewLine;
378    scriptString += @"   }"+writer.NewLine;
379    scriptString += @"   catch(e) {}"+writer.NewLine;
380    scriptString += @"   setTimeout("""+this.ClientID+@"_postRefresh()"","+this.RefreshTime.ToString()+@"000);"+writer.NewLine;
381    scriptString += @"  }"+writer.NewLine;
382    scriptString += @"<";
383    scriptString += @"/";
384    scriptString += @"script>"+writer.NewLine;
385 
386    writer.Write(writer.NewLine);
387    writer.Write(scriptString);
388    writer.Write(writer.NewLine);
389    base.Render(writer);
390   }
391  }
392 
393 
394 注意以上四个类同属于一个名为OnlineUser的工程,他们的命名空间为OnlineUser,编译生成一个dll。
395 
396  
397 
398 
399 ===============================================
400 
401  
402 
403 下面我简单介绍一下调用方法:
404 
405 1、 新建一个名为OnlineUserDemo的asp.net web应用程序
406 2、 在vs的工具箱选项卡上右击,选择[添加/移除项],浏览定位到OnlineUser.dll,确定即可把Refresh 和Script添加到工具箱。
407 3、 把自动生成的WebForm1.aspx删除,并设置web.config
408 <appSettings>
409    <add key="ActiveTimeout" value="30" />
410    <add key="RefreshTimeout" value="1" />
411    <add key="refreshUrl" value="refresh.aspx" />
412  </appSettings>
413 4、添加一个名为Online.aspx的web窗体,给该窗体添加一个Script控件,一个DataGrid控件(id为DataGrid1),两个 HyperLink控件(分别链接到login.aspx和logout.aspx,text属性分别设置为“登陆”和“注销”),调整好四个控件的位置,转到codebehind,在Page_Load中加入如下代码:
414 string myTicket=(string)this.Page.Session["Ticket"];
415    if(myTicket!=null)
416    {
417     OnlineUser.PassPort myPassPort= new OnlineUser.PassPort();
418     if(myPassPort.IsOnline_byTicket(this.Session["Ticket"].ToString()))
419     {
420      myPassPort.ActiveTime(this.Session["Ticket"].ToString());
421      DataGrid1.DataSource=myPassPort.ActiveUsers;
422      DataGrid1.DataBind();
423     }
424     else{
425      //若在线用户列表中找不到当前用户,则定向到注销页面
426      Response.Redirect("Logout.aspx");
427     }
428    }
429    else{
430     Response.Redirect("Login.aspx");
431    }
432 5、添加一个名为login.aspx的web窗体,给该窗体添加一个label控件(id为Label1),设置text属性为“输入一个用户名”,再添加一个textbox控件(id为TextBox1)和一个button控件(id为Button1),调整好他们的位置,双击Button1控件转到 codebehind,为Button1的Click事件加入如下代码:
433 if(TextBox1.Text.Trim()=="")
434    {
435     //不能为空
436     String scriptString = @"<script language=JavaScript>";
437     scriptString += @"alert(""输入一个用户名\n"");";
438     scriptString += @"history.go(-1);";
439     scriptString += @"<";
440     scriptString += @"/";
441     scriptString += @"script>";
442     if(!this.Page.IsStartupScriptRegistered("Startup"))
443      this.Page.RegisterStartupScript("Startup", scriptString);
444    }
445    else{
446     OnlineUser.PassPort myPassPort= new OnlineUser.PassPort();
447     string myTicket=DateTime.Now.ToString("yyyyMMddHHmmss");
448     string myUser=TextBox1.Text.Trim();
449     string myClintIP=this.Request.UserHostAddress;
450     this.Session["Ticket"]=myTicket;
451     OnlineUser.ActiveUser myActiveUser=new OnlineUser.ActiveUser(myTicket,myUser,myUser,"test",myClintIP);
452     myPassPort.Login(myActiveUser,true);
453     Response.Redirect("Online.aspx");
454    }
455 6、 添加一个名为logout.aspx的web窗体,给该窗体添加一个HyperLink控件,指向login.aspx,text属性设置为“重登陆”转到codebehind,在Page_Load中加入如下代码:
456 OnlineUser.PassPort myPassPort= new OnlineUser.PassPort();
457   myPassPort.Logout(this.Session["Ticket"].ToString());
458  this.Session["Ticket"]="";
459 
460 7、 添加一个名为Refresh.txt的文本文件,设置其内容为:
461 <%@ Register TagPrefix="cc2" Namespace="OnlineUser" Assembly="OnlineUser" %>
462 <%@ Page %>
463 <cc2:Refresh id="myRefresh" runat="server"></cc2:Refresh>
464 把Refresh.txt改名为Refresh.aspx
465 
466 8、 编译生成工程。
467 
468 ===============================================
469 
470 
471 下面进行功能测试:
472 
473 1、 打开浏览器,在地址栏输入
474 http://你机器的IP地址/onlineuserdemo/Login.aspx
475 2、 输入一个用户名(假设是test1)登陆,自动转到online.aspx页面
476 3、 找同网段的另外一台机器(设你的机器为a,这台机器为b),重复执行第一步。
477 4、 输入一个用户名(假设是test2)登陆,自动转到online.aspx页面
478 5、 在b机器不断刷新online.aspx,若发现test1用户RefreshTime每过25秒自动更新一次而ActiveTime不变(这个时候a机器不要刷新页面啊),则证明a机器的自动刷新生效。
479 6、 在a机器不断刷新online.aspx,若发现test2用户RefreshTime每过25秒自动更新一次而ActiveTime不变(这个时候b机器不要刷新页面啊),则证明b机器的自动刷新生效。
480 7、 直接关闭一台机器(假设是a)上的online.aspx浏览窗口,在另一台机器(就是b啦)上刷新online.aspx,若发现1分钟后test1掉线在线用户只剩下test2,证明通过_refreshTimeout清除在线用户成功。
481 8、 若5、6、7三步正常,则大功告成,否则就再调试调试~~
482 
483 
484 
posted @ 2010-06-25 16:19  gllg  阅读(463)  评论(0编辑  收藏  举报