Bläddra i källkod

WebSocket连接请求

fuyuchuan 3 dagar sedan
förälder
incheckning
b75f335095

+ 6 - 0
service-meeting/service-meeting-biz/pom.xml

@@ -73,6 +73,12 @@
             <groupId>org.springframework.boot</groupId>
             <artifactId>spring-boot-starter-web</artifactId>
         </dependency>
+        
+        <!-- WebSocket依赖 -->
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-websocket</artifactId>
+        </dependency>
 
         <dependency>
             <groupId>com.auth0</groupId>

+ 22 - 0
service-meeting/service-meeting-biz/src/main/java/com/usky/meeting/config/WebSocketConfig.java

@@ -0,0 +1,22 @@
+package com.usky.meeting.config;
+
+import com.usky.meeting.websocket.MeetingDeviceWebSocketHandler;
+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.server.standard.ServerEndpointExporter;
+
+@Configuration
+@EnableWebSocket
+public class WebSocketConfig {
+
+    @Bean
+    public ServerEndpointExporter serverEndpointExporter() {
+        return new ServerEndpointExporter();
+    }
+
+    @Bean
+    public MeetingDeviceWebSocketHandler meetingDeviceWebSocketHandler() {
+        return new MeetingDeviceWebSocketHandler();
+    }
+}

+ 77 - 0
service-meeting/service-meeting-biz/src/main/java/com/usky/meeting/service/MeetingDeviceWebSocketService.java

@@ -0,0 +1,77 @@
+package com.usky.meeting.service;
+
+import com.baomidou.mybatisplus.core.toolkit.Wrappers;
+import com.usky.common.core.exception.BusinessException;
+import com.usky.meeting.domain.MeetingDevice;
+import com.usky.meeting.domain.MeetingDeviceHeartbeat;
+import com.usky.meeting.mapper.MeetingDeviceHeartbeatMapper;
+import com.usky.meeting.mapper.MeetingDeviceMapper;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import java.time.LocalDateTime;
+import java.util.HashMap;
+import java.util.Map;
+
+@Slf4j
+@Service
+@RequiredArgsConstructor
+public class MeetingDeviceWebSocketService {
+
+    private static final int HEARTBEAT_TIMEOUT_MINUTES = 5;
+
+    @Autowired
+    private MeetingDeviceMapper meetingDeviceMapper;
+
+    @Autowired
+    private MeetingDeviceHeartbeatMapper heartbeatMapper;
+
+    public Map<String, Object> validateDevice(String deviceCode) {
+        Map<String, Object> result = new HashMap<>();
+
+        MeetingDevice device = validateDeviceExists(deviceCode);
+        boolean isOnline = validateDeviceOnline(deviceCode);
+
+        result.put("deviceCode", deviceCode);
+        result.put("deviceName", device.getDeviceName());
+        result.put("deviceStatus", isOnline ? 1 : 0);
+        result.put("isOnline", isOnline);
+        result.put("validateSuccess", true);
+
+        log.info("设备验证成功, deviceCode: {}, isOnline: {}", deviceCode, isOnline);
+        return result;
+    }
+
+    private MeetingDevice validateDeviceExists(String deviceCode) {
+        MeetingDevice device = meetingDeviceMapper.selectOne(
+                Wrappers.<MeetingDevice>lambdaQuery().eq(MeetingDevice::getDeviceCode, deviceCode)
+        );
+        if (device == null) {
+            log.warn("设备不存在, deviceCode: {}", deviceCode);
+            throw new BusinessException("设备不存在: " + deviceCode);
+        }
+        return device;
+    }
+
+    private boolean validateDeviceOnline(String deviceCode) {
+        MeetingDeviceHeartbeat heartbeat = heartbeatMapper.selectOne(
+                Wrappers.<MeetingDeviceHeartbeat>lambdaQuery()
+                        .eq(MeetingDeviceHeartbeat::getDeviceCode, deviceCode)
+                        .orderByDesc(MeetingDeviceHeartbeat::getCreateTime)
+                        .last("LIMIT 1")
+        );
+        if (heartbeat == null) {
+            log.warn("设备心跳记录不存在, deviceCode: {}", deviceCode);
+            throw new BusinessException("设备离线: " + deviceCode + ", 未找到心跳记录");
+        }
+        LocalDateTime heartbeatTime = heartbeat.getCreateTime();
+        LocalDateTime now = LocalDateTime.now();
+        if (heartbeatTime == null || now.isAfter(heartbeatTime.plusMinutes(HEARTBEAT_TIMEOUT_MINUTES))) {
+            log.warn("设备心跳超时, deviceCode: {}, heartbeatTime: {}", deviceCode, heartbeatTime);
+            throw new BusinessException("设备离线: " + deviceCode + ", 心跳超时");
+        }
+        return true;
+    }
+}

