Spring Boot 3 整合 WebSocket (STOMP协议) 和 Vue 3 实现实时通信

news2024/11/26 11:51:15

🚀 作者主页: 有来技术
🔥 开源项目: youlai-mall 🍃 vue3-element-admin 🍃 youlai-boot
🌺 仓库主页: Gitee 💫 Github 💫 GitCode
💖 欢迎点赞 👍 收藏 ⭐留言 📝 如有错误敬请纠正!

topic

目录

  • 引言
  • 核心概念
    • 什么是 WebSocket ?
    • 什么是 STOMP ?
    • STOMP On Spring WebSocket
  • WebSocket 服务端(SpringBoot3)
    • 添加依赖
    • WebSocket 配置类
    • WebSocket 监听和发送
  • WebSocket 客户端(Vue3)
    • 安装依赖
    • WebSocket 客户端连接
    • WebSocket 客户端订阅
    • WebSocket 客户端推送
  • WebSocket 测试
    • WebSocket 连接测试
    • WebSocket 广播测试
    • WebSocket 点对点测试
  • 项目源码
  • 结语

引言

WebSocket是一种在Web浏览器与Web服务器之间建立双向通信的协议,而Spring Boot提供了便捷的WebSocket支持。本篇博客将介绍如何在Spring Boot 3中整合WebSocket,并使用Vue 3构建简单而强大的实时通信应用。

核心概念

什么是 WebSocket ?

WebSocket是一种在单个TCP连接上提供全双工通信的协议,允许客户端和服务器之间实时双向通信,无需客户端发起请求。其优势在于低延迟和高效率,适用于即时通讯、在线游戏、实时协作编辑等场景。

什么是 STOMP ?

STOMP 官网: 官方文档 | 中文文档

STOMP是一种基于文本的消息传递协议,用于实现消息传递系统之间的互操作性。它简单、跨语言、适应性强,支持多种消息传递模式。通常与WebSocket结合,提供浏览器和服务器之间的实时双向通信。

STOMP是一种简单的消息传递协议,初衷是为脚本语言(如 Ruby、 Python 和 Perl)和web框架创建一种基于文本的简单异步消息协议。相比于正式诞生于2011年的WebSocket,STOMP在此之前广泛使用了十年以上,并且得到了很多客户端(如stomp.js、Gozirra、stomp.py、stompngo等)、消息代理端(如ActiveMQ、RabbitMQ等)、工具库的支持,目前最新的协议版本为2012年1.2版本。

具体来说,STOMP是一种基于Frame的协议,每个Frame由一个命令Command、一组Headers和可选的正文Body组成,如下是一个STOMP frame的基本结构示例:

SEND   //作为COMMAND
Authorization:Bearer xxx        //作为Headers
content-type:application/json   //作为Headers

Hello World!  //消息内容,可以多行,直到^@为止  //作为Body
^@ //此Frame结束

STOMP On Spring WebSocket

spring-framework #websocket-stomp

Spring Framework 从 4.0.0 加入了spring-websocket 和 spring-messaging 两大模块。

从Spring文档的篇幅、提供的应用样例以及spring-boot-starter-websocket直接引入了spring-websocket 和 spring-messaging模块(包含了STOMP等相关内容)等各种情况,不难看出基于STOMP做为其消息交互协议的方式,是spring主推的完整的websocket解决方案即 STOMP On Spring WebSocket,即使用STOMP也是spring框架的选择。

WebSocket 服务端(SpringBoot3)

添加依赖

首先,在pom.xml中添加WebSocket依赖:

<!-- WebSocket依赖 -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-websocket</artifactId>
</dependency>

WebSocket 配置类

配置 WebSocket 支持实时双向通信和消息传递,使用STOMP协议。提供连接端点 “/ws”,支持跨域WebSocket连接,配置消息代理实现广播和点对点推送。

package com.youlai.system.config;

/**
 * WebSocket 配置
 *
 * @author haoxr
 * @since 2.4.0
 */
