spring boot+vue3实现stomp协议

📅 2026/7/4 9:45:53
spring boot+vue3实现stomp协议
一、后端1、安装依赖dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-websocket/artifactId /dependency2、定义消息实体类根据特定业务来定义Data AllArgsConstructor public class ChatMessage { String sender; String message; }3、定义配置文件类package com.example.stomptest.config; import lombok.extern.slf4j.Slf4j; import org.springframework.context.annotation.Configuration; import org.springframework.messaging.Message; import org.springframework.messaging.MessageChannel; import org.springframework.messaging.simp.config.ChannelRegistration; import org.springframework.messaging.simp.config.MessageBrokerRegistry; import org.springframework.messaging.simp.stomp.StompCommand; import org.springframework.messaging.simp.stomp.StompHeaderAccessor; import org.springframework.messaging.support.ChannelInterceptor; import org.springframework.messaging.support.MessageHeaderAccessor; 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 Slf4j public class WebSocketConfig implements WebSocketMessageBrokerConfigurer { /** * 注册Stomp服务端点 * param registry */ Override public void registerStompEndpoints(StompEndpointRegistry registry) { // addEndpoint 设置与客户端建立连接的url registry.addEndpoint(/ws) // 设置允许跨域 .setAllowedOriginPatterns(*) // 允许SocketJs使用是为了防止某些浏览器客户端不支持websocket协议的降级策略 .withSockJS(); } /** * 配置消息代理 */ Override public void configureMessageBroker(MessageBrokerRegistry registry) { // 客户端发送消息的请求前缀 registry.setApplicationDestinationPrefixes(/app); // 客户端订阅消息的请求前缀topic一般用于广播推送queue用于点对点推送 registry.enableSimpleBroker(/topic, /queue); // 服务端通知客户端的前缀可以不设置默认为user registry.setUserDestinationPrefix(/user); } Override public void configureClientInboundChannel(ChannelRegistration registration) { registration.interceptors(new ChannelInterceptor() { Override public Message? preSend(Message? message, MessageChannel channel) { // log.info(--websocket信息发送前--); StompHeaderAccessor accessor MessageHeaderAccessor.getAccessor(message, StompHeaderAccessor.class); if (accessor ! null) { // 判断是否是连接Command 如果是,需要获取token对象 if (StompCommand.CONNECT.equals(accessor.getCommand())) { String token accessor.getFirstNativeHeader(token);//下方可执行解析token逻辑 log.info(toekn信息token); // final String token accessor.getFirstNativeHeader(Constant.HEADER_TOKEN); // if (!TokenUtil.validateToken(token)) { // return null; // } // final LoginUser user TokenUtil.getUserFromToken(token); // UserUtil.setUser(user); // sendToUser 需要与这里的user获取的principal一样 // accessor.setUser(new SocketUser(user)); accessor.setUser(() - test); log.info(websocket 连接成功); } } return message; } }); } }4、定义监视器package com.example.collection_service.common.listener; import com.example.collection_service.system.service.IDataPushService; import jakarta.annotation.Resource; import lombok.extern.slf4j.Slf4j; import org.springframework.context.annotation.Lazy; import org.springframework.context.event.EventListener; import org.springframework.messaging.simp.SimpMessageSendingOperations; import org.springframework.stereotype.Component; import org.springframework.web.socket.messaging.SessionConnectEvent; import org.springframework.web.socket.messaging.SessionDisconnectEvent; import org.springframework.web.socket.messaging.SessionSubscribeEvent; Component Slf4j public class WebSocketEventListener { Resource private SimpMessageSendingOperations messagingTemplate; /** * 连接建立事件 * param event */ EventListener public void handleWebSocketConnectListener(SessionConnectEvent event) { log.info(建立一个新的连接); //dataPushService.initConnectRealTimeDataPush(); } EventListener public void handleWebSocketDisconnectListener(SessionDisconnectEvent event) { // StompHeaderAccessor headerAccessor StompHeaderAccessor.wrap(event.getMessage()); // // String username (String) headerAccessor.getSessionAttributes().get(login); log.info(用户断开连接); } EventListener public void onSubscribe(SessionSubscribeEvent event) { log.info(订阅监听事件); String dest event.getMessage().getHeaders().get(simpDestination).toString(); if (dest.contains(/tagRealTimeData)) { //执行订阅成功的逻辑 } } }5、定义控制器测试用package com.example.stomptest.controller; import com.example.stomptest.entity.ChatMessage; import jakarta.annotation.Resource; import lombok.extern.slf4j.Slf4j; import org.springframework.messaging.handler.annotation.DestinationVariable; 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.SimpMessagingTemplate; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RestController; import java.security.Principal; RestController Slf4j public class ChatController { Resource private SimpMessagingTemplate messagingTemplate; MessageMapping(/sendToAll) // SendTo(/topic/notice) public String sendToAll(String message) { String result服务端通知: message; messagingTemplate.convertAndSend(/topic/notice,result); return result; } /** * 点对点发送消息 * p * 模拟 张三 给 李四 发送消息场景 * param username 接收消息的用户 * param message 消息内容 */ MessageMapping(/sendToUser/{username}) public void sendToUser(DestinationVariable String username, String message) { String sender1910; String receiver username; // 接收人 log.info(发送人:{}; 接收人:{}, sender, receiver); // 发送消息给指定用户 /user/{username}/queue/greeting messagingTemplate.convertAndSendToUser(receiver, /queue/message, new ChatMessage(sender, message)); } }二、前端1、安装依赖npm install stomp/stompjs -S npm i sockjs-client -S2、测试代码1、封装JSimport{Client}fromstomp/stompjsimportSockJS fromsockjs-clientconst baseUrlimport.meta.env.VITE_STOMP_API;exportconst getStompClient(onSilentTimeout){//获取stomp客户端letclientnew Client({// ✅ 用 SockJS 创建 WebSocket webSocketFactory:()new SockJS(${baseUrl}/ws), // ✅ 自动重连核心 reconnectDelay:2000, // 断线 2s 后重连 maxReconnectDelay:30000, // 最长重连间隔 // ✅ 心跳可选你之前说不需要 heartbeatIncoming 可留空 heartbeatOutgoing:10000, // heartbeatIncoming:0, // 不启用心跳检测假死你可选 // ✅ 连接头按你后端要求 connectHeaders:{// login:test, // token:3224sdsdfgdfdfsfddfsf},})// ✅ 内部业务心跳定时器letbusinessHeartbeatTimernull const BUSINESS_TIMEOUT5*60*1000//5分钟 // ✅ 内部重置定时器收到数据时调用 const resetBusinessTimer(){if(businessHeartbeatTimer){clearTimeout(businessHeartbeatTimer)}businessHeartbeatTimersetTimeout((){// 超时了执行外部传进来的回调 onSilentTimeoutonSilentTimeout()}, BUSINESS_TIMEOUT)}// ✅ 内部清理定时器 const clearBusinessTimer(){if(businessHeartbeatTimer){clearTimeout(businessHeartbeatTimer)businessHeartbeatTimernull}}// 断线清理检测 client.onWebSocketClose(){console.log(断线清理)clearBusinessTimer()}// ✅ 对外暴露一个钩子让页面通知 Client“收到数据了” client.notifyDataReceived(){resetBusinessTimer()}returnclient}2、业务订阅import{getStompClient}from/utils/webStomp;const stompClientref(null);const initStomp(){//初始化websocket 连接 closeStomp()stompClient.valuegetStompClient((){console.log(5分钟未收到任务数据);})stompClient.value.onConnect(){console.log(连接成功);subscribeRealTimeData()}stompClient.value.activate();}const closeStomp(){//关闭连接if(stompClient.value){stompClient.value.deactivate()stompClient.value.onDisconnect(){console.log(断开连接);};}}const subscribeRealTimeData(){//加载实时数据 stompClient.value.subscribe(/xxxxx/zzzz,(res){stompClient.value.notifyDataReceived()letresponseJSON.parse(res.body);console.log(订阅点对点成功);console.log(response);//执行业务逻辑});}3、注意在index.html中加入如下主要是globalheadscriptwindow.global||window;/script