场景:项目中展示用户登录登出日志,遇到一些问题比如浏览器关闭未记录用户退出日志

项目技术:jsp+springmvc

后端需求:记录用户登录登出日志,要求浏览器关闭认为用户退出。

实现方法:

第一种.使用aop拦截用户登录登出请求,并记录日志 。

第二种.利用shiro监听器,在shiro认证成功后记录用户登录日志,退出时记录用户登出日志。

第三种.使用websocket,在websocket建立链接时记录用户登录日志,websocket断开链接时记录用户登出日志。

遇到的问题

存在问题一:浏览器并没有专门的处理浏览器关闭事件 

存在问题二:根据浏览器关闭时间短,刷新请求接口相对时间长的特性来解决浏览器关闭事件


    var beginTime = 0;
     var differTime = 0;
     window.addEventListener('unload', logData, false);
     function logData() {
               differTime = new Date().getTime()-beginTime;
    	       if (differTime<=5) {
        	    	  var analyticsData={};
        	          navigator.sendBeacon(ctx_js+"/logout", analyticsData);
                }
       } 

     window.addEventListener('beforeunload', logDataBefore, false);
     function logDataBefore(){
    	  beginTime = new Date().getTime();
     }  

onbeforeunload,后onunload事件分析:

关闭页面时:先onbeforeunload,后onunload
刷新页面时:先onbeforeunload,后onunload,再onload

改版之前用的就是这种方法,但是发现时而正常,时而不正常(因为differTime这个对照时间的设定受多方面影响,网速等。 ),无奈不得不重新想其它方法。

最终方案:

思路:使用websocket实现,在websocket建立链接时记录用户登录日志,websocket断开链接时记录用户登出日志。

前端处理:

由于老项目前端登录采用的是form表单提交,因此在登录成功后第一次进入首页时,建立websocket

	    //登录按钮提交
		document.getElementById(id).submit();				
		//登录按钮提交后设置标志firstFlag, 此处为了使websocket在index页面只初始化一次,登录后先清除firstFlag
		var storage=window.localStorage;
		localStorage.removeItem("firstFlag");

在首页初始化的时候建立websocket连接

     /**
	 *  maogy 2017-7
     *  获取菜单定制中的数据
     */
     $(document).ready(function() {
	 	 //在登录后第一次进入index页面时建立websocket连接
	 	 var storage=window.localStorage;
	     if(storage.getItem("firstFlag") == 'false' || storage.getItem("firstFlag")==null){
	    	 initLoginWebSocket();
	      }  
	 });



    /********wxy 2022/10/18 登录成功后建立websocket连接*********/
     function initLoginWebSocket(){
         //开启WebSocket
         var websocket = null;
         //变量loginWebSocketUrl从后端读取
         var websocketUrl = loginWebSocketUrl+"/GisqPlatformExplorer/ws/login.do";
         websocket = new WebSocket(websocketUrl);
         // 方法定义
         websocket.onopen = function(){
             //成功建立连接后设置firstFlag为true
        	 var storage=window.sessionStorage;
             storage.setItem("firstFlag", "true");
         };
         // 接收到服务端的消息
         websocket.onmessage = function(result) {
             //如果已经有弹窗,就不再弹窗
         };
         websocket.onerror = function(result){
         };
         websocket.onclose = function() {
             console.log("websocket close");
         };
    }

后端处理:

package com.gisquest.platform.websocket.config;
import com.gisquest.platform.websocket.handler.LoginWebSocketHandler;
import com.gisquest.platform.websocket.interceptor.LoginOutInterceptor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.config.annotation.EnableWebSocket;
import org.springframework.web.socket.config.annotation.WebSocketConfigurer;
import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry;

/**
 * @author LiuDehuai
 * @fileName:WebSocketConfig
 * @Date:2021/11/5 10:56
 */
@Configuration
@EnableWebSocket
public class WebSocketConfig implements WebSocketConfigurer {
    @Override
    public void registerWebSocketHandlers(WebSocketHandlerRegistry webSocketHandlerRegistry) {
        webSocketHandlerRegistry
                //添加Handler
                .addHandler(loginWebSocketHandler(), "/ws/login.do")
                .addInterceptors(new LoginOutInterceptor())
                //允许跨域
                .setAllowedOrigins("*");
    }


    
    //登录时添加websocket连接
    @Bean
    public LoginWebSocketHandler loginWebSocketHandler() {
    	return new LoginWebSocketHandler();
    }
}

