package com.ruoyi.websocket.controller; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.UUID; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.messaging.handler.annotation.DestinationVariable; import org.springframework.messaging.handler.annotation.MessageMapping; import org.springframework.messaging.handler.annotation.Payload; import org.springframework.messaging.simp.SimpMessageHeaderAccessor; import org.springframework.messaging.simp.SimpMessagingTemplate; import org.springframework.stereotype.Controller; import com.alibaba.fastjson2.JSON; import com.ruoyi.common.core.domain.model.LoginUser; import com.ruoyi.websocket.config.LoginUserPrincipal; import com.ruoyi.common.utils.StringUtils; import com.ruoyi.system.domain.Rooms; import com.ruoyi.system.service.IRoomsService; import com.ruoyi.websocket.dto.RoomMemberDTO; import com.ruoyi.websocket.service.RoomChatService; import com.ruoyi.websocket.service.RoomWebSocketService; /** * WebSocket 房间消息控制器 * * @author ruoyi */ @Controller public class RoomWebSocketController { @Autowired private SimpMessagingTemplate messagingTemplate; @Autowired private RoomWebSocketService roomWebSocketService; @Autowired private RoomChatService roomChatService; @Autowired private IRoomsService roomsService; private static final String TYPE_JOIN = "JOIN"; private static final String TYPE_LEAVE = "LEAVE"; private static final String TYPE_PING = "PING"; private static final String TYPE_CHAT = "CHAT"; private static final String TYPE_PRIVATE_CHAT = "PRIVATE_CHAT"; private static final String TYPE_CHAT_HISTORY = "CHAT_HISTORY"; private static final String TYPE_PRIVATE_CHAT_HISTORY = "PRIVATE_CHAT_HISTORY"; private static final String TYPE_PRIVATE_CHAT_HISTORY_REQUEST = "PRIVATE_CHAT_HISTORY_REQUEST"; private static final String TYPE_MEMBER_JOINED = "MEMBER_JOINED"; private static final String TYPE_MEMBER_LEFT = "MEMBER_LEFT"; private static final String TYPE_MEMBER_LIST = "MEMBER_LIST"; private static final String TYPE_PONG = "PONG"; /** 对象选中/查看:某成员正在查看某条航线,广播给房间内其他人 */ private static final String TYPE_OBJECT_VIEW = "OBJECT_VIEW"; /** 取消对象查看 */ private static final String TYPE_OBJECT_VIEW_CLEAR = "OBJECT_VIEW_CLEAR"; /** 对象编辑锁定:某成员进入编辑,其他人看到锁定 */ private static final String TYPE_OBJECT_EDIT_LOCK = "OBJECT_EDIT_LOCK"; /** 对象编辑解锁 */ private static final String TYPE_OBJECT_EDIT_UNLOCK = "OBJECT_EDIT_UNLOCK"; /** * 处理房间消息:JOIN、LEAVE、PING、CHAT、PRIVATE_CHAT、OBJECT_VIEW、OBJECT_EDIT_LOCK */ @MessageMapping("/room/{roomId}") public void handleRoomMessage(@DestinationVariable Long roomId, @Payload String payload, SimpMessageHeaderAccessor accessor) { LoginUser loginUser = null; if (accessor.getUser() instanceof LoginUserPrincipal) { loginUser = ((LoginUserPrincipal) accessor.getUser()).getLoginUser(); } if (loginUser == null) return; Map body = parsePayload(payload); if (body == null) return; String type = (String) body.get("type"); String sessionId = accessor.getSessionId(); if (sessionId == null) sessionId = UUID.randomUUID().toString(); if (TYPE_JOIN.equals(type)) { handleJoin(roomId, sessionId, loginUser, body); } else if (TYPE_LEAVE.equals(type)) { handleLeave(roomId, sessionId, loginUser); } else if (TYPE_PING.equals(type)) { handlePing(roomId, sessionId, loginUser); } else if (TYPE_CHAT.equals(type)) { handleChat(roomId, sessionId, loginUser, body); } else if (TYPE_PRIVATE_CHAT.equals(type)) { handlePrivateChat(roomId, sessionId, loginUser, body); } else if (TYPE_PRIVATE_CHAT_HISTORY_REQUEST.equals(type)) { handlePrivateChatHistoryRequest(roomId, loginUser, body); } else if (TYPE_OBJECT_VIEW.equals(type)) { handleObjectView(roomId, sessionId, loginUser, body); } else if (TYPE_OBJECT_VIEW_CLEAR.equals(type)) { handleObjectViewClear(roomId, sessionId, loginUser, body); } else if (TYPE_OBJECT_EDIT_LOCK.equals(type)) { handleObjectEditLock(roomId, sessionId, loginUser, body); } else if (TYPE_OBJECT_EDIT_UNLOCK.equals(type)) { handleObjectEditUnlock(roomId, sessionId, loginUser, body); } } /** 广播:某成员正在查看某对象(如航线) */ private void handleObjectView(Long roomId, String sessionId, LoginUser loginUser, Map body) { String objectType = body != null ? String.valueOf(body.get("objectType")) : null; Object objectIdObj = body != null ? body.get("objectId") : null; if (objectType == null || objectIdObj == null) return; Map viewer = new HashMap<>(); viewer.put("userId", loginUser.getUserId()); viewer.put("userName", loginUser.getUsername()); viewer.put("nickName", loginUser.getUser().getNickName()); viewer.put("sessionId", sessionId); Map msg = new HashMap<>(); msg.put("type", TYPE_OBJECT_VIEW); msg.put("objectType", objectType); msg.put("objectId", objectIdObj); msg.put("viewer", viewer); messagingTemplate.convertAndSend("/topic/room/" + roomId, msg); } /** 广播:某成员取消查看某对象 */ private void handleObjectViewClear(Long roomId, String sessionId, LoginUser loginUser, Map body) { String objectType = body != null ? String.valueOf(body.get("objectType")) : null; Object objectIdObj = body != null ? body.get("objectId") : null; if (objectType == null || objectIdObj == null) return; Map msg = new HashMap<>(); msg.put("type", TYPE_OBJECT_VIEW_CLEAR); msg.put("objectType", objectType); msg.put("objectId", objectIdObj); msg.put("sessionId", sessionId); messagingTemplate.convertAndSend("/topic/room/" + roomId, msg); } /** 广播:某成员锁定某对象进入编辑 */ private void handleObjectEditLock(Long roomId, String sessionId, LoginUser loginUser, Map body) { String objectType = body != null ? String.valueOf(body.get("objectType")) : null; Object objectIdObj = body != null ? body.get("objectId") : null; if (objectType == null || objectIdObj == null) return; Map editor = new HashMap<>(); editor.put("userId", loginUser.getUserId()); editor.put("userName", loginUser.getUsername()); editor.put("nickName", loginUser.getUser().getNickName()); editor.put("sessionId", sessionId); Map msg = new HashMap<>(); msg.put("type", TYPE_OBJECT_EDIT_LOCK); msg.put("objectType", objectType); msg.put("objectId", objectIdObj); msg.put("editor", editor); messagingTemplate.convertAndSend("/topic/room/" + roomId, msg); } /** 广播:某成员解锁某对象(结束编辑) */ private void handleObjectEditUnlock(Long roomId, String sessionId, LoginUser loginUser, Map body) { String objectType = body != null ? String.valueOf(body.get("objectType")) : null; Object objectIdObj = body != null ? body.get("objectId") : null; if (objectType == null || objectIdObj == null) return; Map msg = new HashMap<>(); msg.put("type", TYPE_OBJECT_EDIT_UNLOCK); msg.put("objectType", objectType); msg.put("objectId", objectIdObj); msg.put("sessionId", sessionId); messagingTemplate.convertAndSend("/topic/room/" + roomId, msg); } @SuppressWarnings("unchecked") private Map parsePayload(String payload) { try { return JSON.parseObject(payload, Map.class); } catch (Exception e) { return null; } } private void handleJoin(Long roomId, String sessionId, LoginUser loginUser, Map body) { Rooms room = roomsService.selectRoomsById(roomId); if (room == null) return; RoomMemberDTO member = buildMember(loginUser, sessionId, roomId, body); roomWebSocketService.joinRoom(roomId, sessionId, member); List members = roomWebSocketService.getRoomMembers(roomId); Map memberListMsg = new HashMap<>(); memberListMsg.put("type", TYPE_MEMBER_LIST); memberListMsg.put("members", members); Map joinedMsg = new HashMap<>(); joinedMsg.put("type", TYPE_MEMBER_JOINED); joinedMsg.put("member", member); String topic = "/topic/room/" + roomId; messagingTemplate.convertAndSend(topic, memberListMsg); List chatHistory = roomChatService.getGroupChatHistory(roomId); Map chatHistoryMsg = new HashMap<>(); chatHistoryMsg.put("type", TYPE_CHAT_HISTORY); chatHistoryMsg.put("messages", chatHistory); messagingTemplate.convertAndSendToUser(loginUser.getUsername(), "/queue/private", chatHistoryMsg); } private void handleLeave(Long roomId, String sessionId, LoginUser loginUser) { RoomMemberDTO member = buildMember(loginUser, sessionId, roomId, null); roomWebSocketService.leaveRoom(roomId, sessionId, loginUser.getUserId()); Map msg = new HashMap<>(); msg.put("type", TYPE_MEMBER_LEFT); msg.put("member", member); msg.put("sessionId", sessionId); messagingTemplate.convertAndSend("/topic/room/" + roomId, msg); } private void handlePing(Long roomId, String sessionId, LoginUser loginUser) { roomWebSocketService.refreshSessionHeartbeat(sessionId); Map msg = new HashMap<>(); msg.put("type", TYPE_PONG); messagingTemplate.convertAndSendToUser(loginUser.getUsername(), "/queue/private", msg); } /** 群聊:广播给房间内所有人 */ private void handleChat(Long roomId, String sessionId, LoginUser loginUser, Map body) { String content = body != null && body.containsKey("content") ? String.valueOf(body.get("content")) : ""; if (content.isEmpty()) return; Map sender = new HashMap<>(); sender.put("userId", loginUser.getUserId()); sender.put("userName", loginUser.getUsername()); sender.put("nickName", loginUser.getUser().getNickName()); sender.put("avatar", loginUser.getUser().getAvatar()); sender.put("sessionId", sessionId); Map msg = new HashMap<>(); msg.put("type", TYPE_CHAT); msg.put("sender", sender); msg.put("content", content); msg.put("timestamp", System.currentTimeMillis()); roomChatService.saveGroupChat(roomId, loginUser.getUserId(), msg); messagingTemplate.convertAndSend("/topic/room/" + roomId, msg); } /** 私聊:发送给指定用户(仅限同房间成员) */ private void handlePrivateChat(Long roomId, String sessionId, LoginUser loginUser, Map body) { Object targetUserNameObj = body != null ? body.get("targetUserName") : null; Object targetUserIdObj = body != null ? body.get("targetUserId") : null; String content = body != null && body.containsKey("content") ? String.valueOf(body.get("content")) : ""; if (targetUserNameObj == null || content.isEmpty()) return; String targetUserName = String.valueOf(targetUserNameObj); if (targetUserName.equals(loginUser.getUsername())) return; List members = roomWebSocketService.getRoomMembers(roomId); boolean targetInRoom = members.stream().anyMatch(m -> targetUserName.equals(m.getUserName())); if (!targetInRoom) return; Map sender = new HashMap<>(); sender.put("userId", loginUser.getUserId()); sender.put("userName", loginUser.getUsername()); sender.put("nickName", loginUser.getUser().getNickName()); sender.put("avatar", loginUser.getUser().getAvatar()); sender.put("sessionId", sessionId); Map msg = new HashMap<>(); msg.put("type", TYPE_PRIVATE_CHAT); msg.put("sender", sender); msg.put("targetUserId", targetUserIdObj); msg.put("targetUserName", targetUserName); msg.put("content", content); msg.put("timestamp", System.currentTimeMillis()); Long targetUserId = targetUserIdObj instanceof Number ? ((Number) targetUserIdObj).longValue() : null; if (targetUserId != null) { roomChatService.savePrivateChat(loginUser.getUserId(), targetUserId, msg); } messagingTemplate.convertAndSendToUser(targetUserName, "/queue/private", msg); } /** 私聊历史请求:返回与指定用户的聊天记录 */ private void handlePrivateChatHistoryRequest(Long roomId, LoginUser loginUser, Map body) { Object targetUserIdObj = body != null ? body.get("targetUserId") : null; if (targetUserIdObj == null) return; Long targetUserId = targetUserIdObj instanceof Number ? ((Number) targetUserIdObj).longValue() : null; if (targetUserId == null) return; List members = roomWebSocketService.getRoomMembers(roomId); boolean targetInRoom = members.stream().anyMatch(m -> targetUserId.equals(m.getUserId())); if (!targetInRoom) return; List history = roomChatService.getPrivateChatHistory(loginUser.getUserId(), targetUserId); Map resp = new HashMap<>(); resp.put("type", TYPE_PRIVATE_CHAT_HISTORY); resp.put("targetUserId", targetUserId); resp.put("messages", history); messagingTemplate.convertAndSendToUser(loginUser.getUsername(), "/queue/private", resp); } private RoomMemberDTO buildMember(LoginUser loginUser, String sessionId, Long roomId, Map body) { RoomMemberDTO dto = new RoomMemberDTO(); dto.setUserId(loginUser.getUserId()); dto.setUserName(loginUser.getUsername()); dto.setNickName(loginUser.getUser().getNickName()); dto.setAvatar(loginUser.getUser().getAvatar()); dto.setSessionId(sessionId); dto.setDeviceId(body != null && body.containsKey("deviceId") ? String.valueOf(body.get("deviceId")) : "default"); dto.setJoinedAt(System.currentTimeMillis()); Rooms room = roomsService.selectRoomsById(roomId); if (room != null && loginUser.getUserId().equals(room.getOwnerId())) { dto.setRole("owner"); } else if (StringUtils.isNotEmpty(loginUser.getUser().getUserLevel())) { dto.setRole(loginUser.getUser().getUserLevel()); } else { dto.setRole("member"); } return dto; } }