瀏覽代碼

添加websocket 用于事件推送

11868 1 周之前
父節點
當前提交
fe82e9d3b4

+ 5 - 0
supervision-admin/pom.xml

@@ -185,6 +185,11 @@
             <version>1.11.0</version>
         </dependency>
 
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-websocket</artifactId>
+        </dependency>
+
     </dependencies>
 
     <build>

+ 115 - 0
supervision-admin/src/main/java/com/supervision/WebSocket/EventWebSocket.java

@@ -0,0 +1,115 @@
+package com.supervision.WebSocket;
+
+
+import com.alibaba.fastjson.JSON;
+import com.supervision.common.core.domain.model.LoginUser;
+import com.supervision.framework.web.service.TokenService;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.stereotype.Component;
+
+import javax.websocket.*;
+import javax.websocket.server.PathParam;
+import javax.websocket.server.ServerEndpoint;
+import java.io.*;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+/**
+ * WebSocket 实时推送服务(兼容 JDK 1.8)
+ * 每个用户登录后建立独立连接,通过 Token 鉴权
+ */
+@Component
+@ServerEndpoint("/websocket/{token}")
+public class EventWebSocket {
+
+    private static final Logger logger = LoggerFactory.getLogger(EventWebSocket.class);
+
+    /** 保存所有在线用户连接 */
+    private static final Map<Long, Session> USER_SESSION_MAP = new ConcurrentHashMap<>();
+
+    private Long userId;
+
+    /**
+     * 连接建立成功调用的方法
+     * */
+    @OnOpen
+    public void onOpen(Session session, @PathParam("token") String token) {
+        logger.info("🟢 WebSocket 请求到达,----------连接成功");
+        logger.info("🟢 WebSocket 请求到达,----------session: {}", session);
+        logger.info("🟢 WebSocket 请求到达,----------token: {}", token);
+        try {
+            TokenService tokenService = SpringContextHolder.getBean(TokenService.class);
+            LoginUser loginUser = tokenService.getLoginUserByToken(token);
+            if (loginUser == null) {
+                session.close(new CloseReason(CloseReason.CloseCodes.CANNOT_ACCEPT, "Invalid token"));
+                return;
+            }
+
+            this.userId = loginUser.getUserId();
+            USER_SESSION_MAP.put(userId, session);
+            logger.info("✅ 用户 " + loginUser.getUsername() + " 已连接 WebSocket");
+        } catch (Exception e) {
+            try {
+                session.close(new CloseReason(CloseReason.CloseCodes.CANNOT_ACCEPT, "Auth failed"));
+            } catch (IOException ignored) {}
+        }
+    }
+
+    @OnMessage
+    public void onMessage(String message, Session session) {
+        if ("ping".equalsIgnoreCase(message)) {
+            session.getAsyncRemote().sendText("pong");
+        }
+    }
+
+    @OnClose
+    public void onClose() {
+        if (userId != null) {
+            USER_SESSION_MAP.remove(userId);
+            logger.info("❌ 用户 " + userId + " 已断开 WebSocket");
+        }
+    }
+
+    @OnError
+    public void onError(Session session, Throwable error) {
+        System.err.println("WebSocket 错误:" + error.getMessage());
+    }
+
+    /** 广播消息给所有在线用户 */
+    public static void broadcast(String type, Object data) {
+        String msg = JSON.toJSONString(new WebSocketMessage(type, data));
+        for (Session session : USER_SESSION_MAP.values()) {
+            if (session.isOpen()) {
+                session.getAsyncRemote().sendText(msg);
+            }
+        }
+    }
+
+    /** 向特定用户发送消息 */
+    public static void sendToUser(Long userId, Object data) {
+        Session session = USER_SESSION_MAP.get(userId);
+        if (session != null && session.isOpen()) {
+            session.getAsyncRemote().sendText(JSON.toJSONString(data));
+        }
+    }
+
+    /** 简单封装的消息结构 */
+    private static class WebSocketMessage {
+        private String type;
+        private Object data;
+
+        public WebSocketMessage(String type, Object data) {
+            this.type = type;
+            this.data = data;
+        }
+
+        public String getType() {
+            return type;
+        }
+
+        public Object getData() {
+            return data;
+        }
+    }
+}