package com.gisquest.platform.websocket.handler;

import java.util.Map;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.CollectionUtils;
import org.springframework.web.socket.CloseStatus;
import org.springframework.web.socket.WebSocketMessage;
import org.springframework.web.socket.WebSocketSession;
import org.springframework.web.socket.handler.TextWebSocketHandler;

import com.ctrip.framework.apollo.core.utils.StringUtils;
import com.gisquest.platform.common.utils.CommonLogUtils;
import com.gisquest.platform.common.utils.Constants;
import com.gisquest.platform.websocket.util.WebSocketUtil;

/**
 * 浏览器关闭使用WebSocket,记录用户退出日志
 * @author wxy
 * @fileName:LoginWebSocketHandler
 * @Date:2021/11/5 10:14
 */
public class LoginWebSocketHandler extends TextWebSocketHandler {
    public static final Logger LOGGER = LoggerFactory.getLogger(LoginWebSocketHandler.class);
    /**
     * 用戶登錄时WebSocket连接成功建立
     * @param session
     * @throws Exception
     */
    @Override
    public void afterConnectionEstablished(WebSocketSession session) throws Exception {
        if (LOGGER.isDebugEnabled()) {
            LOGGER.debug("WebScoket会话已经建立,SessionId:{}", session.getId());
        }
        String username=StringUtils.EMPTY;
        String ip=StringUtils.EMPTY;
        String loginType=StringUtils.EMPTY;
        String sessionId=StringUtils.EMPTY;
        //获取属性
        Map<String, Object> attributes = session.getAttributes();
        if (!CollectionUtils.isEmpty(attributes)) {
            username = attributes.get("username") != null ? attributes.get("username").toString() : "";
            ip=attributes.get("loginIp").toString();
            loginType = Constants.LOGINMODEL;
            sessionId = session.getId();
        }
        CommonLogUtils.saveLog(username,ip,loginType,sessionId);
        //添加Session
        WebSocketUtil.put(session.getId(),session);
        //获取属性
        super.afterConnectionEstablished(session);
    }

    @Override
    public void handleMessage(WebSocketSession session, WebSocketMessage<?> message) throws Exception {
        if (LOGGER.isDebugEnabled()) {
            LOGGER.debug("WebScoket收到前端消息,SessionId:{},消息:{}", session.getId(), message.getPayload());
        }
        System.out.println(session.getId());
        System.out.println(session);
        super.handleMessage(session, message);
    }

    @Override
    public void handleTransportError(WebSocketSession session, Throwable exception) throws Exception {
        if (LOGGER.isWarnEnabled()) {
            LOGGER.warn("WebScoket连接发生错误,SessionId:{},错误:{}", session.getId(), exception.getMessage());
        }
        System.out.println(session.getId());
        System.out.println(session);
        super.handleTransportError(session, exception);
    }

    /**
     * 在任一方关闭WebSocket连接后或发生传输错误后调用
     * 关闭WebSocket连接
     * @param session
     * @param status
     * @throws Exception
     */
    @Override
    public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception {
        if (LOGGER.isDebugEnabled()) {
            LOGGER.debug("WebScoket连接关闭,SessionId:{}", session.getId());
        }
         //记录用户关系浏览器和退出日志
        String username=StringUtils.EMPTY;
        String ip=StringUtils.EMPTY;
        String loginType=StringUtils.EMPTY;
        String sessionId=StringUtils.EMPTY;
        //获取属性
        Map<String, Object> attributes = session.getAttributes();
        if (!CollectionUtils.isEmpty(attributes)) {
            username = attributes.get("username") != null ? attributes.get("username").toString() : "";
            ip=attributes.get("loginIp").toString();
            loginType = Constants.LOGOUTMODEL;
            sessionId = session.getId();
            CommonLogUtils.saveLog(username,ip,loginType,sessionId);
          }
         //移除Session
         WebSocketUtil.remove(session.getId());
         super.afterConnectionClosed(session, status);
    }
}

更多推荐