当前位置: 首页 > news >正文

SpringBoot 整合 WebSocket——实时消息推送实战

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/爬虫 实战干货,不让你白来。

http://www.jsqmd.com/news/1125871/

相关文章:

  • Cursor 连接慢、AI 代码补全无响应怎么办?开发者 AI 编程工具网络优化指南
  • 植物真的“渴”了吗?一种验证干旱监测结果的新方法
  • 从浏览器内核升级到 AI Agent 沙箱设计:一名 C++ 开发者的安全架构进阶之路
  • 目的:这个项目是干什么的?
  • 低功耗无线监测技术选型:从待机电流到温漂补偿的工程实践分析
  • 城乡居民基本医疗信息管理系统-springboot
  • 网络编程的一些胡思乱想
  • UTBotJava多语言支持指南:Java、Kotlin、Python、Go、JavaScript全覆盖
  • 开源CLI工具安全调用国产大模型API实战
  • 鹤壁办宴席,选烟酒怎么备不浪费又体面?
  • 企业网络管理实战:稳定、安全、高效运维全方案
  • Unity基础:Game视图详解——游戏预览、分辨率模拟与性能显示
  • sklearn 生成数据集 make_classification 参数详解:创建3类不平衡分类数据实战
  • 为什么网卡停止收包?——Intel网卡RX Buffer Replenishment机制深度解析(下)
  • 2026年洛阳新房装修:水管漏水半夜打电话,洛阳这家装修公司居然秒回!
  • 一体化泵站哪家技术强
  • 为什么要让我们的“领域模型”裸奔?(上)
  • 罗氏线圈柔性电流探头在测试中的应用
  • 搜维尔科技:TESOLLO灵巧手与Mnaus数据手套遥操作方案
  • OEXN:“特斯拉加码车型刺激需求”
  • PW7126+PW4406A*4三串锂电池充放电保护板方案,持续6A,过流保护7A
  • Affinity Matrix 构建实战:3种相似度度量(Cosine/Jaccard)对比与 Scikit-learn 实现
  • Python 自动化之批量图片处理——水印、压缩、格式转换
  • gmail loading progress bar 实现原理
  • 基于微软Dryad分布式并行计算平台云技术的研究
  • MIX 11 细节梳理 Windows phone 7 Session
  • Codex代理配置实战:用国产大模型替代OpenAI API的完整指南
  • 绝影马:7.8起美国CPSC电子申报强制执行,未合规将遭清关扣留!
  • ParsecVDisplay:Windows虚拟显示器的终极免费解决方案
  • 从团队项目角度看 AI API 聚合平台:别等成本失控后才补日志