解決 MVC 使用者上線下線狀態問題

atliwen發表於2014-03-07

 以前工作專案中就有一個微博類功能,其中也出現了使用者線上和離線的問題。 

但是因為初入程式猿 使用的是 Session _end 上個事件。 Session _end 這個事件不需要怎麼解釋吧 就是在seesion過期的時候所觸發的事件,但有BUG啊! 因為 iis 中由於有程式回收機制,系統繁忙的話Session會丟失。 當然 微軟解決又弄了個 程式外seesion 來解決了一下 。額 當時楞是沒想起來為啥 ,還是經驗少啊。

 

今天突然看了些  線上狀態的問題  突發奇想   想自己不適用Seesion _end  來做一個 線上狀態的實現。  百度了半天 還是沒啥收穫,最後決定  得  自己寫吧!

 

   首先想到就是 利用靜態物件  得特性 (在程式執行前就建立好物件並且知道程式結束之前不會被釋放掉。)前幾天從新看了一下程式和執行緒   程式和執行緒執行是通過作業系統維護的一個程式表維護著的 排程器  調整執行的。 所以就聯想到 在管道事件 Application_Start() 中來維護一個 靜態集合物件 通過定時器 實現 session_end 這個事件 

 

   Globel.asax.cs 檔案中新增一個事件

 public class MvcApplication : System.Web.HttpApplication
    {
        protected void Application_Start()
        {
            AreaRegistration.RegisterAllAreas();
          
            WebApiConfig.Register(GlobalConfiguration.Configuration);
            FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
            RouteConfig.RegisterRoutes(RouteTable.Routes);
            BundleConfig.RegisterBundles(BundleTable.Bundles);
            // 開啟一個定時器  並且執行一個方法
            StatusMy.GetStatusMy().TestTimer();

        }
    }
 StatusMy 物件類 是關鍵的問題   程式碼並不多 應該很好理解  

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;

namespace OnlineStatus.Models
{
    using System.Data.Entity;
    using System.Data.Entity.Infrastructure;
    using System.Data.Entity.Validation;

    using OnlineStatus.Models.Model;

    public class StatusMy : System.Web.HttpApplication
    {
        #region 單例
        private StatusMy()
        {
        }

        private static StatusMy IStatusMy;

        public static StatusMy GetStatusMy()
        {
            return IStatusMy ?? (IStatusMy = new StatusMy());
        }
        #endregion

        /// <summary>
        /// 定義定時器 如果是public 很有可能被GC掉
        /// </summary>
        private System.Threading.Timer timerClose;

        /// <summary>
        /// 定義個靜態集合  
        /// </summary>
        private static List<MyUser> listMy;

        /// <summary>
        /// 定義一個 Lock 的時候使用的 Object 可以使用this 但是如果當前的這個this 是public 的話會有可能出現異常 保險期間還是使用MSDN 官方
        /// </summary>
        private object thisLock = new object();

        /// <summary>
        /// 執行定時器
        /// </summary>
        public void TestTimer()
        {
            timerClose = new System.Threading.Timer(ToMyContent, null, 0, 30000);
        }


        /// <summary>
        /// 利用Application的特性 維護一個 集合表 表中記錄了最後一次網站時間 如果最後一次訪問時間 小於伺服器時間20分鐘者在資料庫中設定為 離線狀態
        /// </summary>
        /// <param name="o">
        /// The o.
        /// </param>
        private void ToMyContent(object o)
        {
            if (listMy == null || listMy.Count <= 0)
            {
                return;
            }

            DbContext db = MyDbContext.GetCurrentEFContext();//使用的是CallContxt 確保當前程式內唯一

            // 定義一個變數用來判斷  維護著的List 物件是否有值  方便最後EF統一執行SQL 語句
            int i = 0;
            foreach (MyUser myContent in listMy)
            {
                TimeSpan s = new TimeSpan(0, 0, 0, 20);// 這是時間差為20分鐘

                System.TimeSpan ts = DateTime.Now.Subtract(myContent.UTime); //通過當前伺服器時間減去使用者最後一次訪問伺服器的時間的出來的 時間差

                if (ts < s)
                {
                    continue; // 跳出這次迴圈 不執行  if 後續程式碼
                }

                // 做修改標註
                User u = new User { ID = myContent.ID, Status = false, UTime = myContent.UTime, Name = myContent.Name };
                DbEntityEntry<User> entry = db.Entry<User>(u);
                entry.State = System.Data.EntityState.Modified;
                entry.Property(a => a.Status).IsModified = true;

                i++;
            }

            if (i == 0)
            {
                return;
            }

            try
            {
                db.SaveChanges();
            }
            catch (DbEntityValidationException dbEx)
            {

            }
        }


