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

构建实时消息应用:Spring Boot + Vue 与 WebSocket 的有机融合

引言

        在现代Web应用中,实时双向通信已成为提升用户体验的关键。无论是直播弹幕、在线客服、协同编辑还是实时数据大屏,都要求后端能主动、即时地将数据推送给前端。传统的HTTP请求-响应模式(如轮询)难以高效地满足这一需求。

        本文将详细讲解如何整合 Spring Boot(后端)、Vue(前端)并通过 Spring WebSocket + STOMP + SockJS 这一强大组合,构建一个高效、可靠的双向通信机制。最后,我们还会介绍如何用 Nginx 来部署前后端分离的项目。

一、技术选型:为什么是它们?

  1. WebSocket: HTML5提供的一种在单个TCP连接上进行全双工通信的协议,真正实现了低延迟的双向数据交换。

  2. STOMP (Simple Text Oriented Messaging Protocol): 一种简单的基于帧的协议,定义了消息的格式和语义。它位于WebSocket之上,为我们提供了一个类似于消息队列(如RabbitMQ)的发布-订阅模式,使得我们可以像使用@MessageMapping注解那样处理消息,极大地简化了开发。

  3. SockJS: 一个JavaScript库,提供了WebSocket的模拟实现。它会在运行时优先尝试使用原生WebSocket,如果浏览器不支持或网络环境受限(如某些代理阻止WS连接),则会自动降级为其他技术(如长轮询),从而保证应用的兼容性和健壮性。

  4. Spring Boot: 提供了极其便捷的WebSocket支持,通过@EnableWebSocketMessageBroker等注解即可快速配置一个功能强大的WebSocket服务器。

  5. Vue: 轻量级、易上手的前端框架,配合stompjssockjs-client库可以轻松连接WebSocket服务。

  6. Nginx: 高性能的HTTP和反向代理服务器,我们将用它来代理前端静态资源(Vue打包后的文件)和后端API/WebSocket请求。

二、实战

我们将通过一个经典的消息收发场景来串联所有技术点。

第一部分:Spring Boot 后端实现
1. 创建项目并引入依赖

引入 Spring Web 和 WebSocket 依赖。

<!-- pom.xml -->
<dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-websocket</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency>
</dependencies>
2. 配置 WebSocket 和 STOMP 代理

创建一个配置类 WebSocketConfig