@Configuration
@EnableWebSocketMessageBroker // 启用WebSocket消息代理功能和配置STOMP协议,实现实时双向通信和消息传递
@RequiredArgsConstructor
@Slf4j
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {
	
    private final JwtTokenProvider jwtTokenProvider;

    /**
     * 注册一个端点,客户端通过这个端点进行连接
     */
    @Override
    public void registerStompEndpoints(StompEndpointRegistry registry) {
        registry
                .addEndpoint("/ws")   // 注册了一个 /ws 的端点
                .setAllowedOriginPatterns("*") // 允许跨域的 WebSocket 连接
                .withSockJS();  // 启用 SockJS (浏览器不支持WebSocket,SockJS 将会提供兼容性支持)
    }

    /**
     * 配置消息代理
     */
    @Override
    public void configureMessageBroker(MessageBrokerRegistry registry) {
        // 客户端发送消息的请求前缀
        registry.setApplicationDestinationPrefixes("/app");

        // 客户端订阅消息的请求前缀,topic一般用于广播推送,queue用于点对点推送
        registry.enableSimpleBroker("/topic", "/queue");

        // 服务端通知客户端的前缀,可以不设置,默认为user
        registry.setUserDestinationPrefix("/user");
    }
    
    /**
     * 配置客户端入站通道拦截器
     * <p>
     * 添加 ChannelInterceptor 拦截器,用于在消息发送前,从请求头中获取 token 并解析出用户信息(username),用于点对点发送消息给指定用户
     *
     * @param registration 通道注册器
     */
    @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);
                // 如果是连接请求(CONNECT 命令),从请求头中取出 token 并设置到认证信息中
                if (accessor != null && StompCommand.CONNECT.equals(accessor.getCommand())) {
                    // 从连接头中提取授权令牌
                    String bearerToken = accessor.getFirstNativeHeader(HttpHeaders.AUTHORIZATION);

                    // 验证令牌格式并提取用户信息
                    if (StrUtil.isNotBlank(bearerToken) && bearerToken.startsWith("Bearer ")) {
                        try {
                            // 移除 "Bearer " 前缀,从令牌中提取用户信息(username), 并设置到认证信息中
                            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);
                        }
                    }
                }
                // 不是连接请求,直接放行
                return ChannelInterceptor.super.preSend(message, channel);
            }
        });
    }
}

WebSocket 监听和发送

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 测试控制器
 *
 * @author haoxr
 * @since 2.3.0
 */
@RestController
@RequestMapping("/websocket")
@RequiredArgsConstructor
@Slf4j
public class WebsocketController {

    private final SimpMessagingTemplate messagingTemplate;


    /**
     * 广播发送消息
     *
     * @param message 消息内容
     */
    @MessageMapping("/sendToAll")
    @SendTo("/topic/notice")
    public String sendToAll(String message) {
        return "服务端通知: " + message;
    }

    /**
     * 点对点发送消息
     * <p>
     * 模拟 张三 给 李四 发送消息场景
     *
     * @param principal 当前用户
     * @param username  接收消息的用户
     * @param message   消息内容
     */
    @MessageMapping("/sendToUser/{username}")
    public void sendToUser(Principal principal, @DestinationVariable String username, String message) {

        String sender = principal.getName(); // 发送人
        String receiver = username; // 接收人

        log.info("发送人:{}; 接收人:{}", sender, receiver);
        // 发送消息给指定用户 /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 协议的 JavaScript 客户端和类型定义
npm i stompjs
npm i -D @types/stompjs
# net 是 Node核心模块之一,用于创建 TCP 服务器和客户端模块
npm i net -S

WebSocket 客户端连接

下面是 Websocket 连接部分关键代码,完整代码: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);
    }
  );
}
/**
 * 断开连接
 */
function disconnect() {
  if (stompClient && stompClient.connected) {
    stompClient.disconnect(() => {
  	 console.log("断开连接");
    });
  }
}

onMounted(() => {
  connect();
});
</script>


WebSocket 客户端订阅

下面是 Websocket 订阅部分关键代码,完整代码:websocket.vue

stompClient.subscribe("/topic/notice", (res: any) => {
    console.log("订阅广播成功:" + res.body);
});

stompClient.subscribe("/user/queue/greeting", (res) => {
    console.log("订阅点对点成功:" + res.body);
});
  • /topic/notice

    对应服务端广播队列

    image-20231214155237181

  • /user/queue/greeting

    对应服务端点对点队列,其中 /user/ 前缀是 WebSocketConfig 里通过 registry.setUserDestinationPrefix("/user") 设定的服务端通知客户端的前缀。

WebSocket 客户端推送

下面是 Websocket 订阅部分关键代码,完整代码:websocket.vue


function sendToAll() {
  stompClient.send("/app/sendToAll", {},  "亲爱的大冤种们,由于一只史诗级的BUG,系统版本已经被迫回退到了0.0.1。");
  console.log("广播消息发送成功");	
}

