SpringBoot / Vue 对SSE的基本使用(简单上手)

'# SpringBoot / Vue 对SSE的基本使用(简单上手)

一、背景与问题

在现代Web开发中,实时通信需求日益增长。传统的HTTP请求-响应模型无法满足实时数据推送需求,而WebSocket虽然能够实现双向通信,但其建立连接的复杂性和跨域限制使得其在某些场景下并不适用。

Server-Sent Events(SSE)作为HTML5引入的服务器向客户端推送数据的机制,提供了轻量级的实时通信方案。它基于HTTP协议,利用长连接保持通信,同时支持事件流格式(EventStream),在实时通知、数据推送等场景中具有独特优势。

本篇文章将深入解析SSE的原理,通过SpringBoot和Vue的完整案例,展示如何在实际开发中使用SSE技术,同时分析其适用场景、性能优化方法和常见问题。


二、基本原理

1. 工作机制

SSE的核心是通过HTTP长连接实现服务器向客户端的单向数据推送。其关键特征包括:

  • 基于HTTP协议:无需额外协议支持,兼容性好
  • 事件流格式:使用text/event-stream MIME类型传输数据
  • 自动重连机制:客户端自动尝试重新连接
  • 消息格式:支持自定义数据字段和事件类型

通信流程如下:

客户端发送请求 → 服务器保持连接 → 客户端接收事件流数据

2. 通信协议

SSE通信数据包格式如下:

event: notify
id: 1
data: {"type": "message", "content": "Hello, SSE!"}
retry: 5000

关键字段说明:

  • event:事件类型(可选)
  • id:事件ID(用于断线重连)
  • data:事件数据(JSON格式)
  • retry:重连间隔(单位:毫秒)

三、环境准备

1. 技术栈

  • 后端:SpringBoot 2.7 + Java 17
  • 前端:Vue 3 + TypeScript
  • 数据库:MySQL 8.0(可选)

2. 依赖配置

SpringBoot项目中添加依赖:

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

Vue项目中安装依赖:

npm install eventsource

四、核心实现

1. SpringBoot服务端实现

1.1 创建SSE接口

@RestController
public class SseController {

    @GetMapping("/sse")
    public SseEmitter sse() {
        // 设置超时时间(默认30秒)
        return new SseEmitter(30_000L);
    }

    @PostMapping("/send")
    public void send(@RequestParam String id, @RequestBody Map<String, Object> data) {
        // 通过id找到对应的SseEmitter发送数据
        sseEmitterMap.get(id).send(SseEmitter.event()
                .name("message")
                .data(data)
                .id(id)
                .retry(5000)
        );
    }
}

关键点:

  • 使用SseEmitter类创建长连接
  • 通过send()方法发送事件数据
  • 支持name字段指定事件类型
  • retry字段设置重连间隔

1.2 管理连接

@Singleton
public class SseEmitterManager {
    private final Map<String, SseEmitter> emitters = new ConcurrentHashMap<>();

    public void add(String id, SseEmitter emitter) {
        emitters.put(id, emitter);
    }

    public void remove(String id) {
        emitters.remove(id);
    }

    public SseEmitter get(String id) {
        return emitters.get(id);
    }
}

2. Vue客户端实现

2.1 基础使用

<template>
  <div>
    <h2>SSE 接收消息</h2>
    <ul>
      <li v-for="(msg, index) in messages" :key="index">{{ msg }}</li>
    </ul>
  </div>
</template>

<script>
import { ref, onMounted } from 'vue'
import { EventSource } from 'eventsource'

export default {
  setup() {
    const messages = ref([])
    
    onMounted(() => {
      const eventSource = new EventSource('http://localhost:8080/sse')
      
      eventSource.onmessage = (event) => {
        messages.value.push(event.data)
      }
      
      eventSource.onerror = (event) => {
        console.error('SSE连接异常:', event)
      }
    })
    
    return { messages }
  }
}
</script>

关键点:

  • 使用EventSource建立连接
  • onmessage处理普通消息
  • onerror处理连接异常
  • 自动重连机制由浏览器实现

2.2 带事件类型处理

<script>
export default {
  setup() {
    const messages = ref([])
    
    onMounted(() => {
      const eventSource = new EventSource('http://localhost:8080/sse')
      
      eventSource.addEventListener('notify', (event) => {
        const data = JSON.parse(event.data)
        messages.value.push(`通知: ${data.content}`)
      })
      
      eventSource.onerror = (event) => {
        console.error('SSE连接异常:', event)
      }
    })
  }
}
</script>

3. 错误处理与性能优化

3.1 错误处理示例

@PostMapping("/send")
public void send(@RequestParam String id, @RequestBody Map<String, Object> data) {
    SseEmitter emitter = sseEmitterMap.get(id);
    if (emitter == null || emitter.isCompleted()) {
        throw new IllegalStateException("连接已断开");
    }
    emitter.send(SseEmitter.event()
            .name("message")
            .data(data)
            .id(id)
            .retry(5000)
    );
}

