SpringBoot 整合 WebSocket——实时消息推送实战
2026/7/5 3:37:05 网站建设 项目流程

WebSocket 是后端推送消息到前端的标准方案——聊天室、消息通知、实时数据看板都离不开它。相比轮询,WebSocket 在建立一次连接后,服务端可以随时主动推送数据,实时性更高、资源消耗更低。

一、WebSocket 与轮询的对比

轮询(Polling): 客户端 → 请求 → 服务端(有数据吗?) ← 响应(没有) 客户端 → 请求 → 服务端(有数据吗?) ← 响应(有了!) 缺点:频繁请求浪费资源,实时性差 WebSocket: 客户端 → 建立连接 → 服务端 ← 推送数据(有新数据时主动发) ← 推送数据 优点:连接保持,服务端主动推送,实时性好

二、SpringBoot 整合 WebSocket

1. 引入依赖

<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-websocket</artifactId></dependency>

2. WebSocket 配置

@Configuration@EnableWebSocketMessageBrokerpublicclassWebSocketConfigimplementsWebSocketMessageBrokerConfigurer{@OverridepublicvoidconfigureMessageBroker(MessageBrokerRegistryregistry){// 客户端订阅消息的前缀registry.enableSimpleBroker("/topic","/queue");// 客户端发送消息的前缀registry.setApplicationDestinationPrefixes("/app");// 点对点推送前缀registry.setUserDestinationPrefix("/user");}@OverridepublicvoidregisterStompEndpoints(StompEndpointRegistryregistry){// WebSocket 连接端点registry.addEndpoint("/ws").setAllowedOriginPatterns("*").withSockJS();// 兼容不支持 WebSocket 的浏览器}}

3. 前端连接

<!-- 引入 SockJS 和 STOMP --><scriptsrc="https://cdn.jsdelivr.net/npm/sockjs-client@1/dist/sockjs.min.js"></script><scriptsrc="https://cdn.jsdelivr.net/npm/stompjs@2.3.3/lib/stomp.min.js"></script><script>// 建立连接varsocket=newSockJS('/ws');varstompClient=Stomp.over(socket);stompClient.connect({},function(frame){console.log('连接成功: '+frame);// 订阅广播消息stompClient.subscribe('/topic/notice',function(message){vardata=JSON.parse(message.body);alert('收到通知: '+data.content);});// 订阅个人消息stompClient.subscribe('/user/queue/message',function(message){vardata=JSON.parse(message.body);alert('个人消息: '+data.content);});});// 发送消息functionsendMessage(){stompClient.send("/app/sendMessage",{},JSON.stringify({toUserId:1002,content:"你好!"}));}</script>

三、服务端推送

1. 广播消息(推送给所有用户)

@ServicepublicclassWebSocketService{@AutowiredprivateSimpMessagingTemplatemessagingTemplate;/** * 广播通知(所有在线用户都能收到) */publicvoidbroadcast(Stringtype,Stringcontent){Map<String,Object>message=newHashMap<>();message.put("type",type);message.put("content",content);message.put("timestamp",LocalDateTime.now().toString());// 推送到 /topic/notice,所有订阅了该地址的客户端都会收到messagingTemplate.convertAndSend("/topic/notice",message);}}

2. 点对点消息(推送给指定用户)

/** * 发送给指定用户 */publicvoidsendToUser(LonguserId,Stringcontent){Map<String,Object>message=newHashMap<>();message.put("from","系统");message.put("content",content);message.put("timestamp",LocalDateTime.now().toString());// 推送到 /user/{userId}/queue/messagemessagingTemplate.convertAndSendToUser(userId.toString(),"/queue/message",message);}

3. Controller 触发推送

@RestController@RequestMapping("/api/ws")publicclassWebSocketController{@AutowiredprivateWebSocketServicewsService;/** * 发送广播通知 */@PostMapping("/broadcast")publicResultVO<?>broadcast(@RequestBodyNoticeDTOdto){wsService.broadcast("notice",dto.getContent());returnResultVO.success("广播已发送");}/** * 发送给指定用户 */@PostMapping("/send")publicResultVO<?>sendToUser(@RequestBodyMessageDTOdto){wsService.sendToUser(dto.getUserId(),dto.getContent());returnResultVO.success("消息已发送");}}

四、秒杀系统中的 WebSocket 应用

秒杀下单后不需要前端轮询查结果,服务端处理完成后主动推送:

@ServicepublicclassSeckillService{@AutowiredprivateSimpMessagingTemplatemessagingTemplate;publicResultVO<?>doSeckill(LongproductId,LonguserId,StringuserName){// ... 秒杀核心逻辑 ...// 异步处理完成后,推送结果给用户CompletableFuture.runAsync(()->{// 模拟异步处理try{Thread.sleep(2000);}catch(Exceptione){}Map<String,Object>result=newHashMap<>();result.put("productId",productId);result.put("status","success");result.put("orderNo",order.getOrderNo());messagingTemplate.convertAndSendToUser(userId.toString(),"/queue/seckill",result);});returnResultVO.success("订单处理中,请稍候...");}}

前端订阅秒杀结果:

stompClient.subscribe('/user/queue/seckill',function(message){vardata=JSON.parse(message.body);if(data.status==='success'){showResult('🎉','秒杀成功!','订单号: '+data.orderNo);}else{showResult('😅','秒杀失败','');}});

五、WebSocket 事件监听

@ComponentpublicclassWebSocketEventListener{privatestaticfinalLoggerlog=LoggerFactory.getLogger(WebSocketEventListener.class);/** * 监听连接建立 */@EventListenerpublicvoidhandleConnect(SessionConnectedEventevent){log.info("WebSocket 连接建立");}/** * 监听断开连接 */@EventListenerpublicvoidhandleDisconnect(SessionDisconnectEventevent){StompHeaderAccessorheaderAccessor=StompHeaderAccessor.wrap(event.getMessage());StringsessionId=headerAccessor.getSessionId();log.info("WebSocket 断开连接: {}",sessionId);}}

六、在线用户管理

@ComponentpublicclassUserSessionManager{/** 在线用户映射:userId → sessionId */privatefinalMap<Long,String>onlineUsers=newConcurrentHashMap<>();/** * 用户上线 */publicvoiduserOnline(LonguserId,StringsessionId){onlineUsers.put(userId,sessionId);}/** * 用户下线 */publicvoiduserOffline(StringsessionId){onlineUsers.entrySet().removeIf(e->e.getValue().equals(sessionId));}/** * 获取在线人数 */publicintgetOnlineCount(){returnonlineUsers.size();}/** * 判断用户是否在线 */publicbooleanisOnline(LonguserId){returnonlineUsers.containsKey(userId);}}

七、常见问题

1. Nginx 代理配置

server { listen 80; server_name example.com; location /ws { proxy_pass http://localhost:9090; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } }

2. 心跳检测

防止连接因长时间空闲而被断开:

// WebSocket 配置中设置心跳registry.enableSimpleBroker("/topic","/queue").setHeartbeatValue(newlong[]{10000,10000});// 10秒心跳

3. 集群环境

多实例部署时,需要消息中间件广播消息:

@Configuration@EnableWebSocketMessageBrokerpublicclassWebSocketConfigimplementsWebSocketMessageBrokerConfigurer{@OverridepublicvoidconfigureMessageBroker(MessageBrokerRegistryregistry){// 使用 RabbitMQ 作为消息代理(集群环境)registry.enableStompBrokerRelay("/topic","/queue").setRelayHost("localhost").setRelayPort(61613).setSystemLogin("guest").setSystemPasscode("guest");}}

八、与轮询的区别总结

对比WebSocketHTTP 轮询
连接方式长连接,一次握手短连接,每次请求新建
数据流向双向,服务端可主动推客户端请求→服务端响应
实时性毫秒级取决于轮询间隔
资源消耗低(连接保持)高(频繁建连拆连)
实现复杂度中等简单

💡 觉得有用的话,点赞 + 关注【张老师技术栈】吧!每周更新 Java/Python/爬虫 实战干货,不让你白来。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询