前几天在博客园看到一个帖子,讨论两个整数集合比较的算法问题。呵呵,其实任何整数集合的问题都是可以通过位图算法解决。简单地说,就是把值转化为数组下标,将O(n)复杂度降低到O(1)复杂度来获得最高效率。当然,会牺牲一点点空间。
解决单纯的整数集合比较问题,只是纯理论的。实际上,位图算法可以应用在用户登录之后的接口验证上。服务端的设计其实也没什么复杂的地方,就是维护一个数组罢了。只不过这个数组并非是简单的整数集合,而是一个对象List。这个设计的最大优点是在验证方法中,不是通过find方法而是直接访问下标获得对象,更无需查询数据库,可谓是将时间消耗降低到了极致。
好吧,下面看代码,首先是Session类,该类承载了一些必要的用户信息。其中最关键的属性是ID,这个ID的值就是该对象在List<Session>中的下标值。
public class Session { /// <summary> /// 自增ID /// </summary> public int ID { get; set; } /// <summary> /// 会话ID /// </summary> public Guid SessionId { get; set; } /// <summary> /// 登录用户ID /// </summary> public Guid UserId { get; set; } /// <summary> /// 登录部门ID /// </summary> public Guid? DeptId { get; set; } /// <summary> /// 用户账号 /// </summary> public string LoginName { get; set; } /// <summary> /// 登录用户名 /// </summary> public string UserName { get; set; } /// <summary> /// 登录部门全称 /// </summary> public string DeptName { get; set; } /// <summary> /// WCF服务基地址 /// </summary> public string BaseAddress { get; set; } /// <summary> /// 用户签名 /// </summary> public string Signature { get; set; } /// <summary> /// 用户状态 /// </summary> public bool Validity { get; set; } /// <summary> /// 用户机器码 /// </summary> public string MachineId { get; set; } /// <summary> /// 上次连接时间 /// </summary> public DateTime LastConnect { get; set; } /// <summary> /// 用户登录状态 /// </summary> public LoginResult LoginStatus { get; set; } }
然后是OnlineManage类,这个类在服务被启动的时候实例化,并提供了一个验证会话合法性的静态方法。其中关键点是:var us = Sessions[obj.ID]这句,通过下标访问的方法,将时间复杂度降低到了O(1)。
public class OnlineManage { /// <summary> /// 用户会话列表 /// </summary> public static List<Session> Sessions { get; set; } /// <summary> /// 最大在线用户数 /// </summary> public static int MaxAuthorized { get; set; } /// <summary> /// 构造方法 /// </summary> public OnlineManage() { Sessions = new List<Session>(); MaxAuthorized = Convert.ToInt32(ConfigurationManager.AppSettings["MaxAuthorized"]); } /// <summary> /// 会话合法性验证 /// </summary> /// <param name="obj"></param> /// <returns></returns> public static bool Verification(Session obj) { if (obj == null || obj.ID >= Sessions.Count) return false; var us = Sessions[obj.ID]; if (us.SessionId != obj.SessionId || us.Signature != obj.Signature || !us.Validity) return false; us.LastConnect = DateTime.Now; return true; } }
最后是登录验证方法,这个方法通过一个O(n)的方法判断传入的Session对象是否已经存在(以前登录过系统),如果没有的话,通过查询数据库来构建一个新的Session对象被加入List。最后,无论是否登录过系统,该方法都将返回客户端一个Session对象实体。客户端每次调用服务端接口的时候,就可以将Session作为参数来进行合法性验证。
/// <summary> /// 获取用户登录结果 /// </summary> /// <param name="obj">Session对象实体</param> /// <returns>Session对象实体</returns> public Session UserLogin(Session obj) { if (obj == null) return null; if (OnlineManage.Sessions.Count >= OnlineManage.MaxAuthorized) { obj.LoginStatus = LoginResult.Unauthorized; return obj; } var pw = obj.Signature; var us = OnlineManage.Sessions.Find(s => s.LoginName == obj.LoginName); if (us == null) { var user = CommonDAL.GetUser(obj.LoginName); if (user == null) { obj.LoginStatus = LoginResult.NotExist; return obj; } obj.ID = OnlineManage.Sessions.Count; obj.UserId = user.ID; obj.UserName = user.Name; obj.Signature = user.Password; obj.Validity = user.Validity; OnlineManage.Sessions.Add(obj); us = OnlineManage.Sessions[obj.ID]; } else if (us.SessionId != Guid.Empty) { us.LoginStatus = us.MachineId != obj.MachineId ? LoginResult.Online : LoginResult.Multiple; } else { us.SessionId = obj.SessionId; us.LoginStatus = LoginResult.Success; } // 用户被封禁 if (!us.Validity) us.LoginStatus = LoginResult.Banned; // 密码不正确 if (us.Signature != pw) us.LoginStatus = LoginResult.Failure; us.LastConnect = DateTime.Now; return us; }
一些通过传入参数进行验证的例子,如退出后删除登录状态(将SessionId置为空GUID值)等。对了,还有一个好处是,只要一个账号被封禁,会立即生效而非等用户退出。
/// <summary> /// 删除在线用户会话 /// </summary> /// <param name="us">Session对象实体</param> /// <param name="sid">要删除Session的ID</param> /// <returns>bool 是否删除成功</returns> public bool DelOnlineUser(Session us, int? sid) { if (us == null) return false; if (!OnlineManage.Verification(us)) return false; OnlineManage.Sessions[sid ?? us.ID].SessionId = Guid.Empty; return true; } /// <summary> /// 根据ID封禁/解封用户 /// </summary> /// <param name="us">用户会话</param> /// <param name="id">用户ID</param> /// <param name="validity">true:解封;false:封禁</param> /// <returns>bool 是否更新成功</returns> public bool UpdateUserStatus(Session us, Guid id, bool validity) { if (!OnlineManage.Verification(us)) return false; var sql = string.Format("update SYS_User set Validity = '{0}' where ID = '{1}'", validity, id); if (SqlHelper.SqlNonQuery(sql) > 0) { OnlineManage.Sessions.Find(s => s.UserId == id).Validity = validity; return true; } return false; }
这里基本上就写这些,更多的代码请移步我的code。嗯嗯,需要一点时间整理。