Просмотр исходного кода

feat: 添加websocket服务,断开退出登录

weijianghai 2 лет назад
Родитель
Сommit
66b26a9961

+ 4 - 0
pom.xml

@@ -131,6 +131,10 @@
 			<artifactId>caffeine</artifactId>
 			<version>2.9.3</version>
 		</dependency>
+		<dependency>
+			<groupId>org.springframework.boot</groupId>
+			<artifactId>spring-boot-starter-websocket</artifactId>
+		</dependency>
 	</dependencies>
 
 	<build>

+ 13 - 3
src/main/java/com/nokia/hb/config/CacheConfig.java

@@ -8,8 +8,13 @@ import org.springframework.boot.context.properties.ConfigurationProperties;
 import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.Configuration;
 
+import javax.servlet.http.HttpSession;
 import java.util.concurrent.TimeUnit;
 
+/**
+ * 缓存配置
+ *
+ */
 @Slf4j
 @ConfigurationProperties("session")
 @Configuration
@@ -20,12 +25,17 @@ public class CacheConfig {
      */
     private Integer timeout;
 
+    /**
+     * 会话缓存
+     *
+     * @return {@link Cache}<{@link String}, {@link HttpSession}>
+     */
     @Bean
-    public Cache<String, Object> sessionCache() {
+    public Cache<String, HttpSession> sessionCache() {
         return Caffeine.newBuilder()
                 .expireAfterAccess(timeout, TimeUnit.SECONDS)
-                .evictionListener((k, v, c) -> log.debug("sessionCache evictionListener -> c: {}, k: {}, v: {}", c, k, v))
-                .removalListener((k, v, c) -> log.debug("sessionCache removalListener -> c: {}, k: {}, v: {}", c, k, v))
+                .evictionListener((k, v, c) -> log.debug("sessionCache evictionListener: {}, {} -> {}", c, k, v))
+                .removalListener((k, v, c) -> log.debug("sessionCache removalListener: {}, {} -> {}", c, k, v))
                 .build();
     }
 }

+ 1 - 1
src/main/java/com/nokia/hb/config/web/LoginHandlerInterceptor.java

@@ -30,7 +30,7 @@ public class LoginHandlerInterceptor implements HandlerInterceptor {
     public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
         HttpSession session = request.getSession();
         String username = SessionUtil.getUsername(session);
-        log.debug("username: {}", username);
+        log.debug("{}: {}", session.getId(), username);
         if (StringUtils.hasText(username) && sessionService.logged(username)) {
             sessionService.update(session);
             return true;

+ 34 - 0
src/main/java/com/nokia/hb/config/web/WebSocketConfig.java

@@ -0,0 +1,34 @@
+package com.nokia.hb.config.web;
+
+import com.nokia.hb.service.WebSocketService;
+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;
+
+/**
+ * 网络套接字配置
+ *
+ */
+@Configuration
+@EnableWebSocket
+public class WebSocketConfig implements WebSocketConfigurer {
+    private final WebSocketService webSocketService;
+
+    public WebSocketConfig(WebSocketService webSocketService) {
+        this.webSocketService = webSocketService;
+    }
+
+    /**
+     * 注册网络套接字处理程序
+     *
+     * @param registry 注册表
+     */
+    @Override
+    public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
+        registry.addHandler(webSocketService, "/ws")
+                .addInterceptors(new WebSocketHandshakeInterceptor())
+                .setAllowedOriginPatterns("*")
+        ;
+    }
+}

+ 75 - 0
src/main/java/com/nokia/hb/config/web/WebSocketHandshakeInterceptor.java

@@ -0,0 +1,75 @@
+package com.nokia.hb.config.web;
+
+import com.alibaba.fastjson2.JSON;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.http.server.ServerHttpRequest;
+import org.springframework.http.server.ServerHttpResponse;
+import org.springframework.http.server.ServletServerHttpRequest;
+import org.springframework.util.StringUtils;
+import org.springframework.web.socket.WebSocketHandler;
+import org.springframework.web.socket.server.HandshakeInterceptor;
+
+import javax.servlet.http.Cookie;
+import javax.servlet.http.HttpServletRequest;
+import java.util.Map;
+
+/**
+ * 网络套接字握手拦截器
+ *
+ * @author x
+ * @date 2023/03/07
+ */
+@Slf4j
+public class WebSocketHandshakeInterceptor implements HandshakeInterceptor {
+    /**
+     * Invoked before the handshake is processed.
+     *
+     * @param request    the current request
+     * @param response   the current response
+     * @param wsHandler  the target WebSocket handler
+     * @param attributes the attributes from the HTTP handshake to associate with the WebSocket
+     *                   session; the provided attributes are copied, the original map is not used.
+     * @return whether to proceed with the handshake ({@code true}) or abort ({@code false})
+     */
+    @Override
+    public boolean beforeHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler,
+                                   Map<String, Object> attributes) throws Exception {
+        ServletServerHttpRequest servletServerHttpRequest = (ServletServerHttpRequest) request;
+        HttpServletRequest httpServletRequest = servletServerHttpRequest.getServletRequest();
+        String username = httpServletRequest.getParameter("username");
+        Cookie[] cookies = httpServletRequest.getCookies();
+        log.debug("username: {}", username);
+        log.debug("cookies: {}", JSON.toJSONString(cookies));
+        String sessionId = null;
+        for (Cookie cookie : cookies) {
+            if ("JSESSIONID".equals(cookie.getName())) {
+                sessionId = cookie.getValue();
+                log.debug("JSESSIONID: {}", sessionId);
+                attributes.put("JSESSIONID", sessionId);
+            }
+        }
+        if (!StringUtils.hasText(sessionId)) {
+            log.warn("JSESSIONID is null");
+            return false;
+        }
+        attributes.put("username", username);
+        return true;
+    }
+
+    /**
+     * Invoked after the handshake is done. The response status and headers indicate
+     * the results of the handshake, i.e. whether it was successful or not.
+     *
+     * @param request   the current request
+     * @param response  the current response
+     * @param wsHandler the target WebSocket handler
+     * @param exception an exception raised during the handshake, or {@code null} if none
+     */
+    @Override
+    public void afterHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler,
+                               Exception exception) {
+        if (exception != null) {
+            log.error(exception.getMessage(), exception);
+        }
+    }
+}

