You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

JEE Web应用如何在用户关闭全部浏览器窗口时记录登出时间?

解决方案:JEE Web应用捕获全窗口关闭时的登出时间记录

针对你的核心痛点——关闭所有浏览器窗口时无法及时触发登出时间记录,结合JEE CDI会话Bean的特性,推荐以下方案:

核心思路

通过客户端追踪活跃标签页数量,仅在最后一个标签页关闭时向服务器发送登出请求,主动销毁会话并触发CDI Bean的销毁逻辑,同时保留超时场景下的兜底记录。

1. 客户端标签页计数逻辑

使用localStorage维护跨标签页的活跃计数,确保只有最后一个窗口关闭时才触发登出请求:

window.addEventListener('load', function() {
    // 初始化标签页计数
    let tabCount = parseInt(localStorage.getItem('tabCount') || 0);
    tabCount++;
    localStorage.setItem('tabCount', tabCount);

    // 监听其他标签页的计数变化,同步本地计数
    window.addEventListener('storage', function(e) {
        if (e.key === 'tabCount') {
            tabCount = parseInt(e.newValue || 0);
        }
    });

    // 页面关闭/刷新时更新计数并判断是否触发登出
    window.addEventListener('beforeunload', function() {
        tabCount--;
        localStorage.setItem('tabCount', tabCount);
        
        if (tabCount === 0) {
            // 发送同步请求,避免浏览器中断异步请求
            const xhr = new XMLHttpRequest();
            xhr.open('POST', '/logout/last-tab', false);
            xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
            xhr.send();
        }
    });
});

2. 服务器端CDI Bean与请求处理

在LoginController中新增最后标签页登出的处理逻辑,主动销毁会话以触发@PreDestroy:

import javax.enterprise.context.SessionScoped;
import javax.inject.Named;
import javax.servlet.http.HttpSession;
import javax.inject.Inject;
import java.io.Serializable;
import java.time.LocalDateTime;

@Named
@SessionScoped
public class LoginController implements Serializable {

    private LocalDateTime logoutTime;
    @Inject
    private HttpSession session;

    // 已实现的手动登出方法
    public void manualLogout() {
        recordLogoutTime();
        session.invalidate();
    }

    // 处理最后一个标签页关闭的请求
    public void handleLastTabLogout() {
        recordLogoutTime();
        session.invalidate(); // 主动销毁会话,立即触发@PreDestroy
    }

    // 统一的登出时间记录逻辑
    private void recordLogoutTime() {
        logoutTime = LocalDateTime.now();
        // 此处添加持久化逻辑(如写入数据库)
    }

    @PreDestroy
    public void preDestroy() {
        // 兜底处理会话超时销毁的场景
        if (logoutTime == null) {
            recordLogoutTime();
        }
    }
}

3. 请求映射Servlet

新增Servlet映射客户端请求,调用CDI Bean的处理方法:

import javax.inject.Inject;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

@WebServlet("/logout/last-tab")
public class LastTabLogoutServlet extends HttpServlet {

    @Inject
    private LoginController loginController;

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws IOException {
        loginController.handleLastTabLogout();
        resp.setStatus(HttpServletResponse.SC_OK);
    }
}

4. 会话超时兜底监听(可选)

如果需要单独处理会话超时场景,可实现HttpSessionListener:

import javax.servlet.annotation.WebListener;
import javax.servlet.http.HttpSessionEvent;
import javax.servlet.http.HttpSessionListener;

@WebListener
public class SessionListener implements HttpSessionListener {

    @Override
    public void sessionDestroyed(HttpSessionEvent se) {
        LoginController loginController = (LoginController) se.getSession().getAttribute("loginController");
        if (loginController != null) {
            loginController.recordLogoutTime();
        }
    }
}

方案优势

  • 精准触发:仅在最后一个标签页关闭时发送请求,避免单个标签页关闭的误触发
  • 及时记录:主动销毁会话,CDI Bean的@PreDestroy立即执行,无需等待超时
  • 场景全覆盖:兼顾手动登出、全窗口关闭、会话超时三种场景的登出时间记录

内容的提问来源于stack exchange,提问作者jane doe

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.22 05:00:23