Spring STOMP消息未送达目标会话问题排查求助
问题分析与解决方案
核心问题
你遇到的问题主要来自两个方面:
- 消息发送时机错误:前端在WebSocket连接未完全建立时就发送加入群组的请求,导致后端无法正确记录会话ID。
- 订阅路径不匹配:使用
convertAndSendToUser发送消息时,Spring会自动拼接目标路径为/user/{sessionId}/personal,但前端订阅的是/personal,两者不匹配导致无法接收消息。
另外,后端代码存在潜在的空指针风险:当发送消息到不存在的群组时,groupSessions.get(groupName)会返回null,引发异常。
方案一:修复原sessionId管理逻辑
1. 后端代码修改
增加群组存在性判断,避免空指针:
@Controller public class HomeController { @Autowired private SimpMessagingTemplate simpMessagingTemplate; private final Map<String, List<String>> groupSessions = new HashMap<>(); @MessageMapping("/group/{groupName}/join") public void joinGroup(@DestinationVariable String groupName, SimpMessageHeaderAccessor headerAccessor) { // 确保会话ID不为空再添加 String sessionId = headerAccessor.getSessionId(); if (sessionId != null) { groupSessions.computeIfAbsent(groupName, k -> new ArrayList<>()).add(sessionId); } } @MessageMapping("/group/{groupName}/send") public void sendToGroup(@DestinationVariable String groupName, @Payload String message) { List<String> sessions = groupSessions.get(groupName); if (sessions != null) { for (String session : sessions) { simpMessagingTemplate.convertAndSendToUser(session, "/personal", message); } } } }
2. 前端代码修改
调整加入群组的时机,确保连接成功后再发送请求;订阅正确的用户专属路径(需获取当前会话ID):
<script> const socket = new SockJS('http://localhost:8080/ws-reg'); const stompClient = Stomp.over(socket); let currentSessionId = null; function joinGroup() { const groupName = document.getElementById('groupInput').value; stompClient.connect({}, function(frame) { setConnected(true); console.log('Connected: ' + frame); // 从SockJS连接中提取会话ID currentSessionId = socket._transport.url.split('/')[5]; // 订阅当前用户的专属消息路径 stompClient.subscribe(`/user/${currentSessionId}/personal`, function(greeting){ console.log(JSON.parse(greeting.body)); }); // 连接成功后再发送加入群组请求 stompClient.send(`/app/group/${groupName}/join`, {}, JSON.stringify({})); }); } function sendMessage() { const groupName = document.getElementById('groupInput').value; const messageContent = document.getElementById('messageInput').value; stompClient.send(`/app/group/${groupName}/send`, {}, JSON.stringify({ content: messageContent })); } </script>
方案二:改用群组直接订阅(更简洁推荐)
放弃手动管理会话ID,直接利用Spring WebSocket的群组消息能力,实现更简洁的逻辑:
1. 配置类修改
添加/group到消息代理的允许前缀:
@Configuration @EnableWebSocketMessageBroker public class WebSockConfig implements WebSocketMessageBrokerConfigurer { @Override public void registerStompEndpoints(StompEndpointRegistry registry) { registry.addEndpoint("/ws-reg").withSockJS(); } @Override public void configureMessageBroker(MessageBrokerRegistry config) { config.setApplicationDestinationPrefixes("/app"); config.enableSimpleBroker("/all", "/personal", "/group"); } }
2. 控制器简化
无需维护会话映射,直接发送消息到群组目的地:
@Controller public class HomeController { @Autowired private SimpMessagingTemplate simpMessagingTemplate; @MessageMapping("/group/{groupName}/send") public void sendToGroup(@DestinationVariable String groupName, @Payload String message) { simpMessagingTemplate.convertAndSend("/group/" + groupName, message); } }
3. 前端修改
用户加入群组时直接订阅对应群组的消息路径:
<script> const socket = new SockJS('http://localhost:8080/ws-reg'); const stompClient = Stomp.over(socket); function joinGroup() { const groupName = document.getElementById('groupInput').value; stompClient.connect({}, function(frame) { setConnected(true); console.log('Connected: ' + frame); // 订阅目标群组的消息路径 stompClient.subscribe(`/group/${groupName}`, function(greeting){ console.log(JSON.parse(greeting.body)); }); }); } function sendMessage() { const groupName = document.getElementById('groupInput').value; const messageContent = document.getElementById('messageInput').value; stompClient.send(`/app/group/${groupName}/send`, {}, JSON.stringify({ content: messageContent })); } </script>
内容的提问来源于stack exchange,提问作者Akshay Som
相关产品推荐
相关产品推荐

