最近一直忙于系统测试,应客户的需求,要求在现有系统中增加一个控制同一用户不能重复登录的功能。
经过思考,打算在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记录的用户登录标识;
如有错误,请大家多多指教。
分享到:
相关推荐
在本文中,我们将深入探讨如何使用Qt框架,结合MVC(Model-View-Controller)设计模式、SQLite3数据库以及单例模式来实现一个高效的登录注册系统。Qt是一个跨平台的应用程序开发框架,广泛用于GUI(图形用户界面)...
### 使用Java单例模式实现一个简单的登录验证系统 #### 登录验证系统概述 本文档将详细介绍如何使用Java语言结合单例设计模式实现一个简单的登录验证系统。登录验证系统是现代软件应用中的基本功能之一,它负责...
以下是一个简单的C# WinForm单例模式实现: ```csharp public sealed class SingletonForm : Form { private static readonly object syncLock = new object(); private static SingletonForm instance; ...
在软件设计模式中,"简单工厂"、"代理模式"和"单例模式"是非常重要的概念,它们在实际开发中有着广泛的应用。下面将详细解释这三个设计模式,并结合实际示例进行阐述。 **简单工厂模式**是一种创建型设计模式,它...
1. 饿汉式(静态常量):这是最简单的实现方式,它在类加载时就完成了初始化,因此是线程安全的。代码如下: ```java public class Singleton { private static final Singleton INSTANCE = new Singleton(); ...
在React中,实现单例组件主要是为了优化性能,避免重复渲染或初始化相同的组件,特别是在全局状态管理或跨组件通信时。 在需求背景下,比如有一个消息通知弹窗,需要在用户查看后不再重复显示。考虑到多页面系统,...
本篇将深入探讨标题中提及的几种设计模式:Model-View-Controller(MVC)模式、单例模式、代理模式以及工厂模式,尤其是简单工厂模式。 **1. Model-View-Controller (MVC) 模式** MVC模式是一种架构模式,它将应用...
本资源是一个关于如何使用WebUploader实现单例、多例上传图片以及图片回显的演示示例。 在Web开发中,图片上传是一项常见的需求,而WebUploader提供了一套完善的解决方案。首先,我们需要了解WebUploader的基本用法...
**PHP单例模式详解** 单例模式是一种设计模式,它在软件工程中被广泛使用,尤其是在PHP编程中。单例模式的主要目的是控制类的实例...在实际项目中,应根据具体需求权衡是否使用单例模式,以达到最佳的设计和实现效果。
下面是一个简单的单例模式的PHP实现示例: ```php class Singleton { private static $instance = null; private function __construct() {} public static function getInstance() { if (null === self::$...
在游戏编程中,对象工厂非常有用,因为它可以在运行时根据需求加载和创建不同类型的对象,如敌人、道具等,这些对象可能在游戏的不同阶段或者由用户的选择来决定。 对象工厂基于多态性,通常有一个基类和多个派生类...
在编程领域,单例模式是一种常用的软件设计模式,它确保一个类只有一个实例,并提供一个全局访问点。在WPF(Windows Presentation ...在实际项目中,根据需求和场景选择合适的单例实现以及异常处理策略至关重要。
对于登录状态的保持,一种常见做法是使用单例模式的LoginManager类,该类可以管理用户的登录状态和信息。登录成功后,将令牌存储在内存中,并设置登录状态为已登录。之后,每次启动应用或需要判断登录状态时,都检查...
这样的设计可以使得SDK的使用更加简单和一致,降低用户的使用难度,同时也方便SDK的维护和更新。 虚函数继承是C++面向对象编程的一个特性,允许子类重写父类的方法。在SDK中,可能会定义一个基类,这个基类包含了...
在iOS开发中,单例模式是一种非常常见的设计...通过不同的实现方式,我们可以根据需求选择适合的单例模式来保证线程安全和程序的稳定性。然而,使用时也需权衡其可能带来的潜在问题,确保代码的可维护性和可测试性。
选择单例模式的实现方式应根据实际需求,考虑性能、线程安全和代码简洁性等因素。静态内部类方式通常被认为是既安全又简洁的选择。 **Spring的单例** 在Spring框架中,Bean默认就是单例模式,Spring会管理Bean的...
以下是一个简单的单例模式实现: ```java public class SingletonLogin { private static SingletonLogin instance; private SingletonLogin() {} public static synchronized SingletonLogin getInstance()...
通过学习和掌握Servlet,开发者可以创建复杂的Web应用,实现与数据库交互、处理用户会话、实现安全控制等功能,从而满足现代Web服务的各种需求。同时,Servlet容器如Tomcat的使用和管理也是开发者必备的技能,因为它...
在本实例中,我们主要探讨如何使用Java的Servlet技术与MySQL数据库进行交互,这是一个基于DAO(Data Access Object)模式但不使用Bean的简单应用。首先,让我们深入了解一下Servlet、DAO模式以及MySQL数据库的基础...
了解了这两种单例模式的实现方式后,开发者可以根据项目的具体需求选择适合的方法。对于那些需要确保线程安全且性能要求较高的应用,使用GCD实现的单例模式更为合适。而简单的项目或对线程安全要求不高的场景,不...