10分钟极速掌握!SpringBoot+Vue3整合SSE实现实时消息推送

10分钟极速掌握!SpringBoot+Vue3整合SSE实现实时消息推送

一、引言

在当今互联网应用飞速发展的时代,实时消息推送已然成为提升用户体验的关键要素。无论是社交平台的新消息提醒,让你能第一时间与好友互动;还是电商系统的订单状态更新,使你随时掌握购物动态;亦或是在线协作工具的实时通知,助力团队高效沟通协作,实时消息推送都发挥着不可或缺的作用。它就像一座无形的桥梁,紧密连接着用户与应用,为用户带来即时、流畅的交互体验,让信息传递更加高效、便捷 ,极大地增强了用户粘性。

在众多实现实时消息推送的技术方案中,Server-Sent Events(SSE)以其独特的优势脱颖而出,尤其是在结合 Spring Boot 与 Vue3 进行开发时,能碰撞出令人惊喜的火花。Spring Boot 作为一款强大的 Java 开发框架,凭借其自动配置、起步依赖等特性,大大简化了开发流程,提高了开发效率,让后端开发变得轻松愉悦;Vue3 则是前端领域的佼佼者,拥有出色的响应式原理、Composition API 等,为构建交互性强、用户体验好的前端界面提供了有力支持。

今天,就让我们一起深入探索如何利用 Spring Boot 与 Vue3 整合 SSE,快速实现高效的实时消息推送功能,开启一段充满挑战与惊喜的技术之旅!

二、技术介绍

(一)Spring Boot

Spring Boot 在 Java 开发领域堪称中流砥柱,是一款基于 Spring 框架的开源 Java 框架 ,为 Java 开发者们带来了前所未有的便利与高效。它的出现,犹如一阵春风,吹散了传统 Spring 开发中繁琐配置的阴霾,让开发变得轻松愉悦。

Spring Boot 具有众多令人瞩目的特点和优势。其 "约定优于配置" 的理念,就像一位贴心的向导,为开发者指明方向,减少了大量繁琐的 XML 配置工作,让开发者可以专注于业务逻辑的实现,极大地提高了开发效率。以前,在配置一个简单的 Web 项目时,可能需要编写大量的 XML 文件来配置各种组件,而现在使用 Spring Boot,只需要简单的注解和少量的配置,就能快速搭建起一个功能完备的 Web 项目。

它还内置了诸如 Tomcat、Jetty、Undertow 等服务器,开发者无需再为外部容器的配置和部署而烦恼,直接将应用打包成可执行 JAR 文件即可独立运行,真正实现了 "一键部署"。在开发一个小型的后端服务时,使用 Spring Boot,我们可以快速构建项目,并通过内置的服务器轻松运行,大大缩短了项目的上线周期。

Spring Boot 提供的一系列 "Starter" POM 依赖,如同一个个功能强大的工具包,方便开发者引入所需的功能模块,并且会自动管理依赖版本,避免了版本冲突的问题。当我们需要在项目中集成数据库访问功能时,只需要引入spring-boot-starter-data-jpa依赖,Spring Boot 就会自动为我们配置好相关的数据库连接、事务管理等功能,让我们可以专注于数据访问层的代码编写。

(二)Vue 3

Vue 3 在前端开发的舞台上熠熠生辉,是构建现代 Web 应用界面的得力助手。它广泛应用于单页应用(SPA)开发,为用户带来流畅、高效的交互体验;在移动端开发中,也凭借其轻量级和高性能的特点,展现出强大的适应性;与后端结合进行全栈开发时,更是能与各种后端技术无缝对接,为开发者打造出完整的技术解决方案 。

Vue 3 引入了 Composition API,这一创新特性为前端开发带来了全新的思路和方法。它以函数为基础,让开发者可以将相关的逻辑代码聚合在一个函数中,使代码的可读性和可维护性得到了极大的提升。在开发一个复杂的表单组件时,使用 Vue 2 的选项式 API,数据定义、方法定义以及生命周期钩子函数等可能会分散在不同的选项中,导致代码的逻辑连贯性较差。而在 Vue 3 中,利用 Composition API 的 setup 函数,我们可以将相关的响应式数据、计算属性、方法以及生命周期钩子等都集中在一个地方定义,代码结构更加清晰,后续维护和扩展也更加方便。

