1. 程式人生 > >點陣圖演算法在使用者驗證上的應用

點陣圖演算法在使用者驗證上的應用

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