原理:
假设用户在机器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缓存读取,所以轮询没有访问数据库),然后不访问数据库,但是数据量大的话,服务器压力也是挺大的,暂时没有更好的解决方案。