You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 

329 lines
15 KiB

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<String, Object> 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<String, Object> 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<String, Object> viewer = new HashMap<>();
viewer.put("userId", loginUser.getUserId());
viewer.put("userName", loginUser.getUsername());
viewer.put("nickName", loginUser.getUser().getNickName());
viewer.put("sessionId", sessionId);
Map<String, Object> 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<String, Object> 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<String, Object> 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<String, Object> 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<String, Object> editor = new HashMap<>();
editor.put("userId", loginUser.getUserId());
editor.put("userName", loginUser.getUsername());
editor.put("nickName", loginUser.getUser().getNickName());
editor.put("sessionId", sessionId);
Map<String, Object> 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<String, Object> 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<String, Object> 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<String, Object> 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<String, Object> body) {
Rooms room = roomsService.selectRoomsById(roomId);
if (room == null) return;
RoomMemberDTO member = buildMember(loginUser, sessionId, roomId, body);
roomWebSocketService.joinRoom(roomId, sessionId, member);
List<RoomMemberDTO> members = roomWebSocketService.getRoomMembers(roomId);
Map<String, Object> memberListMsg = new HashMap<>();
memberListMsg.put("type", TYPE_MEMBER_LIST);
memberListMsg.put("members", members);
Map<String, Object> joinedMsg = new HashMap<>();
joinedMsg.put("type", TYPE_MEMBER_JOINED);
joinedMsg.put("member", member);
String topic = "/topic/room/" + roomId;
messagingTemplate.convertAndSend(topic, memberListMsg);
List<Object> chatHistory = roomChatService.getGroupChatHistory(roomId);
Map<String, Object> 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<String, Object> 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<String, Object> 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<String, Object> body) {
String content = body != null && body.containsKey("content") ? String.valueOf(body.get("content")) : "";
if (content.isEmpty()) return;
Map<String, Object> 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<String, Object> 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<String, Object> 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<RoomMemberDTO> members = roomWebSocketService.getRoomMembers(roomId);
boolean targetInRoom = members.stream().anyMatch(m -> targetUserName.equals(m.getUserName()));
if (!targetInRoom) return;
Map<String, Object> 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<String, Object> 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<String, Object> 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<RoomMemberDTO> members = roomWebSocketService.getRoomMembers(roomId);
boolean targetInRoom = members.stream().anyMatch(m -> targetUserId.equals(m.getUserId()));
if (!targetInRoom) return;
List<Object> history = roomChatService.getPrivateChatHistory(loginUser.getUserId(), targetUserId);
Map<String, Object> 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<String, Object> 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;
}
}