阅读量:0
一、后端
1、安装依赖
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-websocket</artifactId> </dependency>
2、定义消息实体类(根据特定业务来定义)
@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.stomptest.handle; import com.example.stomptest.entity.ChatMessage; import jakarta.annotation.Resource; import lombok.extern.slf4j.Slf4j; import org.springframework.context.event.EventListener; import org.springframework.messaging.simp.SimpMessageSendingOperations; import org.springframework.messaging.simp.stomp.StompHeaderAccessor; import org.springframework.stereotype.Component; import org.springframework.web.socket.messaging.SessionConnectEvent; import org.springframework.web.socket.messaging.SessionDisconnectEvent; @Component @Slf4j public class WebSocketEventListener { @Resource private SimpMessageSendingOperations messagingTemplate; /** * 连接建立事件 * @param event */ @EventListener public void handleWebSocketConnectListener(SessionConnectEvent event) { log.info("建立一个新的连接"); } @EventListener public void handleWebSocketDisconnectListener(SessionDisconnectEvent event) { // StompHeaderAccessor headerAccessor = StompHeaderAccessor.wrap(event.getMessage()); // // String username = (String) headerAccessor.getSessionAttributes().get("login"); log.info("用户断开连接"); } }
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 sender="1910"; String receiver = username; // 接收人 log.info("发送人:{}; 接收人:{}", sender, receiver); // 发送消息给指定用户 /user/{username}/queue/greeting messagingTemplate.convertAndSendToUser(receiver, "/queue/message", new ChatMessage(sender, message)); } }
二、前端
1、安装依赖
npm i stompjs -S npm i sockjs-client -S
2、测试代码
<template> <el-button type="primary" @click="startSub">订阅广播</el-button> <el-input v-model="input" placeholder="Please input" /> <el-button type="primary" @click="sendMessage">群发消息</el-button> <el-button type="primary" @click="subscribeMessagePoint">点对点订阅消息</el-button> <el-button type="primary" @click="sendMessagePoint">点对点发消息</el-button> <el-button type="primary" @click="connect">建立连接</el-button> <el-button type="primary" @click="disconnect">关闭连接</el-button> </template> <script setup> import { ref, onMounted, onBeforeMount, onBeforeUnmount, nextTick, reactive, watch } from 'vue'; import SockJS from 'sockjs-client/dist/sockjs.min.js'; import Stomp from 'stompjs'; const stompClient = ref(null); const input = ref(''); onMounted(() => { connect(); }); const connect = () => { let socket = new SockJS('http://localhost:3301/ws'); stompClient.value = Stomp.over(socket); stompClient.value.connect( { login: 'test',//对于点对点通信必不可少 token: '3224sdsdfgdfdfsfddfsf', }, () => { console.log('连接成功'); }, (error) => { console.log('连接失败: ' + error); } ); }; const disconnect = () => { if (stompClient.value && stompClient.value.connected) { stompClient.value.disconnect(() => { console.log('断开连接'); }); } }; const startSub = () => { //订阅广播消息 stompClient.value.subscribe('/topic/notice', (res) => { console.log('订阅广播成功:' + res.body); }); }; const sendMessage = () => { //广播发布消息 stompClient.value.send('/app/sendToAll', {}, input.value); }; const subscribeMessagePoint = () => { //订阅用户消息 stompClient.value.subscribe('/user/queue/message', (res) => { let response = JSON.parse(res.body); console.log('订阅点对点成功:'); console.log(response); console.log(response.message); }); }; const sendMessagePoint = () => { //给指定用户发送消息 stompClient.value.send('/app/sendToUser/test', {}, input.value); }; </script> <style lang="scss" scoped></style>