服务端推送必须用SimpMessagingTemplate;广播用convertAndSend发至/topic/路径,点对点用convertAndSendToUser并确保login认证、setUserDestinationPrefix配置正确及payload可序列化。

服务端主动推送必须用 SimpMessagingTemplate,别写错成 SimpMessageSendingOperations 或手动遍历 session —— 后者在集群、断线重连、STOMP 认证等场景下必然失效。
广播推送:用 convertAndSend 发到 topic 地址
广播本质是发消息到 broker 的公共订阅路径,比如 /topic/feed 或 /topic/notice。客户端只要提前 stompClient.subscribe('/topic/feed'),就能收到。
-
convertAndSend第一个参数是完整 destination 字符串,必须带前缀(如/topic/),不能只传feed - destination 必须和
configureMessageBroker().enableSimpleBroker(...)中注册的路径匹配,否则静默丢弃 - 如果用了 Redis 消息代理(非内存 broker),需确保所有节点共享同一套
redisTemplate实例,否则跨节点广播不生效 - 示例:
messagingTemplate.convertAndSend("/topic/notice", new NotifyBean(1, "系统维护中", null));
点对点推送:用 convertAndSendToUser 时注意 user ID 来源
点对点不是“按用户名推”,而是按 STOMP 连接时协商出的 user identifier 推。这个 ID 默认来自 WebSocket 握手时的 HTTP Header(如 user-id)或 STOMP CONNECT 帧中的 login 字段。
WebSocket 8.18.2 是该协议规范的一个重要迭代版本,主要优化了连接稳定性与数据传输效率。它通过全双工通信机制,允许客户端与服务器在单一长连接上实时交换数据,大幅降低传统 HTTP 轮询的开销。该版本增强了心跳保活、自动重连及二进制帧传输能力,适用于即时通讯、在线游戏及金融行情推送等低延迟场景,为开发者提供更可靠的实时网络交互基础。
- 服务端调用
convertAndSendToUser("alice", "/message", payload),实际投递路径是/user/alice/message(前缀由setUserDestinationPrefix控制) - 客户端必须用
stompClient.subscribe('/user/queue/message')订阅,/user/是固定前缀,/queue/是自动加上的内部队列名,不可省略 - 若前端没传 login 或 header,
convertAndSendToUser会静默失败,日志里只有 WARN:“No user found for destination” - 调试建议:监听
SessionConnectEvent,打印StompHeaderAccessor.getPrincipal().getName()确认拿到的 user ID 是什么
convertAndSendToUser 不生效?先查三件事
90% 的“点对点收不到”问题都卡在这三个地方:
- 客户端连接时没带认证信息:SockJS 连接后,STOMP CONNECT 帧里必须有
login: alice,否则 Spring 认为匿名用户,convertAndSendToUser直接跳过 - 服务端配置漏了
setUserDestinationPrefix("/user"),或写成"/users"导致路径不匹配 - 消息 payload 是 null 或未实现
Serializable(尤其用 JDK 序列化时),broker 会丢弃且无报错 - 额外陷阱:若用
@MessageMapping处理请求后再调用convertAndSendToUser,但该方法抛异常未被捕获,整个事务回滚,推送也取消
别混用两种 WebSocket 编程模型
Spring Boot 里存在两套不兼容的 WebSocket 支持:基于 STOMP 的消息代理模式(推荐),和基于 @ServerEndpoint 的原生 WebSocket 模式(手动管理 session)。
- 用
@EnableWebSocketMessageBroker就必须走SimpMessagingTemplate,不能同时在@ServerEndpoint类里调用session.getBasicRemote().sendText() - 反之,如果项目里已存在大量
@ServerEndpoint实现,就别强行接入 STOMP,否则convertAndSendToUser找不到对应 session(它只管 STOMP 连接) - 二者共存会导致连接数翻倍、心跳冲突、session ID 冲突,上线后偶发 404 或 500
真正麻烦的从来不是“怎么写”,而是“谁在哪个环节把 user ID 弄丢了”——尤其是前端用 SockJS + Stomp.js 时,login 字段容易被拦截器过滤、被 Nginx 丢 header、被 Vue Router 的 history 模式干扰 URL。留个心眼,比补十个 try-catch 有用。

















