vue.js

关注公众号 jb51net

关闭
首页 > 网络编程 > JavaScript > javascript类库 > vue.js > Vue3 SpringBoot高效Web实时消息推送系统

利用Vue3 + SpringBoot打造高效Web实时消息推送系统

作者:为java加瓦

在Vue中实现消息推送,可以通过集成WebSocket服务来实现实时通信,这篇文章主要介绍了利用Vue3 + SpringBoot打造高效Web实时消息推送系统的相关资料,文中通过代码介绍的非常详细,需要的朋友可以参考下

好的,作为一名经历过无数项目实战的Java全栈工程师,今天我想和你深入聊聊WebSocket——这个在现代Web应用中几乎不可或缺的实时通信技术。我会通过一个真实的“订单实时通知”项目,带你从理论到实践,再到大厂级别的优化,彻底搞懂它。

从“不停打电话”到“拉起微信群”:通信模式的演进

想象一下这个场景:你在等一份重要的外卖,但不知道骑手到哪里了。传统的方式(HTTP轮询)就像你每隔30秒给骑手打一次电话:“到了吗?”“没呢。”“到了吗?”“还在路上。”…… 效率低下,且令人烦躁。

这就是轮询(Polling)的困境:

而WebSocket,则像是你和骑手建立了一个实时对讲频道。一旦连接建立,双方可以随时主动发送消息。骑手可以主动告诉你:“已取餐”、“到小区门口了”、“电梯上楼中”。这才是真正的高效实时通信。

哪些场景必须用它?想想这些:

实战:构建一个订单实时通知系统

理论说再多,不如一行代码。我们来搭建一个核心架构:Spring Boot 2.x后端 + Vue3前端,实现新订单的实时推送。

一、后端(Spring Boot):建立消息中枢

首先,引入核心依赖,Spring Boot已经为我们封装好了一切。

xml

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-websocket</artifactId>
</dependency>

核心配置:打通WebSocket通道

这里的配置决定了客户端如何连接,以及消息如何路由。

java

@Configuration
@EnableWebSocketMessageBroker // 开启WebSocket消息代理
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {

    @Override
    public void registerStompEndpoints(StompEndpointRegistry registry) {
        // 客户端将通过这个URL来连接WebSocket端点
        registry.addEndpoint("/ws-notification")
                .setAllowedOriginPatterns("*") // 注意:生产环境应指定具体域名
                .withSockJS(); // 启用SockJS后备选项,增强兼容性
    }

    @Override
    public void configureMessageBroker(MessageBrokerRegistry registry) {
        // 启用一个简单的基于内存的消息代理,负责向客户端推送消息
        registry.enableSimpleBroker("/topic"); // 客户端订阅以/topic开头的地址
        // 定义应用自身的目的地前缀,客户端发送消息到服务端时使用
        registry.setApplicationDestinationPrefixes("/app");
    }
}

业务服务:精准推送消息

配置好通道,下一步就是如何发送消息了。

java

@Service
public class NotificationService {

    @Autowired
    private SimpMessagingTemplate messagingTemplate; // Spring提供的消息发送模板

    /**
     * 广播新订单给所有订阅的客户端(例如大屏监控)
     */
    public void broadcastNewOrder(String orderNumber) {
        // 将消息发送到 /topic/new-orders,所有订阅了该目的地的客户端都会收到
        messagingTemplate.convertAndSend("/topic/new-orders", "新订单: " + orderNumber);
    }

    /**
     * 私信特定用户(例如客服端)
     */
    public void notifyUser(String userId, String message) {
        // 发送到用户专属的队列,Spring会自动将其路由为 /user/{userId}/topic/notification
        messagingTemplate.convertAndSendToUser(userId, "/topic/notification", message);
    }
}

在Controller中,当有新订单创建时,只需注入NotificationService并调用broadcastNewOrder方法即可。

二、前端(Vue3):建立连接并监听消息

前端需要使用STOMP客户端库来与后端通信。

bash

npm install sockjs-client stompjs

封装WebSocket工具类

为了更好的可维护性和用户体验(如断线重连),我们将其封装成独立模块。

javascript

// utils/websocket.js
import SockJS from 'sockjs-client';
import Stomp from 'stompjs';

let stompClient = null;
let reconnectTimer = null;
const RETRY_INTERVAL = 5000; // 重连间隔5秒

/**
 * 连接WebSocket
 * @param {string} topic 订阅的消息主题
 * @param {Function} onMessageCallback 收到消息时的回调函数
 * @param {Function} refreshCallback 连接成功后的回调(如刷新列表)
 */
export function connectWebSocket(topic, onMessageCallback, refreshCallback) {
  const socket = new SockJS(import.meta.env.VITE_WS_ENDPOINT || 'http://localhost:8080/ws-notification');
  stompClient = Stomp.over(socket);

  stompClient.connect({},
    // 连接成功回调
    () => {
      console.log('WebSocket连接成功');
      if (reconnectTimer) clearTimeout(reconnectTimer);

      // 订阅指定的主题
      stompClient.subscribe(`/topic/${topic}`, (message) => {
        onMessageCallback(message.body);
        refreshCallback?.(); // 可选:收到消息后触发数据刷新
      });

      refreshCallback?.(); // 连接成功时也刷新一次数据
    },
    // 连接失败回调
    (error) => {
      console.error('WebSocket连接失败,尝试重连...', error);
      scheduleReconnect(topic, onMessageCallback, refreshCallback);
    }
  );
}

