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 依赖。
创建一个跨域配置,因为前后端的端口不同,会出现请求跨域。
@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 连接和推送消息给客户端。
@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 连接的话,可以这样操作。
@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:
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:
<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