        /// <summary>
        ///  使用者訪問的 將其新增維護到 集合中
        /// </summary>
        /// <param name="u"></param>
        public void AddList(User u)
        {
            if (u == null && u.ID <= 0)
                return;

            MyUser myU = new MyUser { ID = u.ID, Name = u.Name, UTime = DateTime.Now, Status = u.Status };

            if (listMy == null)
            {
                lock (thisLock)
                {
                    listMy = new List<MyUser> { myU };
                }

                return;
            }

            MyUser m = listMy.FirstOrDefault(c => c.ID == myU.ID);
            if (m != null && m.ID != 0)
            {
                lock (thisLock)
                {
                    listMy.Remove(m);
                    listMy.Add(myU);
                }
            }
            else
            {
                lock (thisLock)
                {
                    listMy.Add(myU);
                }
            }
        }
    }
}

 

   MyDbContext類   執行緒唯一   EF上下文的問題 你懂的

public  static class MyDbContext
    {
        public static DbContext GetCurrentEFContext()
        {

            DbContext dbContext = CallContext.GetData("EFContext") as DbContext;
            if (dbContext==null)
            {
                dbContext = new Model1Container();
                dbContext.Configuration.ValidateOnSaveEnabled = true;
                CallContext.SetData("EFContext", dbContext);
            }
            return dbContext;
           

        }
    }

 

    MVC 的全域性過濾器  可以做到  不管使用者訪問那個伺服器頁面 都可以進行操作。 額  面向切面程式設計?  

 

  在 FilterConfig 類中新增過濾器類

 public class FilterConfig
    {
        public static void RegisterGlobalFilters(GlobalFilterCollection filters)
        {
            filters.Add(new HandleErrorAttribute());

            filters.Add(new OnlineStatus.Controllers.LoginValidateAttribute());
        }
    }

  LoginValidateAttribute  自定義過濾器的實現 

 public class LoginValidateAttribute : System.Web.Mvc.AuthorizeAttribute
    {

        public override void OnAuthorization(System.Web.Mvc.AuthorizationContext filterContext)
        {

            // 判斷 請求的 控制器是不是等於指定的控制器  如果不留下一個不用驗證的控制器 那麼就會一直在伺服器跳轉
            if (filterContext.RouteData.Values["controller"].ToString() != "RegisterLogin")
            {
                #region 1.驗證使用者是否登陸(Session && Cookie)
                //1.驗證使用者是否登陸(Session && Cookie)
                if (!OnlineStatus.Controllers.Help.ToHelp().IsLogin())
                {
                    filterContext.Result = filterContext.Result = new RedirectResult("/RegisterLogin/index");
                }
                #endregion     
            }

        }
    }

 

ToHelp 類 的實現 

 public class Help
    {

        private Help()
        {
            
        }

        private static Help Ihelp;

        public static Help ToHelp()
        {
            return Ihelp ?? (Ihelp = new Help());
        }


        #region 0.1 Http上下文 及 相關屬性
        /// <summary>
        /// Http上下文
        /// </summary>
        HttpContext ContextHttp
        {
            get
            {
                return HttpContext.Current;
            }
        }

        HttpResponse Response
        {
            get
            {
                return ContextHttp.Response;
            }
        }

        HttpRequest Request
        {
            get
            {
                return ContextHttp.Request;
            }
        }

        System.Web.SessionState.HttpSessionState Session
        {
            get
            {
                return ContextHttp.Session;
            }
        }
        #endregion


        #region 2.1 當前使用者物件 +MODEL.Ou_UserInfo Usr
        // <summary>
        /// 當前使用者物件
        /// </summary>
        public Models.User Usr
        {
            get
            {
                return Session["name"] as Models.User;
            }
            set
            {
                Session["name"] = value;
            }
        }
        #endregion

        /// <summary>
        /// 驗證使用者是否登入
        /// </summary>
        /// <returns></returns>
        public bool IsLogin()
        {
            if (Session["name"] == null)
            {
                if (Request.Cookies["name"] == null)
                {
                    return false;
                }
                string strUser = Request.Cookies["name"].Value;

                int userid = int.Parse(strUser);

                DbContext db = MyDbContext.GetCurrentEFContext();
                OnlineStatus.Models.User use = db.Set<OnlineStatus.Models.User>().FirstOrDefault(c => c.ID == userid);
                if (use == null && use.ID <= 0)
                {
                    return false;
                }
                Usr = use;

                //將其新增到維護的 狀態集合中
                StatusMy.GetStatusMy().AddList(use);


            }
            return true;
        }


        /// <summary>
        /// 登入 這裡就簡單寫了 名稱密碼都對 就true 不對 false  額  以為是想簡單寫一下 資料庫居然沒弄密碼 額 
        /// </summary>
        /// <param name="name"></param>
        /// <param name="pwd"></param>
        /// <returns></returns>
        public bool Login(string name)
        {
            DbContext db = MyDbContext.GetCurrentEFContext();
            OnlineStatus.Models.User use = db.Set<OnlineStatus.Models.User>().FirstOrDefault(c => c.Name == name);
            if (use == null && use.ID >= 0)
            {
                return false;
            }
            //將其新增到維護的 狀態集合中
            StatusMy.GetStatusMy().AddList(use);
            return true;
        }

    }
 
我試了試 OK

一直都是自己一個人研究來研究去的 冒泡出來 純粹是來 希望和大神們交流一下 ,請大神們指點一下。

不喜勿噴。
看帖 求評論啊, 真的很希望能有人 指點指點啊






 

 

 

 

 

 

 

 

相關文章