3.2 性能优化

  • 使用连接池管理SseEmitter
  • 设置合理的超时时间(默认30秒)
  • 对异常连接进行清理
  • 使用Redis存储连接信息(分布式场景)

五、完整案例:实时通知系统

1. 项目结构

src
├── main
│   ├── java
│   │   └── com.example.sse
│   │       ├── controller
│   │       │   └── SseController.java
│   │       └── service
│   │           └── SseService.java
│   └── resources
│       └── application.yml
└── test

2. 核心代码

2.1 控制器

@RestController
@RequestMapping("/sse")
public class SseController {

    @Autowired
    private SseService sseService;

    @GetMapping
    public SseEmitter sse() {
        return sseService.createSseEmitter();
    }

    @PostMapping
    public void send(@RequestParam String id, @RequestBody Map<String, Object> data) {
        sseService.send(id, data);
    }
}

2.2 服务层

@Service
public class SseService {

    private final Map<String, SseEmitter> emitters = new ConcurrentHashMap<>();

    public SseEmitter createSseEmitter() {
        SseEmitter emitter = new SseEmitter(30_000L);
        emitters.put(UUID.randomUUID().toString(), emitter);
        emitter.onCompletion(() -> emitters.remove(emitter.getId()));
        return emitter;
    }

    public void send(String id, Map<String, Object> data) {
        SseEmitter emitter = emitters.get(id);
        if (emitter != null && !emitter.isCompleted()) {
            emitter.send(SseEmitter.event()
                    .name("notification")
                    .data(data)
                    .id(id)
                    .retry(5000)
            );
        }
    }
}

2.3 前端页面

<template>
  <div>
    <h2>实时通知</h2>
    <div v-if="notifications.length">
      <h3>最新通知:</h3>
      <p>{{ notifications[notifications.length - 1] }}</p>
    </div>
    <button @click="sendNotification">发送通知</button>
  </div>
</template>

<script>
export default {
  data() {
    return {
      notifications: []
    }
  },
  mounted() {
    this.connectSSE();
  },
  methods: {
    connectSSE() {
      const eventSource = new EventSource('http://localhost:8080/sse');
      
      eventSource.addEventListener('notification', (event) => {
        this.notifications.push(event.data);
      });
      
      eventSource.onerror = (event) => {
        console.error('SSE连接异常:', event);
      };
    },
    sendNotification() {
      fetch('http://localhost:8080/sse/send', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({
          id: '123',
          content: '这是测试通知'
        })
      });
    }
  }
}
</script>

3. 运行效果

  1. 启动SpringBoot应用
  2. 打开Vue页面,连接SSE服务
  3. 点击"发送通知"按钮,服务器将推送消息到客户端
  4. 页面实时显示通知内容

六、源码解析

1. SpringBoot的SseEmitter机制

SseEmitter内部使用SseEventSource管理连接,关键逻辑如下:

public class SseEmitter {
    private final SseEventSource eventSource;
    private final int timeout;
    
    public SseEmitter(int timeout) {
        this.timeout = timeout;
        this.eventSource = new SseEventSource(timeout);
    }
    
    public void send(SseEvent event) {
        eventSource.send(event);
    }
}

2. 事件流数据格式

SSE数据包经过序列化后,格式为:

data: {"type":"notification","content":"测试消息"}
event: notification
id: 123
retry: 5000

3. 客户端EventSource实现

浏览器端的EventSource实现包含以下关键逻辑:

class EventSource {
    constructor(url) {
        this.url = url;
        this.xmlHttpRequest = new XMLHttpRequest();
        this.xmlHttpRequest.open('GET', this.url, true);
        this.xmlHttpRequest.setRequestHeader('Accept', 'text/event-stream');
        this.xmlHttpRequest.onreadystatechange = () => {
            if (this.xmlHttpRequest.readyState === 4) {
                this.handleResponse();
            }
        };
        this.xmlHttpRequest.onmessage = (event) => {
            this.onmessage(event);
        };
        this.xmlHttpRequest.onerror = (event) => {
            this.onerror(event);
        };
        this.xmlHttpRequest.send();
    }
}

七、进阶使用

1. 事件类型管理

@PostMapping
public void send(String id, Map<String, Object> data) {
    SseEmitter emitter = emitters.get(id);
    if (emitter != null && !emitter.isCompleted()) {
        emitter.send(SseEmitter.event()
                .name("user:status")
                .data(data)
                .id(id)
                .retry(5000)
        );
    }
}

2. 连接管理优化

使用Redis存储连接信息:

public void send(String id, Map<String, Object> data) {
    SseEmitter emitter = redisSseEmitter.get(id);
    if (emitter != null && !emitter.isCompleted()) {
        emitter.send(SseEmitter.event()
                .name("user:status")
                .data(data)
                .id(id)
                .retry(5000)
        );
    }
}

3. 安全增强

@PostMapping
public void send(@RequestParam String id, @RequestBody Map<String, Object> data) {
    if (!SecurityUtils.isAuthorized(id)) {
        throw new UnauthorizedException("无权限发送消息");
    }
    SseEmitter emitter = emitters.get(id);
    if (emitter != null && !emitter.isCompleted()) {
        emitter.send(SseEmitter.event()
                .name("user:status")
                .data(data)
                .id(id)
                .retry(5000)
        );
    }
}