+ 21 - 0
service-meeting/service-meeting-biz/src/main/java/com/usky/meeting/service/vo/WebSocketConnectionResponseVO.java

@@ -0,0 +1,21 @@
+package com.usky.meeting.service.vo;
+
+import lombok.Data;
+
+@Data
+public class WebSocketConnectionResponseVO {
+
+    private String sessionId;
+
+    private String deviceCode;
+
+    private String deviceName;
+
+    private Integer deviceStatus;
+
+    private Boolean isOnline;
+
+    private String message;
+
+    private Long timestamp;
+}

+ 72 - 0
service-meeting/service-meeting-biz/src/main/java/com/usky/meeting/websocket/MeetingDeviceWebSocketController.java

@@ -0,0 +1,72 @@
+package com.usky.meeting.websocket;
+
+import com.usky.common.core.bean.ApiResult;
+import com.usky.meeting.service.MeetingDeviceWebSocketService;
+import com.usky.meeting.service.vo.WebSocketConnectionResponseVO;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Controller;
+import org.springframework.web.bind.annotation.*;
+
+import java.time.LocalDateTime;
+import java.util.HashMap;
+import java.util.Map;
+
+@Slf4j
+@Controller
+@RequiredArgsConstructor
+@RequestMapping("/meetingDevice/websocket")
+public class MeetingDeviceWebSocketController {
+
+    @Autowired
+    private MeetingDeviceWebSocketService deviceWebSocketService;
+
+    @GetMapping("/connect")
+    @ResponseBody
+    public ApiResult<WebSocketConnectionResponseVO> connect(
+            @RequestParam String deviceCode
+            //,@RequestHeader(value = "X-Real-IP", required = false) String realIp
+    ) {
+        log.info("收到WebSocket连接请求, deviceCode: {}", deviceCode);
+        WebSocketConnectionResponseVO response = new WebSocketConnectionResponseVO();
+        try {
+            Map<String, Object> validateResult = deviceWebSocketService.validateDevice(deviceCode);
+            response.setDeviceCode(deviceCode);
+            response.setDeviceName((String) validateResult.get("deviceName"));
+            response.setDeviceStatus((Integer) validateResult.get("deviceStatus"));
+            response.setIsOnline((Boolean) validateResult.get("isOnline"));
+            response.setMessage("设备验证通过,可建立WebSocket连接");
+            response.setTimestamp(System.currentTimeMillis());
+            log.info("设备验证成功, deviceCode: {}", deviceCode);
+            return ApiResult.success(response);
+        } catch (Exception e) {
+            log.error("设备验证失败, deviceCode: {}", deviceCode, e);
+            response.setDeviceCode(deviceCode);
+            response.setMessage("设备验证失败: " + e.getMessage());
+            response.setTimestamp(System.currentTimeMillis());
+            return ApiResult.error(e.getMessage());
+        }
+    }
+
+    @GetMapping("/status/{sessionId}")
+    @ResponseBody
+    public ApiResult<Map<String, Object>> getConnectionStatus(@PathVariable String sessionId) {
+        Map<String, Object> status = new HashMap<>();
+        boolean isOpen = MeetingDeviceWebSocketHandler.isSessionOpen(sessionId);
+        String deviceCode = MeetingDeviceWebSocketHandler.getDeviceCodeBySession(sessionId);
+        status.put("sessionId", sessionId);
+        status.put("deviceCode", deviceCode);
+        status.put("isOpen", isOpen);
+        status.put("checkTime", LocalDateTime.now().toString());
+        return ApiResult.success(status);
+    }
+
+    @GetMapping("/close/{sessionId}")
+    @ResponseBody
+    public ApiResult<Void> closeConnection(@PathVariable String sessionId) {
+        MeetingDeviceWebSocketHandler.removeSession(sessionId);
+        log.info("WebSocket连接已手动关闭, sessionId: {}", sessionId);
+        return ApiResult.success();
+    }
+}

