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

Spring WebSocket+STOMP客户端无法接收私信问题求助

前端客户端无法接收WebSocket服务器消息问题

前端客户端订阅聊天服务后无法接收服务器返回的消息,已尝试订阅/chat和/user/chat两个路径,均未解决。相关代码与日志如下:

JavaScript客户端代码

const stompClient = new StompJs.Client({
        
        brokerURL: 'ws://localhost:8080/ws'
    });

    stompClient.onConnect = (frame) => { 
        //setConnected(true); 
        console.log('Connected: ' + frame); 
        //console.log('/user/queue/specific-user-user' + frame.headers['user-name']);
        stompClient.subscribe('/user/chat', msg => {
            console.log('response: ' + msg);
        }); 
    }; 
    
    stompClient.onWebSocketError = (error) => { 
        console.error('Error with websocket', error); 
    }; 
    
    stompClient.onStompError = (frame) => { 
        console.error('Broker reported error: ' + frame.headers['message']); 
        console.error('Additional details: ' + frame.body); 
    }; 
    
    stompClient.activate(); 

Broker配置

@Configuration
@EnableWebSocketMessageBroker
public class WebSocketBrokerConfiguration implements WebSocketMessageBrokerConfigurer  {

    @Autowired
    private HandshakeHandler handshakeHandler;
    
    @Override
    public void registerStompEndpoints(StompEndpointRegistry registry) {
         registry.addEndpoint("/ws").setAllowedOrigins("http://localhost:4200").setHandshakeHandler(handshakeHandler);
    }

    @Override
    public void configureMessageBroker(MessageBrokerRegistry registry) {
        registry.enableSimpleBroker("/chat/");
        registry.setUserDestinationPrefix("/user");
    }

}

事件监听器

@Component
@Slf4j
public class WebSocketEventListener  {
    
    @Autowired
    private ChatManager chatManager;
    
     @EventListener
        private void handleSessionConnected(SessionConnectEvent event) {
         log.info("{}", event);
        }

        @EventListener
        private void handleSessionDisconnect(SessionDisconnectEvent event) {
            log.info("{}", event);
        }
        
        @EventListener
        private void handleSessionSubscribe(SessionSubscribeEvent event) {
            final Map<String, Object> nativeHeaders = (Map<String, Object>) 
            event.getMessage().getHeaders().get("nativeHeaders");
            final String sessionId = 
            event.getMessage().getHeaders().get("simpSessionId").toString();
            log.info("sessionId: {}", sessionId);
            log.info("subscribed destination: {}", nativeHeaders.get("destination"));
            log.info("subscribed user {}", event.getUser().getName());
            SimpMessageHeaderAccessor headerAccessor = 
            SimpMessageHeaderAccessor.create(SimpMessageType.MESSAGE);
            headerAccessor.setSessionId(sessionId);
    
            log.info("Sending reply to: {}", event.getUser().getName());
            Message message = new Message().setTo(event.getUser().getName()).setBody("hi");
            simpMessagingTemplate.convertAndSendToUser(
            event.getUser().getName(), "/chat", message, headerAccessor.getMessageHeaders()); 
        }

}

握手处理器

@Configuration
@Slf4j
public class HandshakeHandler extends DefaultHandshakeHandler{

    @Override
    protected Principal determineUser(ServerHttpRequest request, WebSocketHandler wsHandler,
            Map<String, Object> attributes) {
        Principal principal = request.getPrincipal();           

        if (principal == null) {
            principal = new AnonymousPrincipal();

            String uniqueName = UUID.randomUUID().toString();

            ((AnonymousPrincipal) principal).setName(uniqueName);
            log.info("new user: {}", uniqueName);
        }

        return principal;

    }

}

TRACE级别日志(org.springframework.messaging)

...configuration.HandshakeHandler         : new user: 8eee251d-2777-430c-b137-a5f462d7dd8f
....s.messaging.simp.stomp.StompDecoder   : Decoded CONNECT {accept-version=[1.2,1.1,1.0], heart-beat=[10000,10000]} session=null
....s.m.s.b.SimpleBrokerMessageHandler    : Processing CONNECT user=8eee251d-2777-430c-b137-...i.listener.WebSocketEventListener : SessionConnectEvent[GenericMessage [payload=byte[0], headers={simpMessageType=CONNECT, stompCommand=CONNECT, nativeHeaders={accept-version=[1.2,1.1,1.0], heart-beat=[10000,10000]}, simpSessionAttributes={}, simpHeartbeat=[J@7d5c7266, simpUser=AnonymousPrincipal(name=8eee251d-2777-430c-b137-a5f462d7dd8f), simpSessionId=d480e89a-813b-7776-23e0-6a0e34e9c70c}]]
...s.messaging.simp.stomp.StompEncoder    : Encoding STOMP CONNECTED, headers={version=[1.2], heart-beat=[0,0], user-name=[8eee251d-2777-430c-b137-a5f462d7dd8f]}
...s.messaging.simp.stomp.StompDecoder    : Decoded SUBSCRIBE {id=[sub-0], destination=[/user/chat]} session=null
..s.m.s.u.UserDestinationMessageHandler   : Translated /user/chat -> [/chat-userd480e89a-813b-7776-23e0-6a0e34e9c70c]
...w.i.listener.WebSocketEventListener    : sessionId: d480e89a-813b-7776-23e0-6a0e34e9c70c
...w.i.listener.WebSocketEventListener    : subscribed destination: [/user/chat]
...w.i.listener.WebSocketEventListener    : subscribed user 8eee251d-2777-430c-b137-a5f462d7dd8f
...websocket.chat.ChatManager             : Sending reply to: 8eee251d-2777-430c-b137-a5f462d7dd8f
..m.s.u.UserDestinationMessageHandler     : Translated /user/8eee251d-2777-430c-b137-a5f462d7dd8f/chat -> [/chat-userd480e89a-813b-7776-23e0-6a0e34e9c70c]

问题排查与解决方案

从日志和代码来看,核心问题出在消息代理的前缀配置以及用户目标路径的匹配上:

  1. Broker前缀配置错误
    当前enableSimpleBroker("/chat/")末尾带了斜杠,但服务器实际发送的目标路径是/chat-userxxx,并不包含/chat/前缀,导致消息无法被代理路由到客户端。
    修正方法:将Broker配置中的前缀改为"/chat"(去掉末尾斜杠):

    @Override
    public void configureMessageBroker(MessageBrokerRegistry registry) {
        registry.enableSimpleBroker("/chat");
        registry.setUserDestinationPrefix("/user");
    }
    
  2. 客户端订阅路径无需修改
    客户端订阅/user/chat是正确的,Spring会自动将其转换为/chat-user<sessionId>,和服务器发送的目标路径完全匹配。

  3. 消息序列化检查
    确保Message类的序列化逻辑正常,避免因消息格式错误导致客户端无法解析。

修正后,服务器发送的消息会被正确路由到客户端的订阅路径,客户端即可接收到"hi"消息。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.20 01:00:08