您的位置:首页 > 其它

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

2007-12-03 17:46 716 查看
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
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