// 安排定时重连
function scheduleReconnect(topic, onMessageCallback, refreshCallback) {
  if (reconnectTimer) clearTimeout(reconnectTimer);
  reconnectTimer = setTimeout(() => {
    connectWebSocket(topic, onMessageCallback, refreshCallback);
  }, RETRY_INTERVAL);
}

// 断开连接
export function disconnectWebSocket() {
  if (stompClient) {
    stompClient.disconnect();
    console.log('WebSocket连接已断开');
  }
  if (reconnectTimer) clearTimeout(reconnectTimer);
}

在Vue组件中使用

vue

<script setup>
import { onMounted, onBeforeUnmount, ref } from 'vue';
import { ElNotification } from 'element-plus'; // 使用Element Plus的提示组件
import { connectWebSocket, disconnectWebSocket } from '@/utils/websocket';

const orderList = ref([]);

// 收到新订单通知后的处理
const showNewOrderNotification = (message) => {
  ElNotification({
    title: '🚨 新订单提醒',
    type: 'success',
    message: message,
    duration: 0, // 不自动关闭,重要订单提醒
  });
  // 可以在这里播放提示音
};

// 刷新订单列表数据
const refreshOrderList = () => {
  // 这里调用你的API来获取最新订单列表
  console.log('刷新订单列表数据...');
  // fetchOrders().then(data => orderList.value = data);
};

onMounted(() => {
  // 组件挂载时,连接WebSocket并订阅"new-orders"主题
  connectWebSocket('new-orders', showNewOrderNotification, refreshOrderList);
});

onBeforeUnmount(() => {
  // 组件销毁前,断开连接,清理资源
  disconnectWebSocket();
});
</script>

<template>
  <div>
    <!-- 你的订单列表UI -->
    <div v-for="order in orderList" :key="order.id">
      {{ order.number }}
    </div>
  </div>
</template>

三、大厂级别的优化:从“能用”到“好用”

上面的代码已经可以跑了,但要上生产环境,还必须解决以下几个问题:

1. 部署上线:Nginx反向代理配置

前端直接连接后端地址在开发时没问题,但生产环境通常需要通过Nginx。

nginx

server {
    listen 80;
    server_name yourdomain.com;

    location / {
        # 代理你的Vue应用静态资源
        root /usr/share/nginx/html;
        try_files $uri $uri/ /index.html;
    }

    location /ws-notification {
        proxy_pass http://backend-server:8080; # 你的Spring Boot应用地址
        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_read_timeout 3600s; # WebSocket连接超时时间设置长一些
    }

    # 其他API请求也代理到后端
    location /api/ {
        proxy_pass http://backend-server:8080;
    }
}

2. 安全第一:WebSocket连接鉴权

绝不能允许任何人随便连接你的WebSocket服务。

服务端拦截器:

java

@Component
public class AuthChannelInterceptor implements ChannelInterceptor {

    @Override
    public Message<?> preSend(Message<?> message, MessageChannel channel) {
        StompHeaderAccessor accessor = MessageHeaderAccessor.getAccessor(message, StompHeaderAccessor.class);
        
        // 拦截CONNECT命令,进行身份验证
        if (StompCommand.CONNECT.equals(accessor.getCommand())) {
            // 从Header中获取Token
            List<String> authHeaders = accessor.getNativeHeader("Authorization");
            String token = (authHeaders != null && !authHeaders.isEmpty()) ? authHeaders.get(0) : null;

            if (token == null || !TokenUtil.verify(token)) {
                throw new IllegalArgumentException("未经授权的连接请求");
            }

            // 验证通过,将用户信息存入会话,后续可通过@MessageMapping方法中的Principal参数获取
            String username = TokenUtil.extractUsername(token);
            accessor.setUser(new UsernamePasswordAuthenticationToken(username, null, new ArrayList<>()));
        }
        return message;
    }
}

WebSocketConfig中注册这个拦截器:

java

@Override
public void configureClientInboundChannel(ChannelRegistration registration) {
    registration.interceptors(new AuthChannelInterceptor());
}

前端连接时携带Token:

javascript

// 在连接时,将登录后获取的Token放入Header
stompClient.connect(
  { 
    Authorization: `Bearer ${getToken()}` // 例如: "Bearer eyJhbGciOiJIUzI1NiIsInR5..."
  }, 
  // ... 其他回调不变
);

总结:我们构建了一个怎样的系统?

通过这一套组合拳,我们不再仅仅是一个“Demo”,而是打造了一个产品级的实时推送体系:

在实际业务中,你可能还会遇到更多深层次问题,比如分布式环境下Session共享(需引入Redis等外部消息代理替代enableSimpleBroker)、消息可靠性保证(防止推送丢失)、海量连接下的性能调优等。但掌握了以上核心架构与思想,你就已经拿到了进入实时Web应用开发大门的钥匙。希望这篇来自实战的经验总结,能对你的下一个项目有所启发。

到此这篇关于利用Vue3 + SpringBoot打造高效Web实时消息推送系统的文章就介绍到这了,更多相关Vue3 SpringBoot高效Web实时消息推送系统内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

您可能感兴趣的文章:
阅读全文