最近一直忙于系统测试,应客户的需求,要求在现有系统中增加一个控制同一用户不能重复登录的功能。
经过思考,打算在Servlet容器初始化时实例化一个私有静态成员ConcurrentMap变量,用来记录已登录用户的用户名(唯一标识)和用户的sessionId,若用户用相同用户名在同一机器登录或者其他机器登录时,判断此ConcurrentMap中是否已存在相同的用户名来阻止用户重复登录,而实现用户单例登录。
由于系统测试基本完成,而客户要求必须加入单例登录控制,那么,出于最小改动的前提下,我决定自定义一个Listener,通过实现HttpSessionAttributeListener、HttpSessionListener、ServletContextListener来满足我的需求;
在我的Listener中定义private static ConcurrentMap<String, String> onlineUsers成员变量, 用来记录已登录用户的用户名(唯一标识)和用户的sessionId;并通过实现ServletContextListener接口的public void contextInitialized(ServletContextEvent event) 和 public void contextDestroyed(ServletContextEvent event)方法来满足我对onlineUsers成员变量的初始化和销毁;
同时,通过实现HttpSessionAttributeListener的public void attributeAdded(HttpSessionBindingEvent event) 和 public void attributeRemoved(HttpSessionBindingEvent event) 的方法,来满足用户成功登录或退出系统,在后台保存或删除用户登录信息的时候,来记录或移除用户登录的用户名和sessionId;
此外,在用户登录后超过web.xml设置的session有效期后(默认20分钟)后,系统在session销毁时应自动移除ConcurrentMap中记录的用户登录标识,这里的可通过实现HttpSessionListener接口中的public void sessionDestroyed(HttpSessionEvent event)方法可以做到。
以上只是我们用来记录用户已登录标识的实现,我们还需要提供一个方法来判断用户是否已经登录系统,在用户登录验证的时候去调用。
以下是代码,实现比较简单,不完善。并不完善,如果有错误请指出,我虚心请教;
public class MultiLoginListener implements HttpSessionAttributeListener, HttpSessionListener, ServletContextListener { private static ConcurrentMap<String, String> onlineUsers = null; public void contextInitialized(ServletContextEvent event) { onlineUsers = new ConcurrentHashMap<String, String>(); } public void contextDestroyed(ServletContextEvent event) { onlineUsers = null; } public void sessionCreated(HttpSessionEvent event) { } public void sessionDestroyed(HttpSessionEvent event) { HttpSession session = event.getSession(); if (session != null) { GuiYuan guiYuan = (GuiYuan)session.getAttribute("guiYuan"); if (guiYuan != null) { String guiyuanhao = guiYuan.getGuiyuanhao(); if (onlineUsers.containsKey(guiyuanhao)) { onlineUsers.remove(guiyuanhao); } } } } public void attributeAdded(HttpSessionBindingEvent event) { String name = event.getName(); Object value = event.getValue(); if (name != null && value != null) { if (name.equals("guiYuan") && (value instanceof GuiYuan)) { String guiyuanhao = ((GuiYuan)value).getGuiyuanhao(); if (!onlineUsers.containsKey(guiyuanhao)) { String sessionId = event.getSession().getId(); onlineUsers.put(guiyuanhao, sessionId); } } } } public void attributeRemoved(HttpSessionBindingEvent event) { String name = event.getName(); Object value = event.getValue(); if (name != null && value != null) { if (name.equals("guiYuan") && (value instanceof GuiYuan)) { String guiyuanhao = ((GuiYuan)value).getGuiyuanhao(); if (onlineUsers.containsKey(guiyuanhao)) { onlineUsers.remove(guiyuanhao); } } } } public void attributeReplaced(HttpSessionBindingEvent arg0) { } /** * <h3>检测重复登录</h3> * @param guiyuanhao 登录柜员号 * @return true:重复; false:不重复 */ public static boolean checkIsMultiLogin (String guiyuanhao) { if (onlineUsers != null && onlineUsers.containsKey(guiyuanhao)) { return true; } else { return false; } } /** * <h3>重置单例登录</h3> * @param guiyuanhao 柜员号 */ public static void resetSingleLogin (String guiyuanhao) { if (onlineUsers != null && onlineUsers.containsKey(guiyuanhao)) { onlineUsers.remove(guiyuanhao); } } }
除此之外,由于用户时常会点击IE浏览器的关闭按钮来直接退出系统,这样会锁死用户登录,对此我增加了JS对浏览器关闭事件的处理,并且在管理员模块加了紧急情况管理员解锁的功能;以下是浏览器关闭事件的处理:
// 单击浏览器关闭按钮,提交至logoff.html清除session(除去登录页面,其他所有页面都响应) window.onbeforeunload = function(){ var n = window.event.screenX - window.screenLeft; var b = n > document.documentElement.scrollWidth-20; if(b && window.event.clientY < 0 || window.event.altKey) { //alert("关闭而非刷新"); window.location.href = 'logoff.html'; } }
用户关闭浏览器时,调用后台"logoff.html"来remove掉session,此时之前实现的attributeRemoved(HttpSessionBindingEvent event)会监听到,并实现移除ConcurrentMap记录的用户登录标识;
如有错误,请大家多多指教。