+ 76 - 0
service-meeting/service-meeting-biz/src/main/java/com/usky/meeting/websocket/MeetingDeviceWebSocketHandler.java

@@ -0,0 +1,76 @@
+package com.usky.meeting.websocket;
+
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Component;
+import org.springframework.web.socket.CloseStatus;
+import org.springframework.web.socket.TextMessage;
+import org.springframework.web.socket.WebSocketSession;
+import org.springframework.web.socket.handler.TextWebSocketHandler;
+
+import java.io.IOException;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+@Slf4j
+//@Component
+public class MeetingDeviceWebSocketHandler extends TextWebSocketHandler {
+
+    private static final Map<String, WebSocketSession> SESSION_MAP = new ConcurrentHashMap<>();
+    private static final Map<String, String> SESSION_DEVICE_MAP = new ConcurrentHashMap<>();
+
+    @Override
+    public void afterConnectionEstablished(WebSocketSession session) throws Exception {
+        String sessionId = session.getId();
+        String deviceCode = (String) session.getAttributes().get("deviceCode");
+        SESSION_MAP.put(sessionId, session);
+        if (deviceCode != null) {
+            SESSION_DEVICE_MAP.put(sessionId, deviceCode);
+        }
+        log.info("WebSocket连接已建立, sessionId: {}, deviceCode: {}", sessionId, deviceCode);
+    }
+
+    @Override
+    protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {
+        String payload = message.getPayload();
+        log.info("收到消息, sessionId: {}, message: {}", session.getId(), payload);
+        String deviceCode = SESSION_DEVICE_MAP.get(session.getId());
+        session.sendMessage(new TextMessage("{\"type\":\"ack\",\"deviceCode\":\"" + deviceCode + "\",\"message\":\"消息已收到\"}"));
+    }
+
+    @Override
+    public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception {
+        String sessionId = session.getId();
+        SESSION_MAP.remove(sessionId);
+        SESSION_DEVICE_MAP.remove(sessionId);
+        log.info("WebSocket连接已关闭, sessionId: {}, status: {}", sessionId, status);
+    }
+
+    @Override
+    public void handleTransportError(WebSocketSession session, Throwable exception) throws Exception {
+        log.error("WebSocket传输错误, sessionId: {}", session.getId(), exception);
+        if (session.isOpen()) {
+            session.close();
+        }
+    }
+
+    public static void sendMessage(String sessionId, String message) throws IOException {
+        WebSocketSession session = SESSION_MAP.get(sessionId);
+        if (session != null && session.isOpen()) {
+            session.sendMessage(new TextMessage(message));
+        }
+    }
+
+    public static boolean isSessionOpen(String sessionId) {
+        WebSocketSession session = SESSION_MAP.get(sessionId);
+        return session != null && session.isOpen();
+    }
+
+    public static void removeSession(String sessionId) {
+        SESSION_MAP.remove(sessionId);
+        SESSION_DEVICE_MAP.remove(sessionId);
+    }
+
+    public static String getDeviceCodeBySession(String sessionId) {
+        return SESSION_DEVICE_MAP.get(sessionId);
+    }
+}