Vue 3 重写了 DOM - diff 算法,在对比新旧虚拟 DOM 树时,能够精准地识别并跳过静态节点,仅对动态节点进行细致的比较和更新,大大减少了不必要的计算量和操作次数,提升了渲染性能。在一个包含大量静态文本和少量动态数据展示的页面中,Vue 2 需要对整个 DOM 树进行全面的遍历和比较,而 Vue 3 则会直接跳过静态文本部分,只关注动态数据相关的节点,更新渲染速度大幅提升,经实际测试,在某些场景下,更新渲染速度比 Vue 2 快 133%。

Vue 3 还利用 ES6 的 Proxy 实现了更高效的数据劫持,相较于 Vue2 使用的 Object.defineProperty,Proxy 可以直接代理整个对象,在处理多层嵌套的对象时,无需递归遍历每个属性,性能开销更小,内存占用显著降低,经测试,内存使用相比 Vue 2 减少了 54%。

(三)SSE

SSE,即 Server-Sent Events,是一种让服务器能够向客户端推送实时消息的技术 。它就像一座单向的信息桥梁,建立在 HTTP 协议之上,客户端通过 EventSource 与服务端建立一条长期的单向连接,服务端可以持续不断地将文本事件推送给客户端。

SSE 最大的特点就是单向通信,这种特性使得它在实时消息推送场景中具有独特的优势。在实时日志监控面板中,服务器可以将最新的日志信息源源不断地推送给客户端,让运维人员能够实时了解系统的运行状态;在实时通知和活动推送中,如消息提醒、系统广播等,SSE 能够快速将信息送达用户的客户端,确保用户不会错过任何重要消息;在数据流场景,比如股票行情的只读流中,SSE 可以实时将股票价格的变化等信息推送给投资者,帮助他们及时做出决策 。而且,SSE 基于普通 HTTP 协议,使用服务器连续写入并 flush 的方式发送数据,从 HTTP1.1 起就开始支持,具有广泛的兼容性和较低的使用门槛。

三、环境搭建

(一)后端 Spring Boot 环境

在后端,我们使用 Spring Initializr 来快速创建 Spring Boot 项目,这就像是在搭建一座房子时,有一个现成的房屋框架,让我们可以轻松地开始构建。

首先,打开 Spring Initializr 官网(start.spring.io/ ),这是一个非常便捷的在线工具,就像一个魔法工厂,能帮我们快速生成项目的初始结构。在这个页面中,我们可以根据自己的需求进行各种配置。

在 "Project Metadata" 部分,填写项目的基本信息。"Group" 相当于项目的分组标识,比如 "com.example",它就像是给项目划分了一个归属类别;"Artifact" 则是项目的唯一标识符,例如 "sse - demo",用于在项目中唯一确定这个项目。"Name" 是项目的名称,一般和 "Artifact" 保持一致,方便我们识别和管理项目;"Description" 可以简单描述一下项目的功能和用途,帮助我们和团队成员更好地理解项目;"Package name" 是项目的包名,它与 "Group" 和 "Artifact" 相关,共同确定了项目中 Java 类的命名空间,就像一个地址,让我们能准确找到每个类的位置。选择合适的 "Spring Boot" 版本,这里我们选择最新的稳定版本,以获取最新的功能和性能优化。

接着,在 "Dependencies" 部分,添加我们项目所需的依赖。点击 "Add Dependencies" 按钮,搜索并添加 "Spring Web" 依赖,它就像一座桥梁,让我们的项目能够接收网络请求,实现与外界的通信;添加 "Spring Data JPA" 依赖,用于方便地进行数据库操作,就像一个得力助手,帮我们高效地管理数据;添加 "MySQL Driver" 依赖,这是连接 MySQL 数据库的必备工具,就像一把钥匙,打开项目与数据库之间的通道;还要添加 "Servlet API" 依赖,它为 Java Web 应用提供了核心的接口和类,是构建 Web 应用的基础。

完成所有配置后,点击 "Generate" 按钮,Spring Initializr 会根据我们的设置生成一个压缩包。下载并解压这个压缩包,然后将项目导入到我们喜欢的集成开发环境(IDE)中,比如 IntelliJ IDEA 或 Eclipse,就像把房子的框架搬进了建筑场地,接下来就可以在这个框架上进行进一步的建设和装修了。