import org.springframework.context.annotation.Configuration;
import org.springframework.messaging.simp.config.MessageBrokerRegistry;
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 // 1. 启用WebSocket消息代理
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {@Overridepublic void configureMessageBroker(MessageBrokerRegistry config) {// 2. 配置消息代理// 启用一个简单的基于内存的消息代理,将消息定向到以 `/topic` 为前缀的目的地config.enableSimpleBroker("/topic");// 设置应用程序目的地的前缀,所有以 `/app` 开头的消息都会路由到 @MessageMapping 注解的方法config.setApplicationDestinationPrefixes("/app");}@Overridepublic void registerStompEndpoints(StompEndpointRegistry registry) {// 3. 注册一个STOMP端点,客户端将使用它来连接registry.addEndpoint("/ws-chat") // 端点URL.setAllowedOriginPatterns("*") // 允许跨域。生产环境应严格限制为前端域名!.withSockJS(); // 启用SockJS回退选项}
}
  • /topic: 用于发布-订阅模式(一对多)。服务端向所有订阅了/topic/xxx的客户端广播消息。

  • /app: 用于点对点模式。客户端发送消息到/app/xxx,由服务端的@MessageMapping(“xxx”)方法处理。

  • /ws-chat: 这是WebSocket握手的HTTP端点URL。

3. 创建消息处理控制器

创建一个控制器来处理消息。

import org.springframework.messaging.handler.annotation.MessageMapping;
import org.springframework.messaging.handler.annotation.SendTo;
import org.springframework.stereotype.Controller;@Controller
public class ChatController {// 处理所有发送到 `/app/chat` 目的地的消息@MessageMapping("/chat") // 等价于 @RequestMapping@SendTo("/topic/messages") // 将方法的返回值广播给所有订阅了 `/topic/messages` 的客户端public ChatMessage sendMessage(ChatMessage message) {// 这里可以处理消息,比如保存到数据库等System.out.println("Received message: " + message.getContent());return message; // 直接将消息广播出去}
}

消息实体

public class ChatMessage {private String from;private String content;private String timestamp;// 务必提供默认构造函数和getter/setter方法public ChatMessage() {}public ChatMessage(String from, String content, String timestamp) {this.from = from;this.content = content;this.timestamp = timestamp;}// ... getters and setters ...
}

至此,后端服务就完成了!它提供了一个WebSocket端点,能够接收/app/chat的消息,并将其广播到/topic/messages

第二部分:Vue 前端实现
1. 创建Vue项目并安装依赖
npm create vue@latest my-websocket-chat
cd my-websocket-chat
npm install sockjs-client stompjs
2. 创建WebSocket工具类 (src/utils/websocket.js)
import SockJS from 'sockjs-client';
import { Stomp } from '@stomp/stompjs';// 导出连接、断开、发送消息的方法
export const webSocketService = {stompClient: null,isConnected: false,// 连接WebSocketconnect(config) {const { url, onConnect, onError } = config;const socket = new SockJS(url);this.stompClient = Stomp.over(socket);// 禁用调试信息(生产环境)this.stompClient.debug = () => {};this.stompClient.connect({},(frame) => {console.log('Connected: ' + frame);this.isConnected = true;if (onConnect) onConnect(this.stompClient);},(error) => {console.error('Connection error: ', error);this.isConnected = false;if (onError) onError(error);});},// 断开连接disconnect() {if (this.stompClient !== null) {this.stompClient.disconnect();this.isConnected = false;}console.log("Disconnected");},// 订阅主题subscribe(destination, callback) {if (this.stompClient && this.isConnected) {return this.stompClient.subscribe(destination, (message) => {if (callback) callback(JSON.parse(message.body));});}return null;},// 发送消息sendMessage(destination, message) {if (this.stompClient && this.isConnected) {this.stompClient.send(destination, {}, JSON.stringify(message));} else {console.error('WebSocket is not connected. Cannot send message.');}}
};
3. 在Vue组件中使用 (src/App.vue)
<template><div id="app"><h1>Spring Boot + Vue Chat Room</h1><div class="chat-box"><div v-for="(msg, index) in messages" :key="index" class="message"><strong>{{ msg.from }}:</strong> {{ msg.content }} <em>({{ msg.timestamp }})</em></div></div><div class="input-area"><input v-model="currentMessage" @keyup.enter="sendMessage" placeholder="Type a message..." /><button @click="sendMessage" :disabled="!isConnected">Send</button><p>Status: {{ isConnected ? 'Connected' : 'Disconnected' }}</p></div></div>
</template><script>
import { webSocketService } from './utils/websocket.js';export default {name: 'App',data() {return {isConnected: false,currentMessage: '',messages: [] // 存储收到的消息};},mounted() {this.connectWebSocket();},beforeUnmount() {// 组件销毁前断开连接webSocketService.disconnect();},methods: {connectWebSocket() {// 后端WebSocket端点,注意是 http,SockJS会自己处理const serverUrl = 'http://localhost:8080/ws-chat';webSocketService.connect({url: serverUrl,onConnect: (stompClient) => {this.isConnected = true;console.log('WebSocket connected successfully!');// 订阅 `/topic/messages`,接收广播消息webSocketService.subscribe('/topic/messages', (message) => {this.messages.push(message); // 将收到的消息添加到列表});},onError: (error) => {this.isConnected = false;console.error('Could not connect to WebSocket server.', error);}});},sendMessage() {if (this.currentMessage.trim() && this.isConnected) {const chatMessage = {from: 'VueUser', // 这里可以改成用户输入的名字content: this.currentMessage,timestamp: new Date().toLocaleTimeString()};// 发送消息到 `/app/chat`webSocketService.sendMessage('/app/chat', chatMessage);this.currentMessage = ''; // 清空输入框}}}
};
</script><style>
/* 添加一些简单的样式 */
.chat-box {border: 1px solid #ccc;height: 300px;overflow-y: scroll;margin-bottom: 10px;padding: 10px;
}
.message {margin-bottom: 5px;
}
.input-area {display: flex;gap: 10px;
}
input {flex-grow: 1;padding: 5px;
}
</style>
第三部分:Nginx 部署配置

现在,我们有了独立的前端(Vue,通常在8081端口)和后端(Spring Boot,在8080端口)。我们需要Nginx作为反向代理,让用户通过一个统一的域名和端口(通常是80/443)来访问整个应用。

1. 打包前端项目
npm run build

这会生成一个 dist 目录,里面是静态资源文件(HTML, JS, CSS)。

2. 编写Nginx配置文件 (nginx.conf 或 sites-available/your-site)
http {# ... 其他全局配置 ...server {listen 80;server_name your-domain.com; # 你的域名,本地测试可用 localhost# 1. 代理所有静态资源请求到Vue的dist目录location / {root /path/to/your/vue-project/dist; # 替换为你的dist目录绝对路径index index.html;try_files $uri $uri/ /index.html; # 支持Vue Router的history模式}# 2. 代理后端API请求到Spring Boot应用location /api/ {proxy_pass http://localhost:8080/; # 代理到后端服务器proxy_set_header Host $host;proxy_set_header X-Real-IP $remote_addr;proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;proxy_set_header X-Forwarded-Proto $scheme;}# 3. 代理WebSocket连接请求!# 关键:因为WebSocket使用HTTP Upgrade机制,需要特殊配置location /ws-chat/ {proxy_pass http://localhost:8080; # 注意这里没有尾随的 `/`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;proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;proxy_set_header X-Forwarded-Proto $scheme;proxy_read_timeout 86400; # WebSocket连接保持时间可以设长一些}# 也可以代理其他WebSocket端点,比如 /ws-notification/}
}
3. 重启Nginx使配置生效
sudo nginx -s reload
4. 修改前端连接配置

在生产环境中,前端不再直接连接 localhost:8080,而是连接相同的域名(或相对路径)。

// 在 websocket.js 或 App.vue 中修改
// const serverUrl = 'http://localhost:8080/ws-chat'; // 开发环境
const serverUrl = '/ws-chat'; // 生产环境:使用相对路径,Nginx会自动代理

现在,访问 http://your-domain.com,Nginx会:

  • 将 / 请求指向Vue静态页面。

  • 将 /api/xxx 请求转发给后端的Spring Boot应用。

  • 将 /ws-chat 的WebSocket升级请求也转发给后端,从而建立起全双工通信。

第四部分:SimpMessagingTemplate 支持

使用 Spring Boot 的 SimpMessagingTemplate 给客户端发送消息非常方便。使用 convertAndSend(String destination, Object payload) 方法,所有订阅了该目的地(destination)的客户端都会收到消息。

private final SimpMessagingTemplate messagingTemplate;@Autowired // 通过构造器注入
public MessageSendingService(SimpMessagingTemplate messagingTemplate) {this.messagingTemplate = messagingTemplate;
}public void broadcastMessage(String messageContent) {// 构建你的消息对象,这里用一个简单的字符串示例String greeting = "Hello, everyone! " + messageContent;// 发送给所有订阅了 "/topic/greetings" 的客户端messagingTemplate.convertAndSend("/topic/greetings", greeting);
}

前端订阅示例(使用 Stomp.js):

stompClient.subscribe('/topic/greetings', function(message) {console.log('Received broadcast: ' + message.body);// 更新UI...
});


文章转载自:

http://LQeeJr7h.mmzfL.cn
http://ktIpioiI.mmzfL.cn
http://27QPlyWq.mmzfL.cn
http://MhmTLfpg.mmzfL.cn
http://JmO4Hpqw.mmzfL.cn
http://depVfxHX.mmzfL.cn
http://q9XhnGMh.mmzfL.cn
http://WwLXZTRW.mmzfL.cn
http://nlf2mcvX.mmzfL.cn
http://G14zgaEX.mmzfL.cn
http://pSFy7vE3.mmzfL.cn
http://4yRPfUHN.mmzfL.cn
http://sC9hSJ0U.mmzfL.cn
http://f41piK6U.mmzfL.cn
http://2niwl8yu.mmzfL.cn
http://kCFfIP3h.mmzfL.cn
http://gx0rOvfg.mmzfL.cn
http://9hOiX72n.mmzfL.cn
http://NJ7shLfz.mmzfL.cn
http://1kaUxK6j.mmzfL.cn
http://OwYieFOy.mmzfL.cn
http://KCFB6iHC.mmzfL.cn
http://nqxu2SJv.mmzfL.cn
http://HeDDc3vp.mmzfL.cn
http://oBL1EVj2.mmzfL.cn
http://XuNuOrgu.mmzfL.cn
http://CguI6I9v.mmzfL.cn
http://cQsgqzg7.mmzfL.cn
http://zNRu7vIO.mmzfL.cn
http://tIEqV7vM.mmzfL.cn
http://www.dtcms.com/a/377936.html

相关文章:

  • Aosp13 手机sim卡信号格显示修改
  • 小杰机器学习(five)——PyTorch、Tensor(torch库)、Tensor的基本属性、连续性、张量、随机树种子(seed)。
  • ARM 架构的异常模型(Exception Model)
  • 深度学习——基于 PyTorch 的 CBOW 模型实现自然语言处理
  • Spring Cloud Alibaba快速入门03-OpenFeign进阶用法
  • 【PyTorch】多对象分割
  • npm : 无法加载文件 C:\Program Files\nodejs\npm.ps1,因为在此系统上禁止运行脚
  • NodeJS 8 ,从 0 到 1:npm 包发布与更新全流程指南( 含多场景适配与踩坑总结 )
  • Debian 系统上安装与配置 MediaMTX
  • 【PyTorch训练】准确率计算(代码片段拆解)
  • 【Linux】线程池——详细讲解
  • Linux epoll 机制的核心控制函数——`epoll_ctl`
  • 粒子群优化(PSO)算法详解:从鸟群行为到强大优化工具
  • 从两分钟到毫秒级:一次真实看板接口性能优化实战(已上线)
  • Java入门级教程17——利用Java SPI机制制作验证码、利用Java RMI机制实现分布式登录验证系统
  • 【Redis】常用数据结构之List篇:从常用命令到典型使用场景
  • 掌握单元测试的利器:JUnit 注解从入门到精通
  • 【Vue2手录05】响应式原理与双向绑定 v-model
  • spring项目部署后为什么会生成 logback-spring.xml文件
  • Java 日期字符串万能解析工具类(支持多种日期格式智能转换)
  • 在VS2022的WPF仿真,为什么在XAML实时预览点击 ce.xaml页面控件,却不会自动跳转到具体代码,这样不方便我修改代码,
  • 【数组】区间和
  • Qt 基础编程核心知识点全解析:含 Hello World 实现、对象树、坐标系及开发工具使用
  • 解决推理能力瓶颈,用因果推理提升LLM智能决策
  • 【大前端】常用 Android 工具类整理
  • Gradle Task的理解和实战使用
  • 强大的鸿蒙HarmonyOS网络调试工具PageSpy 介绍及使用
  • C++/QT 1
  • 软件测试用例详解
  • 【ROS2】基础概念-进阶篇