场景描述

项目中需要获取所有在线的用户,当用户登录时,就记录住在线状态,当退出时或一定时间中无操作(超时)时,就记录离线状态

设想办法

众所周知,当用户登录后,会把登录信息放入session中,既然session中存有登录信息,那么是否可以在session中查找用户,session中有的就是在线用户呢?显然这是不行的,首先,要了解什么是session。

session是一个容器,也是一个会话,当有请求到服务器时,服务器就产生一个session,会有一个唯一标识sessionId,session对象是一个用户全局变量,也可以看作一个私有的,在整个会话期间一直都有效(浏览器不关闭,当然及时关闭了也不会立马清除),当然如果一直无操作的话,也会超时失效,默认应该是半个小时,这个可以自己设置。由于session是在服务器上的私有空间,因此是不能访问别人的session信息的。因此不能根据session来判断是否在线。

1.数据库保存登录状态

当用户登录时可以把状态设置1(在线),退出时设置0(离线),看着感觉是可行的,但感觉设计上不是很好。

2.放在服务器的公共对象中

放在共享的空间,每个用户都能访问或共享的对象中

解决办法

根据上面分析session之所以不能满足需求,就是因为它是私有的,用户之间不能共享,那么问题就简单了,如果有一个公共的,用户可以共享的对象,那么把登录的用户放进去,岂不是可以判断出在线的用户?
一个web服务器启动时,会自动的创建一个application对象,直到web服务停止,在整个服务器的生命周期中,所有的用户都公用这个application对象,它是公共的,因此可以拿来存放共享的数据,那么问题就简单了,当用户登录时,不仅把登录信息放进session,同时也把用户放进application中,当用户退出或者超时时,也就是session销毁时,把用户从application中移除,那么就可以在application中一直都保存登录的用户,当查询在线用户时,只需要把application中的数据取出来即可。

代码分享

核心就是写一个监听器,实现HttpSessionListenerServletContextListener,在服务器启动初始化ServletContext时,往application中放一个set的空集合(存用户帐号,不会有重复数据),当每一次用户进行登录时取出application中的set,把帐号保存set中,再放入application中


import java.util.HashSet;
import java.util.Set;

import javax.servlet.ServletContext;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import javax.servlet.http.HttpSession;
import javax.servlet.http.HttpSessionEvent;
import javax.servlet.http.HttpSessionListener;


public class LoginListener implements HttpSessionListener,ServletContextListener{
    private ServletContext application = null;
    public void contextDestroyed(ServletContextEvent sce) {
        System.out.println("context destory");
    }

    public void contextInitialized(ServletContextEvent sce) {
        System.out.println("context init");
        application = sce.getServletContext();
        Set<String> onlineUserSet = new HashSet<String>();
        application.setAttribute("onlineUserSet", onlineUserSet);
    }

    public void sessionCreated(HttpSessionEvent se) {

        System.out.println("session create");
    }

    public void sessionDestroyed(HttpSessionEvent se) {
        HttpSession session = se.getSession();
        Set<String> onlineUserSet = (Set<String>)application.getAttribute("onlineUserSet");
        String username = (String)session.getAttribute("username");
        onlineUserSet.remove(username);
        application.setAttribute("onlineUserSet", onlineUserSet);
        onlineUserSet = (Set<String>)application.getAttribute("onlineUserSet");
        System.out.println(onlineUserSet.toString());
        System.out.println(username + "超时退出");
        System.out.println("session destory");
    }

}

登录时代码

    HttpServletRequest request = ServletActionContext.getRequest();
    HttpSession session = request.getSession();
    session.setMaxInactiveInterval(60);
    ServletContext application = session.getServletContext();
    Set<String> onlineUserSet = new HashSet<String>();
    session.setAttribute("username", name);
    onlineUserSet = (Set)application.getAttribute("onlineUserSet");
    onlineUserSet.add(name);
    application.setAttribute("onlineUserList", onlineUserSet);
    onlineUserSet = (Set)application.getAttribute("onlineUserSet");

Mark

Mark1——2017-01-25

工作上实际并不是按照上面的方法解决的

项目里用的redis,把登陆用户信息存在redis中,每隔一段时间删除,前端登陆成功后向后台发送请求,每隔一段时间请求一次,重新把当前登录用户写入redis,这样保证登陆用户一直存在redis中

Logo

权威|前沿|技术|干货|国内首个API全生命周期开发者社区

更多推荐