(二)前端 Vue 3 环境

在前端,我们借助 Vue CLI 来搭建 Vue 3 项目,它就像是一个专业的建筑工具,帮助我们快速搭建起前端项目的架构。

首先,确保我们的计算机上已经安装了 Node.js,这是运行 Vue 项目的基础环境,就像建造房子需要先有一块坚实的土地。可以从 Node.js 官方网站(nodejs.org/ )下载并安装最新版本。安装完成后,打开命令行工具,输入 "node -v" 和 "npm -v" 命令,检查 Node.js 和 npm(Node Package Manager,用于管理项目依赖)的版本,确保它们都安装成功并且版本符合要求。

然后,通过以下命令全局安装 Vue CLI:

bash 复制代码
npm install -g @vue/cli

这一步就像是把 Vue CLI 这个建筑工具安装到我们的开发环境中,让我们随时可以使用它来创建项目。安装完成后,输入 "vue --version" 命令,检查 Vue CLI 的版本,确认安装是否成功。

接下来,使用 Vue CLI 创建一个新的 Vue 3 项目。在命令行中执行以下命令:

bash 复制代码
vue create my - vue3 - sse

这里的 "my - vue3 - sse" 是我们项目的名称,可以根据自己的喜好进行修改,就像给房子取一个独特的名字。执行命令后,Vue CLI 会提示我们选择一个预设。我们可以选择默认的预设(babel 和 eslint),也可以手动选择特性。为了使用 Vue 3,我们需要手动选择特性,并确保选择了 Vue 3 版本。在手动选择特性时,确保勾选了 "Vue 3.x (Preview)" 选项,同时根据项目需求,还可以勾选 "Router" 用于路由管理,就像在房子里规划不同房间之间的通道;勾选 "Vuex" 用于状态管理,方便管理项目中的数据状态,就像一个智能管家,帮我们整理和管理数据;勾选 "CSS Pre - processors" 选择喜欢的 CSS 预处理器,如 Sass 或 Less,让我们可以更高效地编写样式。

完成选择后,Vue CLI 会开始创建项目,并自动安装所需的依赖。这就像是建筑工人按照我们的要求开始搭建房子,并准备好各种建筑材料。等待安装完成后,进入项目目录:

bash 复制代码
cd my - vue3 - sse

然后,安装项目运行所需的依赖,执行以下命令:

bash 复制代码
npm install

这一步会根据项目的 "package.json" 文件,安装所有项目依赖的包,确保项目能够正常运行,就像把房子里的各种家具和设备都安装好,让房子可以正常居住。

四、后端实现

(一)创建 SseController

在后端,我们需要创建一个控制器类SseController,用于处理 SSE 相关的请求。这个类就像是一个交通枢纽,负责接收客户端的请求,并将处理后的结果发送回客户端。

SseController中,我们定义了一个subscribe方法,用于处理客户端的订阅请求。当客户端发起订阅请求时,这个方法就会被调用,就像交通枢纽接收到一辆车的进站请求一样。代码如下:

java 复制代码
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;

import java.util.HashMap;
import java.util.Map;

@RestController
public class SseController {
    // 使用ConcurrentHashMap来存储SseEmitter,保证线程安全
    private final Map<String, SseEmitter> emitterMap = new HashMap<>();

