核心实现方案
HttpSessionListener 监听会话状态
通过监听会话创建/销毁事件自动更新在线用户列表。
// 创建监听器 public class OnlineUserListener implements HttpSessionListener { private static final Set<String> onlineUsers = Collections.synchronizedSet(new HashSet<>()); @Override public void sessionCreated(HttpSessionEvent se) { String userId = (String) se.getSession().getAttribute("userId"); if (userId != null) onlineUsers.add(userId); } @Override public void sessionDestroyed(HttpSessionEvent se) { String userId = (String) se.getSession().getAttribute("userId"); onlineUsers.remove(userId); } public static Set<String> getOnlineUsers() { return new HashSet<>(onlineUsers); } }
注册监听器(web.xml):
<listener> <listener-class>com.example.OnlineUserListener</listener-class> </listener>
优点:实时性强,自动管理会话生命周期
缺点:无法捕获用户非正常退出(如直接关闭浏览器)
Filter + 心跳检测(推荐)
通过用户活动时间戳判断活跃状态,避免会话超时误判。
// 心跳过滤器 public class SessionActivityFilter implements Filter { private static final Map<String, Long> userLastActivity = new ConcurrentHashMap<>(); @Override public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException { HttpServletRequest request = (HttpServletRequest) req; String userId = (String) request.getSession().getAttribute("userId"); if (userId != null) { userLastActivity.put(userId, System.currentTimeMillis()); // 更新活动时间 } chain.doFilter(req, res); } // 获取活跃用户(5分钟内活动) public static Set<String> getActiveUsers() { long now = System.currentTimeMillis(); return userLastActivity.entrySet().stream() .filter(entry -> now - entry.getValue() < 300_000) // 5分钟 .map(Map.Entry::getKey) .collect(Collectors.toSet()); } }
配置过滤路径(web.xml):
<filter> <filter-name>sessionActivityFilter</filter-name> <filter-class>com.example.SessionActivityFilter</filter-class> </filter> <filter-mapping> <filter-name>sessionActivityFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping>
结合Redis存储分布式状态
适用于集群环境,解决多服务器间状态同步问题。
// 使用Redis记录活动时间 try (Jedis jedis = jedisPool.getResource()) { String userId = getCurrentUserId(); jedis.setex("user:activity:" + userId, 300, String.valueOf(System.currentTimeMillis())); // 5分钟过期 } // 查询在线用户 Set<String> onlineUsers = jedis.keys("user:activity:*").stream() .map(key -> key.split(":")[2]) .collect(Collectors.toSet());
优势:支持水平扩展,数据持久化
工具建议:Spring Data Redis、Lettuce
在线用户信息增强
可扩展存储更多会话数据:
public class UserSession { private String userId; private String ipAddress; private LocalDateTime loginTime; // 存储到ConcurrentHashMap或Redis } public static Map<String, UserSession> activeSessions = new ConcurrentHashMap<>();
安全性及性能优化
- 隐私保护
- 脱敏处理:仅展示部分用户ID(如
user***123
) - 权限控制:限制管理员访问
/admin/online-users
- 脱敏处理:仅展示部分用户ID(如
- 性能提升
- 缓存查询结果:使用Guava Cache定时刷新
LoadingCache<String, Set<String>> onlineUsersCache = CacheBuilder.newBuilder() .refreshAfterWrite(1, TimeUnit.MINUTES) // 自动刷新 .build(this::loadActiveUsersFromRedis);
- 缓存查询结果:使用Guava Cache定时刷新
- 会话可靠性
- 配置web.xml确保会话超时时间:
<session-config> <session-timeout>30</session-timeout> <!-- 30分钟 --> </session-config>
- 配置web.xml确保会话超时时间:
方案选型指南
场景 | 推荐方案 | 特点 |
---|---|---|
单机应用 | HttpSessionListener | 简单易用,零额外依赖 |
高精度活跃检测 | Filter + 时间戳 | 避免会话超时误差 |
微服务/集群 | Redis存储 | 分布式支持,高可用 |
需要详细会话数据 | 扩展UserSession对象 | 记录IP、设备等元信息 |
关键注意事项
- 会话固定攻击防护
用户登录后重置Session ID:request.getSession().invalidate(); HttpSession newSession = request.getSession(true);
- 内存泄漏预防
使用WeakReference存储会话数据,或在sessionDestroyed()中显式清除引用 - 跨平台兼容
对于WebSocket连接,可通过@OnOpen
/@OnClose
注解管理状态
最佳实践:生产环境建议采用 Filter+Redis 组合方案,平衡实时性、扩展性和资源开销,通过Spring Boot Actuator可集成监控端点,实时查看在线用户数据。
引用说明:本文代码实现参考Oracle官方HttpSessionListener文档,Redis会话管理方案基于Spring Session最佳实践,安全建议依据OWASP会话管理标准。
原创文章,发布者:酷盾叔,转转请注明出处:https://www.kd.cn/ask/9535.html