+ 23 - 4
src/main/java/com/nokia/hb/service/SessionService.java

@@ -5,17 +5,20 @@ import com.nokia.hb.config.CacheConfig;
 import com.nokia.hb.utils.SessionUtil;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.stereotype.Service;
+import org.springframework.util.StringUtils;
 
 import javax.servlet.http.HttpSession;
-import java.time.LocalDateTime;
 
+/**
+ * 会话服务
+ */
 @Slf4j
 @Service
 public class SessionService {
     private final CacheConfig cacheConfig;
-    private final Cache<String, Object> sessionCache;
+    private final Cache<String, HttpSession> sessionCache;
 
-    public SessionService(CacheConfig cacheConfig, Cache<String, Object> sessionCache) {
+    public SessionService(CacheConfig cacheConfig, Cache<String, HttpSession> sessionCache) {
         this.cacheConfig = cacheConfig;
         this.sessionCache = sessionCache;
     }
@@ -28,7 +31,7 @@ public class SessionService {
     public void update(HttpSession session) {
         session.setMaxInactiveInterval(cacheConfig.getTimeout());
         String username = SessionUtil.getUsername(session);
-        log.info("{} last login at {}", username, sessionCache.get(username, k -> LocalDateTime.now()));
+        sessionCache.get(username, k -> session);
     }
 
     /**
@@ -51,4 +54,20 @@ public class SessionService {
         session.invalidate();
         sessionCache.invalidate(username);
     }
+
+    /**
+     * 无效
+     *
+     * @param username 用户名
+     */
+    public void invalidate(String username) {
+        if (!StringUtils.hasText(username)) {
+            return;
+        }
+        HttpSession session = sessionCache.getIfPresent(username);
+        if (session != null) {
+            session.invalidate();
+        }
+        sessionCache.invalidate(username);
+    }
 }

+ 63 - 0
src/main/java/com/nokia/hb/service/WebSocketService.java

@@ -0,0 +1,63 @@
+package com.nokia.hb.service;
+
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Service;
+import org.springframework.web.socket.CloseStatus;
+import org.springframework.web.socket.WebSocketHandler;
+import org.springframework.web.socket.WebSocketMessage;
+import org.springframework.web.socket.WebSocketSession;
+
+import java.util.Map;
+
+/**
+ * 网络套接字服务
+ */
+@Slf4j
+@Service
+public class WebSocketService implements WebSocketHandler {
+    private final SessionService sessionService;
+
+    public WebSocketService(SessionService sessionService) {
+        this.sessionService = sessionService;
+    }
+
+    @Override
+    public void afterConnectionEstablished(WebSocketSession session) throws Exception {
+        Map<String, Object> attributes = session.getAttributes();
+        String sessionId = (String) attributes.get("JSESSIONID");
+        String username = (String) attributes.get("username");
+        log.info("websocket connection established: {} -> {} -> {}", session.getId(), sessionId, username);
+    }
+
+    @Override
+    public void handleMessage(WebSocketSession session, WebSocketMessage<?> message) throws Exception {
+        Map<String, Object> attributes = session.getAttributes();
+        String sessionId = (String) attributes.get("JSESSIONID");
+        String username = (String) attributes.get("username");
+        log.info("receive websocket message: {} -> {} -> {} -> {}", session.getId(), sessionId, username,
+                message.getPayload());
+    }
+
+    @Override
+    public void handleTransportError(WebSocketSession session, Throwable exception) throws Exception {
+        Map<String, Object> attributes = session.getAttributes();
+        String sessionId = (String) attributes.get("JSESSIONID");
+        String username = (String) attributes.get("username");
+        log.error("websocket transport error: {} -> {} -> {} -> {}", session.getId(), sessionId, username,
+                exception.getMessage(), exception);
+    }
+
+    @Override
+    public void afterConnectionClosed(WebSocketSession session, CloseStatus closeStatus) throws Exception {
+        Map<String, Object> attributes = session.getAttributes();
+        String sessionId = (String) attributes.get("JSESSIONID");
+        String username = (String) attributes.get("username");
+        log.info("websocket connection closed: {}, {} -> {} -> {}", closeStatus, session.getId(), sessionId, username);
+        sessionService.invalidate(username);
+    }
+
+    @Override
+    public boolean supportsPartialMessages() {
+        return false;
+    }
+}

+ 4 - 0
src/main/resources/application.yml

@@ -2,6 +2,10 @@ server:
   port: 9003
   servlet:
     context-path: /
+#    session:
+#      cookie:
+#        secure: false
+#        http-only: false
   tomcat:
     uri-encoding: UTF-8
     max-threads: 800

+ 24 - 13
src/main/resources/templates/template.html

@@ -164,6 +164,23 @@
 <script src="./js/jquery-3.5.1.min.js"></script>
 <script src="./js/layui/layui.js"></script>
 <script>
+    const host = window.location.host
+    const username = window.sessionStorage.getItem('userName')
+    // if(!username){
+    //     window.location.href = '/login'
+    // }
+    const socket = new WebSocket(`ws://${host}/ws?username=${username}`);
+    socket.onopen = (event) => {
+        console.log(event)
+    }
+
+    socket.onclose = (event) => {
+        console.log(event)
+    }
+
+    socket.onerror = (event) => {
+        console.log(event)
+    }
         var dataA;
         var layer;
         var tree;
@@ -213,9 +230,6 @@
             initTreeIndicatorTemplate(tree)
             // console.log(window.sessionStorage.getItem('userName'))
             $("#userName").text(window.sessionStorage.getItem('userName'))
-            if(!window.sessionStorage.getItem('userName')){
-                setTimeout(() => window.location.href = '/login', 1000)
-            }
         })
 
         var treeCity;
@@ -472,19 +486,14 @@
 
         // 退出登录
         function logOutFun() {
-            console.log('tuichu')
             $.ajax({
                 type: "POST",
                 // url: '/api/json/citys.json',
                 url: '/api/logout',
                 success: function (r) {
-                    console.log('r: ', r);
-                    if (r?.success) {
-                        layer.msg('退出登录成功');
-                        setTimeout(() => window.location.href = '/login', 1000)
-                    }else{
-                        alert('退出登录失败');
-                    }
+                    layer.msg('退出登录成功');
+                    window.sessionStorage.clear()
+                    setTimeout(() => window.location.href = '/login', 1000)
                 }
             });
         }
@@ -499,8 +508,10 @@
                 });
             },1000)
         });
-        window.onbeforeunload=function(e){     
-            logOutFun()
+        window.onbeforeunload=function(e){
+            if (!window.sessionStorage.getItem('userName')) {
+                logOutFun()
+            }
         }
         // window.addEventListener('unload',logOutFun());
     </script>