You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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"}

排查与解决思路

  1. 修正前端订阅路径格式
    Spring Boot中convertAndSendToUser会自动为目标路径添加配置的/user前缀,生成的最终路径为/user/{username}/topic/ConnectionTest。当前前端订阅路径缺少开头的斜杠,需修改为:
stompClient.subscribe(`/user/john/topic/ConnectionTest`, onUserMoved)
  1. 绑定用户身份
    使用用户名发送用户消息时,Spring需要关联WebSocket连接与用户身份。可在前端连接时传递用户头信息:
stompClient.connect({username: "john"}, stompClientOnConnected, onError)

后端可通过SimpMessageHeaderAccessor.getUser()获取用户信息,确保SimpUserRegistry中存在该用户记录。

  1. 移除手动Header设置
    手动创建SimpMessageHeaderAccessor并设置sessionId会干扰框架的自动路由逻辑,convertAndSendToUser无需手动指定sessionId,直接简化代码:
messagingTemplate.convertAndSendToUser("john", "/topic/ConnectionTest", message);
  1. 更新消息代理配置
    用户消息的目标路径包含/user前缀,需确保消息代理支持该路径,修改配置:
registry.enableSimpleBroker("/topic", "/user");
  1. sessionId发送方式优化
    若使用sessionId发送消息,前端订阅路径需对应为/user/{sessionId}/topic/ConnectionTest,但此方式依赖临时sessionId,推荐优先使用用户名绑定用户身份的方式。

内容的提问来源于stack exchange,提问作者user3397839

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.07 11:25:56