SSE 实践:用 Vue 和 Spring Boot 实现实时数据传输

前言

大家好,我是雪荷。最近我在灵犀 BI 项目中引入了 SSE 技术,以保证图表的实时渲染,当图表渲染完毕服务端推送消息至浏览器端触发重新渲染。

什么是 SSE?

SSE 全称为 Server-Send Events 意思是服务端推送事件。

SSE 相比于 Websocket 它基于 Http 实现无需实现其他协议、更加轻量级、支持自动重连,但只能服务端推送消息给客户端,客户端不能推送消息给服务端。适用于服务端主动推送数据,客户端只需接收数据的场景。以下为 SSE 与 Websocket 的对比:

SSE Websocket
通信 单向通信(服务端到客户端) 双向通信
协议 Http Websocket
自动重连 支持 不支持,要客户端自行支持
数据格式 文本格式,如果要二进制数据得自行编码 默认二进制数据、支持文本格式
浏览器兼容性 大部分支持 主流浏览器支持较好

Vue + Spring Boot 实现一个 SSE demo

后端实现

由于 SSE 基于 http 协议实现,因此我们无需引入第三方 jar 包。首先通过 idea 创建一个 spring boot 项目,记得勾选 spring web 依赖。

创建一个跨域配置,因为前后端的端口不同,会出现请求跨域。

java 复制代码
@Configuration
public class CorsConfig implements WebMvcConfigurer {
​
    @Override
    public void addCorsMappings(CorsRegistry registry) {
        registry.addMapping("/**")
                .allowedOriginPatterns("*")
                .allowedMethods("GET", "POST", "PUT", "DELETE")
                .allowedHeaders("*")
                .allowCredentials(true);
    }
}
复制代码
随后创建一个 SSEcontroller,用来创建 SSE 连接和推送消息给客户端。
java 复制代码
@RestController
@RequestMapping("/sse")
public class SSEController {
​
    private static final Map<Integer, SseEmitter> sseEmitters = new ConcurrentHashMap<>();
​
    @GetMapping("/create")
    public SseEmitter createSSEConnection() {
        // 创建SSE连接
        SseEmitter emitter = new SseEmitter();
        // 将 sseEmitter 赛道 map 中,方便管理多个 sse 连接,比如可以为每个用户创建一个 sse 连接,以用户 id 为 map 的 key
        sseEmitters.put(1, emitter);
        emitter.onCompletion(() -> sseEmitters.remove(1));
        emitter.onTimeout(() -> sseEmitters.remove(1));
        return emitter;
    }
​
    @PostMapping("/send")
    public void sendSSEMessage() {
        // 发送消息
        SseEmitter sseEmitter = sseEmitters.get(1);
        if (sseEmitter != null) {
            for (int i = 0; i < 3; i++) {
                try {
                    sseEmitter.send(SseEmitter.event()
                            .data("hello world") // 发送的数据,是 Object 类型
                            .name("sse-message")); // 定义 SSE 事件的名称,方便前端监听
                } catch (IOException e) {
                    sseEmitters.remove(1);
                }
            }
        }
    }
​
}

在上面的代码中,createSSEConnection 用来创建 sse 连接,注意其请求方法为 get,post 是不能创建的,因为前端发的就是 get 请求。我还创建了一个 sseEmitters 的 Map<Integer, SseEmitter> 来管理 sse 连接,在项目中 map 的 key 可以作为客户端标识,可以是用户 id 或者某些业务参数。当创建完毕后将 sseEmiter 塞到 map 中,当 sse 连接超时或者完成就删掉在 map 中对应的 value。

而 sendSSEMessage 方法就是服务端向客户端推送消息的实例代码,这里我们方便我写了一个请求来触发服务端发消息给客户端,实际是根据具体业务来主动推消息给客户端。比如异步生成图表或者插入数据时,当此动作完毕后可以发消息给前端表明操作成功。那如何发送消息就不用我说了,先从 map 取出 sseEmitter,接着添加事件名和要发送的数据就好了。

如果是前端传送参数来创建 sse 连接的话,可以这样操作。

