點陣圖演算法在使用者驗證上的應用
阿新 • • 發佈:2019-02-02
前幾天在部落格園看到一個帖子,討論兩個整數集合比較的演算法問題。呵呵,其實任何整數集合的問題都是可以通過點陣圖演算法解決。簡單地說,就是把值轉化為陣列下標,將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。嗯嗯,需要一點時間整理。