延续简单的重复登录控制(java版)中的思路,用ASP.NET做了类似的实现。
第一步,构造一个简单的在线会员对象。
[Serializable] public class OnlineMember { /// <summary> /// MemberID /// </summary> public int MemberID { get; set; } /// <summary> /// SessionID /// </summary> public string SessionID { get; set; } /// <summary> /// 最后通知时间 /// </summary> public DateTime LastNotifyTime { get; set; } /// <summary> /// 登录IP /// </summary> public string LoginIP { get; set; } }
第二步,实现在线会员处理功能。
/// <summary> /// 简易在线会员功能 /// 遵循类似QQ登录的原则:同一帐号登录,总是后登录的挤掉前登录的 /// </summary> public class OnlineBiz { private static Dictionary<int, OnlineMember> _onlineMember = new Dictionary<int, OnlineMember>(); private static object _syncObj = new object(); private static DateTime _lastClearTime = DateTime.Now; /// <summary> /// 添加 /// </summary> /// <param name="MemberID"></param> /// <param name="SessionID"></param> /// <param name="LoginIP"></param> public static void Add(int MemberID, string SessionID, string LoginIP) { lock (_syncObj) { if (_onlineMember.ContainsKey(MemberID)) { _onlineMember.Remove(MemberID); } OnlineMember member = new OnlineMember(); member.MemberID = MemberID; member.SessionID = SessionID; member.LoginIP = LoginIP; member.LastNotifyTime = DateTime.Now; _onlineMember.Add(MemberID, member); } } /// <summary> /// 当前会员是否有效 /// </summary> /// <param name="MemberID"></param> /// <param name="SessionID"></param> /// <returns></returns> public static bool IsValid(int MemberID, string SessionID) { if (!_onlineMember.ContainsKey(MemberID)) return false; if (!_onlineMember[MemberID].SessionID.Equals(SessionID)) return false; _onlineMember[MemberID].LastNotifyTime = DateTime.Now; ClearTimeoutMember(); return true; } /// <summary> /// 移除 /// </summary> /// <param name="MemberID"></param> public static void Remove(int MemberID) { if (_onlineMember.ContainsKey(MemberID)) _onlineMember.Remove(MemberID); } /// <summary> /// 清除超时会员 /// </summary> private static void ClearTimeoutMember() { int MemberLoginTimeout = int.Parse(ConfigHelper.GetParameterValue("MemberLoginTimeout")); TimeSpan time1 = DateTime.Now - _lastClearTime; if (time1.Minutes > MemberLoginTimeout * 5) { lock (_syncObj) { _lastClearTime = DateTime.Now; foreach (KeyValuePair<int, OnlineMember> kvp in _onlineMember) { TimeSpan time2 = DateTime.Now - kvp.Value.LastNotifyTime; if (time2.Minutes > MemberLoginTimeout) { _onlineMember.Remove(kvp.Key); } } } } } }
第三步,在会员登录成功时将会员放入队列。
//将当前会员写入在线列表 OnlineBiz.Add(member.MemberID, Session.SessionID, log.LoginIP);
第四步,实现一个在线状态维护的轮询功能,比如用AJAX定时读取某个页面,该页面中进行会员有效性检查。
//当前会员是否有效,重复登录的会员遵循后登录的挤掉前登录的原则 if (!OnlineBiz.IsValid(member.MemberID, context.Session.SessionID)) { context.Session.Clear(); context.Response.Write(context.Request.QueryString["jsoncallback"] + "({result: 0})"); return; }
同时,为了保证严谨性,使已踢掉的登录不能继续操作,还可在页面基类中进行同样的检查。
第五步,在客户端进行提示。
if (json.result == 0) { alert("本帐号已在别处登录,你已被迫下线!"); self.location = "<%=Request.ApplicationPath%>/"; return; }
需要说明的是,“超时会员清理”采用了一个偷懒且不严谨的处理方式:
- 清理动作由轮询页面触发,当后续没有会员在线的情况下,不会执行该操作。
- 假设会员超时时间为20分钟,为了稍许提高性能,我的代码每100分钟以上才会做次扫描,剔除已超时的会员。
- 假设会员超时时间为20分钟,轮询间隔时间应当小于它,比如设为2分钟、5分钟之类的比较合适。
该功能已在实际系统中应用,暂时没有发现特别的问题,若你采用了这种处理方式,发现其中存在问题,请告知我。