function sendToUser() {
  stompClient.send("/app/sendToUser/root", {}, "嗨! root 我是 admin ,想和您交个朋友");
  console.log("点对点消息发送成功");	
}
  • /app/sendToAll

    客户端发送的目标地址,指定服务端 @MessageMapping("/sendToAll") 方法处理此消息

    其中 /app 是 WebSocketConfig 里通过 registry.setApplicationDestinationPrefixes("/app") 设定的客户端发送目标地址的前缀

  • /app/sendToUser/root

    对应服务端处理消息的方法如下:

WebSocket 测试

在线测试地址: vue3-element-admin#websocket

WebSocket 连接测试

WebSocket 广播测试

topic

WebSocket 点对点测试

项目源码

GithubGitee
后端youlai-boot 🍃youlai-boot 🍃
前端vue3-element-admin 🌺vue3-element-admin 🌺

结语

本文深入介绍了在Spring Boot 3中整合WebSocket及Vue 3构建实时通信应用。选择STOMP协议,配置服务端、客户端,实现连接、消息广播和点对点推送。通过在线测试验证整合效果,包括连接、广播和点对点消息。希望读者通过这个实例更好地理解WebSocket在Spring Boot中的应用。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1311629.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

基于 Webpack5 Module Federation 的业务解耦实践

前言 本文中会提到很多目前数栈中使用的特定名词&#xff0c;统一做下解释描述 dt-common&#xff1a;每个子产品都会引入的公共包(类似 NPM 包) AppMenus&#xff1a;在子产品中快速进入到其他子产品的导航栏&#xff0c;统一维护在 dt-common 中&#xff0c;子产品从 dt-com…

【Https】工作流程

HTTPS 也是⼀个应用层协议。是在 HTTP 协议的基础上引入了⼀个加密层。 前言 由于Http是明文传输&#xff0c;因此如果有人想修改/截获数据都是非常容易&#xff0c;因此就出现了运营商劫持问题。 加密基础知识 明文密钥>密文 加密 密文密钥>明文 解密 对称加密和非对…

亚马逊云科技 re:Invent 大会 - ElastiCache Serverless模式来袭

亚马逊云科技 re:Invent 大会 - ElastiCache Serverless模式来袭 本篇文章授权活动官方亚马逊云科技文章转发、改写权&#xff0c;包括不限于在 亚马逊云科技开发者社区, 知乎&#xff0c;自媒体平台&#xff0c;第三方开发者媒体等亚马逊云科技官方渠道。 文章目录 亚马逊云…

jenkins 运行接口自动化测试脚本,安装第三方依赖库的一些总结

之前在做接口自动化时&#xff0c;jenkins 执行 job 安装 requirements.txt 中的第三方依赖库时折腾了许久&#xff0c;网上查的解决方案均未生效&#xff0c;后来找出一条解决方法&#xff0c;做个记录&#xff0c;希望帮助到遇到同样问题的小伙伴。 我们都知道可以通过生成 …

软考机考考试第一批经验分享

由于机考的特殊性&#xff0c;考试环境与传统笔试环境有所不同。下面是与考试环境相关的总结&#xff1a; 草稿纸&#xff1a;考场提供足够数量的草稿纸&#xff0c;每位考生都会分发一张白纸作为草稿纸。在草稿纸上需要写上准考证号。如果不够用&#xff0c;可以向监考老师再次…

Android 11.0 framework关于systemUI状态栏透明背景的功能实现

1.概述 在11.0的系统rom产品定制化开发中,在对于系统原生SystemUI的状态栏背景在沉浸式状态栏的 情况下默认是会随着背景颜色的变化而改变的,在一些特定背景下状态栏的背景也是会改变的,所以由于产品开发需要 要求需要设置状态栏背景为透明的,所以就需要在Activity创建的时…

排序算法(二)-冒泡排序、选择排序、插入排序、希尔排序、快速排序、归并排序、基数排序

排序算法(二) 前面介绍了排序算法的时间复杂度和空间复杂数据结构与算法—排序算法&#xff08;一&#xff09;时间复杂度和空间复杂度介绍-CSDN博客&#xff0c;这次介绍各种排序算法——冒泡排序、选择排序、插入排序、希尔排序、快速排序、归并排序、基数排序。 文章目录 排…

web前端之正弦波浪动功能、repeat、calc

MENU 效果图htmlstylecalcrepeat 效果图 html <div class"grid"><span class"line"></span><span class"line"></span><span class"line"></span><span class"line"><…

Python采集知乎专栏文章保存成pdf

嗨喽&#xff0c;大家好呀~这里是爱看美女的茜茜呐 环境使用: Python 3.8 Pycharm wkhtmltopdf 软件 – 文末获取 模块使用: requests >>> pip install requests 数据请求 parsel >>> pip install parsel 数据解析 re >>> 内置模块 不需要安装…