java 复制代码
    @GetMapping("/create")
    public SseEmitter createSSEConnection(@RequestParam Integer id) {
        // 创建SSE连接
        SseEmitter emitter = new SseEmitter();
        sseEmitters.put(id, emitter);
        emitter.onCompletion(() -> sseEmitters.remove(1));
        emitter.onTimeout(() -> sseEmitters.remove(1));
        return emitter;
    }

前端实现

首先创建一个 vue 项目,引入一些基础依赖,比如 aixos,项目具体结构如下:

myAxios.js:

javascript 复制代码
import axios from 'axios';
​
const myAxios = axios.create({
    baseURL: 'http://localhost:8080',
})
​
myAxios.defaults.withCredentials = true; //设置为true
​
​
export default myAxios;

然后 SSEDemo.vue 就是主要创建 sse 连接和接受服务端发送消息

SSEDemo.vue:

javascript 复制代码
<template>
  <div>
    <h3>接收到的消息:</h3>
    <ul>
      <li v-for="(message, index) in messages" :key="index">{{ message }}</li>
    </ul>
  </div>
  
  <button @click="sendMessage">发送消息</button>
</template>
​
<script>
import {onBeforeUnmount, onMounted, ref} from 'vue';
import myAxios from "@/plugins/myAxios.js";
​
export default {
  setup() {
    const messages = ref([]); // 存储接收到的消息
    let eventSource = null; // 用于保存 EventSource 实例
​
    onMounted(() => {
      // 创建 EventSource 实例,连接到服务器端的 SSE 端点
      eventSource = new EventSource('http://localhost:8080/sse/create');
​
      // 监听特定事件名 'sse-message'
      eventSource.addEventListener('sse-message', (event) => {
        console.log('收到消息:', event.data);
        // 解析消息并保存到 messages 中
        messages.value.push(event.data);
      });
​
      // 监听连接关闭或错误
      eventSource.onerror = (error) => {
        console.error('SSE 连接出错:', error);
        eventSource.close(); // 关闭连接
      };
    });
​
    onBeforeUnmount(() => {
      // 组件销毁时关闭 SSE 连接
      if (eventSource) {
        eventSource.close();
      }
    });
​
    const sendMessage = async () => {
      await myAxios.post('/sse/send');
    }
​
    return {
      messages,
      sendMessage
    };
  },
};
</script>

new EventSource 新建一个事件源,其会向后端发送请求方法为 get 的 http 请求,利用 eventSource.addEventListener 创建一个事件监听器并带上事件名就可以完成事件的监听和消息发送啦。

最后运行前后端项目,打开前端地址会看到有一个 http 显示待处理。

点击发送消息按钮使后端发送消息给前端,前端接收到就会实时显示。

开源项目

厚米匹配

网址:厚米匹配系统

前端仓库:https://github.com/dnwwdwd/homieMatching-fronted

后端仓库:https://github.com/dnwwdwd/homieMatching

灵犀 BI

网址:鱼智能 BI

前端仓库:https://github.com/dnwwdwd/Lingxi-BI-fronted

后端仓库:https://github.com/dnwwdwd/Lingxi-BI

相关推荐
无限大.18 分钟前
基于 HTML5 Canvas 制作一个精美的 2048 小游戏--day 1
前端·html·html5
laopeng30120 分钟前
2.使用Spring Boot&Spring AI快速构建AI应用程序
人工智能·spring boot·spring
爱编程的鱼24 分钟前
HTML5 教程(下)
前端·html·html5
web150854159351 小时前
Node.js npm 安装过程中 EBUSY 错误的分析与解决方案
前端·npm·node.js
涔溪1 小时前
使用 electron-builder 构建一个 Electron 应用程序
前端·javascript·electron
shmily麻瓜小菜鸡1 小时前
el-table中使用el-image图片预览被其他表格遮挡,使用z-index层级设置无效
前端·javascript·vue.js
qq_530245191 小时前
自定义提示确认弹窗-vue
前端·vue.js·typescript
lozhyf1 小时前
基于 Spring Boot、Vue 实现的调问开源问卷系统
vue.js·spring boot·开源
肉三1 小时前
使用 spring boot 2.5.6 版本时缺少 jvm 配置项
jvm·spring boot·后端
远洋录1 小时前
Electron 开发者的 Tauri 2.0 实战指南:安全实践
前端·人工智能·react