Spring Boot中messagingTemplate.convertAndSendToUser前端收不到消息问题
Spring Boot WebSocket Stomp 消息发送失败排查与解决
我之前使用Node.js的Socket.IO,现在正在学习Spring Boot的WebSocket,采用Stomp客户端和SimpMessageSendingOperations进行消息收发。需求是在部分逻辑中让消息发送者无法收到消息,@SendTo可以正常向所有订阅者(包括发送者)发送消息,但使用messagingTemplate时,后端日志显示消息已发送,前端却无法接收。简化代码后,尝试用用户名john和sessionId调用convertAndSendToUser均未成功,浏览器控制台显示连接、订阅及消息请求均正常,却收不到返回消息。
相关代码与控制台输出
Web配置文件
package com.example.demo.SocketClass; import org.springframework.context.annotation.Configuration; import org.springframework.messaging.simp.config.MessageBrokerRegistry; import org.springframework.messaging.simp.user.SimpUserRegistry; import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker; import org.springframework.web.socket.config.annotation.StompEndpointRegistry; import org.springframework.web.socket.config.annotation.WebSocketMessageBrokerConfigurer; @Configuration @EnableWebSocketMessageBroker public class SocketConfig implements WebSocketMessageBrokerConfigurer { SimpUserRegistry simpUserRegistry; @Override public void registerStompEndpoints(StompEndpointRegistry registry) { registry.addEndpoint("/ws").withSockJS(); } @Override public void configureMessageBroker(MessageBrokerRegistry registry) { registry.setApplicationDestinationPrefixes("/app"); registry.enableSimpleBroker("/topic"); registry.setUserDestinationPrefix("/user"); } }
前端代码
let sessionId; let getRandom = integer => Math.floor(Math.random()*integer) let socket = new SockJS('http://localhost:8080/ws',[],{sessionId:8}) var stompClient = Stomp.over(socket) let username = "john" stompClient.connect({},stompClientOnConnected, onError) function stompClientOnConnected(frame){ stompClient.subscribe(`user/john/topic/ConnectionTest`, onUserMoved) sessionId = getRandom(10000000).toString() setTimeout( () => stompClient.send("/app/draw.ConnectionTest",{},JSON.stringify({ username: 'john' })), 3000) function onUserMoved (data){ console.log("Data after user moved: ",data) } } function onError(err){ console.log("thats an error :", err) }
Controller代码(使用用户名john)
package com.example.demo.SocketClass; import com.example.demo.DrawAppUser.DrawAppSession; import com.example.demo.SocketData.*; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.messaging.handler.annotation.Header; import org.springframework.messaging.handler.annotation.MessageMapping; import org.springframework.messaging.handler.annotation.Payload; import org.springframework.messaging.handler.annotation.SendTo; import org.springframework.messaging.simp.SimpMessageHeaderAccessor; import org.springframework.messaging.simp.SimpMessageSendingOperations; import org.springframework.messaging.simp.SimpMessageType; import org.springframework.messaging.simp.SimpMessagingTemplate; import org.springframework.messaging.simp.stomp.StompHeaderAccessor; import org.springframework.stereotype.Controller; import java.util.ArrayList; import java.util.Map; @Controller public class SocketController { private final SimpMessageSendingOperations messagingTemplate; @Autowired public SocketController(SimpMessageSendingOperations messagingTemplate){ this.messagingTemplate= messagingTemplate; } @MessageMapping("/draw.ConnectionTest") public void userConnectedEvent(SimpMessageHeaderAccessor accessor, @Payload String load) { String simpSessionId = accessor.getSessionId(); System.out.println("Received userConnectedEvent. Session ID: " + simpSessionId); String message = "Your session ID is: " + simpSessionId; SimpMessageHeaderAccessor responseAccessor = StompHeaderAccessor.create(SimpMessageType.MESSAGE); responseAccessor.setSessionId(simpSessionId); System.out.println("Sending message: " + message); messagingTemplate.convertAndSendToUser("john", "/topic/ConnectionTest", message, responseAccessor.getMessageHeaders()); } }
Controller代码(使用sessionId)
@MessageMapping("/draw.ConnectionTest") public void userConnectedEvent(SimpMessageHeaderAccessor accessor, @Payload String load) { String simpSessionId = accessor.getSessionId(); System.out.println("Received userConnectedEvent. Session ID: " + simpSessionId); String message = "Your session ID is: " + simpSessionId; SimpMessageHeaderAccessor responseAccessor = StompHeaderAccessor.create(SimpMessageType.MESSAGE); responseAccessor.setSessionId(simpSessionId); System.out.println("Sending message: " + message); messagingTemplate.convertAndSendToUser(simpSessionId, "/topic/ConnectionTest", message, responseAccessor.getMessageHeaders()); }
浏览器控制台输出
Opening Web Socket... stomp.min.js:8 Web Socket Opened... stomp.min.js:8 >>> CONNECT accept-version:1.1,1.0 heart-beat:10000,10000 <<< CONNECTED version:1.1 heart-beat:0,0 connected to server undefined >>> SUBSCRIBE id:sub-0 destination:user/john/topic/ConnectionTest connected!! mytest.js:16 this is frame: n {command: 'CONNECTED', headers: {…}, body: ''} >>> SEND destination:/app/draw.ConnectionTest content-length:19 {"username":"john"}
排查与解决思路
- 修正前端订阅路径格式
Spring Boot中convertAndSendToUser会自动为目标路径添加配置的/user前缀,生成的最终路径为/user/{username}/topic/ConnectionTest。当前前端订阅路径缺少开头的斜杠,需修改为:
stompClient.subscribe(`/user/john/topic/ConnectionTest`, onUserMoved)
- 绑定用户身份
使用用户名发送用户消息时,Spring需要关联WebSocket连接与用户身份。可在前端连接时传递用户头信息:
stompClient.connect({username: "john"}, stompClientOnConnected, onError)
后端可通过SimpMessageHeaderAccessor.getUser()获取用户信息,确保SimpUserRegistry中存在该用户记录。
- 移除手动Header设置
手动创建SimpMessageHeaderAccessor并设置sessionId会干扰框架的自动路由逻辑,convertAndSendToUser无需手动指定sessionId,直接简化代码:
messagingTemplate.convertAndSendToUser("john", "/topic/ConnectionTest", message);
- 更新消息代理配置
用户消息的目标路径包含/user前缀,需确保消息代理支持该路径,修改配置:
registry.enableSimpleBroker("/topic", "/user");
- sessionId发送方式优化
若使用sessionId发送消息,前端订阅路径需对应为/user/{sessionId}/topic/ConnectionTest,但此方式依赖临时sessionId,推荐优先使用用户名绑定用户身份的方式。
内容的提问来源于stack exchange,提问作者user3397839
相关产品推荐
相关产品推荐