+ 21 - 0
supervision-admin/src/main/java/com/supervision/WebSocket/SpringContextHolder.java

@@ -0,0 +1,21 @@
+package com.supervision.WebSocket;
+
+import org.springframework.beans.BeansException;
+import org.springframework.context.ApplicationContext;
+import org.springframework.context.ApplicationContextAware;
+import org.springframework.stereotype.Component;
+
+@Component
+public class SpringContextHolder implements ApplicationContextAware {
+
+    private static ApplicationContext context;
+
+    @Override
+    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
+        SpringContextHolder.context = applicationContext;
+    }
+
+    public static <T> T getBean(Class<T> clazz) {
+        return context.getBean(clazz);
+    }
+}

+ 23 - 0
supervision-admin/src/main/java/com/supervision/WebSocket/WebSocketConfig.java

@@ -0,0 +1,23 @@
+package com.supervision.WebSocket;
+
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.web.socket.server.standard.ServerEndpointExporter;
+
+/**
+ * 启用 WebSocket 支持
+ */
+@Configuration
+public class WebSocketConfig {
+
+    private static final Logger logger = LoggerFactory.getLogger(WebSocketConfig.class);
+
+    @Bean
+    public ServerEndpointExporter serverEndpointExporter() {
+        logger.info("✅ WebSocket ServerEndpointExporter 已加载");
+        return new ServerEndpointExporter();
+    }
+}

+ 1 - 0
supervision-admin/src/main/java/com/supervision/web/ConnectSDK/util/DeviceSimulator.java

@@ -3,6 +3,7 @@ package com.supervision.web.ConnectSDK.util;
 import com.supervision.web.ConnectSDK.carCamera.CarCameraDeviceManager;
 import com.supervision.web.ConnectSDK.carDoor.CarDoorDeviceManager;
 import com.supervision.web.ConnectSDK.peopleDoor.PeopleDoorDeviceManager;
+import com.supervision.WebSocket.EventWebSocket;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Component;
 

+ 2 - 1
supervision-common/src/main/java/com/supervision/common/core/controller/BaseController.java

@@ -27,7 +27,8 @@ import com.supervision.common.utils.sql.SqlUtil;
  * @author supervision
  */
 public class BaseController {
-    protected final Logger logger = LoggerFactory.getLogger(this.getClass());
+
+    protected final Logger logger = LoggerFactory.getLogger(BaseController.class);
 
     /**
      * 将前台传递过来的日期格式的字符串,自动转化为Date类型

+ 2 - 1
supervision-framework/src/main/java/com/supervision/framework/config/SecurityConfig.java

@@ -116,7 +116,8 @@ public class SecurityConfig extends WebSecurityConfigurerAdapter
                 .antMatchers("/api/v1/situation/**").anonymous()
                 .antMatchers("/api/huaRuApi/**").anonymous()
 //                .antMatchers("/materialManagement/materialRegistration/echarts/storage/list").anonymous()
-
+                // ✅ 放行 WebSocket
+                .antMatchers("/websocket/**").permitAll()
                 // 除上面外的所有请求全部需要鉴权认证
                 .anyRequest().authenticated()
                 .and()

+ 17 - 0
supervision-framework/src/main/java/com/supervision/framework/web/service/TokenService.java

@@ -80,6 +80,23 @@ public class TokenService
         return null;
     }
 
+    /**
+     * 根据 token 字符串获取用户信息(WebSocket 可用)
+     */
+    public LoginUser getLoginUserByToken(String token) {
+        if (StringUtils.isNotEmpty(token)) {
+            try {
+                Claims claims = parseToken(token); // JWT 解析
+                String uuid = (String) claims.get(Constants.LOGIN_USER_KEY);
+                String userKey = getTokenKey(uuid);
+                return redisCache.getCacheObject(userKey);
+            } catch (Exception e) {
+                return null;
+            }
+        }
+        return null;
+    }
+
     /**
      * 设置用户身份信息
      */