八、性能与工程实践

1. 性能优化策略

优化项方法效果
连接复用使用连接池降低资源消耗
超时设置设置合理超时时间防止资源泄露
异常处理定期检查连接状态提高稳定性
负载均衡使用Nginx反向代理提升并发能力

2. 例外处理方案

public void send(String id, Map<String, Object> data) {
    SseEmitter emitter = emitters.get(id);
    if (emitter == null || emitter.isCompleted()) {
        // 记录日志并尝试重连
        log.warn("连接已断开: {}", id);
        return;
    }
    emitter.send(SseEmitter.event()
            .name("user:status")
            .data(data)
            .id(id)
            .retry(5000)
    );
}

3. 安全风险分析

  • CSRF攻击:需在请求中加入防伪令牌
  • 身份验证:需要对接认证系统(如JWT)
  • 数据泄露:需加密敏感数据
  • DDoS防护:需限制并发连接数

九、常见问题与踩坑

1. 常见错误

问题原因解决方案
连接断开服务器端未正确关闭使用onCompletion回调
未收到数据未设置正确的Content-Type在响应头中设置text/event-stream
重连失败未正确处理异常添加onerror回调
数据解析错误数据格式不规范严格校验JSON格式

2. 常见坑点

2.1 超时处理

错误示例:

new SseEmitter(30_000L) // 超时30秒

改进方案:

new SseEmitter(30_000L)
    .onCompletion(() -> {
        sseEmitterMap.remove(id);
    })

2.2 重复事件ID

错误示例:

emitter.send(SseEmitter.event().id("123"))

改进方案:

emitter.send(SseEmitter.event()
        .id(UUID.randomUUID().toString())
        .name("notification")
)

十、最佳实践

1. 使用场景推荐

场景是否适用说明
实时通知✅适合推送通知、消息
股票行情✅实时数据更新
日志监控✅实时日志推送
即时聊天❌需要双向通信
高并发实时数据❌服务器压力较大

2. 推荐实践方案

  1. 连接管理:使用连接池或Redis存储连接信息
  2. 事件类型:使用event字段区分不同类型的事件
  3. 超时控制:设置合理的超时时间(建议5-30秒)
  4. 安全机制:对接认证系统,防止未授权访问
  5. 异常处理:添加详细的错误日志和重试机制

十一、总结

SSE作为一种轻量级的实时通信方案,在现代Web开发中具有重要价值。通过SpringBoot和Vue的结合,我们可以实现服务器向客户端的实时数据推送,适用于通知系统、实时监控等场景。

本文深入解析了SSE的原理,提供了完整的代码示例和性能优化方案,分析了常见错误和解决方案,并给出了最佳实践建议。在实际开发中,需要根据具体需求选择合适的通信方案,合理处理连接管理、安全性和性能优化等问题,才能充分发挥SSE的优势。

对于需要双向通信的场景,建议使用WebSocket;对于高并发的实时数据推送,可以考虑结合消息队列(如Kafka)进行优化。SSE的正确使用,能够显著提升用户体验,是现代Web应用不可或缺的技术之一。

最后修改于:2026年09月22日 21:06

评论已关闭

推荐阅读

AIGC实战——Transformer模型
2024年12月01日
Socket TCP 和 UDP 编程基础(Python)
2024年11月30日
python , tcp , udp
如何使用 ChatGPT 进行学术润色?你需要这些指令
2024年12月01日
AI
最新 Python 调用 OpenAi 详细教程实现问答、图像合成、图像理解、语音合成、语音识别(详细教程)
2024年11月24日
ChatGPT 和 DALL·E 2 配合生成故事绘本
2024年12月01日
omegaconf,一个超强的 Python 库!
2024年11月24日
【视觉AIGC识别】误差特征、人脸伪造检测、其他类型假图检测
2024年12月01日
[超级详细]如何在深度学习训练模型过程中使用 GPU 加速
2024年11月29日
Python 物理引擎pymunk最完整教程
2024年11月27日
MediaPipe 人体姿态与手指关键点检测教程
2024年11月27日
深入了解 Taipy:Python 打造 Web 应用的全面教程
2024年11月26日
基于Transformer的时间序列预测模型
2024年11月25日
Python在金融大数据分析中的AI应用(股价分析、量化交易)实战
2024年11月25日
AIGC Gradio系列学习教程之Components
2024年12月01日
Python3 `asyncio` — 异步 I/O,事件循环和并发工具
2024年11月30日
llama-factory SFT系列教程:大模型在自定义数据集 LoRA 训练与部署
2024年12月01日
Python 多线程和多进程用法
2024年11月24日
Python socket详解,全网最全教程
2024年11月27日
python之plot()和subplot()画图
2024年11月26日
理解 DALL·E 2、Stable Diffusion 和 Midjourney 工作原理
2024年12月01日