    @GetMapping(value = "/subscribe", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
    public SseEmitter subscribe() {
        // 创建SseEmitter实例,设置超时时间为60秒(60000毫秒)
        SseEmitter emitter = new SseEmitter(60000L);
        // 生成唯一的客户端ID,这里可以使用UUID等方式
        String clientId = "client-" + System.currentTimeMillis();
        emitterMap.put(clientId, emitter);

        // 连接完成时的回调函数,用于在连接正常关闭时清理资源
        emitter.onCompletion(() -> emitterMap.remove(clientId));
        // 连接超时时的回调函数,用于在连接超时时清理资源
        emitter.onTimeout(() -> emitterMap.remove(clientId));

        return emitter;
    }
}

在这段代码中,首先创建了一个SseEmitter实例,并设置了超时时间为 60 秒。超时时间就像是一个倒计时器,如果在这个时间内客户端没有响应,连接就会自动关闭。接着,生成一个唯一的客户端 ID,并将SseEmitter实例存储到emitterMap中,以便后续管理。然后,通过onCompletiononTimeout方法,分别设置了连接完成和超时时的回调函数,用于在相应情况下清理emitterMap中的资源,就像在车辆出站后清理站台一样。

(二)SseEmitter 详解

SseEmitter是 Spring 提供的用于处理 Server-Sent Events 的核心类,它就像是一座桥梁,连接着服务器和客户端,负责在两者之间传递实时消息。

SseEmitter的创建方式有两种:一种是使用默认构造函数new SseEmitter(),此时会使用默认的超时时间(30 秒);另一种是使用带参数的构造函数new SseEmitter(Long timeout),可以自定义超时时间,以满足不同的业务需求。在上述代码中,我们使用了new SseEmitter(60000L),将超时时间设置为 60 秒,这样可以确保在网络状况不佳等情况下,连接也能保持足够长的时间,保证消息的稳定传输。

SseEmitter提供了一系列方法来实现消息的发送和连接的管理。其中,send(Object object)方法用于向客户端发送消息,发送的消息可以是任何类型的对象,它会将数据直接发送到客户端,并在响应体中流式返回;send(SseEmitter.SseEventBuilder event)方法则以事件构建器的形式发送一条消息,通过SseEventBuilder可以自定义事件的各个属性,如iddataname等,让消息的传递更加灵活和可控;complete()方法表示 SSE 流完成并关闭连接,就像桥梁的使命完成后关闭通道一样;completeWithError(Throwable ex)方法用于在发生错误时关闭连接,并以错误的形式告知客户端,以便客户端能够及时做出相应的处理。

SseControllersubscribe方法中,我们使用emitter.onCompletion(() -> emitterMap.remove(clientId))emitter.onTimeout(() -> emitterMap.remove(clientId))来设置连接完成和超时时的回调函数。当连接正常关闭或超时时,会触发这些回调函数,将对应的SseEmitteremitterMap中移除,避免资源的浪费和内存泄漏,确保系统的稳定运行。

(三)消息发送与管理

为了实现消息的发送,我们在SseController中添加一个sendMessage方法,用于向指定的客户端发送消息。代码如下:

java 复制代码
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;

@RestController
public class SseController {
    // 省略之前的代码

    @PostMapping("/sendMessage")
    public void sendMessage(@RequestParam String clientId, @RequestParam String message) {
        SseEmitter emitter = emitterMap.get(clientId);
        if (emitter != null) {
            try {
                // 使用SseEventBuilder构建消息并发送
                emitter.send(SseEmitter.event().data(message).name("message"));
            } catch (Exception e) {
                emitter.completeWithError(e);
                emitterMap.remove(clientId);
            }
        }
    }
}

sendMessage方法中,首先根据clientIdemitterMap中获取对应的SseEmitter实例,就像在仓库中找到对应的货物一样。如果找到SseEmitter,则使用emitter.send(SseEmitter.event().data(message).name("message"))方法向客户端发送消息。这里使用SseEventBuilder构建了一个消息,设置了消息的数据为message,事件名称为"message",这样客户端可以根据事件名称来识别和处理不同类型的消息。如果在发送过程中发生异常,会调用emitter.completeWithError(e)方法关闭连接并告知客户端错误信息,同时将该SseEmitteremitterMap中移除,以保证系统的健壮性。

随着客户端连接的不断增加,管理这些连接变得尤为重要。我们需要确保无效的连接能够及时被清理,以释放资源,提高系统的性能。在上述代码中,通过onCompletiononTimeout回调函数,在连接完成或超时时,将对应的SseEmitteremitterMap中移除,实现了对无效连接的清理。还可以定期检查emitterMap中连接的状态,对于长时间没有活动的连接,主动关闭并移除,就像定期清理仓库中的过期货物一样,确保系统始终保持高效运行。

五、前端实现

(一)数据类型定义

在前端项目中,为了确保数据的准确处理和类型安全,我们需要定义相关的数据类型。这里我们定义了News类型来表示新闻数据,以及ErrorType类型来处理可能出现的错误信息。代码如下:

typescript 复制代码
// 定义新闻数据类型
export type News = {
    title: string;
    content: string;
};

// 定义错误类型
export type ErrorType = {
    message: string;
};

News类型包含title(新闻标题)和content(新闻内容)两个属性,这就像给新闻数据搭建了一个标准的框架,确保在处理新闻相关信息时,数据的结构是统一和规范的。ErrorType类型则包含message属性,用于存储错误信息,方便我们在出现问题时,能够清晰地了解错误的具体内容,就像一个错误提示牌,为我们指引解决问题的方向。

(二)SSE 服务类封装

为了更好地管理 SSE 连接,我们将其封装成一个服务类SseService。这个类就像是一个专业的管家,负责处理 SSE 连接的各种事务。

首先,引入EventSource,它是浏览器提供的用于处理 SSE 连接的原生对象,就像一把打开 SSE 连接大门的钥匙。然后,在SseService类中,定义source属性来保存EventSource实例,以及callbacks对象来存储不同事件的回调函数,就像一个仓库,存放着各种处理事件的工具。代码如下:

typescript 复制代码
import { News, ErrorType } from './types';

export class SseService {
    private source: EventSource | null = null;
    private callbacks: {
        onMessage: ((news: News) => void)[];
        onError: ((error: ErrorType) => void)[];
    } = {
        onMessage: [],
        onError: []
    };