linux 防火墙systemctl (个人笔记)

查看 systemctl status firewalld 开启 systemctl start firewalld 关闭 systemctl stop firewalld.service 查看所有 firewall-cmd --zonepublic --list-ports 开放端口&#xff1a;// --permanent 永久生效,没有此参数重启后失效 firewall-cmd --zonepublic --add-port9527/…

[PyTorch][chapter 7][李宏毅深度学习][深度学习简介]

前言&#xff1a; 深度学习常用的开发平台 TensorFlow torch theano caffe DSSTNE mxnet libdnn CNTK 目录&#xff1a; 1&#xff1a; 深度学习发展历史 2&#xff1a; DeepLearning 工程简介 3&#xff1a; DNN 简介 一 发展历史 二 DeepLearning 工程简介 深度学习三…

如何删除/替换3D模型的材质贴图

在线工具推荐&#xff1a; 3D数字孪生场景编辑器 - GLTF/GLB材质纹理编辑器 - 3D模型在线转换 - Three.js AI自动纹理开发包 - YOLO 虚幻合成数据生成器 - 三维模型预览图生成器 - 3D模型语义搜索引擎 在3D设计和动画领域&#xff0c;材质是呈现真实感和逼真效果的关键因…

从广东到俄罗斯:一段跨越万里的电子消费展之旅

作为一名广东电子消费品行业的从业者&#xff0c;我们经常要奔赴全球不同国家拓展海外业务&#xff0c;而展会就是重要的平台。最近大家在热议的俄罗斯国际消费类电子电器展我参加过一届&#xff0c;跨越万里的旅程&#xff0c;让我深刻体会到了这种国际展览的魅力和挑战。 我参…

Mac部署Odoo环境-Odoo本地环境部署

Odoo本地环境部署 安装Python安装Homebrew安装依赖brew install libxmlsec1 Python运行环境Pycharm示例配置 Mac部署Odoo环境-Odoo本地环境部署 安装Python 新机&#xff0c;若系统没有预装Python&#xff0c;则安装需要版本的Python 点击查询Python官网下载 安装Homebrew 一…

实战——Mac M2 安装mat工具

线上环境出现内存飙升的情况&#xff0c;需要工具定位问题发生点就需要用到mat工具了&#xff0c;之前都是在intel芯片环境上安装的&#xff0c;现在换了m2芯片&#xff0c;导致出现了问题&#xff0c;经过一系列调研都解决了&#xff0c;特此记录下&#xff0c;以备后查 开发…

基于开源的JAVA mongodb jdbc 驱动 使用教程

基于开源的JAVA mongodb jdbc 驱动 使用教程介绍 介绍 本文介绍一款开源的基于JAVA的 Mongodb JDBC 驱动使用教程 开源地址 https://gitee.com/bgong/jdbc-mongodb-driver功能价值 与mybaits融合&#xff1a;复用mybatis的功能特性&#xff0c;如:缓存,if动态判断标签等特…

LSTM和GRU的介绍以及Pytorch源码解析

介绍一下LSTM模型的结构以及源码&#xff0c;用作自己复习的材料。 LSTM模型所对应的源码在&#xff1a;\PyTorch\Lib\site-packages\torch\nn\modules\RNN.py文件中。 上次上一篇文章介绍了RNN序列模型&#xff0c;但是RNN模型存在比较严重的梯度爆炸和梯度消失问题。 本文…

HPM6750系列--第七篇 Visual Studio Code使用openocd调试查看外设信息

一、目的 在《HPM6750系列--第四篇 搭建Visual Studio Code开发调试环境》我们已经手把手指导大家如何在visual studio code中进行开发&#xff0c;包括编译调试等步骤以及相关配置文件。 但是在实际调试时发现找不到芯片寄存器实时显示的窗口&#xff0c;本篇主要讲解如何实现…

【Linux】信号--信号初识/信号的产生方式/信号的保存

文章目录 一、信号初步理解1.生活角度的信号2.技术应用角度的信号 二、信号的产生方式1.通过终端按键产生信号2.调用系统函数向进程发信号3.硬件异常产生信号4.由软件条件产生信号5.进程退出时的核心转储问题 三、信号的保存1.信号其他相关常见概念2.信号在内核中的表示3.sigse…

《PCL多线程加速处理》-配准-icp

《PCL多线程加速处理》-配准-icp 一、效果展示二、具体实现三、代码一、效果展示 数据越大,速度提升效果越快 1、48万点 2、十万点 3、三万点 4、9000点 配准数据 二、具体实现