?? 作者主頁(yè): 有來(lái)技術(shù)
?? 開(kāi)源項(xiàng)目: youlai-mall ?? vue3-element-admin ?? youlai-boot
?? 倉(cāng)庫(kù)主頁(yè): Gitee ?? Github ?? GitCode
?? 歡迎點(diǎn)贊 ?? 收藏 ?留言 ?? 如有錯(cuò)誤敬請(qǐng)糾正!
引言
WebSocket是一種在Web瀏覽器與Web服務(wù)器之間建立雙向通信的協(xié)議,而Spring Boot提供了便捷的WebSocket支持。本篇博客將介紹如何在Spring Boot 3中整合WebSocket,并使用Vue 3構(gòu)建簡(jiǎn)單而強(qiáng)大的實(shí)時(shí)通信應(yīng)用。
核心概念
什么是 WebSocket ?
WebSocket是一種在單個(gè)TCP連接上提供全雙工通信的協(xié)議,允許客戶端和服務(wù)器之間實(shí)時(shí)雙向通信,無(wú)需客戶端發(fā)起請(qǐng)求。其優(yōu)勢(shì)在于低延遲和高效率,適用于即時(shí)通訊、在線游戲、實(shí)時(shí)協(xié)作編輯等場(chǎng)景。
什么是 STOMP ?
STOMP 官網(wǎng): 官方文檔 | 中文文檔
STOMP是一種基于文本的消息傳遞協(xié)議,用于實(shí)現(xiàn)消息傳遞系統(tǒng)之間的互操作性。它簡(jiǎn)單、跨語(yǔ)言、適應(yīng)性強(qiáng),支持多種消息傳遞模式。通常與WebSocket結(jié)合,提供瀏覽器和服務(wù)器之間的實(shí)時(shí)雙向通信。
STOMP是一種簡(jiǎn)單的消息傳遞協(xié)議,初衷是為腳本語(yǔ)言(如 Ruby、 Python 和 Perl)和web框架創(chuàng)建一種基于文本的簡(jiǎn)單異步消息協(xié)議。相比于正式誕生于2011年的WebSocket,STOMP在此之前廣泛使用了十年以上,并且得到了很多客戶端(如stomp.js、Gozirra、stomp.py、stompngo等)、消息代理端(如ActiveMQ、RabbitMQ等)、工具庫(kù)的支持,目前最新的協(xié)議版本為2012年1.2版本。
具體來(lái)說(shuō),STOMP是一種基于Frame
的協(xié)議,每個(gè)Frame
由一個(gè)命令Command
、一組Headers
和可選的正文Body
組成,如下是一個(gè)STOMP frame的基本結(jié)構(gòu)示例:
SEND //作為COMMAND
Authorization:Bearer xxx //作為Headers
content-type:application/json //作為Headers
Hello World! //消息內(nèi)容,可以多行,直到^@為止 //作為Body
^@ //此Frame結(jié)束
STOMP On Spring WebSocket
spring-framework #websocket-stomp
Spring Framework 從 4.0.0 加入了spring-websocket 和 spring-messaging 兩大模塊。
從Spring文檔的篇幅、提供的應(yīng)用樣例以及spring-boot-starter-websocket
直接引入了spring-websocket 和 spring-messaging模塊(包含了STOMP等相關(guān)內(nèi)容)等各種情況,不難看出基于STOMP做為其消息交互協(xié)議的方式,是spring主推的完整的websocket解決方案即 STOMP On Spring WebSocket
,即使用STOMP也是spring框架的選擇。
WebSocket 服務(wù)端(SpringBoot3)
添加依賴
首先,在pom.xml
中添加WebSocket依賴:
<!-- WebSocket依賴 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
WebSocket 配置類
配置 WebSocket 支持實(shí)時(shí)雙向通信和消息傳遞,使用STOMP協(xié)議。提供連接端點(diǎn) “/ws”,支持跨域WebSocket連接,配置消息代理實(shí)現(xiàn)廣播和點(diǎn)對(duì)點(diǎn)推送。
package com.youlai.system.config;
/**
* WebSocket 配置
*
* @author haoxr
* @since 2.4.0
*/
@Configuration
@EnableWebSocketMessageBroker // 啟用WebSocket消息代理功能和配置STOMP協(xié)議,實(shí)現(xiàn)實(shí)時(shí)雙向通信和消息傳遞
@RequiredArgsConstructor
@Slf4j
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {
private final JwtTokenProvider jwtTokenProvider;
/**
* 注冊(cè)一個(gè)端點(diǎn),客戶端通過(guò)這個(gè)端點(diǎn)進(jìn)行連接
*/
@Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry
.addEndpoint("/ws") // 注冊(cè)了一個(gè) /ws 的端點(diǎn)
.setAllowedOriginPatterns("*") // 允許跨域的 WebSocket 連接
.withSockJS(); // 啟用 SockJS (瀏覽器不支持WebSocket,SockJS 將會(huì)提供兼容性支持)
}
/**
* 配置消息代理
*/
@Override
public void configureMessageBroker(MessageBrokerRegistry registry) {
// 客戶端發(fā)送消息的請(qǐng)求前綴
registry.setApplicationDestinationPrefixes("/app");
// 客戶端訂閱消息的請(qǐng)求前綴,topic一般用于廣播推送,queue用于點(diǎn)對(duì)點(diǎn)推送
registry.enableSimpleBroker("/topic", "/queue");
// 服務(wù)端通知客戶端的前綴,可以不設(shè)置,默認(rèn)為user
registry.setUserDestinationPrefix("/user");
}
/**
* 配置客戶端入站通道攔截器
* <p>
* 添加 ChannelInterceptor 攔截器,用于在消息發(fā)送前,從請(qǐng)求頭中獲取 token 并解析出用戶信息(username),用于點(diǎn)對(duì)點(diǎn)發(fā)送消息給指定用戶
*
* @param registration 通道注冊(cè)器
*/
@Override
public void configureClientInboundChannel(ChannelRegistration registration) {
registration.interceptors(new ChannelInterceptor() {
@Override
public Message<?> preSend(Message<?> message, MessageChannel channel) {
StompHeaderAccessor accessor = MessageHeaderAccessor.getAccessor(message, StompHeaderAccessor.class);
// 如果是連接請(qǐng)求(CONNECT 命令),從請(qǐng)求頭中取出 token 并設(shè)置到認(rèn)證信息中
if (accessor != null && StompCommand.CONNECT.equals(accessor.getCommand())) {
// 從連接頭中提取授權(quán)令牌
String bearerToken = accessor.getFirstNativeHeader(HttpHeaders.AUTHORIZATION);
// 驗(yàn)證令牌格式并提取用戶信息
if (StrUtil.isNotBlank(bearerToken) && bearerToken.startsWith("Bearer ")) {
try {
// 移除 "Bearer " 前綴,從令牌中提取用戶信息(username), 并設(shè)置到認(rèn)證信息中
String tokenWithoutPrefix = bearerToken.substring(7);
String username = jwtTokenProvider.getUsername(tokenWithoutPrefix);
if (StrUtil.isNotBlank(username)) {
accessor.setUser(() -> username);
return message;
}
} catch (Exception e) {
log.error("Failed to process authentication token.", e);
}
}
}
// 不是連接請(qǐng)求,直接放行
return ChannelInterceptor.super.preSend(message, channel);
}
});
}
}
WebSocket 監(jiān)聽(tīng)和發(fā)送
package com.youlai.system.controller;
import com.youlai.system.model.dto.ChatMessage;
import lombok.RequiredArgsConstructor;
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.SendTo;
import org.springframework.messaging.simp.SimpMessagingTemplate;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.security.Principal;
/**
* WebSocket 測(cè)試控制器
*
* @author haoxr
* @since 2.3.0
*/
@RestController
@RequestMapping("/websocket")
@RequiredArgsConstructor
@Slf4j
public class WebsocketController {
private final SimpMessagingTemplate messagingTemplate;
/**
* 廣播發(fā)送消息
*
* @param message 消息內(nèi)容
*/
@MessageMapping("/sendToAll")
@SendTo("/topic/notice")
public String sendToAll(String message) {
return "服務(wù)端通知: " + message;
}
/**
* 點(diǎn)對(duì)點(diǎn)發(fā)送消息
* <p>
* 模擬 張三 給 李四 發(fā)送消息場(chǎng)景
*
* @param principal 當(dāng)前用戶
* @param username 接收消息的用戶
* @param message 消息內(nèi)容
*/
@MessageMapping("/sendToUser/{username}")
public void sendToUser(Principal principal, @DestinationVariable String username, String message) {
String sender = principal.getName(); // 發(fā)送人
String receiver = username; // 接收人
log.info("發(fā)送人:{}; 接收人:{}", sender, receiver);
// 發(fā)送消息給指定用戶 /user/{username}/queue/greeting
messagingTemplate.convertAndSendToUser(receiver, "/queue/greeting", new ChatMessage(sender, message));
}
}
WebSocket 客戶端(Vue3)
安裝依賴
# WebSocket 客戶端和類型定義
npm i sockjs-client
npm i -D @types/sockjs-client
# STOMP 協(xié)議的 JavaScript 客戶端和類型定義
npm i stompjs
npm i -D @types/stompjs
# net 是 Node核心模塊之一,用于創(chuàng)建 TCP 服務(wù)器和客戶端模塊
npm i net -S
WebSocket 客戶端連接
下面是 Websocket 連接部分關(guān)鍵代碼,完整代碼:websocket.vue
<!-- websocket 示例 -->
<script setup lang="ts">
import SockJS from "sockjs-client";
import Stomp from "stompjs";
import { useUserStoreHook } from "@/store/modules/user";
const userStore = useUserStoreHook(); // websocket 連接傳遞 token
const isConnected = ref(false);
const socketEndpoint = ref("http://localhost:8989/ws");
let stompClient: Stomp.Client;
/**
* 連接
*/
function connect() {
let socket = new SockJS(socketEndpoint.value);
stompClient = Stomp.over(socket);
stompClient.connect(
{ Authorization: userStore.token },
() => {
console.log("連接成功");
},
(error) => {
console.log("連接失敗: " + error);
}
);
}
/**
* 斷開(kāi)連接
*/
function disconnect() {
if (stompClient && stompClient.connected) {
stompClient.disconnect(() => {
console.log("斷開(kāi)連接");
});
}
}
onMounted(() => {
connect();
});
</script>
WebSocket 客戶端訂閱
下面是 Websocket 訂閱部分關(guān)鍵代碼,完整代碼:websocket.vue
stompClient.subscribe("/topic/notice", (res: any) => {
console.log("訂閱廣播成功:" + res.body);
});
stompClient.subscribe("/user/queue/greeting", (res) => {
console.log("訂閱點(diǎn)對(duì)點(diǎn)成功:" + res.body);
});
-
/topic/notice
對(duì)應(yīng)服務(wù)端廣播隊(duì)列
-
/user/queue/greeting
對(duì)應(yīng)服務(wù)端點(diǎn)對(duì)點(diǎn)隊(duì)列,其中 /user/ 前綴是 WebSocketConfig 里通過(guò)
registry.setUserDestinationPrefix("/user")
設(shè)定的服務(wù)端通知客戶端的前綴。
WebSocket 客戶端推送
下面是 Websocket 訂閱部分關(guān)鍵代碼,完整代碼:websocket.vue
function sendToAll() {
stompClient.send("/app/sendToAll", {}, "親愛(ài)的大冤種們,由于一只史詩(shī)級(jí)的BUG,系統(tǒng)版本已經(jīng)被迫回退到了0.0.1。");
console.log("廣播消息發(fā)送成功");
}
function sendToUser() {
stompClient.send("/app/sendToUser/root", {}, "嗨! root 我是 admin ,想和您交個(gè)朋友");
console.log("點(diǎn)對(duì)點(diǎn)消息發(fā)送成功");
}
-
/app/sendToAll
客戶端發(fā)送的目標(biāo)地址,指定服務(wù)端
@MessageMapping("/sendToAll")
方法處理此消息其中
/app
是 WebSocketConfig 里通過(guò)registry.setApplicationDestinationPrefixes("/app")
設(shè)定的客戶端發(fā)送目標(biāo)地址的前綴 -
/app/sendToUser/root
對(duì)應(yīng)服務(wù)端處理消息的方法如下:
WebSocket 測(cè)試
在線測(cè)試地址: vue3-element-admin#websocket
WebSocket 連接測(cè)試
WebSocket 廣播測(cè)試
WebSocket 點(diǎn)對(duì)點(diǎn)測(cè)試
文章來(lái)源:http://www.zghlxwxcb.cn/news/detail-783094.html
項(xiàng)目源碼
Github | Gitee | |
---|---|---|
后端 | youlai-boot ?? | youlai-boot ?? |
前端 | vue3-element-admin ?? | vue3-element-admin ?? |
結(jié)語(yǔ)
本文深入介紹了在Spring Boot 3中整合WebSocket及Vue 3構(gòu)建實(shí)時(shí)通信應(yīng)用。選擇STOMP協(xié)議,配置服務(wù)端、客戶端,實(shí)現(xiàn)連接、消息廣播和點(diǎn)對(duì)點(diǎn)推送。通過(guò)在線測(cè)試驗(yàn)證整合效果,包括連接、廣播和點(diǎn)對(duì)點(diǎn)消息。希望讀者通過(guò)這個(gè)實(shí)例更好地理解WebSocket在Spring Boot中的應(yīng)用。文章來(lái)源地址http://www.zghlxwxcb.cn/news/detail-783094.html
到了這里,關(guān)于Spring Boot 3 + Vue 3 整合 WebSocket (STOMP協(xié)議) 實(shí)現(xiàn)廣播和點(diǎn)對(duì)點(diǎn)實(shí)時(shí)消息的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!