    constructor() {
        this.connect();
    }

    private connect() {
        // 检查浏览器是否支持EventSource
        if (typeof window === 'undefined' ||!('EventSource' in window)) {
            console.error('当前浏览器不支持Server - Sent Events');
            return;
        }

        // 创建EventSource实例,连接到后端的SSE接口
        this.source = new EventSource('/subscribe');

        // 监听消息事件,当接收到服务器推送的消息时触发
        this.source.onmessage = (event) => {
            const news: News = JSON.parse(event.data);
            this.callbacks.onMessage.forEach(callback => callback(news));
        };

        // 监听错误事件,当连接发生错误时触发
        this.source.onerror = (error) => {
            const errorData: ErrorType = { message: error.message };
            this.callbacks.onError.forEach(callback => callback(errorData));
        };
    }

    // 添加消息回调函数
    public addOnMessageCallback(callback: (news: News) => void) {
        this.callbacks.onMessage.push(callback);
    }

    // 添加错误回调函数
    public addOnErrorCallback(callback: (error: ErrorType) => void) {
        this.callbacks.onError.push(callback);
    }

    // 关闭SSE连接
    public close() {
        if (this.source) {
            this.source.close();
            this.source = null;
        }
    }
}

connect方法中,首先检查浏览器是否支持EventSource,如果不支持则输出错误信息并返回。然后,创建EventSource实例,连接到后端的/subscribe接口,这就像是在客户端和服务器之间搭建了一条信息传输的通道。接着,通过onmessageonerror方法,分别设置消息接收和错误处理的回调函数,当接收到服务器推送的消息或连接发生错误时,会调用相应的回调函数进行处理。

addOnMessageCallbackaddOnErrorCallback方法用于添加消息和错误的回调函数,就像在管家的工具仓库中添加新的工具,方便在不同场景下使用。close方法则用于关闭 SSE 连接,当不再需要接收服务器推送的消息时,可以调用这个方法关闭通道,释放资源。

(三)组件中使用 SSE

在 Vue 组件中,我们可以轻松地引入并使用SseService来实现消息的接收和处理。首先,在组件中创建SseService的实例,就像请来了一位管家来管理 SSE 事务。然后,通过addOnMessageCallbackaddOnErrorCallback方法,添加消息和错误的回调函数,用于处理接收到的消息和可能出现的错误。代码如下:

html 复制代码
<template>
    <div>
        <h1>实时新闻推送</h1>
        <ul>
            <li v-for="(news, index) in newsList" :key="index">
                <strong>{{ news.title }}</strong>:{{ news.content }}
            </li>
        </ul>
        <div v-if="error" class="error">{{ error.message }}</div>
    </div>
</template>

<script setup lang="ts">
import { ref } from 'vue';
import { SseService } from './SseService';
import { News, ErrorType } from './types';

// 创建SseService实例
const sseService = new SseService();

// 存储接收到的新闻列表
const newsList = ref<News[]>([]);
// 存储错误信息
const error = ref<ErrorType | null>(null);

// 添加消息回调函数,将接收到的新闻添加到newsList中
sseService.addOnMessageCallback((news) => {
    newsList.value.push(news);
});

// 添加错误回调函数,将错误信息存储到error中
sseService.addOnErrorCallback((errorData) => {
    error.value = errorData;
});

// 在组件卸载时关闭SSE连接
onUnmounted(() => {
    sseService.close();
});
</script>

<style scoped>
.error {
    color: red;
}
</style>

在上述代码中,通过ref创建了newsListerror响应式数据,分别用于存储接收到的新闻列表和错误信息。在addOnMessageCallback回调函数中,将接收到的新闻添加到newsList中,这样当有新的新闻推送过来时,页面会自动更新显示。在addOnErrorCallback回调函数中,将错误信息存储到error中,并在页面上通过v - if指令判断是否显示错误信息。最后,在组件卸载时,通过onUnmounted钩子函数调用sseService.close()方法关闭 SSE 连接,避免资源浪费和潜在的问题。

六、运行与测试

(一)启动项目

完成前后端的代码编写后,就可以启动项目进行测试了。

首先,启动后端 Spring Boot 项目。如果使用的是 IntelliJ IDEA,在项目中找到主应用类(通常是带有@SpringBootApplication注解的类),点击运行按钮即可启动。或者在命令行中进入项目根目录,执行以下命令:

bash 复制代码
mvn spring-boot:run

这就像是点燃了后端服务的引擎,让后端程序开始运行,等待接收客户端的请求。

接着,启动前端 Vue 3 项目。打开命令行工具,进入前端项目的根目录,执行以下命令:

bash 复制代码
npm run serve

执行该命令后,Vue CLI 会启动开发服务器,并自动打开浏览器,访问http://localhost:8080(默认端口,可在vue.config.js中修改),就可以看到前端页面了,这就像是打开了与用户交互的大门,让用户可以通过页面与后端服务进行交互。

(二)测试实时消息推送

在浏览器中打开前端页面后,就可以开始测试实时消息推送功能了。当后端有新的消息推送时,前端页面会实时显示出来。

可以通过调用后端的/sendMessage接口来发送测试消息。使用 Postman 等工具,向/sendMessage接口发送 POST 请求,设置请求参数clientId为之前订阅时生成的客户端 ID,message为要发送的消息内容。点击发送按钮后,如果一切正常,前端页面应该能立即收到并显示这条消息,就像在一个即时通讯工具中发送消息,对方能马上收到一样。

为了验证测试结果,在前端页面的开发者工具中,查看网络请求和响应,确认 SSE 连接是否正常建立,以及消息是否正确接收。在控制台中,查看是否有错误信息输出,确保整个过程没有出现异常。如果消息能够正常显示,并且没有错误信息,那就说明我们的实时消息推送功能已经成功实现了,这就像是完成了一场精彩的演出,所有的演员和道具都配合默契,达到了预期的效果。

相关推荐
刀法如飞1 小时前
AI编程时代,为什么35岁以上程序员会更吃香?
人工智能·后端·ai编程
小码哥_常1 小时前
Spring Boot 遇上 HMAC-SHA256,API 安全大升级!
后端
大黄说说3 小时前
深入 Go 语言 GMP 调度模型:高并发的秘密武器
后端
云原生指北3 小时前
Omnipub E2E 测试文章 - 自动化验证
后端
IT_陈寒4 小时前
SpringBoot自动配置揭秘:5个让开发效率翻倍的隐藏技巧
前端·人工智能·后端
添尹4 小时前
Go语言基础之数组
后端·golang
luom01026 小时前
SpringBoot - Cookie & Session 用户登录及登录状态保持功能实现
java·spring boot·后端
黄俊懿6 小时前
【架构师从入门到进阶】第二章:系统衡量指标——第一节:伸缩性、扩展性、安全性
分布式·后端·中间件·架构·系统架构·架构设计
希望永不加班6 小时前
SpringBoot 核心配置文件:application.yml 与 application.properties
java·spring boot·后端·spring