原理:
假設用戶在機器A登陸后,
這時用戶再次在機器B登陸,會以當前會話的SessionID作為鍵,用戶id作為值,插入dictionary集合中,集合再保存在application(保存在服務器的全局變量,多用戶可以共享)變量中,
同時判斷集合中是否有其他值,這里A機器已經登陸,所以會有A機器登陸的鍵值對,將A機器的鍵對應值修改為“_offline_”,以表示強制下線,
A機器的頁面通過js輪詢去查詢dictionary集合,發現中SessionID鍵對應的值被修改為“_offline_”,從而注銷登陸,並提示被迫下線。
1、global中的代碼:
public class MvcApplication : System.Web.HttpApplication { protected void Application_Start() { AreaRegistration.RegisterAllAreas(); FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters); RouteConfig.RegisterRoutes(RouteTable.Routes); } //保證同一次會話的SessionID不變 protected void Session_Start(object sender, EventArgs e) { } protected void Session_End(object sender, EventArgs e) { Hashtable hOnline = (Hashtable)Application["Online"]; if (hOnline != null) { if (hOnline[Session.SessionID] != null) { hOnline.Remove(Session.SessionID); Application.Lock(); Application["Online"] = hOnline; Application.UnLock(); } } } }
注:保證同一次會話的SessionID不變,這點很重要
2、用戶登陸代碼:
..... HttpContext httpContext = System.Web.HttpContext.Current; var userOnline = (Dictionary<string,string>)httpContext.Application["Online"]; if (userOnline != null) {
IDictionaryEnumerator enumerator = userOnline.GetEnumerator();
while (enumerator.MoveNext())
{
if (enumerator.Value != null && enumerator.Value.ToString().Equals(userID.ToString()))
{
userOnline[enumerator.Key.ToString()] = "_offline_";
break;
}
}
}
else { userOnline = new Hashtable(); } userOnline[Session.SessionID] = userID.ToString(); httpContext.Application.Lock(); httpContext.Application["Online"] = userOnline; httpContext.Application.UnLock(); ......
4、頁面輪詢(可以在母版頁,公共頁)
前台js用的easyui
$(document).ready(function () { //定時檢測是否被強制下線 setInterval(function () { CheckIsForcedLogout(); }, 5000); }); //檢測是否被強制下線 function CheckIsForcedLogout() { $.ajax({ url: "/Home/CheckIsForcedLogout", type: "POST", dataType: "json", success: function (msg) { if (msg.OperateResult == "Success") { $.messager.alert('', msg.OperateData, 'error', function () { window.location.href = "/Account/Login"; }); } }, error: function (ex) { } }); }
[HttpPost] public JsonResult CheckIsForcedLogout() { try { HttpContext httpContext = System.Web.HttpContext.Current; Hashtable userOnline = (Hashtable)httpContext.Application["Online"];if (userOnline != null) { if (userOnline.ContainsKey(httpContext.Session.SessionID)) { var value=userOnline[httpContext.Session.SessionID]; //判斷當前session保存的值是否為被注銷值 if (value != null && "_offline_".Equals(value)) { //驗證被注銷則清空session userOnline.Remove(httpContext.Session.SessionID); httpContext.Application.Lock(); httpContext.Application["online"] = userOnline; httpContext.Application.UnLock(); string msg = "下線通知:當前賬號另一地點登錄, 您被迫下線。若非本人操作,您的登錄密碼很可能已經泄露,請及時改密。"; //登出,清除cookie FormsAuthentication.SignOut(); return Json(new { OperateResult = "Success", OperateData = msg }, JsonRequestBehavior.AllowGet); } } } return Json(new { OperateResult ="Failed" }, JsonRequestBehavior.AllowGet); } catch (Exception ex) { return Json(new { OperateResult = "Failed" }, JsonRequestBehavior.AllowGet); } }
這里登陸后,每5秒輪詢服務器(獲取最后登陸時間、ip是從redis緩存讀取,所以輪詢沒有訪問數據庫),然后不訪問數據庫,但是數據量大的話,服務器壓力也是挺大的,暫時沒有更好的解決方案。