同一账号同一时间在不同地点登陆实现登陆剔出功能
公司项目中最近涉及到在网站登陆账号时,同一账号同一时间在不同地点登陆时要实现踢出功能,通俗的讲也就是用户的账号若已经登陆,那么此时此用户再在别的地方登陆就要将先前登陆的账号踢下线。
先理一下思路,我们知道在客户端浏览器访问服务器的时候,服务器把客户端信息以某种形式记录在服务器上,这就是session。session在用户第一次访问服务器的时候自动创建,也就是说每个用户对应一个session。那么我们只要使先前登陆的用户失效就能实现踢出功能。
方案实现,在登陆逻辑处添加一个方法,在方法中我们实现相应的同一账号登陆账号具备踢出下线功能。首先设定一个Map1,在里面我们以键值对的方式存储当前在线用户的用户ID和其对应的session,同时设定Map2,同样以键值对的方式存储被踢出用户的用户ID和其对应的session,另外为了防止java的垃圾回收机制将我们设定的Map回收,还要将两个Map存储到全局缓存application中。每当有用户登陆时会先判断是否有以此用户ID为Key的Map存在,若存在则说明用户已登陆,就将已登陆用户的用户ID和其对应的session存放到Map2中,并将已登陆用户对应的session注销,此时已登陆用户就会因session失效而掉线;若不存在则说明此用户没有在别处登陆。无论用户有没有在别处登陆,都将当前登陆的用户ID和其对应的session存入Map1中。这样就会实现用户登陆剔出功能。但这样还没有结束,我们将已登陆的用户剔出后,还要以弹框的形式提示他“当前用户已在别处登陆,请返回登陆页重新登陆”。弹框功能的实现是通过前台ajax监测后台登陆状态的形式实现的,ajax每隔一段时间向后台发送固定参数,以用户ID为唯一标识,检测登陆状态,若发现用户session已被放到踢出队列(即相应的Map中),则进行提示。另外,由于在JS中的函数是放在单独文件中实现的,所以无法实时获取对应的用户ID,在这里采用的是以cookie的形式将用户信息在登陆的html页面中存入,并在JS中获取并传入ajax的参数中。最后将正常登陆用户和被踢出的用户ID、登陆时间、登陆IP、登陆(或踢出状态)、sessionid都存入数据库中,以备出现问题可以进行查询。
--------------------------------------------------------------------------------------------下面上代码---------------------------------------------------------------------
校验用户是否登陆代码:
public void isLoaded(String sUser,HttpSession sessions,PageData pd) throws Exception{ LoginDao dao=new LoginDao(pd); ServletContext application=pd.getSession().getServletContext(); SimpleDateFormat mDate=new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); String cTime=mDate.format(new Date()); String IpAdrr=dao.getClientIp(pd); HashMap<String, HttpSession> cMap=(HashMap<String, HttpSession>) application.getAttribute(sUser); try { //判断是否当前用户名已登录 if(!(cMap==null)){ HttpSession mSession=cMap.get(sUser); String mIP=fUserMap.get(sUser); String mSessionId=mSession.getId();//被踢掉session String cSessionId=sessions.getId();//在线session //判断是否为同一浏览器登陆 if(!(mSessionId.equals(cSessionId))){ passMap.put(sUser+"T", mSession); application.setAttribute(sUser+"T", passMap); dao.addLoginInfo(sUser, cTime, mIP, mSessionId, "用户在别处登陆"); if(!(mSession.getAttribute("ifNull")==null)){ mSession.invalidate(); } } } } catch (Exception e) { log.debug("session已失效"); }finally{ //无论当前用户名登陆与否,都将当前用户名与其对应session存储到map中 sessions.setAttribute("ifNull", "a"); hUserMap.put(sUser, sessions); fUserMap.put(sUser, IpAdrr); application.setAttribute(sUser,hUserMap); log.debug("登录的用户名:"+sUser); log.debug("登录的时间:"+cTime); log.debug("登录的IP地址:"+IpAdrr); log.debug("当前sessionId:"+sessions.getId()); //将登陆信息存入数据库 dao.addLoginInfo(sUser, cTime, IpAdrr, sessions.getId(), "正常登陆"); } }
校验用户是否已被剔出代码,并形成弹框提示状态:
public boolean tUserState(String tUser,PageData pd){ String CurrentSessionId=pd.getSession().getId(); ServletContext application=pd.getSession().getServletContext(); //被踢掉的存储map HashMap<String, HttpSession> pMap=(HashMap<String, HttpSession>) application.getAttribute(tUser+"T"); //在线map HashMap<String, HttpSession> zMap=(HashMap<String, HttpSession>) application.getAttribute(tUser); if(!(pMap==null)){ //只有被踢掉的sessionid与请求的sessionid相同时才有提示框,也就是说只有被踢掉的浏览器才提示 if(!(zMap.get(tUser).getId()).equals(CurrentSessionId)){ //检测到有同一用户被踢后,就将该用户信息在application中删除,防止其他用户登录检测到不为空 application.removeAttribute(tUser+"T"); return true; } else{ return false; } }else{ return false; } }接收ajax参数,并向ajax返回用户状态代码:
public void loginState(IRequestCycle cycle) throws Exception{ PageData pd = getPageData(); IData data =pd.getData(); IData mloginState=new DataMap(); LoginBean bean = (LoginBean)BeanFactory.getBeanFactory().getBean(LoginBean.class); SimpleDateFormat mDate=new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); String cTime=mDate.format(new Date()); boolean mState=bean.tUserState(data.getString("tUser"),pd); log.debug("Myajax:"+data.getString("tUser")); String nState=String.valueOf(mState); mloginState.put("STATE", nState); log.debug("loginStateRun:"+nState+",time:"+cTime); pd.setAjaxData(mloginState); }html登陆页面cookie获取参数代码:
function setCookie() { var Days = 60; //cookie 将被保存两个月 var exp = new Date(); //获得当前时间 exp.setTime(exp.getTime() + Days * 24 * 60 * 60 * 1000); //换成毫秒 document.cookie = "kang"+ "=" + escape($("#STAFF_LOGINID").val()) + ";expires=" + exp.toGMTString(); }
js文件ajax登陆状态监测代码:
$(function () { setInterval("loginAjax()", 20000); }); function getCookie(name) { //取出cookie var strCookie = document.cookie; //cookie的保存格式是 分号加空格 "; " var arrCookie = strCookie.split("; "); for ( var i = 0; i < arrCookie.length; i++) { var arr = arrCookie[i].split("="); if (arr[0] == name) { return arr[1]; } } return ""; } function loginAjax(){ var t=getCookie("kang"); var params = {page:"Home", listener:"loginState", param:"&tUser="+t, partId:null, formId:"loginForm", afterFn:afterState}; $.ajaxSubmit(params); } function afterState(data){ var state=data[0].STATE; if(state=="true"){ layer.alert("您的账号已在别处登陆,请返回登录页重新登陆!", 8, "温馨提示"); } }写在最后,实现此功能还有其他方法,比如说用监听器监听等。本方法中尚存在不足,由于ajax的监听是每隔一段时间监测,所以提示弹框的实时性可能不是太高,若时间设置太短可能会对服务器造成较大的压力,但功能还是能实现。若有更好方案的同志请留言给我,我们可以一起讨论一下。